QueryTest.php 45 KB

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