QueryRegressionTest.php 50 KB

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