QueryTest.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875
  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 MIT License (http://www.opensource.org/licenses/mit-license.php)
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\Database\Expression\IdentifierExpression;
  17. use Cake\Database\Expression\OrderByExpression;
  18. use Cake\Database\Expression\QueryExpression;
  19. use Cake\Database\TypeMap;
  20. use Cake\Datasource\ConnectionManager;
  21. use Cake\ORM\Query;
  22. use Cake\ORM\ResultSet;
  23. use Cake\ORM\Table;
  24. use Cake\ORM\TableRegistry;
  25. use Cake\TestSuite\TestCase;
  26. /**
  27. * Tests Query class
  28. *
  29. */
  30. class QueryTest extends TestCase {
  31. /**
  32. * Fixture to be used
  33. *
  34. * @var array
  35. */
  36. public $fixtures = ['core.article', 'core.author', 'core.tag',
  37. 'core.articles_tag', 'core.post'];
  38. /**
  39. * setUp method
  40. *
  41. * @return void
  42. */
  43. public function setUp() {
  44. parent::setUp();
  45. $this->connection = ConnectionManager::get('test');
  46. $schema = [
  47. 'id' => ['type' => 'integer'],
  48. '_constraints' => [
  49. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  50. ]
  51. ];
  52. $schema1 = [
  53. 'id' => ['type' => 'integer'],
  54. 'name' => ['type' => 'string'],
  55. 'phone' => ['type' => 'string'],
  56. '_constraints' => [
  57. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  58. ]
  59. ];
  60. $schema2 = [
  61. 'id' => ['type' => 'integer'],
  62. 'total' => ['type' => 'string'],
  63. 'placed' => ['type' => 'datetime'],
  64. '_constraints' => [
  65. 'primary' => ['type' => 'primary', 'columns' => ['id']]
  66. ]
  67. ];
  68. $this->table = $table = TableRegistry::get('foo', ['schema' => $schema]);
  69. $clients = TableRegistry::get('clients', ['schema' => $schema1]);
  70. $orders = TableRegistry::get('orders', ['schema' => $schema2]);
  71. $companies = TableRegistry::get('companies', ['schema' => $schema, 'table' => 'organizations']);
  72. $orderTypes = TableRegistry::get('orderTypes', ['schema' => $schema]);
  73. $stuff = TableRegistry::get('stuff', ['schema' => $schema, 'table' => 'things']);
  74. $stuffTypes = TableRegistry::get('stuffTypes', ['schema' => $schema]);
  75. $categories = TableRegistry::get('categories', ['schema' => $schema]);
  76. $table->belongsTo('clients');
  77. $clients->hasOne('orders');
  78. $clients->belongsTo('companies');
  79. $orders->belongsTo('orderTypes');
  80. $orders->hasOne('stuff');
  81. $stuff->belongsTo('stuffTypes');
  82. $companies->belongsTo('categories');
  83. }
  84. /**
  85. * tearDown method
  86. *
  87. * @return void
  88. */
  89. public function tearDown() {
  90. parent::tearDown();
  91. TableRegistry::clear();
  92. }
  93. /**
  94. * Tests that results are grouped correctly when using contain()
  95. * and results are not hydrated
  96. *
  97. * @return void
  98. */
  99. public function testContainResultFetchingOneLevel() {
  100. $table = TableRegistry::get('articles', ['table' => 'articles']);
  101. $table->belongsTo('authors');
  102. $query = new Query($this->connection, $table);
  103. $results = $query->select()
  104. ->contain('authors')
  105. ->hydrate(false)
  106. ->order(['articles.id' => 'asc'])
  107. ->toArray();
  108. $expected = [
  109. [
  110. 'id' => 1,
  111. 'title' => 'First Article',
  112. 'body' => 'First Article Body',
  113. 'author_id' => 1,
  114. 'published' => 'Y',
  115. 'author' => [
  116. 'id' => 1,
  117. 'name' => 'mariano'
  118. ]
  119. ],
  120. [
  121. 'id' => 2,
  122. 'title' => 'Second Article',
  123. 'body' => 'Second Article Body',
  124. 'author_id' => 3,
  125. 'published' => 'Y',
  126. 'author' => [
  127. 'id' => 3,
  128. 'name' => 'larry'
  129. ]
  130. ],
  131. [
  132. 'id' => 3,
  133. 'title' => 'Third Article',
  134. 'body' => 'Third Article Body',
  135. 'author_id' => 1,
  136. 'published' => 'Y',
  137. 'author' => [
  138. 'id' => 1,
  139. 'name' => 'mariano'
  140. ]
  141. ],
  142. ];
  143. $this->assertEquals($expected, $results);
  144. }
  145. /**
  146. * Data provider for the two types of strategies HasMany implements
  147. *
  148. * @return void
  149. */
  150. public function strategiesProvider() {
  151. return [['subquery'], ['select']];
  152. }
  153. /**
  154. * Tests that HasMany associations are correctly eager loaded and results
  155. * correctly nested when no hydration is used
  156. * Also that the query object passes the correct parent model keys to the
  157. * association objects in order to perform eager loading with select strategy
  158. *
  159. * @dataProvider strategiesProvider
  160. * @return void
  161. */
  162. public function testHasManyEagerLoadingNoHydration($strategy) {
  163. $table = TableRegistry::get('authors');
  164. TableRegistry::get('articles');
  165. $table->hasMany('articles', [
  166. 'propertyName' => 'articles',
  167. 'strategy' => $strategy,
  168. 'sort' => ['articles.id' => 'asc']
  169. ]);
  170. $query = new Query($this->connection, $table);
  171. $results = $query->select()
  172. ->contain('articles')
  173. ->hydrate(false)
  174. ->toArray();
  175. $expected = [
  176. [
  177. 'id' => 1,
  178. 'name' => 'mariano',
  179. 'articles' => [
  180. [
  181. 'id' => 1,
  182. 'title' => 'First Article',
  183. 'body' => 'First Article Body',
  184. 'author_id' => 1,
  185. 'published' => 'Y',
  186. ],
  187. [
  188. 'id' => 3,
  189. 'title' => 'Third Article',
  190. 'body' => 'Third Article Body',
  191. 'author_id' => 1,
  192. 'published' => 'Y',
  193. ],
  194. ]
  195. ],
  196. [
  197. 'id' => 2,
  198. 'name' => 'nate',
  199. ],
  200. [
  201. 'id' => 3,
  202. 'name' => 'larry',
  203. 'articles' => [
  204. [
  205. 'id' => 2,
  206. 'title' => 'Second Article',
  207. 'body' => 'Second Article Body',
  208. 'author_id' => 3,
  209. 'published' => 'Y'
  210. ]
  211. ]
  212. ],
  213. [
  214. 'id' => 4,
  215. 'name' => 'garrett',
  216. ]
  217. ];
  218. $this->assertEquals($expected, $results);
  219. $results = $query->repository($table)
  220. ->select()
  221. ->contain(['articles' => ['conditions' => ['id' => 2]]])
  222. ->hydrate(false)
  223. ->toArray();
  224. unset($expected[0]['articles']);
  225. $this->assertEquals($expected, $results);
  226. $this->assertEquals($table->association('articles')->strategy(), $strategy);
  227. }
  228. /**
  229. * Tests that it is possible to count results containing hasMany associations
  230. * both hydrating and not hydrating the results.
  231. *
  232. * @dataProvider strategiesProvider
  233. * @return void
  234. */
  235. public function testHasManyEagerLoadingCount($strategy) {
  236. $table = TableRegistry::get('authors');
  237. TableRegistry::get('articles');
  238. $table->hasMany('articles', [
  239. 'property' => 'articles',
  240. 'strategy' => $strategy,
  241. 'sort' => ['articles.id' => 'asc']
  242. ]);
  243. $query = new Query($this->connection, $table);
  244. $query = $query->select()
  245. ->contain('articles');
  246. $expected = 4;
  247. $results = $query->hydrate(false)
  248. ->count();
  249. $this->assertEquals($expected, $results);
  250. $results = $query->hydrate(true)
  251. ->count();
  252. $this->assertEquals($expected, $results);
  253. }
  254. /**
  255. * Tests that it is possible to set fields & order in a hasMany result set
  256. *
  257. * @dataProvider strategiesProvider
  258. * @return void
  259. **/
  260. public function testHasManyEagerLoadingFieldsAndOrderNoHydration($strategy) {
  261. $table = TableRegistry::get('authors');
  262. TableRegistry::get('articles');
  263. $table->hasMany('articles', ['propertyName' => 'articles'] + compact('strategy'));
  264. $query = new Query($this->connection, $table);
  265. $results = $query->select()
  266. ->contain([
  267. 'articles' => [
  268. 'fields' => ['title', 'author_id'],
  269. 'sort' => ['id' => 'DESC']
  270. ]
  271. ])
  272. ->hydrate(false)
  273. ->toArray();
  274. $expected = [
  275. [
  276. 'id' => 1,
  277. 'name' => 'mariano',
  278. 'articles' => [
  279. ['title' => 'Third Article', 'author_id' => 1],
  280. ['title' => 'First Article', 'author_id' => 1],
  281. ]
  282. ],
  283. [
  284. 'id' => 2,
  285. 'name' => 'nate',
  286. ],
  287. [
  288. 'id' => 3,
  289. 'name' => 'larry',
  290. 'articles' => [
  291. ['title' => 'Second Article', 'author_id' => 3],
  292. ]
  293. ],
  294. [
  295. 'id' => 4,
  296. 'name' => 'garrett',
  297. ],
  298. ];
  299. $this->assertEquals($expected, $results);
  300. }
  301. /**
  302. * Tests that deep associations can be eagerly loaded
  303. *
  304. * @dataProvider strategiesProvider
  305. * @return void
  306. */
  307. public function testHasManyEagerLoadingDeep($strategy) {
  308. $table = TableRegistry::get('authors');
  309. $article = TableRegistry::get('articles');
  310. $table->hasMany('articles', [
  311. 'propertyName' => 'articles',
  312. 'strategy' => $strategy,
  313. 'sort' => ['articles.id' => 'asc']
  314. ]);
  315. $article->belongsTo('authors');
  316. $query = new Query($this->connection, $table);
  317. $results = $query->select()
  318. ->contain(['articles' => ['authors']])
  319. ->hydrate(false)
  320. ->toArray();
  321. $expected = [
  322. [
  323. 'id' => 1,
  324. 'name' => 'mariano',
  325. 'articles' => [
  326. [
  327. 'id' => 1,
  328. 'title' => 'First Article',
  329. 'author_id' => 1,
  330. 'body' => 'First Article Body',
  331. 'published' => 'Y',
  332. 'author' => ['id' => 1, 'name' => 'mariano']
  333. ],
  334. [
  335. 'id' => 3,
  336. 'title' => 'Third Article',
  337. 'author_id' => 1,
  338. 'body' => 'Third Article Body',
  339. 'published' => 'Y',
  340. 'author' => ['id' => 1, 'name' => 'mariano']
  341. ],
  342. ]
  343. ],
  344. [
  345. 'id' => 2,
  346. 'name' => 'nate'
  347. ],
  348. [
  349. 'id' => 3,
  350. 'name' => 'larry',
  351. 'articles' => [
  352. [
  353. 'id' => 2,
  354. 'title' => 'Second Article',
  355. 'author_id' => 3,
  356. 'body' => 'Second Article Body',
  357. 'published' => 'Y',
  358. 'author' => ['id' => 3, 'name' => 'larry']
  359. ],
  360. ]
  361. ],
  362. [
  363. 'id' => 4,
  364. 'name' => 'garrett'
  365. ]
  366. ];
  367. $this->assertEquals($expected, $results);
  368. }
  369. /**
  370. * Tests that hasMany associations can be loaded even when related to a secondary
  371. * model in the query
  372. *
  373. * @dataProvider strategiesProvider
  374. * @return void
  375. */
  376. public function testHasManyEagerLoadingFromSecondaryTable($strategy) {
  377. $author = TableRegistry::get('authors');
  378. $article = TableRegistry::get('articles');
  379. $post = TableRegistry::get('posts');
  380. $author->hasMany('posts', compact('strategy'));
  381. $article->belongsTo('authors');
  382. $query = new Query($this->connection, $article);
  383. $results = $query->select()
  384. ->contain(['authors' => ['posts']])
  385. ->order(['articles.id' => 'ASC'])
  386. ->hydrate(false)
  387. ->toArray();
  388. $expected = [
  389. [
  390. 'id' => 1,
  391. 'title' => 'First Article',
  392. 'body' => 'First Article Body',
  393. 'author_id' => 1,
  394. 'published' => 'Y',
  395. 'author' => [
  396. 'id' => 1,
  397. 'name' => 'mariano',
  398. 'posts' => [
  399. [
  400. 'id' => '1',
  401. 'title' => 'First Post',
  402. 'body' => 'First Post Body',
  403. 'author_id' => 1,
  404. 'published' => 'Y',
  405. ],
  406. [
  407. 'id' => '3',
  408. 'title' => 'Third Post',
  409. 'body' => 'Third Post Body',
  410. 'author_id' => 1,
  411. 'published' => 'Y',
  412. ],
  413. ]
  414. ]
  415. ],
  416. [
  417. 'id' => 2,
  418. 'title' => 'Second Article',
  419. 'body' => 'Second Article Body',
  420. 'author_id' => 3,
  421. 'published' => 'Y',
  422. 'author' => [
  423. 'id' => 3,
  424. 'name' => 'larry',
  425. 'posts' => [
  426. [
  427. 'id' => 2,
  428. 'title' => 'Second Post',
  429. 'body' => 'Second Post Body',
  430. 'author_id' => 3,
  431. 'published' => 'Y',
  432. ]
  433. ]
  434. ]
  435. ],
  436. [
  437. 'id' => 3,
  438. 'title' => 'Third Article',
  439. 'body' => 'Third Article Body',
  440. 'author_id' => 1,
  441. 'published' => 'Y',
  442. 'author' => [
  443. 'id' => 1,
  444. 'name' => 'mariano',
  445. 'posts' => [
  446. [
  447. 'id' => '1',
  448. 'title' => 'First Post',
  449. 'body' => 'First Post Body',
  450. 'author_id' => 1,
  451. 'published' => 'Y',
  452. ],
  453. [
  454. 'id' => '3',
  455. 'title' => 'Third Post',
  456. 'body' => 'Third Post Body',
  457. 'author_id' => 1,
  458. 'published' => 'Y',
  459. ],
  460. ]
  461. ]
  462. ],
  463. ];
  464. $this->assertEquals($expected, $results);
  465. }
  466. /**
  467. * Tests that BelongsToMany associations are correctly eager loaded.
  468. * Also that the query object passes the correct parent model keys to the
  469. * association objects in order to perform eager loading with select strategy
  470. *
  471. * @dataProvider strategiesProvider
  472. * @return void
  473. **/
  474. public function testBelongsToManyEagerLoadingNoHydration($strategy) {
  475. $table = TableRegistry::get('Articles');
  476. TableRegistry::get('Tags');
  477. TableRegistry::get('ArticlesTags', [
  478. 'table' => 'articles_tags'
  479. ]);
  480. $table->belongsToMany('Tags', ['strategy' => $strategy]);
  481. $query = new Query($this->connection, $table);
  482. $results = $query->select()->contain('Tags')->hydrate(false)->toArray();
  483. $expected = [
  484. [
  485. 'id' => 1,
  486. 'author_id' => 1,
  487. 'title' => 'First Article',
  488. 'body' => 'First Article Body',
  489. 'published' => 'Y',
  490. 'tags' => [
  491. [
  492. 'id' => 1,
  493. 'name' => 'tag1',
  494. '_joinData' => ['article_id' => 1, 'tag_id' => 1]
  495. ],
  496. [
  497. 'id' => 2,
  498. 'name' => 'tag2',
  499. '_joinData' => ['article_id' => 1, 'tag_id' => 2]
  500. ]
  501. ]
  502. ],
  503. [
  504. 'id' => 2,
  505. 'title' => 'Second Article',
  506. 'body' => 'Second Article Body',
  507. 'author_id' => 3,
  508. 'published' => 'Y',
  509. 'tags' => [
  510. [
  511. 'id' => 1,
  512. 'name' => 'tag1',
  513. '_joinData' => ['article_id' => 2, 'tag_id' => 1]
  514. ],
  515. [
  516. 'id' => 3,
  517. 'name' => 'tag3',
  518. '_joinData' => ['article_id' => 2, 'tag_id' => 3]
  519. ]
  520. ]
  521. ],
  522. [
  523. 'id' => 3,
  524. 'title' => 'Third Article',
  525. 'body' => 'Third Article Body',
  526. 'author_id' => 1,
  527. 'published' => 'Y',
  528. ],
  529. ];
  530. $this->assertEquals($expected, $results);
  531. $results = $query->select()
  532. ->contain(['Tags' => ['conditions' => ['id' => 3]]])
  533. ->hydrate(false)
  534. ->toArray();
  535. $expected = [
  536. [
  537. 'id' => 1,
  538. 'author_id' => 1,
  539. 'title' => 'First Article',
  540. 'body' => 'First Article Body',
  541. 'published' => 'Y',
  542. ],
  543. [
  544. 'id' => 2,
  545. 'title' => 'Second Article',
  546. 'body' => 'Second Article Body',
  547. 'author_id' => 3,
  548. 'published' => 'Y',
  549. 'tags' => [
  550. [
  551. 'id' => 3,
  552. 'name' => 'tag3',
  553. '_joinData' => ['article_id' => 2, 'tag_id' => 3]
  554. ]
  555. ]
  556. ],
  557. [
  558. 'id' => 3,
  559. 'title' => 'Third Article',
  560. 'body' => 'Third Article Body',
  561. 'author_id' => 1,
  562. 'published' => 'Y',
  563. ],
  564. ];
  565. $this->assertEquals($expected, $results);
  566. $this->assertEquals($table->association('Tags')->strategy(), $strategy);
  567. }
  568. /**
  569. * Tests that tables results can be filtered by the result of a HasMany
  570. *
  571. * @return void
  572. */
  573. public function testFilteringByHasManyNoHydration() {
  574. $query = new Query($this->connection, $this->table);
  575. $table = TableRegistry::get('authors');
  576. TableRegistry::get('articles');
  577. $table->hasMany('articles');
  578. $results = $query->repository($table)
  579. ->select()
  580. ->hydrate(false)
  581. ->matching('articles', function($q) {
  582. return $q->where(['articles.id' => 2]);
  583. })
  584. ->toArray();
  585. $expected = [
  586. [
  587. 'id' => 3,
  588. 'name' => 'larry',
  589. 'articles' => [
  590. 'id' => 2,
  591. 'title' => 'Second Article',
  592. 'body' => 'Second Article Body',
  593. 'author_id' => 3,
  594. 'published' => 'Y',
  595. ]
  596. ]
  597. ];
  598. $this->assertEquals($expected, $results);
  599. }
  600. /**
  601. * Tests that BelongsToMany associations are correctly eager loaded.
  602. * Also that the query object passes the correct parent model keys to the
  603. * association objects in order to perform eager loading with select strategy
  604. *
  605. * @return void
  606. **/
  607. public function testFilteringByBelongsToManyNoHydration() {
  608. $query = new Query($this->connection, $this->table);
  609. $table = TableRegistry::get('Articles');
  610. TableRegistry::get('Tags');
  611. TableRegistry::get('ArticlesTags', [
  612. 'table' => 'articles_tags'
  613. ]);
  614. $table->belongsToMany('Tags');
  615. $results = $query->repository($table)->select()
  616. ->matching('Tags', function($q) {
  617. return $q->where(['Tags.id' => 3]);
  618. })
  619. ->hydrate(false)
  620. ->toArray();
  621. $expected = [
  622. [
  623. 'id' => 2,
  624. 'author_id' => 3,
  625. 'title' => 'Second Article',
  626. 'body' => 'Second Article Body',
  627. 'published' => 'Y',
  628. 'tags' => [
  629. 'id' => 3,
  630. 'name' => 'tag3'
  631. ]
  632. ]
  633. ];
  634. $this->assertEquals($expected, $results);
  635. $query = new Query($this->connection, $table);
  636. $results = $query->select()
  637. ->matching('Tags', function($q) {
  638. return $q->where(['Tags.name' => 'tag2']);
  639. })
  640. ->hydrate(false)
  641. ->toArray();
  642. $expected = [
  643. [
  644. 'id' => 1,
  645. 'title' => 'First Article',
  646. 'body' => 'First Article Body',
  647. 'author_id' => 1,
  648. 'published' => 'Y',
  649. 'tags' => [
  650. 'id' => 2,
  651. 'name' => 'tag2'
  652. ]
  653. ]
  654. ];
  655. $this->assertEquals($expected, $results);
  656. }
  657. /**
  658. * Tests that it is possible to filter by deep associations
  659. *
  660. * @return void
  661. */
  662. public function testMatchingDotNotation() {
  663. $query = new Query($this->connection, $this->table);
  664. $table = TableRegistry::get('authors');
  665. TableRegistry::get('articles');
  666. $table->hasMany('articles');
  667. TableRegistry::get('articles')->belongsToMany('tags');
  668. $results = $query->repository($table)
  669. ->select()
  670. ->hydrate(false)
  671. ->matching('articles.tags', function($q) {
  672. return $q->where(['tags.id' => 2]);
  673. })
  674. ->toArray();
  675. $expected = [
  676. [
  677. 'id' => 1,
  678. 'name' => 'mariano',
  679. 'articles' => [
  680. 'id' => 1,
  681. 'title' => 'First Article',
  682. 'body' => 'First Article Body',
  683. 'author_id' => 1,
  684. 'published' => 'Y',
  685. 'tags' => [
  686. 'id' => 2,
  687. 'name' => 'tag2'
  688. ]
  689. ]
  690. ]
  691. ];
  692. $this->assertEquals($expected, $results);
  693. }
  694. /**
  695. * Test setResult()
  696. *
  697. * @return void
  698. */
  699. public function testSetResult() {
  700. $query = new Query($this->connection, $this->table);
  701. $stmt = $this->getMock('Cake\Database\StatementInterface');
  702. $results = new ResultSet($query, $stmt);
  703. $query->setResult($results);
  704. $this->assertSame($results, $query->all());
  705. }
  706. /**
  707. * Tests that applying array options to a query will convert them
  708. * to equivalent function calls with the correspondent array values
  709. *
  710. * @return void
  711. */
  712. public function testApplyOptions() {
  713. $options = [
  714. 'fields' => ['field_a', 'field_b'],
  715. 'conditions' => ['field_a' => 1, 'field_b' => 'something'],
  716. 'limit' => 1,
  717. 'order' => ['a' => 'ASC'],
  718. 'offset' => 5,
  719. 'group' => ['field_a'],
  720. 'having' => ['field_a >' => 100],
  721. 'contain' => ['table_a' => ['table_b']],
  722. 'join' => ['table_a' => ['conditions' => ['a > b']]]
  723. ];
  724. $query = new Query($this->connection, $this->table);
  725. $query->applyOptions($options);
  726. $this->assertEquals(['field_a', 'field_b'], $query->clause('select'));
  727. $typeMap = new TypeMap(['foo.id' => 'integer', 'id' => 'integer']);
  728. $expected = new QueryExpression($options['conditions']);
  729. $expected->typeMap($typeMap);
  730. $result = $query->clause('where');
  731. $this->assertEquals($expected, $result);
  732. $this->assertEquals(1, $query->clause('limit'));
  733. $expected = new QueryExpression(['a > b']);
  734. $expected->typeMap($typeMap);
  735. $result = $query->clause('join');
  736. $this->assertEquals([
  737. 'table_a' => ['alias' => 'table_a', 'type' => 'INNER', 'conditions' => $expected]
  738. ], $result);
  739. $expected = new OrderByExpression(['a' => 'ASC']);
  740. $this->assertEquals($expected, $query->clause('order'));
  741. $this->assertEquals(5, $query->clause('offset'));
  742. $this->assertEquals(['field_a'], $query->clause('group'));
  743. $expected = new QueryExpression($options['having']);
  744. $expected->typeMap($typeMap);
  745. $this->assertEquals($expected, $query->clause('having'));
  746. $expected = ['table_a' => ['table_b' => []]];
  747. $this->assertEquals($expected, $query->contain());
  748. }
  749. /**
  750. * ApplyOptions should ignore null values.
  751. *
  752. * @return void
  753. */
  754. public function testApplyOptionsIgnoreNull() {
  755. $options = [
  756. 'fields' => null,
  757. ];
  758. $query = new Query($this->connection, $this->table);
  759. $query->applyOptions($options);
  760. $this->assertEquals([], $query->clause('select'));
  761. }
  762. /**
  763. * Tests getOptions() method
  764. *
  765. * @return void
  766. */
  767. public function testGetOptions() {
  768. $options = ['doABarrelRoll' => true, 'fields' => ['id', 'name']];
  769. $query = new Query($this->connection, $this->table);
  770. $query->applyOptions($options);
  771. $expected = ['doABarrelRoll' => true];
  772. $this->assertEquals($expected, $query->getOptions());
  773. $expected = ['doABarrelRoll' => false, 'doAwesome' => true];
  774. $query->applyOptions($expected);
  775. $this->assertEquals($expected, $query->getOptions());
  776. }
  777. /**
  778. * Tests registering mappers with mapReduce()
  779. *
  780. * @return void
  781. */
  782. public function testMapReduceOnlyMapper() {
  783. $mapper1 = function() {
  784. };
  785. $mapper2 = function() {
  786. };
  787. $query = new Query($this->connection, $this->table);
  788. $this->assertSame($query, $query->mapReduce($mapper1));
  789. $this->assertEquals(
  790. [['mapper' => $mapper1, 'reducer' => null]],
  791. $query->mapReduce()
  792. );
  793. $this->assertEquals($query, $query->mapReduce($mapper2));
  794. $result = $query->mapReduce();
  795. $this->assertSame(
  796. [
  797. ['mapper' => $mapper1, 'reducer' => null],
  798. ['mapper' => $mapper2, 'reducer' => null]
  799. ],
  800. $result
  801. );
  802. }
  803. /**
  804. * Tests registering mappers and reducers with mapReduce()
  805. *
  806. * @return void
  807. */
  808. public function testMapReduceBothMethods() {
  809. $mapper1 = function() {
  810. };
  811. $mapper2 = function() {
  812. };
  813. $reducer1 = function() {
  814. };
  815. $reducer2 = function() {
  816. };
  817. $query = new Query($this->connection, $this->table);
  818. $this->assertSame($query, $query->mapReduce($mapper1, $reducer1));
  819. $this->assertEquals(
  820. [['mapper' => $mapper1, 'reducer' => $reducer1]],
  821. $query->mapReduce()
  822. );
  823. $this->assertSame($query, $query->mapReduce($mapper2, $reducer2));
  824. $this->assertEquals(
  825. [
  826. ['mapper' => $mapper1, 'reducer' => $reducer1],
  827. ['mapper' => $mapper2, 'reducer' => $reducer2]
  828. ],
  829. $query->mapReduce()
  830. );
  831. }
  832. /**
  833. * Tests that it is possible to overwrite previous map reducers
  834. *
  835. * @return void
  836. */
  837. public function testOverwriteMapReduce() {
  838. $mapper1 = function() {
  839. };
  840. $mapper2 = function() {
  841. };
  842. $reducer1 = function() {
  843. };
  844. $reducer2 = function() {
  845. };
  846. $query = new Query($this->connection, $this->table);
  847. $this->assertEquals($query, $query->mapReduce($mapper1, $reducer1));
  848. $this->assertEquals(
  849. [['mapper' => $mapper1, 'reducer' => $reducer1]],
  850. $query->mapReduce()
  851. );
  852. $this->assertEquals($query, $query->mapReduce($mapper2, $reducer2, true));
  853. $this->assertEquals(
  854. [['mapper' => $mapper2, 'reducer' => $reducer2]],
  855. $query->mapReduce()
  856. );
  857. }
  858. /**
  859. * Tests that multiple map reducers can be stacked
  860. *
  861. * @return void
  862. */
  863. public function testResultsAreWrappedInMapReduce() {
  864. $params = [$this->connection, $this->table];
  865. $query = $this->getMock('\Cake\ORM\Query', ['execute'], $params);
  866. $statement = $this->getMock(
  867. '\Database\StatementInterface',
  868. ['fetch', 'closeCursor', 'rowCount']
  869. );
  870. $statement->expects($this->exactly(3))
  871. ->method('fetch')
  872. ->will($this->onConsecutiveCalls(['a' => 1], ['a' => 2], false));
  873. $query->expects($this->once())
  874. ->method('execute')
  875. ->will($this->returnValue($statement));
  876. $query->mapReduce(function($v, $k, $mr) {
  877. $mr->emit($v['a']);
  878. });
  879. $query->mapReduce(
  880. function($v, $k, $mr) {
  881. $mr->emitIntermediate($v, $k);
  882. },
  883. function($v, $k, $mr) {
  884. $mr->emit($v[0] + 1);
  885. }
  886. );
  887. $this->assertEquals([2, 3], iterator_to_array($query->all()));
  888. }
  889. /**
  890. * Tests first() method when the query has not been executed before
  891. *
  892. * @return void
  893. */
  894. public function testFirstDirtyQuery() {
  895. $table = TableRegistry::get('articles', ['table' => 'articles']);
  896. $query = new Query($this->connection, $table);
  897. $result = $query->select(['id'])->hydrate(false)->first();
  898. $this->assertEquals(['id' => 1], $result);
  899. $this->assertEquals(1, $query->clause('limit'));
  900. $result = $query->select(['id'])->first();
  901. $this->assertEquals(['id' => 1], $result);
  902. }
  903. /**
  904. * Tests that first can be called again on an already executed query
  905. *
  906. * @return void
  907. */
  908. public function testFirstCleanQuery() {
  909. $table = TableRegistry::get('articles', ['table' => 'articles']);
  910. $query = new Query($this->connection, $table);
  911. $query->select(['id'])->toArray();
  912. $first = $query->hydrate(false)->first();
  913. $this->assertEquals(['id' => 1], $first);
  914. $this->assertEquals(1, $query->clause('limit'));
  915. }
  916. /**
  917. * Tests that first() will not execute the same query twice
  918. *
  919. * @return void
  920. */
  921. public function testFirstSameResult() {
  922. $table = TableRegistry::get('articles', ['table' => 'articles']);
  923. $query = new Query($this->connection, $table);
  924. $query->select(['id'])->toArray();
  925. $first = $query->hydrate(false)->first();
  926. $resultSet = $query->all();
  927. $this->assertEquals(['id' => 1], $first);
  928. $this->assertSame($resultSet, $query->all());
  929. }
  930. /**
  931. * Tests that first can be called against a query with a mapReduce
  932. *
  933. * @return void
  934. */
  935. public function testFirstMapReduce() {
  936. $map = function($row, $key, $mapReduce) {
  937. $mapReduce->emitIntermediate($row['id'], 'id');
  938. };
  939. $reduce = function($values, $key, $mapReduce) {
  940. $mapReduce->emit(array_sum($values));
  941. };
  942. $table = TableRegistry::get('articles', ['table' => 'articles']);
  943. $query = new Query($this->connection, $table);
  944. $query->select(['id'])
  945. ->hydrate(false)
  946. ->mapReduce($map, $reduce);
  947. $first = $query->first();
  948. $this->assertEquals(1, $first);
  949. }
  950. /**
  951. * Testing hydrating a result set into Entity objects
  952. *
  953. * @return void
  954. */
  955. public function testHydrateSimple() {
  956. $table = TableRegistry::get('articles', ['table' => 'articles']);
  957. $query = new Query($this->connection, $table);
  958. $results = $query->select()->toArray();
  959. $this->assertCount(3, $results);
  960. foreach ($results as $r) {
  961. $this->assertInstanceOf('\Cake\ORM\Entity', $r);
  962. }
  963. $first = $results[0];
  964. $this->assertEquals(1, $first->id);
  965. $this->assertEquals(1, $first->author_id);
  966. $this->assertEquals('First Article', $first->title);
  967. $this->assertEquals('First Article Body', $first->body);
  968. $this->assertEquals('Y', $first->published);
  969. }
  970. /**
  971. * Tests that has many results are also hydrated correctly
  972. *
  973. * @return void
  974. */
  975. public function testHydrateHasMany() {
  976. $table = TableRegistry::get('authors');
  977. TableRegistry::get('articles');
  978. $table->hasMany('articles', [
  979. 'propertyName' => 'articles',
  980. 'sort' => ['articles.id' => 'asc']
  981. ]);
  982. $query = new Query($this->connection, $table);
  983. $results = $query->select()
  984. ->contain('articles')
  985. ->toArray();
  986. $first = $results[0];
  987. foreach ($first->articles as $r) {
  988. $this->assertInstanceOf('\Cake\ORM\Entity', $r);
  989. }
  990. $this->assertCount(2, $first->articles);
  991. $expected = [
  992. 'id' => 1,
  993. 'title' => 'First Article',
  994. 'body' => 'First Article Body',
  995. 'author_id' => 1,
  996. 'published' => 'Y',
  997. ];
  998. $this->assertEquals($expected, $first->articles[0]->toArray());
  999. $expected = [
  1000. 'id' => 3,
  1001. 'title' => 'Third Article',
  1002. 'author_id' => 1,
  1003. 'body' => 'Third Article Body',
  1004. 'published' => 'Y',
  1005. ];
  1006. $this->assertEquals($expected, $first->articles[1]->toArray());
  1007. }
  1008. /**
  1009. * Tests that belongsToMany associations are also correctly hydrated
  1010. *
  1011. * @return void
  1012. */
  1013. public function testHydrateBelongsToMany() {
  1014. $table = TableRegistry::get('Articles');
  1015. TableRegistry::get('Tags');
  1016. TableRegistry::get('ArticlesTags', [
  1017. 'table' => 'articles_tags'
  1018. ]);
  1019. $table->belongsToMany('Tags');
  1020. $query = new Query($this->connection, $table);
  1021. $results = $query
  1022. ->select()
  1023. ->contain('Tags')
  1024. ->toArray();
  1025. $first = $results[0];
  1026. foreach ($first->tags as $r) {
  1027. $this->assertInstanceOf('\Cake\ORM\Entity', $r);
  1028. }
  1029. $this->assertCount(2, $first->tags);
  1030. $expected = [
  1031. 'id' => 1,
  1032. 'name' => 'tag1',
  1033. '_joinData' => ['article_id' => 1, 'tag_id' => 1]
  1034. ];
  1035. $this->assertEquals($expected, $first->tags[0]->toArray());
  1036. $expected = [
  1037. 'id' => 2,
  1038. 'name' => 'tag2',
  1039. '_joinData' => ['article_id' => 1, 'tag_id' => 2]
  1040. ];
  1041. $this->assertEquals($expected, $first->tags[1]->toArray());
  1042. }
  1043. /**
  1044. * Tests that belongsTo relations are correctly hydrated
  1045. *
  1046. * @return void
  1047. */
  1048. public function testHydrateBelongsTo() {
  1049. $table = TableRegistry::get('articles');
  1050. TableRegistry::get('authors');
  1051. $table->belongsTo('authors');
  1052. $query = new Query($this->connection, $table);
  1053. $results = $query->select()
  1054. ->contain('authors')
  1055. ->order(['articles.id' => 'asc'])
  1056. ->toArray();
  1057. $this->assertCount(3, $results);
  1058. $first = $results[0];
  1059. $this->assertInstanceOf('\Cake\ORM\Entity', $first->author);
  1060. $expected = ['id' => 1, 'name' => 'mariano'];
  1061. $this->assertEquals($expected, $first->author->toArray());
  1062. }
  1063. /**
  1064. * Tests that deeply nested associations are also hydrated correctly
  1065. *
  1066. * @return void
  1067. */
  1068. public function testHydrateDeep() {
  1069. $table = TableRegistry::get('authors');
  1070. $article = TableRegistry::get('articles');
  1071. $table->hasMany('articles', [
  1072. 'propertyName' => 'articles',
  1073. 'sort' => ['articles.id' => 'asc']
  1074. ]);
  1075. $article->belongsTo('authors');
  1076. $query = new Query($this->connection, $table);
  1077. $results = $query->select()
  1078. ->contain(['articles' => ['authors']])
  1079. ->toArray();
  1080. $this->assertCount(4, $results);
  1081. $first = $results[0];
  1082. $this->assertInstanceOf('\Cake\ORM\Entity', $first->articles[0]->author);
  1083. $expected = ['id' => 1, 'name' => 'mariano'];
  1084. $this->assertEquals($expected, $first->articles[0]->author->toArray());
  1085. $this->assertFalse(isset($results[3]->articles));
  1086. }
  1087. /**
  1088. * Tests that it is possible to use a custom entity class
  1089. *
  1090. * @return void
  1091. */
  1092. public function testHydrateCustomObject() {
  1093. $class = $this->getMockClass('\Cake\ORM\Entity', ['fakeMethod']);
  1094. $table = TableRegistry::get('articles', [
  1095. 'table' => 'articles',
  1096. 'entityClass' => '\\' . $class
  1097. ]);
  1098. $query = new Query($this->connection, $table);
  1099. $results = $query->select()->toArray();
  1100. $this->assertCount(3, $results);
  1101. foreach ($results as $r) {
  1102. $this->assertInstanceOf($class, $r);
  1103. }
  1104. $first = $results[0];
  1105. $this->assertEquals(1, $first->id);
  1106. $this->assertEquals(1, $first->author_id);
  1107. $this->assertEquals('First Article', $first->title);
  1108. $this->assertEquals('First Article Body', $first->body);
  1109. $this->assertEquals('Y', $first->published);
  1110. }
  1111. /**
  1112. * Tests that has many results are also hydrated correctly
  1113. * when specified a custom entity class
  1114. *
  1115. * @return void
  1116. */
  1117. public function testHydrateHasManyCustomEntity() {
  1118. $authorEntity = $this->getMockClass('\Cake\ORM\Entity', ['foo']);
  1119. $articleEntity = $this->getMockClass('\Cake\ORM\Entity', ['foo']);
  1120. $table = TableRegistry::get('authors', [
  1121. 'entityClass' => '\\' . $authorEntity
  1122. ]);
  1123. TableRegistry::get('articles', [
  1124. 'entityClass' => '\\' . $articleEntity
  1125. ]);
  1126. $table->hasMany('articles', [
  1127. 'propertyName' => 'articles',
  1128. 'sort' => ['articles.id' => 'asc']
  1129. ]);
  1130. $query = new Query($this->connection, $table);
  1131. $results = $query->select()
  1132. ->contain('articles')
  1133. ->toArray();
  1134. $first = $results[0];
  1135. $this->assertInstanceOf($authorEntity, $first);
  1136. foreach ($first->articles as $r) {
  1137. $this->assertInstanceOf($articleEntity, $r);
  1138. }
  1139. $this->assertCount(2, $first->articles);
  1140. $expected = [
  1141. 'id' => 1,
  1142. 'title' => 'First Article',
  1143. 'body' => 'First Article Body',
  1144. 'author_id' => 1,
  1145. 'published' => 'Y',
  1146. ];
  1147. $this->assertEquals($expected, $first->articles[0]->toArray());
  1148. }
  1149. /**
  1150. * Tests that belongsTo relations are correctly hydrated into a custom entity class
  1151. *
  1152. * @return void
  1153. */
  1154. public function testHydrateBelongsToCustomEntity() {
  1155. $authorEntity = $this->getMockClass('\Cake\ORM\Entity', ['foo']);
  1156. $table = TableRegistry::get('articles');
  1157. TableRegistry::get('authors', [
  1158. 'entityClass' => '\\' . $authorEntity
  1159. ]);
  1160. $table->belongsTo('authors');
  1161. $query = new Query($this->connection, $table);
  1162. $results = $query->select()
  1163. ->contain('authors')
  1164. ->order(['articles.id' => 'asc'])
  1165. ->toArray();
  1166. $first = $results[0];
  1167. $this->assertInstanceOf($authorEntity, $first->author);
  1168. }
  1169. /**
  1170. * Test getting counts from queries.
  1171. *
  1172. * @return void
  1173. */
  1174. public function testCount() {
  1175. $table = TableRegistry::get('articles');
  1176. $result = $table->find('all')->count();
  1177. $this->assertSame(3, $result);
  1178. $query = $table->find('all')
  1179. ->where(['id >' => 1])
  1180. ->limit(1);
  1181. $result = $query->count();
  1182. $this->assertSame(2, $result);
  1183. $result = $query->all();
  1184. $this->assertCount(1, $result);
  1185. $this->assertEquals(2, $result->first()->id);
  1186. }
  1187. /**
  1188. * Test that count() returns correct results with group by.
  1189. *
  1190. * @return void
  1191. */
  1192. public function testCountWithGroup() {
  1193. $table = TableRegistry::get('articles');
  1194. $query = $table->find('all');
  1195. $query->select(['author_id', 's' => $query->func()->sum('id')])
  1196. ->group(['author_id']);
  1197. $result = $query->count();
  1198. $this->assertEquals(2, $result);
  1199. }
  1200. /**
  1201. * Tests that it is possible to provide a callback for calculating the count
  1202. * of a query
  1203. *
  1204. * @return void
  1205. */
  1206. public function testCountWithCustomCounter() {
  1207. $table = TableRegistry::get('articles');
  1208. $query = $table->find('all');
  1209. $query
  1210. ->select(['author_id', 's' => $query->func()->sum('id')])
  1211. ->where(['id >' => 2])
  1212. ->group(['author_id'])
  1213. ->counter(function($q) use ($query) {
  1214. $this->assertNotSame($q, $query);
  1215. return $q->select([], true)->group([], true)->count();
  1216. });
  1217. $result = $query->count();
  1218. $this->assertEquals(1, $result);
  1219. }
  1220. /**
  1221. * Test update method.
  1222. *
  1223. * @return void
  1224. */
  1225. public function testUpdate() {
  1226. $table = TableRegistry::get('articles');
  1227. $result = $table->query()
  1228. ->update()
  1229. ->set(['title' => 'First'])
  1230. ->execute();
  1231. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  1232. $this->assertTrue($result->rowCount() > 0);
  1233. }
  1234. /**
  1235. * Test insert method.
  1236. *
  1237. * @return void
  1238. */
  1239. public function testInsert() {
  1240. $table = TableRegistry::get('articles');
  1241. $result = $table->query()
  1242. ->insert(['title'])
  1243. ->values(['title' => 'First'])
  1244. ->values(['title' => 'Second'])
  1245. ->execute();
  1246. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  1247. $this->assertEquals(2, $result->rowCount());
  1248. }
  1249. /**
  1250. * Test delete method.
  1251. *
  1252. * @return void
  1253. */
  1254. public function testDelete() {
  1255. $table = TableRegistry::get('articles');
  1256. $result = $table->query()
  1257. ->delete()
  1258. ->where(['id >=' => 1])
  1259. ->execute();
  1260. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  1261. $this->assertTrue($result->rowCount() > 0);
  1262. }
  1263. /**
  1264. * Provides a list of collection methods that can be proxied
  1265. * from the query
  1266. *
  1267. * @return array
  1268. */
  1269. public function collectionMethodsProvider() {
  1270. $identity = function($a) {
  1271. return $a;
  1272. };
  1273. return [
  1274. ['filter', $identity],
  1275. ['reject', $identity],
  1276. ['every', $identity],
  1277. ['some', $identity],
  1278. ['contains', $identity],
  1279. ['map', $identity],
  1280. ['reduce', $identity],
  1281. ['extract', $identity],
  1282. ['max', $identity],
  1283. ['min', $identity],
  1284. ['sortBy', $identity],
  1285. ['groupBy', $identity],
  1286. ['countBy', $identity],
  1287. ['shuffle', $identity],
  1288. ['sample', $identity],
  1289. ['take', 1],
  1290. ['append', new \ArrayIterator],
  1291. ['compile', 1],
  1292. ];
  1293. }
  1294. /**
  1295. * Tests that query can proxy collection methods
  1296. *
  1297. * @dataProvider collectionMethodsProvider
  1298. * @return void
  1299. */
  1300. public function testCollectionProxy($method, $arg) {
  1301. $query = $this->getMock(
  1302. '\Cake\ORM\Query', ['all'],
  1303. [$this->connection, $this->table]
  1304. );
  1305. $query->select();
  1306. $resultSet = $this->getMock('\Cake\ORM\ResultSet', [], [$query, null]);
  1307. $query->expects($this->once())
  1308. ->method('all')
  1309. ->will($this->returnValue($resultSet));
  1310. $resultSet->expects($this->once())
  1311. ->method($method)
  1312. ->with($arg, 'extra')
  1313. ->will($this->returnValue(new \Cake\Collection\Collection([])));
  1314. $this->assertInstanceOf(
  1315. '\Cake\Collection\Collection',
  1316. $query->{$method}($arg, 'extra')
  1317. );
  1318. }
  1319. /**
  1320. * Tests that calling an inexistent method in query throws an
  1321. * exception
  1322. *
  1323. * @expectedException \BadMethodCallException
  1324. * @expectedExceptionMessage Unknown method "derpFilter"
  1325. * @return void
  1326. */
  1327. public function testCollectionProxyBadMethod() {
  1328. TableRegistry::get('articles')->find('all')->derpFilter();
  1329. }
  1330. /**
  1331. * cache() should fail on non select queries.
  1332. *
  1333. * @expectedException RuntimeException
  1334. * @return void
  1335. */
  1336. public function testCacheErrorOnNonSelect() {
  1337. $table = TableRegistry::get('articles', ['table' => 'articles']);
  1338. $query = new Query($this->connection, $table);
  1339. $query->insert(['test']);
  1340. $query->cache('my_key');
  1341. }
  1342. /**
  1343. * Integration test for query caching.
  1344. *
  1345. * @return void
  1346. */
  1347. public function testCacheReadIntegration() {
  1348. $query = $this->getMock(
  1349. '\Cake\ORM\Query', ['execute'],
  1350. [$this->connection, $this->table]
  1351. );
  1352. $resultSet = $this->getMock('\Cake\ORM\ResultSet', [], [$query, null]);
  1353. $query->expects($this->never())
  1354. ->method('execute');
  1355. $cacher = $this->getMock('Cake\Cache\CacheEngine');
  1356. $cacher->expects($this->once())
  1357. ->method('read')
  1358. ->with('my_key')
  1359. ->will($this->returnValue($resultSet));
  1360. $query->cache('my_key', $cacher)
  1361. ->where(['id' => 1]);
  1362. $results = $query->all();
  1363. $this->assertSame($resultSet, $results);
  1364. }
  1365. /**
  1366. * Integration test for query caching.
  1367. *
  1368. * @return void
  1369. */
  1370. public function testCacheWriteIntegration() {
  1371. $table = TableRegistry::get('Articles');
  1372. $query = new Query($this->connection, $table);
  1373. $query->select(['id', 'title']);
  1374. $cacher = $this->getMock('Cake\Cache\CacheEngine');
  1375. $cacher->expects($this->once())
  1376. ->method('write')
  1377. ->with(
  1378. 'my_key',
  1379. $this->isInstanceOf('Cake\ORM\ResultSet')
  1380. );
  1381. $query->cache('my_key', $cacher)
  1382. ->where(['id' => 1]);
  1383. $query->all();
  1384. }
  1385. /**
  1386. * Integration test to show filtering associations using contain and a closure
  1387. *
  1388. * @return void
  1389. */
  1390. public function testContainWithClosure() {
  1391. $table = TableRegistry::get('authors');
  1392. $table->hasMany('articles');
  1393. $query = new Query($this->connection, $table);
  1394. $query
  1395. ->select()
  1396. ->contain(['articles' => function($q) {
  1397. return $q->where(['articles.id' => 1]);
  1398. }]);
  1399. $ids = [];
  1400. foreach ($query as $entity) {
  1401. foreach ((array)$entity->articles as $article) {
  1402. $ids[] = $article->id;
  1403. }
  1404. }
  1405. $this->assertEquals([1], array_unique($ids));
  1406. }
  1407. /**
  1408. * Tests the formatResults method
  1409. *
  1410. * @return void
  1411. */
  1412. public function testFormatResults() {
  1413. $callback1 = function() {
  1414. };
  1415. $callback2 = function() {
  1416. };
  1417. $table = TableRegistry::get('authors');
  1418. $query = new Query($this->connection, $table);
  1419. $this->assertSame($query, $query->formatResults($callback1));
  1420. $this->assertSame([$callback1], $query->formatResults());
  1421. $this->assertSame($query, $query->formatResults($callback2));
  1422. $this->assertSame([$callback1, $callback2], $query->formatResults());
  1423. $query->formatResults($callback2, true);
  1424. $this->assertSame([$callback2], $query->formatResults());
  1425. $query->formatResults(null, true);
  1426. $this->assertSame([], $query->formatResults());
  1427. $query->formatResults($callback1);
  1428. $query->formatResults($callback2, $query::PREPEND);
  1429. $this->assertSame([$callback2, $callback1], $query->formatResults());
  1430. }
  1431. /**
  1432. * Test fetching results from a qurey with a custom formatter
  1433. *
  1434. * @return void
  1435. */
  1436. public function testQueryWithFormatter() {
  1437. $table = TableRegistry::get('authors');
  1438. $query = new Query($this->connection, $table);
  1439. $query->select()->formatResults(function($results, $q) use ($query) {
  1440. $this->assertSame($query, $q);
  1441. $this->assertInstanceOf('\Cake\ORM\ResultSet', $results);
  1442. return $results->indexBy('id');
  1443. });
  1444. $this->assertEquals([1, 2, 3, 4], array_keys($query->toArray()));
  1445. }
  1446. /**
  1447. * Test fetching results from a qurey with a two custom formatters
  1448. *
  1449. * @return void
  1450. */
  1451. public function testQueryWithStackedFormatters() {
  1452. $table = TableRegistry::get('authors');
  1453. $query = new Query($this->connection, $table);
  1454. $query->select()->formatResults(function($results, $q) use ($query) {
  1455. $this->assertSame($query, $q);
  1456. $this->assertInstanceOf('\Cake\ORM\ResultSet', $results);
  1457. return $results->indexBy('id');
  1458. });
  1459. $query->formatResults(function($results) {
  1460. return $results->extract('name');
  1461. });
  1462. $expected = [
  1463. 1 => 'mariano',
  1464. 2 => 'nate',
  1465. 3 => 'larry',
  1466. 4 => 'garrett'
  1467. ];
  1468. $this->assertEquals($expected, $query->toArray());
  1469. }
  1470. /**
  1471. * Tests that getting results from a query having a contained association
  1472. * will no attach joins twice if count() is called on it afterwards
  1473. *
  1474. * @return void
  1475. */
  1476. public function testCountWithContainCallingAll() {
  1477. $table = TableRegistry::get('articles');
  1478. $table->belongsTo('authors');
  1479. $query = $table->find()
  1480. ->select(['id', 'title'])
  1481. ->contain('authors')
  1482. ->limit(2);
  1483. $results = $query->all();
  1484. $this->assertCount(2, $results);
  1485. $this->assertEquals(3, $query->count());
  1486. }
  1487. /**
  1488. * Tests that it is possible to apply formatters inside the query builder
  1489. * for belongsTo associations
  1490. *
  1491. * @return void
  1492. */
  1493. public function testFormatBelongsToRecords() {
  1494. $table = TableRegistry::get('articles');
  1495. $table->belongsTo('authors');
  1496. $query = $table->find()
  1497. ->contain(['authors' => function($q) {
  1498. return $q
  1499. ->formatResults(function($authors) {
  1500. return $authors->map(function($author) {
  1501. $author->idCopy = $author->id;
  1502. return $author;
  1503. });
  1504. })
  1505. ->formatResults(function($authors) {
  1506. return $authors->map(function($author) {
  1507. $author->idCopy = $author->idCopy + 2;
  1508. return $author;
  1509. });
  1510. });
  1511. }]);
  1512. $query->formatResults(function($results) {
  1513. return $results->combine('id', 'author.idCopy');
  1514. });
  1515. $results = $query->toArray();
  1516. $expected = [1 => 3, 2 => 5, 3 => 3];
  1517. $this->assertEquals($expected, $results);
  1518. }
  1519. /**
  1520. * Tests it is possible to apply formatters to deep relations.
  1521. *
  1522. * @return void
  1523. */
  1524. public function testFormatDeepAssocationRecords() {
  1525. $table = TableRegistry::get('ArticlesTags');
  1526. $table->belongsTo('Articles');
  1527. $table->association('Articles')->target()->belongsTo('Authors');
  1528. $builder = function($q) {
  1529. return $q
  1530. ->formatResults(function($results) {
  1531. return $results->map(function($result) {
  1532. $result->idCopy = $result->id;
  1533. return $result;
  1534. });
  1535. })
  1536. ->formatResults(function($results) {
  1537. return $results->map(function($result) {
  1538. $result->idCopy = $result->idCopy + 2;
  1539. return $result;
  1540. });
  1541. });
  1542. };
  1543. $query = $table->find()
  1544. ->contain(['Articles' => $builder, 'Articles.Authors' => $builder]);
  1545. $query->formatResults(function($results) {
  1546. return $results->map(function($row) {
  1547. return sprintf(
  1548. '%s - %s - %s',
  1549. $row->tag_id,
  1550. $row->article->idCopy,
  1551. $row->article->author->idCopy
  1552. );
  1553. });
  1554. });
  1555. $expected = ['1 - 3 - 3', '2 - 3 - 3', '1 - 4 - 5', '3 - 4 - 5'];
  1556. $this->assertEquals($expected, $query->toArray());
  1557. }
  1558. /**
  1559. * Tests that formatters cna be applied to deep associaitons that are fetched using
  1560. * additional queries
  1561. *
  1562. * @return void
  1563. */
  1564. public function testFormatDeepDistantAssociationRecords() {
  1565. $table = TableRegistry::get('authors');
  1566. $table->hasMany('articles');
  1567. $articles = $table->association('articles')->target();
  1568. $articles->hasMany('articlesTags');
  1569. $articles->association('articlesTags')->target()->belongsTo('tags');
  1570. $query = $table->find()->contain(['articles.articlesTags.tags' => function($q) {
  1571. return $q->formatResults(function($results) {
  1572. return $results->map(function($tag) {
  1573. $tag->name .= ' - visited';
  1574. return $tag;
  1575. });
  1576. });
  1577. }]);
  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 it is possible to attach more association when using a query
  1590. * builder for other associaitons
  1591. *
  1592. * @return void
  1593. */
  1594. public function testContainInAssociationQuery() {
  1595. $table = TableRegistry::get('ArticlesTags');
  1596. $table->belongsTo('Articles');
  1597. $table->association('Articles')->target()->belongsTo('Authors');
  1598. $query = $table->find()->contain(['Articles' => function($q) {
  1599. return $q->contain('Authors');
  1600. }]);
  1601. $results = $query->extract('article.author.name')->toArray();
  1602. $expected = ['mariano', 'mariano', 'larry', 'larry'];
  1603. $this->assertEquals($expected, $results);
  1604. }
  1605. /**
  1606. * Tests that it is possible to apply more `matching` conditions inside query
  1607. * builders for associations
  1608. *
  1609. * @return void
  1610. */
  1611. public function testContainInAssociationMatching() {
  1612. $table = TableRegistry::get('authors');
  1613. $table->hasMany('articles');
  1614. $articles = $table->association('articles')->target();
  1615. $articles->hasMany('articlesTags');
  1616. $articles->association('articlesTags')->target()->belongsTo('tags');
  1617. $query = $table->find()->matching('articles.articlesTags', function($q) {
  1618. return $q->matching('tags', function($q) {
  1619. return $q->where(['tags.name' => 'tag3']);
  1620. });
  1621. });
  1622. $results = $query->toArray();
  1623. $this->assertCount(1, $results);
  1624. $this->assertEquals('tag3', $results[0]->articles->articles_tags->tag->name);
  1625. }
  1626. /**
  1627. * Tests __debugInfo
  1628. *
  1629. * @return void
  1630. */
  1631. public function testDebugInfo() {
  1632. $table = TableRegistry::get('authors');
  1633. $table->hasMany('articles');
  1634. $query = $table->find()
  1635. ->where(['id > ' => 1])
  1636. ->bufferResults(false)
  1637. ->hydrate(false)
  1638. ->matching('articles')
  1639. ->applyOptions(['foo' => 'bar'])
  1640. ->formatResults(function($results) {
  1641. return $results;
  1642. })
  1643. ->mapReduce(function($item, $key, $mr) {
  1644. $mr->emit($item);
  1645. });
  1646. $expected = [
  1647. 'sql' => $query->sql(),
  1648. 'params' => $query->valueBinder()->bindings(),
  1649. 'defaultTypes' => [
  1650. 'authors.id' => 'integer',
  1651. 'id' => 'integer',
  1652. 'authors.name' => 'string',
  1653. 'name' => 'string'
  1654. ],
  1655. 'decorators' => 0,
  1656. 'executed' => false,
  1657. 'hydrate' => false,
  1658. 'buffered' => false,
  1659. 'formatters' => 1,
  1660. 'mapReducers' => 1,
  1661. 'contain' => [
  1662. 'articles' => [
  1663. 'queryBuilder' => null,
  1664. 'matching' => true
  1665. ]
  1666. ],
  1667. 'extraOptions' => ['foo' => 'bar'],
  1668. 'repository' => $table
  1669. ];
  1670. $this->assertSame($expected, $query->__debugInfo());
  1671. }
  1672. /**
  1673. * Tests that the eagerLoaded function works and is transmitted correctly to eagerly
  1674. * loaded associations
  1675. *
  1676. * @return void
  1677. */
  1678. public function testEagerLoaded() {
  1679. $table = TableRegistry::get('authors');
  1680. $table->hasMany('articles');
  1681. $query = $table->find()->contain(['articles' => function($q) {
  1682. $this->assertTrue($q->eagerLoaded());
  1683. return $q;
  1684. }]);
  1685. $this->assertFalse($query->eagerLoaded());
  1686. $table->getEventManager()->attach(function($e, $q, $o, $primary) {
  1687. $this->assertTrue($primary);
  1688. }, 'Model.beforeFind');
  1689. TableRegistry::get('articles')
  1690. ->getEventManager()->attach(function($e, $q, $o, $primary) {
  1691. $this->assertFalse($primary);
  1692. }, 'Model.beforeFind');
  1693. $query->all();
  1694. }
  1695. /**
  1696. * Tests that columns from manual joins are also contained in the result set
  1697. *
  1698. * @return void
  1699. */
  1700. public function testColumnsFromJoin() {
  1701. $table = TableRegistry::get('articles');
  1702. $results = $table->find()
  1703. ->select(['title', 'person.name'])
  1704. ->join([
  1705. 'person' => [
  1706. 'table' => 'authors',
  1707. 'conditions' => ['person.id = articles.author_id']
  1708. ]
  1709. ])
  1710. ->order(['articles.id' => 'ASC'])
  1711. ->hydrate(false)
  1712. ->toArray();
  1713. $expected = [
  1714. ['title' => 'First Article', 'person' => ['name' => 'mariano']],
  1715. ['title' => 'Second Article', 'person' => ['name' => 'larry']],
  1716. ['title' => 'Third Article', 'person' => ['name' => 'mariano']],
  1717. ];
  1718. $this->assertSame($expected, $results);
  1719. }
  1720. }