QueryRegressionTest.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\Core\Plugin;
  17. use Cake\I18n\Time;
  18. use Cake\ORM\TableRegistry;
  19. use Cake\TestSuite\TestCase;
  20. /**
  21. * Contains regression test for the Query builder
  22. *
  23. */
  24. class QueryRegressionTest extends TestCase
  25. {
  26. /**
  27. * Fixture to be used
  28. *
  29. * @var array
  30. */
  31. public $fixtures = [
  32. 'core.articles',
  33. 'core.tags',
  34. 'core.articles_tags',
  35. 'core.authors',
  36. 'core.authors_tags',
  37. 'core.comments',
  38. 'core.featured_tags',
  39. 'core.special_tags',
  40. 'core.tags_translations',
  41. 'core.translates',
  42. 'core.users'
  43. ];
  44. /**
  45. * Tear down
  46. *
  47. * @return void
  48. */
  49. public function tearDown()
  50. {
  51. parent::tearDown();
  52. TableRegistry::clear();
  53. }
  54. /**
  55. * Test for https://github.com/cakephp/cakephp/issues/3087
  56. *
  57. * @return void
  58. */
  59. public function testSelectTimestampColumn()
  60. {
  61. $table = TableRegistry::get('users');
  62. $user = $table->find()->where(['id' => 1])->first();
  63. $this->assertEquals(new Time('2007-03-17 01:16:23'), $user->created);
  64. $this->assertEquals(new Time('2007-03-17 01:18:31'), $user->updated);
  65. }
  66. /**
  67. * Tests that EagerLoader does not try to create queries for associations having no
  68. * keys to compare against
  69. *
  70. * @return void
  71. */
  72. public function testEagerLoadingFromEmptyResults()
  73. {
  74. $table = TableRegistry::get('Articles');
  75. $table->belongsToMany('ArticlesTags');
  76. $results = $table->find()->where(['id >' => 100])->contain('ArticlesTags')->toArray();
  77. $this->assertEmpty($results);
  78. }
  79. /**
  80. * Tests that eagerloading belongsToMany with find list fails with a helpful message.
  81. *
  82. * @expectedException \RuntimeException
  83. * @return void
  84. */
  85. public function testEagerLoadingBelongsToManyList()
  86. {
  87. $table = TableRegistry::get('Articles');
  88. $table->belongsToMany('Tags', [
  89. 'finder' => 'list'
  90. ]);
  91. $table->find()->contain('Tags')->toArray();
  92. }
  93. /**
  94. * Tests that duplicate aliases in contain() can be used, even when they would
  95. * naturally be attached to the query instead of eagerly loaded. What should
  96. * happen here is that One of the duplicates will be changed to be loaded using
  97. * an extra query, but yielding the same results
  98. *
  99. * @return void
  100. */
  101. public function testDuplicateAttachableAliases()
  102. {
  103. TableRegistry::get('Stuff', ['table' => 'tags']);
  104. TableRegistry::get('Things', ['table' => 'articles_tags']);
  105. $table = TableRegistry::get('Articles');
  106. $table->belongsTo('Authors');
  107. $table->hasOne('Things', ['propertyName' => 'articles_tag']);
  108. $table->Authors->target()->hasOne('Stuff', [
  109. 'foreignKey' => 'id',
  110. 'propertyName' => 'favorite_tag'
  111. ]);
  112. $table->Things->target()->belongsTo('Stuff', [
  113. 'foreignKey' => 'tag_id',
  114. 'propertyName' => 'foo'
  115. ]);
  116. $results = $table->find()
  117. ->contain(['Authors.Stuff', 'Things.Stuff'])
  118. ->order(['Articles.id' => 'ASC'])
  119. ->toArray();
  120. $this->assertEquals(1, $results[0]->articles_tag->foo->id);
  121. $this->assertEquals(1, $results[0]->author->favorite_tag->id);
  122. $this->assertEquals(2, $results[1]->articles_tag->foo->id);
  123. $this->assertEquals(1, $results[0]->author->favorite_tag->id);
  124. $this->assertEquals(1, $results[2]->articles_tag->foo->id);
  125. $this->assertEquals(3, $results[2]->author->favorite_tag->id);
  126. $this->assertEquals(3, $results[3]->articles_tag->foo->id);
  127. $this->assertEquals(3, $results[3]->author->favorite_tag->id);
  128. }
  129. /**
  130. * Test for https://github.com/cakephp/cakephp/issues/3410
  131. *
  132. * @return void
  133. */
  134. public function testNullableTimeColumn()
  135. {
  136. $table = TableRegistry::get('users');
  137. $entity = $table->newEntity(['username' => 'derp', 'created' => null]);
  138. $this->assertSame($entity, $table->save($entity));
  139. $this->assertNull($entity->created);
  140. }
  141. /**
  142. * Test for https://github.com/cakephp/cakephp/issues/3626
  143. *
  144. * Checks that join data is actually created and not tried to be updated every time
  145. * @return void
  146. */
  147. public function testCreateJointData()
  148. {
  149. $articles = TableRegistry::get('Articles');
  150. $articles->belongsToMany('Highlights', [
  151. 'className' => 'TestApp\Model\Table\TagsTable',
  152. 'foreignKey' => 'article_id',
  153. 'targetForeignKey' => 'tag_id',
  154. 'through' => 'SpecialTags'
  155. ]);
  156. $entity = $articles->get(2);
  157. $data = [
  158. 'id' => 2,
  159. 'highlights' => [
  160. [
  161. 'name' => 'New Special Tag',
  162. '_joinData' => ['highlighted' => true, 'highlighted_time' => '2014-06-01 10:10:00']
  163. ]
  164. ]
  165. ];
  166. $entity = $articles->patchEntity($entity, $data, ['Highlights._joinData']);
  167. $articles->save($entity);
  168. $entity = $articles->get(2, ['contain' => ['Highlights']]);
  169. $this->assertEquals(4, $entity->highlights[0]->_joinData->tag_id);
  170. $this->assertEquals('2014-06-01', $entity->highlights[0]->_joinData->highlighted_time->format('Y-m-d'));
  171. }
  172. /**
  173. * Tests that the junction table instance taken from both sides of a belongsToMany
  174. * relationship is actually the same object.
  175. *
  176. * @return void
  177. */
  178. public function testReciprocalBelongsToMany()
  179. {
  180. $articles = TableRegistry::get('Articles');
  181. $tags = TableRegistry::get('Tags');
  182. $articles->belongsToMany('Tags');
  183. $tags->belongsToMany('Articles');
  184. $left = $articles->Tags->junction();
  185. $right = $tags->Articles->junction();
  186. $this->assertSame($left, $right);
  187. }
  188. /**
  189. * Test for https://github.com/cakephp/cakephp/issues/4253
  190. *
  191. * Makes sure that the belongsToMany association is not overwritten with conflicting information
  192. * by any of the sides when the junction() function is invoked
  193. *
  194. * @return void
  195. */
  196. public function testReciprocalBelongsToManyNoOverwrite()
  197. {
  198. $articles = TableRegistry::get('Articles');
  199. $tags = TableRegistry::get('Tags');
  200. $articles->belongsToMany('Tags');
  201. $tags->belongsToMany('Articles');
  202. $sub = $articles->Tags->find()->select(['Tags.id'])->matching('Articles', function ($q) {
  203. return $q->where(['Articles.id' => 1]);
  204. });
  205. $query = $articles->Tags->find()->where(['Tags.id NOT IN' => $sub]);
  206. $this->assertEquals(1, $query->count());
  207. }
  208. /**
  209. * Returns an array with the saving strategies for a belongsTo association
  210. *
  211. * @return array
  212. */
  213. public function strategyProvider()
  214. {
  215. return [['append', 'replace']];
  216. }
  217. /**
  218. * Test for https://github.com/cakephp/cakephp/issues/3677 and
  219. * https://github.com/cakephp/cakephp/issues/3714
  220. *
  221. * Checks that only relevant associations are passed when saving _joinData
  222. * Tests that _joinData can also save deeper associations
  223. *
  224. * @dataProvider strategyProvider
  225. * @param string $strategy
  226. * @return void
  227. */
  228. public function testBelongsToManyDeepSave($strategy)
  229. {
  230. $articles = TableRegistry::get('Articles');
  231. $articles->belongsToMany('Highlights', [
  232. 'className' => 'TestApp\Model\Table\TagsTable',
  233. 'foreignKey' => 'article_id',
  234. 'targetForeignKey' => 'tag_id',
  235. 'through' => 'SpecialTags',
  236. 'saveStrategy' => $strategy
  237. ]);
  238. $articles->Highlights->junction()->belongsTo('Authors');
  239. $articles->Highlights->hasOne('Authors', [
  240. 'foreignKey' => 'id'
  241. ]);
  242. $entity = $articles->get(2, ['contain' => ['Highlights']]);
  243. $data = [
  244. 'highlights' => [
  245. [
  246. 'name' => 'New Special Tag',
  247. '_joinData' => [
  248. 'highlighted' => true,
  249. 'highlighted_time' => '2014-06-01 10:10:00',
  250. 'author' => [
  251. 'name' => 'mariano'
  252. ]
  253. ],
  254. 'author' => ['name' => 'mark']
  255. ]
  256. ]
  257. ];
  258. $options = [
  259. 'associated' => [
  260. 'Highlights._joinData.Authors', 'Highlights.Authors'
  261. ]
  262. ];
  263. $entity = $articles->patchEntity($entity, $data, $options);
  264. $articles->save($entity, $options);
  265. $entity = $articles->get(2, [
  266. 'contain' => [
  267. 'SpecialTags' => ['sort' => ['SpecialTags.id' => 'ASC']],
  268. 'SpecialTags.Authors',
  269. 'Highlights.Authors'
  270. ]
  271. ]);
  272. $this->assertEquals('mark', end($entity->highlights)->author->name);
  273. $lastTag = end($entity->special_tags);
  274. $this->assertTrue($lastTag->highlighted);
  275. $this->assertEquals('2014-06-01 10:10:00', $lastTag->highlighted_time->format('Y-m-d H:i:s'));
  276. $this->assertEquals('mariano', $lastTag->author->name);
  277. }
  278. /**
  279. * Tests that no exceptions are generated becuase of ambiguous column names in queries
  280. * during a save operation
  281. *
  282. * @see https://github.com/cakephp/cakephp/issues/3803
  283. * @return void
  284. */
  285. public function testSaveWithCallbacks()
  286. {
  287. $articles = TableRegistry::get('Articles');
  288. $articles->belongsTo('Authors');
  289. $articles->eventManager()->attach(function ($event, $query) {
  290. return $query->contain('Authors');
  291. }, 'Model.beforeFind');
  292. $article = $articles->newEntity();
  293. $article->title = 'Foo';
  294. $article->body = 'Bar';
  295. $this->assertSame($article, $articles->save($article));
  296. }
  297. /**
  298. * Test that save() works with entities containing expressions
  299. * as properties.
  300. *
  301. * @return void
  302. */
  303. public function testSaveWithExpressionProperty()
  304. {
  305. $articles = TableRegistry::get('Articles');
  306. $article = $articles->newEntity();
  307. $article->title = new \Cake\Database\Expression\QueryExpression("SELECT 'jose'");
  308. $this->assertSame($article, $articles->save($article));
  309. }
  310. /**
  311. * Tests that whe saving deep associations for a belongsToMany property,
  312. * data is not removed becuase of excesive associations filtering.
  313. *
  314. * @see https://github.com/cakephp/cakephp/issues/4009
  315. * @return void
  316. */
  317. public function testBelongsToManyDeepSave2()
  318. {
  319. $articles = TableRegistry::get('Articles');
  320. $articles->belongsToMany('Highlights', [
  321. 'className' => 'TestApp\Model\Table\TagsTable',
  322. 'foreignKey' => 'article_id',
  323. 'targetForeignKey' => 'tag_id',
  324. 'through' => 'SpecialTags',
  325. ]);
  326. $articles->Highlights->hasMany('TopArticles', [
  327. 'className' => 'TestApp\Model\Table\ArticlesTable',
  328. 'foreignKey' => 'author_id',
  329. ]);
  330. $entity = $articles->get(2, ['contain' => ['Highlights']]);
  331. $data = [
  332. 'highlights' => [
  333. [
  334. 'name' => 'New Special Tag',
  335. '_joinData' => [
  336. 'highlighted' => true,
  337. 'highlighted_time' => '2014-06-01 10:10:00',
  338. ],
  339. 'top_articles' => [
  340. ['title' => 'First top article'],
  341. ['title' => 'Second top article'],
  342. ]
  343. ]
  344. ]
  345. ];
  346. $options = [
  347. 'associated' => [
  348. 'Highlights._joinData', 'Highlights.TopArticles'
  349. ]
  350. ];
  351. $entity = $articles->patchEntity($entity, $data, $options);
  352. $articles->save($entity, $options);
  353. $entity = $articles->get(2, [
  354. 'contain' => [
  355. 'Highlights.TopArticles'
  356. ]
  357. ]);
  358. $highlights = $entity->highlights[0];
  359. $this->assertEquals('First top article', $highlights->top_articles[0]->title);
  360. $this->assertEquals('Second top article', $highlights->top_articles[1]->title);
  361. $this->assertEquals(
  362. new Time('2014-06-01 10:10:00'),
  363. $highlights->_joinData->highlighted_time
  364. );
  365. }
  366. /**
  367. * An integration test that spot checks that associations use the
  368. * correct alias names to generate queries.
  369. *
  370. * @return void
  371. */
  372. public function testPluginAssociationQueryGeneration()
  373. {
  374. Plugin::load('TestPlugin');
  375. $articles = TableRegistry::get('Articles');
  376. $articles->hasMany('TestPlugin.Comments');
  377. $articles->belongsTo('TestPlugin.Authors');
  378. $result = $articles->find()
  379. ->where(['Articles.id' => 2])
  380. ->contain(['Comments', 'Authors'])
  381. ->first();
  382. $this->assertNotEmpty(
  383. $result->comments[0]->id,
  384. 'No SQL error and comment exists.'
  385. );
  386. $this->assertNotEmpty(
  387. $result->author->id,
  388. 'No SQL error and author exists.'
  389. );
  390. }
  391. /**
  392. * Tests that loading associations having the same alias in the
  393. * joinable associations chain is not sensitive to the order in which
  394. * the associations are selected.
  395. *
  396. * @see https://github.com/cakephp/cakephp/issues/4454
  397. * @return void
  398. */
  399. public function testAssociationChainOrder()
  400. {
  401. $articles = TableRegistry::get('Articles');
  402. $articles->belongsTo('Authors');
  403. $articles->hasOne('ArticlesTags');
  404. $articlesTags = TableRegistry::get('ArticlesTags');
  405. $articlesTags->belongsTo('Authors', [
  406. 'foreignKey' => 'tag_id'
  407. ]);
  408. $resultA = $articles->find()
  409. ->contain(['ArticlesTags.Authors', 'Authors'])
  410. ->first();
  411. $resultB = $articles->find()
  412. ->contain(['Authors', 'ArticlesTags.Authors'])
  413. ->first();
  414. $this->assertEquals($resultA, $resultB);
  415. $this->assertNotEmpty($resultA->author);
  416. $this->assertNotEmpty($resultA->articles_tag->author);
  417. }
  418. /**
  419. * Test that offset/limit are elided from subquery loads.
  420. *
  421. * @return void
  422. */
  423. public function testAssociationSubQueryNoOffset()
  424. {
  425. $table = TableRegistry::get('Articles');
  426. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  427. $table->locale('eng');
  428. $query = $table->find('translations')
  429. ->order(['Articles.id' => 'ASC'])
  430. ->limit(10)
  431. ->offset(1);
  432. $result = $query->toArray();
  433. $this->assertCount(2, $result);
  434. }
  435. /**
  436. * Tests that using the subquery strategy in a deep association returns the right results
  437. *
  438. * @see https://github.com/cakephp/cakephp/issues/4484
  439. * @return void
  440. */
  441. public function testDeepBelongsToManySubqueryStrategy()
  442. {
  443. $table = TableRegistry::get('Authors');
  444. $table->hasMany('Articles');
  445. $table->Articles->belongsToMany('Tags', [
  446. 'strategy' => 'subquery'
  447. ]);
  448. $result = $table->find()->contain(['Articles.Tags'])->toArray();
  449. $this->assertEquals(
  450. ['tag1', 'tag3'],
  451. collection($result[2]->articles[0]->tags)->extract('name')->toArray()
  452. );
  453. }
  454. /**
  455. * Tests that using the subquery strategy in a deep association returns the right results
  456. *
  457. * @see https://github.com/cakephp/cakephp/issues/5769
  458. * @return void
  459. */
  460. public function testDeepBelongsToManySubqueryStrategy2()
  461. {
  462. $table = TableRegistry::get('Authors');
  463. $table->hasMany('Articles');
  464. $table->Articles->belongsToMany('Tags', [
  465. 'strategy' => 'subquery'
  466. ]);
  467. $table->belongsToMany('Tags', [
  468. 'strategy' => 'subquery',
  469. ]);
  470. $table->Articles->belongsTo('Authors');
  471. $result = $table->Articles->find()
  472. ->where(['Authors.id >' => 1])
  473. ->contain(['Authors.Tags'])
  474. ->toArray();
  475. $this->assertEquals(
  476. ['tag1', 'tag2'],
  477. collection($result[0]->author->tags)->extract('name')->toArray()
  478. );
  479. $this->assertEquals(3, $result[0]->author->id);
  480. }
  481. /**
  482. * Tests that finding on a table with a primary key other than `id` will work
  483. * seamlessly with either select or subquery.
  484. *
  485. * @see https://github.com/cakephp/cakephp/issues/6781
  486. * @return void
  487. */
  488. public function testDeepHasManyEitherStrategy()
  489. {
  490. $tags = TableRegistry::get('Tags');
  491. $featuredTags = TableRegistry::get('FeaturedTags');
  492. $featuredTags->belongsTo('Tags');
  493. $tags->hasMany('TagsTranslations', [
  494. 'foreignKey' => 'id',
  495. 'strategy' => 'select'
  496. ]);
  497. $findViaSelect = $featuredTags
  498. ->find()
  499. ->where(['FeaturedTags.tag_id' => 2])
  500. ->contain('Tags.TagsTranslations');
  501. $tags->hasMany('TagsTranslations', [
  502. 'foreignKey' => 'id',
  503. 'strategy' => 'subquery'
  504. ]);
  505. $findViaSubquery = $featuredTags
  506. ->find()
  507. ->where(['FeaturedTags.tag_id' => 2])
  508. ->contain('Tags.TagsTranslations');
  509. $expected = [2 => 'tag 2 translated into en_us'];
  510. $this->assertEquals($expected, $findViaSelect->combine('tag_id', 'tag.tags_translations.0.name')->toArray());
  511. $this->assertEquals($expected, $findViaSubquery->combine('tag_id', 'tag.tags_translations.0.name')->toArray());
  512. }
  513. /**
  514. * Tests that getting the count of a query having containments return
  515. * the correct results
  516. *
  517. * @see https://github.com/cakephp/cakephp/issues/4511
  518. * @return void
  519. */
  520. public function testCountWithContain()
  521. {
  522. $table = TableRegistry::get('Articles');
  523. $table->belongsTo('Authors', ['joinType' => 'inner']);
  524. $count = $table
  525. ->find()
  526. ->contain(['Authors' => function ($q) {
  527. return $q->where(['Authors.id' => 1]);
  528. }])
  529. ->count();
  530. $this->assertEquals(2, $count);
  531. }
  532. /**
  533. * Test that deep containments don't generate empty entities for
  534. * intermediary relations.
  535. *
  536. * @return void
  537. */
  538. public function testContainNoEmptyAssociatedObjects()
  539. {
  540. $comments = TableRegistry::get('Comments');
  541. $comments->belongsTo('Users');
  542. $users = TableRegistry::get('Users');
  543. $users->hasMany('Articles', [
  544. 'foreignKey' => 'author_id'
  545. ]);
  546. $comments->updateAll(['user_id' => 99], ['id' => 1]);
  547. $result = $comments->find()
  548. ->contain(['Users'])
  549. ->where(['Comments.id' => 1])
  550. ->first();
  551. $this->assertNull($result->user, 'No record should be null.');
  552. $result = $comments->find()
  553. ->contain(['Users', 'Users.Articles'])
  554. ->where(['Comments.id' => 1])
  555. ->first();
  556. $this->assertNull($result->user, 'No record should be null.');
  557. }
  558. /**
  559. * Tests that using a comparison expression inside an OR condition works
  560. *
  561. * @see https://github.com/cakephp/cakephp/issues/5081
  562. * @return void
  563. */
  564. public function testOrConditionsWithExpression()
  565. {
  566. $table = TableRegistry::get('Articles');
  567. $query = $table->find();
  568. $query->where([
  569. 'OR' => [
  570. new \Cake\Database\Expression\Comparison('id', 1, 'integer', '>'),
  571. new \Cake\Database\Expression\Comparison('id', 3, 'integer', '<')
  572. ]
  573. ]);
  574. $results = $query->toArray();
  575. $this->assertCount(3, $results);
  576. }
  577. /**
  578. * Tests that calling count on a query having a union works correctly
  579. *
  580. * @see https://github.com/cakephp/cakephp/issues/5107
  581. * @return void
  582. */
  583. public function testCountWithUnionQuery()
  584. {
  585. $table = TableRegistry::get('Articles');
  586. $query = $table->find()->where(['id' => 1]);
  587. $query2 = $table->find()->where(['id' => 2]);
  588. $query->union($query2);
  589. $this->assertEquals(2, $query->count());
  590. }
  591. /**
  592. * Integration test when selecting no fields on the primary table.
  593. *
  594. * @return void
  595. */
  596. public function testSelectNoFieldsOnPrimaryAlias()
  597. {
  598. $table = TableRegistry::get('Articles');
  599. $table->belongsTo('Users');
  600. $query = $table->find()
  601. ->select(['Users__id' => 'id']);
  602. $results = $query->toArray();
  603. $this->assertCount(3, $results);
  604. }
  605. /**
  606. * Tests that calling first on the query results will not remove all other results
  607. * from the set.
  608. *
  609. * @return void
  610. */
  611. public function testFirstOnResultSet()
  612. {
  613. $results = TableRegistry::get('Articles')->find()->all();
  614. $this->assertEquals(3, $results->count());
  615. $this->assertNotNull($results->first());
  616. $this->assertCount(3, $results->toArray());
  617. }
  618. /**
  619. * Checks that matching and contain can be called for the same belongsTo association
  620. *
  621. * @see https://github.com/cakephp/cakephp/issues/5463
  622. * @return void
  623. */
  624. public function testFindMatchingAndContain()
  625. {
  626. $table = TableRegistry::get('Articles');
  627. $table->belongsTo('Authors');
  628. $article = $table->find()
  629. ->contain('Authors')
  630. ->matching('Authors', function ($q) {
  631. return $q->where(['Authors.id' => 1]);
  632. })
  633. ->first();
  634. $this->assertNotNull($article->author);
  635. $this->assertEquals($article->author, $article->_matchingData['Authors']);
  636. }
  637. /**
  638. * Checks that matching and contain can be called for the same belongsTo association
  639. *
  640. * @see https://github.com/cakephp/cakephp/issues/5463
  641. * @return void
  642. */
  643. public function testFindMatchingAndContainWithSubquery()
  644. {
  645. $table = TableRegistry::get('authors');
  646. $table->hasMany('articles', ['strategy' => 'subquery']);
  647. $table->articles->belongsToMany('tags');
  648. $result = $table->find()
  649. ->matching('articles.tags', function ($q) {
  650. return $q->where(['tags.id' => 2]);
  651. })
  652. ->contain('articles');
  653. $this->assertCount(2, $result->first()->articles);
  654. }
  655. /**
  656. * Tests that matching does not overwrite associations in contain
  657. *
  658. * @see https://github.com/cakephp/cakephp/issues/5584
  659. * @return void
  660. */
  661. public function testFindMatchingOverwrite()
  662. {
  663. $comments = TableRegistry::get('Comments');
  664. $comments->belongsTo('Articles');
  665. $articles = TableRegistry::get('Articles');
  666. $articles->belongsToMany('Tags');
  667. $result = $comments
  668. ->find()
  669. ->matching('Articles.Tags', function ($q) {
  670. return $q->where(['Tags.id' => 2]);
  671. })
  672. ->contain('Articles')
  673. ->first();
  674. $this->assertEquals(1, $result->id);
  675. $this->assertEquals(1, $result->_matchingData['Articles']->id);
  676. $this->assertEquals(2, $result->_matchingData['Tags']->id);
  677. $this->assertNotNull($result->article);
  678. $this->assertEquals($result->article, $result->_matchingData['Articles']);
  679. }
  680. /**
  681. * Tests that matching does not overwrite associations in contain
  682. *
  683. * @see https://github.com/cakephp/cakephp/issues/5584
  684. * @return void
  685. */
  686. public function testFindMatchingOverwrite2()
  687. {
  688. $comments = TableRegistry::get('Comments');
  689. $comments->belongsTo('Articles');
  690. $articles = TableRegistry::get('Articles');
  691. $articles->belongsTo('Authors');
  692. $articles->belongsToMany('Tags');
  693. $result = $comments
  694. ->find()
  695. ->matching('Articles.Tags', function ($q) {
  696. return $q->where(['Tags.id' => 2]);
  697. })
  698. ->contain('Articles.Authors')
  699. ->first();
  700. $this->assertNotNull($result->article->author);
  701. }
  702. /**
  703. * Tests that trying to contain an inexistent association
  704. * throws an exception and not a fatal error.
  705. *
  706. * @expectedException InvalidArgumentException
  707. * @return void
  708. */
  709. public function testQueryNotFatalError()
  710. {
  711. $comments = TableRegistry::get('Comments');
  712. $comments->find()->contain('Deprs')->all();
  713. }
  714. /**
  715. * Tests that using matching and contain on belongsTo associations
  716. * works correctly.
  717. *
  718. * @see https://github.com/cakephp/cakephp/issues/5721
  719. * @return void
  720. */
  721. public function testFindMatchingWithContain()
  722. {
  723. $comments = TableRegistry::get('Comments');
  724. $comments->belongsTo('Articles');
  725. $comments->belongsTo('Users');
  726. $result = $comments->find()
  727. ->contain(['Articles', 'Users'])
  728. ->matching('Articles', function ($q) {
  729. return $q->where(['Articles.id >=' => 1]);
  730. })
  731. ->matching('Users', function ($q) {
  732. return $q->where(['Users.id >=' => 1]);
  733. })
  734. ->order(['Comments.id' => 'ASC'])
  735. ->first();
  736. $this->assertInstanceOf('Cake\ORM\Entity', $result->article);
  737. $this->assertInstanceOf('Cake\ORM\Entity', $result->user);
  738. $this->assertEquals(2, $result->user->id);
  739. $this->assertEquals(1, $result->article->id);
  740. }
  741. /**
  742. * Tests that HasMany associations don't use duplicate PK values.
  743. *
  744. * @return void
  745. */
  746. public function testHasManyEagerLoadingUniqueKey()
  747. {
  748. $table = TableRegistry::get('ArticlesTags');
  749. $table->belongsTo('Articles', [
  750. 'strategy' => 'select'
  751. ]);
  752. $result = $table->find()
  753. ->contain(['Articles' => function ($q) {
  754. $result = $q->sql();
  755. $this->assertNotContains(':c2', $result, 'Only 2 bindings as there are only 2 rows.');
  756. $this->assertNotContains(':c3', $result, 'Only 2 bindings as there are only 2 rows.');
  757. return $q;
  758. }])
  759. ->toArray();
  760. $this->assertNotEmpty($result[0]->article);
  761. }
  762. /**
  763. * Tests that using contain but selecting no fields from the association
  764. * does not trigger any errors and fetches the right results.
  765. *
  766. * @see https://github.com/cakephp/cakephp/issues/6214
  767. * @return void
  768. */
  769. public function testContainWithNoFields()
  770. {
  771. $table = TableRegistry::get('Comments');
  772. $table->belongsTo('Users');
  773. $results = $table->find()
  774. ->select(['Comments.id', 'Comments.user_id'])
  775. ->contain(['Users'])
  776. ->where(['Users.id' => 1])
  777. ->combine('id', 'user_id');
  778. $this->assertEquals([3 => 1, 4 => 1, 5 => 1], $results->toArray());
  779. }
  780. /**
  781. * Tests that using matching and selecting no fields for that association
  782. * will no trigger any errors and fetch the right results
  783. *
  784. * @see https://github.com/cakephp/cakephp/issues/6223
  785. * @return void
  786. */
  787. public function testMatchingWithNoFields()
  788. {
  789. $table = TableRegistry::get('Users');
  790. $table->hasMany('Comments');
  791. $results = $table->find()
  792. ->select(['Users.id'])
  793. ->matching('Comments', function ($q) {
  794. return $q->where(['Comments.id' => 1]);
  795. })
  796. ->extract('id')
  797. ->toList();
  798. $this->assertEquals([2], $results);
  799. }
  800. /**
  801. * Test that empty conditions in a matching clause don't cause errors.
  802. *
  803. * @return void
  804. */
  805. public function testMatchingEmptyQuery()
  806. {
  807. $table = TableRegistry::get('Articles');
  808. $table->belongsToMany('Tags');
  809. $rows = $table->find()
  810. ->matching('Tags', function ($q) {
  811. return $q->where([]);
  812. })
  813. ->all();
  814. $this->assertNotEmpty($rows);
  815. $rows = $table->find()
  816. ->matching('Tags', function ($q) {
  817. return $q->where(null);
  818. })
  819. ->all();
  820. $this->assertNotEmpty($rows);
  821. }
  822. /**
  823. * Tests that using a subquery as part of an expression will not make invalid SQL
  824. *
  825. * @return void
  826. */
  827. public function testSubqueryInSelectExpression()
  828. {
  829. $table = TableRegistry::get('Comments');
  830. $ratio = $table->find()
  831. ->select(function ($query) use ($table) {
  832. $allCommentsCount = $table->find()->select($query->func()->count('*'));
  833. $countToFloat = $query->newExpr([$query->func()->count('*'), '1.0'])->type('*');
  834. return [
  835. 'ratio' => $query
  836. ->newExpr($countToFloat)
  837. ->add($allCommentsCount)
  838. ->type('/')
  839. ];
  840. })
  841. ->where(['user_id' => 1])
  842. ->first()
  843. ->ratio;
  844. $this->assertEquals(0.5, $ratio);
  845. }
  846. /**
  847. * Tests calling last on an empty table
  848. *
  849. * @see https://github.com/cakephp/cakephp/issues/6683
  850. * @return void
  851. */
  852. public function testFindLastOnEmptyTable()
  853. {
  854. $table = TableRegistry::get('Comments');
  855. $table->deleteAll(['1 = 1']);
  856. $this->assertEquals(0, $table->find()->count());
  857. $this->assertNull($table->find()->last());
  858. }
  859. /**
  860. * Tests calling contain in a nested closure
  861. *
  862. * @see https://github.com/cakephp/cakephp/issues/7591
  863. * @return void
  864. */
  865. public function testContainInNestedClosure()
  866. {
  867. $table = TableRegistry::get('Comments');
  868. $table->belongsTo('Articles');
  869. $table->Articles->belongsTo('Authors');
  870. $table->Articles->Authors->belongsToMany('Tags');
  871. $query = $table->find()->where(['Comments.id' => 5])->contain(['Articles' => function ($q) {
  872. return $q->contain(['Authors' => function ($q) {
  873. return $q->contain('Tags');
  874. }]);
  875. }]);
  876. $this->assertCount(2, $query->first()->article->author->tags);
  877. }
  878. /**
  879. * Test that the typemaps used in function expressions
  880. * create the correct results.
  881. *
  882. * @return void
  883. */
  884. public function testTypemapInFunctions()
  885. {
  886. $table = TableRegistry::get('Comments');
  887. $table->updateAll(['published' => null], ['1 = 1']);
  888. $query = $table->find();
  889. $query->select([
  890. 'id',
  891. 'coalesced' => $query->func()->coalesce(
  892. ['published' => 'identifier', -1],
  893. ['integer']
  894. )
  895. ]);
  896. $result = $query->all()->first();
  897. $this->assertSame(
  898. -1,
  899. $result['coalesced'],
  900. 'Output values for functions should be casted'
  901. );
  902. }
  903. /**
  904. * Test that the typemaps used in function expressions
  905. * create the correct results.
  906. *
  907. * @return void
  908. */
  909. public function testTypemapInFunctions2()
  910. {
  911. $table = TableRegistry::get('Comments');
  912. $query = $table->find();
  913. $query->select([
  914. 'max' => $query->func()->max('created', ['datetime'])
  915. ]);
  916. $result = $query->all()->first();
  917. $this->assertEquals(new Time('2007-03-18 10:55:23'), $result['max']);
  918. }
  919. /**
  920. * Test that contain queries map types correctly.
  921. *
  922. * @return void
  923. */
  924. public function testBooleanConditionsInContain()
  925. {
  926. $table = TableRegistry::get('Articles');
  927. $table->belongsToMany('Tags', [
  928. 'foreignKey' => 'article_id',
  929. 'associationForeignKey' => 'tag_id',
  930. 'through' => 'SpecialTags'
  931. ]);
  932. $query = $table->find()
  933. ->contain(['Tags' => function ($q) {
  934. return $q->where(['SpecialTags.highlighted_time >' => new Time('2014-06-01 00:00:00')]);
  935. }])
  936. ->where(['Articles.id' => 2]);
  937. $result = $query->first();
  938. $this->assertEquals(2, $result->id);
  939. $this->assertNotEmpty($result->tags, 'Missing tags');
  940. $this->assertNotEmpty($result->tags[0]->_joinData, 'Missing join data');
  941. }
  942. /**
  943. * Test that contain queries map types correctly.
  944. *
  945. * @return void
  946. */
  947. public function testComplexTypesInJoinedWhere()
  948. {
  949. $table = TableRegistry::get('Users');
  950. $table->hasOne('Comments', [
  951. 'foreignKey' => 'user_id',
  952. ]);
  953. $query = $table->find()
  954. ->contain('Comments')
  955. ->where([
  956. 'Comments.updated >' => new \DateTime('2007-03-18 10:55:00')
  957. ]);
  958. $result = $query->first();
  959. $this->assertNotEmpty($result);
  960. $this->assertInstanceOf('Cake\I18n\Time', $result->comment->updated);
  961. }
  962. /**
  963. * Test that nested contain queries map types correctly.
  964. *
  965. * @return void
  966. */
  967. public function testComplexNestedTypesInJoinedWhere()
  968. {
  969. $table = TableRegistry::get('Users');
  970. $table->hasOne('Comments', [
  971. 'foreignKey' => 'user_id',
  972. ]);
  973. $table->Comments->belongsTo('Articles');
  974. $table->Comments->Articles->belongsTo('Authors', [
  975. 'className' => 'Users',
  976. 'foreignKey' => 'author_id'
  977. ]);
  978. $query = $table->find()
  979. ->contain('Comments.Articles.Authors')
  980. ->where([
  981. 'Authors.created >' => new \DateTime('2007-03-17 01:16:00')
  982. ]);
  983. $result = $query->first();
  984. $this->assertNotEmpty($result);
  985. $this->assertInstanceOf('Cake\I18n\Time', $result->comment->article->author->updated);
  986. }
  987. /**
  988. * Tests that it is possible to use matching with dot notation
  989. * even when part of the part of the path in the dot notation is
  990. * shared for two different calls
  991. *
  992. * @return void
  993. */
  994. public function testDotNotationNotOverride()
  995. {
  996. $table = TableRegistry::get('Comments');
  997. $articles = $table->belongsTo('Articles');
  998. $specialTags = $articles->hasMany('SpecialTags');
  999. $specialTags->belongsTo('Authors');
  1000. $specialTags->belongsTo('Tags');
  1001. $results = $table
  1002. ->find()
  1003. ->select(['name' => 'Authors.name', 'tag' => 'Tags.name'])
  1004. ->matching('Articles.SpecialTags.Tags')
  1005. ->matching('Articles.SpecialTags.Authors', function ($q) {
  1006. return $q->where(['Authors.id' => 2]);
  1007. })
  1008. ->distinct()
  1009. ->hydrate(false)
  1010. ->toArray();
  1011. $this->assertEquals([['name' => 'nate', 'tag' => 'tag1']], $results);
  1012. }
  1013. /**
  1014. * Test expression based ordering with unions.
  1015. *
  1016. * @return void
  1017. */
  1018. public function testComplexOrderWithUnion()
  1019. {
  1020. $table = TableRegistry::get('Comments');
  1021. $query = $table->find();
  1022. $inner = $table->find()
  1023. ->select(['content' => 'comment'])
  1024. ->where(['id >' => 3]);
  1025. $inner2 = $table->find()
  1026. ->select(['content' => 'comment'])
  1027. ->where(['id <' => 3]);
  1028. $order = $query->func()->concat(['content' => 'literal', 'test']);
  1029. $query->select(['inside.content'])
  1030. ->from(['inside' => $inner->unionAll($inner2)])
  1031. ->orderAsc($order);
  1032. $results = $query->toArray();
  1033. $this->assertCount(5, $results);
  1034. }
  1035. /**
  1036. * Test that associations that are loaded with subqueries
  1037. * do not cause errors when the subquery has a limit & order clause.
  1038. *
  1039. * @return void
  1040. */
  1041. public function testEagerLoadOrderAndSubquery()
  1042. {
  1043. $table = TableRegistry::get('Articles');
  1044. $table->hasMany('Comments', [
  1045. 'strategy' => 'subquery'
  1046. ]);
  1047. $query = $table->find()
  1048. ->select(['score' => 100])
  1049. ->autoFields(true)
  1050. ->contain(['Comments'])
  1051. ->limit(5)
  1052. ->order(['score' => 'desc']);
  1053. $result = $query->all();
  1054. $this->assertCount(3, $result);
  1055. }
  1056. /**
  1057. * Tests that decorating the results does not result in a memory leak
  1058. *
  1059. * @return void
  1060. */
  1061. public function testFormatResultsMemory()
  1062. {
  1063. $table = TableRegistry::get('Articles');
  1064. $table->belongsTo('Authors');
  1065. $table->belongsToMany('Tags');
  1066. gc_collect_cycles();
  1067. $memory = memory_get_usage() / 1024 / 1024;
  1068. foreach (range(1, 3) as $time) {
  1069. $table->find()
  1070. ->contain(['Authors', 'Tags'])
  1071. ->formatResults(function ($results) {
  1072. return $results;
  1073. })
  1074. ->all();
  1075. }
  1076. gc_collect_cycles();
  1077. $endMemory = memory_get_usage() / 1024 / 1024;
  1078. $this->assertWithinRange($endMemory, $memory, 1.25, 'Memory leak in ResultSet');
  1079. }
  1080. /**
  1081. * Tests that having bound placeholders in the order clause does not result
  1082. * in an error when trying to count a query.
  1083. *
  1084. * @return void
  1085. */
  1086. public function testCountWithComplexOrderBy()
  1087. {
  1088. $table = TableRegistry::get('Articles');
  1089. $query = $table->find();
  1090. $query->orderDesc($query->newExpr()->addCase(
  1091. [$query->newExpr()->add(['id' => 3])],
  1092. [1, 0]
  1093. ));
  1094. $query->order(['title' => 'desc']);
  1095. // Executing the normal query before getting the count
  1096. $query->all();
  1097. $this->assertEquals(3, $query->count());
  1098. $table = TableRegistry::get('Articles');
  1099. $query = $table->find();
  1100. $query->orderDesc($query->newExpr()->addCase(
  1101. [$query->newExpr()->add(['id' => 3])],
  1102. [1, 0]
  1103. ));
  1104. $query->orderDesc($query->newExpr()->add(['id' => 3]));
  1105. // Not executing the query first, just getting the count
  1106. $this->assertEquals(3, $query->count());
  1107. }
  1108. /**
  1109. * Tests that the now() function expression can be used in the
  1110. * where clause of a query
  1111. *
  1112. * @see https://github.com/cakephp/cakephp/issues/7943
  1113. * @return void
  1114. */
  1115. public function testFunctionInWhereClause()
  1116. {
  1117. $table = TableRegistry::get('Comments');
  1118. $table->updateAll(['updated' => Time::tomorrow()], ['id' => 6]);
  1119. $query = $table->find();
  1120. $result = $query->where(['updated >' => $query->func()->now('datetime')])->first();
  1121. $this->assertSame(6, $result->id);
  1122. }
  1123. }