QueryRegressionTest.php 50 KB

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