QueryRegressionTest.php 65 KB

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