QueryRegressionTest.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273
  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 \InvalidArgumentException
  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->assertCount(5, $results);
  121. $this->assertEquals(1, $results[0]->articles_tag->foo->id);
  122. $this->assertEquals(1, $results[0]->author->favorite_tag->id);
  123. $this->assertEquals(2, $results[1]->articles_tag->foo->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. * Tests that getting the count of a query with bind is correct
  534. *
  535. * @see https://github.com/cakephp/cakephp/issues/8466
  536. * @return void
  537. */
  538. public function testCountWithBind()
  539. {
  540. $table = TableRegistry::get('Articles');
  541. $query = $table
  542. ->find()
  543. ->select(['title', 'id'])
  544. ->where("title LIKE :val")
  545. ->group(['id', 'title'])
  546. ->bind(':val', '%Second%');
  547. $count = $query->count();
  548. $this->assertEquals(1, $count);
  549. }
  550. /**
  551. * Tests that bind in subqueries works.
  552. *
  553. * @return void
  554. */
  555. public function testSubqueryBind()
  556. {
  557. $table = TableRegistry::get('Articles');
  558. $sub = $table->find()
  559. ->select(['id'])
  560. ->where("title LIKE :val")
  561. ->bind(':val', 'Second %');
  562. $query = $table
  563. ->find()
  564. ->select(['title'])
  565. ->where(["id NOT IN" => $sub]);
  566. $result = $query->toArray();
  567. $this->assertCount(2, $result);
  568. $this->assertEquals('First Article', $result[0]->title);
  569. $this->assertEquals('Third Article', $result[1]->title);
  570. }
  571. /**
  572. * Test that deep containments don't generate empty entities for
  573. * intermediary relations.
  574. *
  575. * @return void
  576. */
  577. public function testContainNoEmptyAssociatedObjects()
  578. {
  579. $comments = TableRegistry::get('Comments');
  580. $comments->belongsTo('Users');
  581. $users = TableRegistry::get('Users');
  582. $users->hasMany('Articles', [
  583. 'foreignKey' => 'author_id'
  584. ]);
  585. $comments->updateAll(['user_id' => 99], ['id' => 1]);
  586. $result = $comments->find()
  587. ->contain(['Users'])
  588. ->where(['Comments.id' => 1])
  589. ->first();
  590. $this->assertNull($result->user, 'No record should be null.');
  591. $result = $comments->find()
  592. ->contain(['Users', 'Users.Articles'])
  593. ->where(['Comments.id' => 1])
  594. ->first();
  595. $this->assertNull($result->user, 'No record should be null.');
  596. }
  597. /**
  598. * Tests that using a comparison expression inside an OR condition works
  599. *
  600. * @see https://github.com/cakephp/cakephp/issues/5081
  601. * @return void
  602. */
  603. public function testOrConditionsWithExpression()
  604. {
  605. $table = TableRegistry::get('Articles');
  606. $query = $table->find();
  607. $query->where([
  608. 'OR' => [
  609. new \Cake\Database\Expression\Comparison('id', 1, 'integer', '>'),
  610. new \Cake\Database\Expression\Comparison('id', 3, 'integer', '<')
  611. ]
  612. ]);
  613. $results = $query->toArray();
  614. $this->assertCount(3, $results);
  615. }
  616. /**
  617. * Tests that calling count on a query having a union works correctly
  618. *
  619. * @see https://github.com/cakephp/cakephp/issues/5107
  620. * @return void
  621. */
  622. public function testCountWithUnionQuery()
  623. {
  624. $table = TableRegistry::get('Articles');
  625. $query = $table->find()->where(['id' => 1]);
  626. $query2 = $table->find()->where(['id' => 2]);
  627. $query->union($query2);
  628. $this->assertEquals(2, $query->count());
  629. }
  630. /**
  631. * Integration test when selecting no fields on the primary table.
  632. *
  633. * @return void
  634. */
  635. public function testSelectNoFieldsOnPrimaryAlias()
  636. {
  637. $table = TableRegistry::get('Articles');
  638. $table->belongsTo('Users');
  639. $query = $table->find()
  640. ->select(['Users__id' => 'id']);
  641. $results = $query->toArray();
  642. $this->assertCount(3, $results);
  643. }
  644. /**
  645. * Tests that calling first on the query results will not remove all other results
  646. * from the set.
  647. *
  648. * @return void
  649. */
  650. public function testFirstOnResultSet()
  651. {
  652. $results = TableRegistry::get('Articles')->find()->all();
  653. $this->assertEquals(3, $results->count());
  654. $this->assertNotNull($results->first());
  655. $this->assertCount(3, $results->toArray());
  656. }
  657. /**
  658. * Checks that matching and contain can be called for the same belongsTo association
  659. *
  660. * @see https://github.com/cakephp/cakephp/issues/5463
  661. * @return void
  662. */
  663. public function testFindMatchingAndContain()
  664. {
  665. $table = TableRegistry::get('Articles');
  666. $table->belongsTo('Authors');
  667. $article = $table->find()
  668. ->contain('Authors')
  669. ->matching('Authors', function ($q) {
  670. return $q->where(['Authors.id' => 1]);
  671. })
  672. ->first();
  673. $this->assertNotNull($article->author);
  674. $this->assertEquals($article->author, $article->_matchingData['Authors']);
  675. }
  676. /**
  677. * Checks that matching and contain can be called for the same belongsTo association
  678. *
  679. * @see https://github.com/cakephp/cakephp/issues/5463
  680. * @return void
  681. */
  682. public function testFindMatchingAndContainWithSubquery()
  683. {
  684. $table = TableRegistry::get('authors');
  685. $table->hasMany('articles', ['strategy' => 'subquery']);
  686. $table->articles->belongsToMany('tags');
  687. $result = $table->find()
  688. ->matching('articles.tags', function ($q) {
  689. return $q->where(['tags.id' => 2]);
  690. })
  691. ->contain('articles');
  692. $this->assertCount(2, $result->first()->articles);
  693. }
  694. /**
  695. * Tests that matching does not overwrite associations in contain
  696. *
  697. * @see https://github.com/cakephp/cakephp/issues/5584
  698. * @return void
  699. */
  700. public function testFindMatchingOverwrite()
  701. {
  702. $comments = TableRegistry::get('Comments');
  703. $comments->belongsTo('Articles');
  704. $articles = TableRegistry::get('Articles');
  705. $articles->belongsToMany('Tags');
  706. $result = $comments
  707. ->find()
  708. ->matching('Articles.Tags', function ($q) {
  709. return $q->where(['Tags.id' => 2]);
  710. })
  711. ->contain('Articles')
  712. ->first();
  713. $this->assertEquals(1, $result->id);
  714. $this->assertEquals(1, $result->_matchingData['Articles']->id);
  715. $this->assertEquals(2, $result->_matchingData['Tags']->id);
  716. $this->assertNotNull($result->article);
  717. $this->assertEquals($result->article, $result->_matchingData['Articles']);
  718. }
  719. /**
  720. * Tests that matching does not overwrite associations in contain
  721. *
  722. * @see https://github.com/cakephp/cakephp/issues/5584
  723. * @return void
  724. */
  725. public function testFindMatchingOverwrite2()
  726. {
  727. $comments = TableRegistry::get('Comments');
  728. $comments->belongsTo('Articles');
  729. $articles = TableRegistry::get('Articles');
  730. $articles->belongsTo('Authors');
  731. $articles->belongsToMany('Tags');
  732. $result = $comments
  733. ->find()
  734. ->matching('Articles.Tags', function ($q) {
  735. return $q->where(['Tags.id' => 2]);
  736. })
  737. ->contain('Articles.Authors')
  738. ->first();
  739. $this->assertNotNull($result->article->author);
  740. }
  741. /**
  742. * Tests that trying to contain an inexistent association
  743. * throws an exception and not a fatal error.
  744. *
  745. * @expectedException InvalidArgumentException
  746. * @return void
  747. */
  748. public function testQueryNotFatalError()
  749. {
  750. $comments = TableRegistry::get('Comments');
  751. $comments->find()->contain('Deprs')->all();
  752. }
  753. /**
  754. * Tests that using matching and contain on belongsTo associations
  755. * works correctly.
  756. *
  757. * @see https://github.com/cakephp/cakephp/issues/5721
  758. * @return void
  759. */
  760. public function testFindMatchingWithContain()
  761. {
  762. $comments = TableRegistry::get('Comments');
  763. $comments->belongsTo('Articles');
  764. $comments->belongsTo('Users');
  765. $result = $comments->find()
  766. ->contain(['Articles', 'Users'])
  767. ->matching('Articles', function ($q) {
  768. return $q->where(['Articles.id >=' => 1]);
  769. })
  770. ->matching('Users', function ($q) {
  771. return $q->where(['Users.id >=' => 1]);
  772. })
  773. ->order(['Comments.id' => 'ASC'])
  774. ->first();
  775. $this->assertInstanceOf('Cake\ORM\Entity', $result->article);
  776. $this->assertInstanceOf('Cake\ORM\Entity', $result->user);
  777. $this->assertEquals(2, $result->user->id);
  778. $this->assertEquals(1, $result->article->id);
  779. }
  780. /**
  781. * Tests that HasMany associations don't use duplicate PK values.
  782. *
  783. * @return void
  784. */
  785. public function testHasManyEagerLoadingUniqueKey()
  786. {
  787. $table = TableRegistry::get('ArticlesTags');
  788. $table->belongsTo('Articles', [
  789. 'strategy' => 'select'
  790. ]);
  791. $result = $table->find()
  792. ->contain(['Articles' => function ($q) {
  793. $result = $q->sql();
  794. $this->assertNotContains(':c2', $result, 'Only 2 bindings as there are only 2 rows.');
  795. $this->assertNotContains(':c3', $result, 'Only 2 bindings as there are only 2 rows.');
  796. return $q;
  797. }])
  798. ->toArray();
  799. $this->assertNotEmpty($result[0]->article);
  800. }
  801. /**
  802. * Tests that using contain but selecting no fields from the association
  803. * does not trigger any errors and fetches the right results.
  804. *
  805. * @see https://github.com/cakephp/cakephp/issues/6214
  806. * @return void
  807. */
  808. public function testContainWithNoFields()
  809. {
  810. $table = TableRegistry::get('Comments');
  811. $table->belongsTo('Users');
  812. $results = $table->find()
  813. ->select(['Comments.id', 'Comments.user_id'])
  814. ->contain(['Users'])
  815. ->where(['Users.id' => 1])
  816. ->combine('id', 'user_id');
  817. $this->assertEquals([3 => 1, 4 => 1, 5 => 1], $results->toArray());
  818. }
  819. /**
  820. * Tests that using matching and selecting no fields for that association
  821. * will no trigger any errors and fetch the right results
  822. *
  823. * @see https://github.com/cakephp/cakephp/issues/6223
  824. * @return void
  825. */
  826. public function testMatchingWithNoFields()
  827. {
  828. $table = TableRegistry::get('Users');
  829. $table->hasMany('Comments');
  830. $results = $table->find()
  831. ->select(['Users.id'])
  832. ->matching('Comments', function ($q) {
  833. return $q->where(['Comments.id' => 1]);
  834. })
  835. ->extract('id')
  836. ->toList();
  837. $this->assertEquals([2], $results);
  838. }
  839. /**
  840. * Test that empty conditions in a matching clause don't cause errors.
  841. *
  842. * @return void
  843. */
  844. public function testMatchingEmptyQuery()
  845. {
  846. $table = TableRegistry::get('Articles');
  847. $table->belongsToMany('Tags');
  848. $rows = $table->find()
  849. ->matching('Tags', function ($q) {
  850. return $q->where([]);
  851. })
  852. ->all();
  853. $this->assertNotEmpty($rows);
  854. $rows = $table->find()
  855. ->matching('Tags', function ($q) {
  856. return $q->where(null);
  857. })
  858. ->all();
  859. $this->assertNotEmpty($rows);
  860. }
  861. /**
  862. * Tests that using a subquery as part of an expression will not make invalid SQL
  863. *
  864. * @return void
  865. */
  866. public function testSubqueryInSelectExpression()
  867. {
  868. $table = TableRegistry::get('Comments');
  869. $ratio = $table->find()
  870. ->select(function ($query) use ($table) {
  871. $allCommentsCount = $table->find()->select($query->func()->count('*'));
  872. $countToFloat = $query->newExpr([$query->func()->count('*'), '1.0'])->type('*');
  873. return [
  874. 'ratio' => $query
  875. ->newExpr($countToFloat)
  876. ->add($allCommentsCount)
  877. ->type('/')
  878. ];
  879. })
  880. ->where(['user_id' => 1])
  881. ->first()
  882. ->ratio;
  883. $this->assertEquals(0.5, $ratio);
  884. }
  885. /**
  886. * Tests calling last on an empty table
  887. *
  888. * @see https://github.com/cakephp/cakephp/issues/6683
  889. * @return void
  890. */
  891. public function testFindLastOnEmptyTable()
  892. {
  893. $table = TableRegistry::get('Comments');
  894. $table->deleteAll(['1 = 1']);
  895. $this->assertEquals(0, $table->find()->count());
  896. $this->assertNull($table->find()->last());
  897. }
  898. /**
  899. * Tests calling contain in a nested closure
  900. *
  901. * @see https://github.com/cakephp/cakephp/issues/7591
  902. * @return void
  903. */
  904. public function testContainInNestedClosure()
  905. {
  906. $table = TableRegistry::get('Comments');
  907. $table->belongsTo('Articles');
  908. $table->Articles->belongsTo('Authors');
  909. $table->Articles->Authors->belongsToMany('Tags');
  910. $query = $table->find()->where(['Comments.id' => 5])->contain(['Articles' => function ($q) {
  911. return $q->contain(['Authors' => function ($q) {
  912. return $q->contain('Tags');
  913. }]);
  914. }]);
  915. $this->assertCount(2, $query->first()->article->author->tags);
  916. }
  917. /**
  918. * Test that the typemaps used in function expressions
  919. * create the correct results.
  920. *
  921. * @return void
  922. */
  923. public function testTypemapInFunctions()
  924. {
  925. $table = TableRegistry::get('Comments');
  926. $table->updateAll(['published' => null], ['1 = 1']);
  927. $query = $table->find();
  928. $query->select([
  929. 'id',
  930. 'coalesced' => $query->func()->coalesce(
  931. ['published' => 'identifier', -1],
  932. ['integer']
  933. )
  934. ]);
  935. $result = $query->all()->first();
  936. $this->assertSame(
  937. -1,
  938. $result['coalesced'],
  939. 'Output values for functions should be casted'
  940. );
  941. }
  942. /**
  943. * Test that the typemaps used in function expressions
  944. * create the correct results.
  945. *
  946. * @return void
  947. */
  948. public function testTypemapInFunctions2()
  949. {
  950. $table = TableRegistry::get('Comments');
  951. $query = $table->find();
  952. $query->select([
  953. 'max' => $query->func()->max('created', ['datetime'])
  954. ]);
  955. $result = $query->all()->first();
  956. $this->assertEquals(new Time('2007-03-18 10:55:23'), $result['max']);
  957. }
  958. /**
  959. * Test that contain queries map types correctly.
  960. *
  961. * @return void
  962. */
  963. public function testBooleanConditionsInContain()
  964. {
  965. $table = TableRegistry::get('Articles');
  966. $table->belongsToMany('Tags', [
  967. 'foreignKey' => 'article_id',
  968. 'associationForeignKey' => 'tag_id',
  969. 'through' => 'SpecialTags'
  970. ]);
  971. $query = $table->find()
  972. ->contain(['Tags' => function ($q) {
  973. return $q->where(['SpecialTags.highlighted_time >' => new Time('2014-06-01 00:00:00')]);
  974. }])
  975. ->where(['Articles.id' => 2]);
  976. $result = $query->first();
  977. $this->assertEquals(2, $result->id);
  978. $this->assertNotEmpty($result->tags, 'Missing tags');
  979. $this->assertNotEmpty($result->tags[0]->_joinData, 'Missing join data');
  980. }
  981. /**
  982. * Test that contain queries map types correctly.
  983. *
  984. * @return void
  985. */
  986. public function testComplexTypesInJoinedWhere()
  987. {
  988. $table = TableRegistry::get('Users');
  989. $table->hasOne('Comments', [
  990. 'foreignKey' => 'user_id',
  991. ]);
  992. $query = $table->find()
  993. ->contain('Comments')
  994. ->where([
  995. 'Comments.updated >' => new \DateTime('2007-03-18 10:55:00')
  996. ]);
  997. $result = $query->first();
  998. $this->assertNotEmpty($result);
  999. $this->assertInstanceOf('Cake\I18n\Time', $result->comment->updated);
  1000. }
  1001. /**
  1002. * Test that nested contain queries map types correctly.
  1003. *
  1004. * @return void
  1005. */
  1006. public function testComplexNestedTypesInJoinedWhere()
  1007. {
  1008. $table = TableRegistry::get('Users');
  1009. $table->hasOne('Comments', [
  1010. 'foreignKey' => 'user_id',
  1011. ]);
  1012. $table->Comments->belongsTo('Articles');
  1013. $table->Comments->Articles->belongsTo('Authors', [
  1014. 'className' => 'Users',
  1015. 'foreignKey' => 'author_id'
  1016. ]);
  1017. $query = $table->find()
  1018. ->contain('Comments.Articles.Authors')
  1019. ->where([
  1020. 'Authors.created >' => new \DateTime('2007-03-17 01:16:00')
  1021. ]);
  1022. $result = $query->first();
  1023. $this->assertNotEmpty($result);
  1024. $this->assertInstanceOf('Cake\I18n\Time', $result->comment->article->author->updated);
  1025. }
  1026. /**
  1027. * Tests that it is possible to use matching with dot notation
  1028. * even when part of the part of the path in the dot notation is
  1029. * shared for two different calls
  1030. *
  1031. * @return void
  1032. */
  1033. public function testDotNotationNotOverride()
  1034. {
  1035. $table = TableRegistry::get('Comments');
  1036. $articles = $table->belongsTo('Articles');
  1037. $specialTags = $articles->hasMany('SpecialTags');
  1038. $specialTags->belongsTo('Authors');
  1039. $specialTags->belongsTo('Tags');
  1040. $results = $table
  1041. ->find()
  1042. ->select(['name' => 'Authors.name', 'tag' => 'Tags.name'])
  1043. ->matching('Articles.SpecialTags.Tags')
  1044. ->matching('Articles.SpecialTags.Authors', function ($q) {
  1045. return $q->where(['Authors.id' => 2]);
  1046. })
  1047. ->distinct()
  1048. ->hydrate(false)
  1049. ->toArray();
  1050. $this->assertEquals([['name' => 'nate', 'tag' => 'tag1']], $results);
  1051. }
  1052. /**
  1053. * Test expression based ordering with unions.
  1054. *
  1055. * @return void
  1056. */
  1057. public function testComplexOrderWithUnion()
  1058. {
  1059. $table = TableRegistry::get('Comments');
  1060. $query = $table->find();
  1061. $inner = $table->find()
  1062. ->select(['content' => 'comment'])
  1063. ->where(['id >' => 3]);
  1064. $inner2 = $table->find()
  1065. ->select(['content' => 'comment'])
  1066. ->where(['id <' => 3]);
  1067. $order = $query->func()->concat(['content' => 'literal', 'test']);
  1068. $query->select(['inside.content'])
  1069. ->from(['inside' => $inner->unionAll($inner2)])
  1070. ->orderAsc($order);
  1071. $results = $query->toArray();
  1072. $this->assertCount(5, $results);
  1073. }
  1074. /**
  1075. * Test that associations that are loaded with subqueries
  1076. * do not cause errors when the subquery has a limit & order clause.
  1077. *
  1078. * @return void
  1079. */
  1080. public function testEagerLoadOrderAndSubquery()
  1081. {
  1082. $table = TableRegistry::get('Articles');
  1083. $table->hasMany('Comments', [
  1084. 'strategy' => 'subquery'
  1085. ]);
  1086. $query = $table->find()
  1087. ->select(['score' => 100])
  1088. ->autoFields(true)
  1089. ->contain(['Comments'])
  1090. ->limit(5)
  1091. ->order(['score' => 'desc']);
  1092. $result = $query->all();
  1093. $this->assertCount(3, $result);
  1094. }
  1095. /**
  1096. * Tests that decorating the results does not result in a memory leak
  1097. *
  1098. * @return void
  1099. */
  1100. public function testFormatResultsMemoryLeak()
  1101. {
  1102. $this->skipIf(env('CODECOVERAGE') == 1, 'Running coverage this causes this tests to fail sometimes.');
  1103. $table = TableRegistry::get('Articles');
  1104. $table->belongsTo('Authors');
  1105. $table->belongsToMany('Tags');
  1106. gc_collect_cycles();
  1107. $memory = memory_get_usage() / 1024 / 1024;
  1108. foreach (range(1, 3) as $time) {
  1109. $table->find()
  1110. ->contain(['Authors', 'Tags'])
  1111. ->formatResults(function ($results) {
  1112. return $results;
  1113. })
  1114. ->all();
  1115. }
  1116. gc_collect_cycles();
  1117. $endMemory = memory_get_usage() / 1024 / 1024;
  1118. $this->assertWithinRange($endMemory, $memory, 1.25, 'Memory leak in ResultSet');
  1119. }
  1120. /**
  1121. * Tests that having bound placeholders in the order clause does not result
  1122. * in an error when trying to count a query.
  1123. *
  1124. * @return void
  1125. */
  1126. public function testCountWithComplexOrderBy()
  1127. {
  1128. $table = TableRegistry::get('Articles');
  1129. $query = $table->find();
  1130. $query->orderDesc($query->newExpr()->addCase(
  1131. [$query->newExpr()->add(['id' => 3])],
  1132. [1, 0]
  1133. ));
  1134. $query->order(['title' => 'desc']);
  1135. // Executing the normal query before getting the count
  1136. $query->all();
  1137. $this->assertEquals(3, $query->count());
  1138. $table = TableRegistry::get('Articles');
  1139. $query = $table->find();
  1140. $query->orderDesc($query->newExpr()->addCase(
  1141. [$query->newExpr()->add(['id' => 3])],
  1142. [1, 0]
  1143. ));
  1144. $query->orderDesc($query->newExpr()->add(['id' => 3]));
  1145. // Not executing the query first, just getting the count
  1146. $this->assertEquals(3, $query->count());
  1147. }
  1148. /**
  1149. * Tests that the now() function expression can be used in the
  1150. * where clause of a query
  1151. *
  1152. * @see https://github.com/cakephp/cakephp/issues/7943
  1153. * @return void
  1154. */
  1155. public function testFunctionInWhereClause()
  1156. {
  1157. $table = TableRegistry::get('Comments');
  1158. $table->updateAll(['updated' => Time::tomorrow()], ['id' => 6]);
  1159. $query = $table->find();
  1160. $result = $query->where(['updated >' => $query->func()->now('datetime')])->first();
  1161. $this->assertSame(6, $result->id);
  1162. }
  1163. }