ConnectionTest.php 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Database;
  16. use Cake\Core\Configure;
  17. use Cake\Database\Connection;
  18. use Cake\Database\Driver\Mysql;
  19. use Cake\Database\Exception\NestedTransactionRollbackException;
  20. use Cake\Datasource\ConnectionManager;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * Tests Connection class
  24. */
  25. class ConnectionTest extends TestCase
  26. {
  27. public $fixtures = ['core.things'];
  28. public function setUp()
  29. {
  30. parent::setUp();
  31. $this->connection = ConnectionManager::get('test');
  32. Configure::write('App.namespace', 'TestApp');
  33. }
  34. public function tearDown()
  35. {
  36. $this->connection->useSavePoints(false);
  37. unset($this->connection);
  38. parent::tearDown();
  39. }
  40. /**
  41. * Auxiliary method to build a mock for a driver so it can be injected into
  42. * the connection object
  43. *
  44. * @return \Cake\Database\Driver
  45. */
  46. public function getMockFormDriver()
  47. {
  48. $driver = $this->getMockBuilder('Cake\Database\Driver')->getMock();
  49. $driver->expects($this->once())
  50. ->method('enabled')
  51. ->will($this->returnValue(true));
  52. return $driver;
  53. }
  54. /**
  55. * Tests connecting to database
  56. *
  57. * @return void
  58. */
  59. public function testConnect()
  60. {
  61. $this->assertTrue($this->connection->connect());
  62. $this->assertTrue($this->connection->isConnected());
  63. }
  64. /**
  65. * Tests creating a connection using no driver throws an exception
  66. *
  67. * @expectedException \Cake\Database\Exception\MissingDriverException
  68. * @expectedExceptionMessage Database driver could not be found.
  69. * @return void
  70. */
  71. public function testNoDriver()
  72. {
  73. $connection = new Connection([]);
  74. }
  75. /**
  76. * Tests creating a connection using an invalid driver throws an exception
  77. *
  78. * @expectedException \Cake\Database\Exception\MissingDriverException
  79. * @expectedExceptionMessage Database driver could not be found.
  80. * @return void
  81. */
  82. public function testEmptyDriver()
  83. {
  84. $connection = new Connection(['driver' => false]);
  85. }
  86. /**
  87. * Tests creating a connection using an invalid driver throws an exception
  88. *
  89. * @expectedException \Cake\Database\Exception\MissingDriverException
  90. * @expectedExceptionMessage Database driver \Foo\InvalidDriver could not be found.
  91. * @return void
  92. */
  93. public function testMissingDriver()
  94. {
  95. $connection = new Connection(['driver' => '\Foo\InvalidDriver']);
  96. }
  97. /**
  98. * Tests trying to use a disabled driver throws an exception
  99. *
  100. * @expectedException \Cake\Database\Exception\MissingExtensionException
  101. * @expectedExceptionMessage Database driver DriverMock cannot be used due to a missing PHP extension or unmet dependency
  102. * @return void
  103. */
  104. public function testDisabledDriver()
  105. {
  106. $mock = $this->getMockBuilder(Mysql::class)
  107. ->setMethods(['enabled'])
  108. ->setMockClassName('DriverMock')
  109. ->getMock();
  110. $connection = new Connection(['driver' => $mock]);
  111. }
  112. /**
  113. * Tests that the `driver` option supports the short classname/plugin syntax.
  114. *
  115. * @return void
  116. */
  117. public function testDriverOptionClassNameSupport()
  118. {
  119. $connection = new Connection(['driver' => 'TestDriver']);
  120. $this->assertInstanceOf('\TestApp\Database\Driver\TestDriver', $connection->driver());
  121. $connection = new Connection(['driver' => 'TestPlugin.TestDriver']);
  122. $this->assertInstanceOf('\TestPlugin\Database\Driver\TestDriver', $connection->driver());
  123. list(, $name) = namespaceSplit(get_class($this->connection->driver()));
  124. $connection = new Connection(['driver' => $name]);
  125. $this->assertInstanceOf(get_class($this->connection->driver()), $connection->driver());
  126. }
  127. /**
  128. * Tests that connecting with invalid credentials or database name throws an exception
  129. *
  130. * @expectedException \Cake\Database\Exception\MissingConnectionException
  131. * @return void
  132. */
  133. public function testWrongCredentials()
  134. {
  135. $config = ConnectionManager::config('test');
  136. $this->skipIf(isset($config['url']), 'Datasource has dsn, skipping.');
  137. $connection = new Connection(['database' => '/dev/nonexistent'] + ConnectionManager::config('test'));
  138. $connection->connect();
  139. }
  140. /**
  141. * Tests creation of prepared statements
  142. *
  143. * @return void
  144. */
  145. public function testPrepare()
  146. {
  147. $sql = 'SELECT 1 + 1';
  148. $result = $this->connection->prepare($sql);
  149. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  150. $this->assertEquals($sql, $result->queryString);
  151. $query = $this->connection->newQuery()->select('1 + 1');
  152. $result = $this->connection->prepare($query);
  153. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  154. $sql = '#SELECT [`"\[]?1 \+ 1[`"\]]?#';
  155. $this->assertRegExp($sql, $result->queryString);
  156. }
  157. /**
  158. * Tests executing a simple query using bound values
  159. *
  160. * @return void
  161. */
  162. public function testExecuteWithArguments()
  163. {
  164. $sql = 'SELECT 1 + ?';
  165. $statement = $this->connection->execute($sql, [1], ['integer']);
  166. $this->assertCount(1, $statement);
  167. $result = $statement->fetch();
  168. $this->assertEquals([2], $result);
  169. $statement->closeCursor();
  170. $sql = 'SELECT 1 + ? + ? AS total';
  171. $statement = $this->connection->execute($sql, [2, 3], ['integer', 'integer']);
  172. $this->assertCount(1, $statement);
  173. $result = $statement->fetch('assoc');
  174. $this->assertEquals(['total' => 6], $result);
  175. $statement->closeCursor();
  176. $sql = 'SELECT 1 + :one + :two AS total';
  177. $statement = $this->connection->execute($sql, ['one' => 2, 'two' => 3], ['one' => 'integer', 'two' => 'integer']);
  178. $this->assertCount(1, $statement);
  179. $result = $statement->fetch('assoc');
  180. $statement->closeCursor();
  181. $this->assertEquals(['total' => 6], $result);
  182. }
  183. /**
  184. * Tests executing a query with params and associated types
  185. *
  186. * @return void
  187. */
  188. public function testExecuteWithArgumentsAndTypes()
  189. {
  190. $sql = "SELECT '2012-01-01' = ?";
  191. $statement = $this->connection->execute($sql, [new \DateTime('2012-01-01')], ['date']);
  192. $result = $statement->fetch();
  193. $statement->closeCursor();
  194. $this->assertTrue((bool)$result[0]);
  195. }
  196. /**
  197. * Tests that passing a unknown value to a query throws an exception
  198. *
  199. * @expectedException \InvalidArgumentException
  200. * @return void
  201. */
  202. public function testExecuteWithMissingType()
  203. {
  204. $sql = 'SELECT ?';
  205. $statement = $this->connection->execute($sql, [new \DateTime('2012-01-01')], ['bar']);
  206. }
  207. /**
  208. * Tests executing a query with no params also works
  209. *
  210. * @return void
  211. */
  212. public function testExecuteWithNoParams()
  213. {
  214. $sql = 'SELECT 1';
  215. $statement = $this->connection->execute($sql);
  216. $result = $statement->fetch();
  217. $this->assertCount(1, $result);
  218. $this->assertEquals([1], $result);
  219. $statement->closeCursor();
  220. }
  221. /**
  222. * Tests it is possible to insert data into a table using matching types by key name
  223. *
  224. * @return void
  225. */
  226. public function testInsertWithMatchingTypes()
  227. {
  228. $data = ['id' => '3', 'title' => 'a title', 'body' => 'a body'];
  229. $result = $this->connection->insert(
  230. 'things',
  231. $data,
  232. ['id' => 'integer', 'title' => 'string', 'body' => 'string']
  233. );
  234. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  235. $result->closeCursor();
  236. $result = $this->connection->execute('SELECT * from things where id = 3');
  237. $this->assertCount(1, $result);
  238. $row = $result->fetch('assoc');
  239. $result->closeCursor();
  240. $this->assertEquals($data, $row);
  241. }
  242. /**
  243. * Tests it is possible to insert data into a table using matching types by array position
  244. *
  245. * @return void
  246. */
  247. public function testInsertWithPositionalTypes()
  248. {
  249. $data = ['id' => '3', 'title' => 'a title', 'body' => 'a body'];
  250. $result = $this->connection->insert(
  251. 'things',
  252. $data,
  253. ['integer', 'string', 'string']
  254. );
  255. $result->closeCursor();
  256. $this->assertInstanceOf('Cake\Database\StatementInterface', $result);
  257. $result = $this->connection->execute('SELECT * from things where id = 3');
  258. $this->assertCount(1, $result);
  259. $row = $result->fetch('assoc');
  260. $result->closeCursor();
  261. $this->assertEquals($data, $row);
  262. }
  263. /**
  264. * Tests an statement class can be reused for multiple executions
  265. *
  266. * @return void
  267. */
  268. public function testStatementReusing()
  269. {
  270. $total = $this->connection->execute('SELECT COUNT(*) AS total FROM things');
  271. $result = $total->fetch('assoc');
  272. $this->assertEquals(2, $result['total']);
  273. $total->closeCursor();
  274. $total->execute();
  275. $result = $total->fetch('assoc');
  276. $this->assertEquals(2, $result['total']);
  277. $total->closeCursor();
  278. $result = $this->connection->execute('SELECT title, body FROM things');
  279. $row = $result->fetch('assoc');
  280. $this->assertEquals('a title', $row['title']);
  281. $this->assertEquals('a body', $row['body']);
  282. $row = $result->fetch('assoc');
  283. $result->closeCursor();
  284. $this->assertEquals('another title', $row['title']);
  285. $this->assertEquals('another body', $row['body']);
  286. $result->execute();
  287. $row = $result->fetch('assoc');
  288. $result->closeCursor();
  289. $this->assertEquals('a title', $row['title']);
  290. }
  291. /**
  292. * Tests that it is possible to pass PDO constants to the underlying statement
  293. * object for using alternate fetch types
  294. *
  295. * @return void
  296. */
  297. public function testStatementFetchObject()
  298. {
  299. $result = $this->connection->execute('SELECT title, body FROM things');
  300. $row = $result->fetch(\PDO::FETCH_OBJ);
  301. $this->assertEquals('a title', $row->title);
  302. $this->assertEquals('a body', $row->body);
  303. }
  304. /**
  305. * Tests rows can be updated without specifying any conditions nor types
  306. *
  307. * @return void
  308. */
  309. public function testUpdateWithoutConditionsNorTypes()
  310. {
  311. $title = 'changed the title!';
  312. $body = 'changed the body!';
  313. $this->connection->update('things', ['title' => $title, 'body' => $body]);
  314. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  315. $this->assertCount(2, $result);
  316. $result->closeCursor();
  317. }
  318. /**
  319. * Tests it is possible to use key => value conditions for update
  320. *
  321. * @return void
  322. */
  323. public function testUpdateWithConditionsNoTypes()
  324. {
  325. $title = 'changed the title!';
  326. $body = 'changed the body!';
  327. $this->connection->update('things', ['title' => $title, 'body' => $body], ['id' => 2]);
  328. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  329. $this->assertCount(1, $result);
  330. $result->closeCursor();
  331. }
  332. /**
  333. * Tests it is possible to use key => value and string conditions for update
  334. *
  335. * @return void
  336. */
  337. public function testUpdateWithConditionsCombinedNoTypes()
  338. {
  339. $title = 'changed the title!';
  340. $body = 'changed the body!';
  341. $this->connection->update('things', ['title' => $title, 'body' => $body], ['id' => 2, 'body is not null']);
  342. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  343. $this->assertCount(1, $result);
  344. $result->closeCursor();
  345. }
  346. /**
  347. * Tests you can bind types to update values
  348. *
  349. * @return void
  350. */
  351. public function testUpdateWithTypes()
  352. {
  353. $title = 'changed the title!';
  354. $body = new \DateTime('2012-01-01');
  355. $values = compact('title', 'body');
  356. $this->connection->update('things', $values, [], ['body' => 'date']);
  357. $result = $this->connection->execute('SELECT * FROM things WHERE title = :title AND body = :body', $values, ['body' => 'date']);
  358. $this->assertCount(2, $result);
  359. $row = $result->fetch('assoc');
  360. $this->assertEquals('2012-01-01', $row['body']);
  361. $row = $result->fetch('assoc');
  362. $this->assertEquals('2012-01-01', $row['body']);
  363. $result->closeCursor();
  364. }
  365. /**
  366. * Tests you can bind types to update values
  367. *
  368. * @return void
  369. */
  370. public function testUpdateWithConditionsAndTypes()
  371. {
  372. $title = 'changed the title!';
  373. $body = new \DateTime('2012-01-01');
  374. $values = compact('title', 'body');
  375. $this->connection->update('things', $values, ['id' => '1-string-parsed-as-int'], ['body' => 'date', 'id' => 'integer']);
  376. $result = $this->connection->execute('SELECT * FROM things WHERE title = :title AND body = :body', $values, ['body' => 'date']);
  377. $this->assertCount(1, $result);
  378. $row = $result->fetch('assoc');
  379. $this->assertEquals('2012-01-01', $row['body']);
  380. $result->closeCursor();
  381. }
  382. /**
  383. * Tests delete from table with no conditions
  384. *
  385. * @return void
  386. */
  387. public function testDeleteNoConditions()
  388. {
  389. $this->connection->delete('things');
  390. $result = $this->connection->execute('SELECT * FROM things');
  391. $this->assertCount(0, $result);
  392. $result->closeCursor();
  393. }
  394. /**
  395. * Tests delete from table with conditions
  396. * @return void
  397. */
  398. public function testDeleteWithConditions()
  399. {
  400. $this->connection->delete('things', ['id' => '1-rest-is-omitted'], ['id' => 'integer']);
  401. $result = $this->connection->execute('SELECT * FROM things');
  402. $this->assertCount(1, $result);
  403. $result->closeCursor();
  404. $this->connection->delete('things', ['id' => '1-rest-is-omitted'], ['id' => 'integer']);
  405. $result = $this->connection->execute('SELECT * FROM things');
  406. $this->assertCount(1, $result);
  407. $result->closeCursor();
  408. $this->connection->delete('things', ['id' => '2-rest-is-omitted'], ['id' => 'integer']);
  409. $result = $this->connection->execute('SELECT * FROM things');
  410. $this->assertCount(0, $result);
  411. $result->closeCursor();
  412. }
  413. /**
  414. * Tests that it is possible to use simple database transactions
  415. *
  416. * @return void
  417. */
  418. public function testSimpleTransactions()
  419. {
  420. $this->connection->begin();
  421. $this->connection->delete('things', ['id' => 1]);
  422. $this->connection->rollback();
  423. $result = $this->connection->execute('SELECT * FROM things');
  424. $this->assertCount(2, $result);
  425. $result->closeCursor();
  426. $this->connection->begin();
  427. $this->connection->delete('things', ['id' => 1]);
  428. $this->connection->commit();
  429. $result = $this->connection->execute('SELECT * FROM things');
  430. $this->assertCount(1, $result);
  431. }
  432. /**
  433. * Tests that it is possible to use virtualized nested transaction
  434. * with early rollback algorithm
  435. *
  436. * @return void
  437. */
  438. public function testVirtualNestedTransaction()
  439. {
  440. //starting 3 virtual transaction
  441. $this->connection->begin();
  442. $this->connection->begin();
  443. $this->connection->begin();
  444. $this->connection->delete('things', ['id' => 1]);
  445. $result = $this->connection->execute('SELECT * FROM things');
  446. $this->assertCount(1, $result);
  447. $this->connection->commit();
  448. $this->connection->rollback();
  449. $result = $this->connection->execute('SELECT * FROM things');
  450. $this->assertCount(2, $result);
  451. }
  452. /**
  453. * Tests that it is possible to use virtualized nested transaction
  454. * with early rollback algorithm
  455. *
  456. * @return void
  457. */
  458. public function testVirtualNestedTransaction2()
  459. {
  460. //starting 3 virtual transaction
  461. $this->connection->begin();
  462. $this->connection->begin();
  463. $this->connection->begin();
  464. $this->connection->delete('things', ['id' => 1]);
  465. $result = $this->connection->execute('SELECT * FROM things');
  466. $this->assertCount(1, $result);
  467. $this->connection->rollback();
  468. $result = $this->connection->execute('SELECT * FROM things');
  469. $this->assertCount(2, $result);
  470. }
  471. /**
  472. * Tests that it is possible to use virtualized nested transaction
  473. * with early rollback algorithm
  474. *
  475. * @return void
  476. */
  477. public function testVirtualNestedTransaction3()
  478. {
  479. //starting 3 virtual transaction
  480. $this->connection->begin();
  481. $this->connection->begin();
  482. $this->connection->begin();
  483. $this->connection->delete('things', ['id' => 1]);
  484. $result = $this->connection->execute('SELECT * FROM things');
  485. $this->assertCount(1, $result);
  486. $this->connection->commit();
  487. $this->connection->commit();
  488. $this->connection->commit();
  489. $result = $this->connection->execute('SELECT * FROM things');
  490. $this->assertCount(1, $result);
  491. }
  492. /**
  493. * Tests that it is possible to real use nested transactions
  494. *
  495. * @return void
  496. */
  497. public function testSavePoints()
  498. {
  499. $this->skipIf(!$this->connection->useSavePoints(true));
  500. $this->connection->begin();
  501. $this->connection->delete('things', ['id' => 1]);
  502. $result = $this->connection->execute('SELECT * FROM things');
  503. $this->assertCount(1, $result);
  504. $this->connection->begin();
  505. $this->connection->delete('things', ['id' => 2]);
  506. $result = $this->connection->execute('SELECT * FROM things');
  507. $this->assertCount(0, $result);
  508. $this->connection->rollback();
  509. $result = $this->connection->execute('SELECT * FROM things');
  510. $this->assertCount(1, $result);
  511. $this->connection->rollback();
  512. $result = $this->connection->execute('SELECT * FROM things');
  513. $this->assertCount(2, $result);
  514. }
  515. /**
  516. * Tests that it is possible to real use nested transactions
  517. *
  518. * @return void
  519. */
  520. public function testSavePoints2()
  521. {
  522. $this->skipIf(!$this->connection->useSavePoints(true));
  523. $this->connection->begin();
  524. $this->connection->delete('things', ['id' => 1]);
  525. $result = $this->connection->execute('SELECT * FROM things');
  526. $this->assertCount(1, $result);
  527. $this->connection->begin();
  528. $this->connection->delete('things', ['id' => 2]);
  529. $result = $this->connection->execute('SELECT * FROM things');
  530. $this->assertCount(0, $result);
  531. $this->connection->rollback();
  532. $result = $this->connection->execute('SELECT * FROM things');
  533. $this->assertCount(1, $result);
  534. $this->connection->commit();
  535. $result = $this->connection->execute('SELECT * FROM things');
  536. $this->assertCount(1, $result);
  537. }
  538. /**
  539. * Tests inTransaction()
  540. *
  541. * @return void
  542. */
  543. public function testInTransaction()
  544. {
  545. $this->connection->begin();
  546. $this->assertTrue($this->connection->inTransaction());
  547. $this->connection->begin();
  548. $this->assertTrue($this->connection->inTransaction());
  549. $this->connection->commit();
  550. $this->assertTrue($this->connection->inTransaction());
  551. $this->connection->commit();
  552. $this->assertFalse($this->connection->inTransaction());
  553. $this->connection->begin();
  554. $this->assertTrue($this->connection->inTransaction());
  555. $this->connection->begin();
  556. $this->connection->rollback();
  557. $this->assertFalse($this->connection->inTransaction());
  558. }
  559. /**
  560. * Tests inTransaction() with save points
  561. *
  562. * @return void
  563. */
  564. public function testInTransactionWithSavePoints()
  565. {
  566. $this->skipIf(
  567. $this->connection->driver() instanceof \Cake\Database\Driver\Sqlserver,
  568. 'SQLServer fails when this test is included.'
  569. );
  570. $this->skipIf(!$this->connection->useSavePoints(true));
  571. $this->connection->begin();
  572. $this->assertTrue($this->connection->inTransaction());
  573. $this->connection->begin();
  574. $this->assertTrue($this->connection->inTransaction());
  575. $this->connection->commit();
  576. $this->assertTrue($this->connection->inTransaction());
  577. $this->connection->commit();
  578. $this->assertFalse($this->connection->inTransaction());
  579. $this->connection->begin();
  580. $this->assertTrue($this->connection->inTransaction());
  581. $this->connection->begin();
  582. $this->connection->rollback();
  583. $this->assertTrue($this->connection->inTransaction());
  584. $this->connection->rollback();
  585. $this->assertFalse($this->connection->inTransaction());
  586. }
  587. /**
  588. * Tests connection can quote values to be safely used in query strings
  589. *
  590. * @return void
  591. */
  592. public function testQuote()
  593. {
  594. $this->skipIf(!$this->connection->supportsQuoting());
  595. $expected = "'2012-01-01'";
  596. $result = $this->connection->quote(new \DateTime('2012-01-01'), 'date');
  597. $this->assertEquals($expected, $result);
  598. $expected = "'1'";
  599. $result = $this->connection->quote(1, 'string');
  600. $this->assertEquals($expected, $result);
  601. $expected = "'hello'";
  602. $result = $this->connection->quote('hello', 'string');
  603. $this->assertEquals($expected, $result);
  604. }
  605. /**
  606. * Tests identifier quoting
  607. *
  608. * @return void
  609. */
  610. public function testQuoteIdentifier()
  611. {
  612. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlite')
  613. ->setMethods(['enabled'])
  614. ->getMock();
  615. $driver->expects($this->once())
  616. ->method('enabled')
  617. ->will($this->returnValue(true));
  618. $connection = new Connection(['driver' => $driver]);
  619. $result = $connection->quoteIdentifier('name');
  620. $expected = '"name"';
  621. $this->assertEquals($expected, $result);
  622. $result = $connection->quoteIdentifier('Model.*');
  623. $expected = '"Model".*';
  624. $this->assertEquals($expected, $result);
  625. $result = $connection->quoteIdentifier('MTD()');
  626. $expected = 'MTD()';
  627. $this->assertEquals($expected, $result);
  628. $result = $connection->quoteIdentifier('(sm)');
  629. $expected = '(sm)';
  630. $this->assertEquals($expected, $result);
  631. $result = $connection->quoteIdentifier('name AS x');
  632. $expected = '"name" AS "x"';
  633. $this->assertEquals($expected, $result);
  634. $result = $connection->quoteIdentifier('Model.name AS x');
  635. $expected = '"Model"."name" AS "x"';
  636. $this->assertEquals($expected, $result);
  637. $result = $connection->quoteIdentifier('Function(Something.foo)');
  638. $expected = 'Function("Something"."foo")';
  639. $this->assertEquals($expected, $result);
  640. $result = $connection->quoteIdentifier('Function(SubFunction(Something.foo))');
  641. $expected = 'Function(SubFunction("Something"."foo"))';
  642. $this->assertEquals($expected, $result);
  643. $result = $connection->quoteIdentifier('Function(Something.foo) AS x');
  644. $expected = 'Function("Something"."foo") AS "x"';
  645. $this->assertEquals($expected, $result);
  646. $result = $connection->quoteIdentifier('name-with-minus');
  647. $expected = '"name-with-minus"';
  648. $this->assertEquals($expected, $result);
  649. $result = $connection->quoteIdentifier('my-name');
  650. $expected = '"my-name"';
  651. $this->assertEquals($expected, $result);
  652. $result = $connection->quoteIdentifier('Foo-Model.*');
  653. $expected = '"Foo-Model".*';
  654. $this->assertEquals($expected, $result);
  655. $result = $connection->quoteIdentifier('Team.P%');
  656. $expected = '"Team"."P%"';
  657. $this->assertEquals($expected, $result);
  658. $result = $connection->quoteIdentifier('Team.G/G');
  659. $expected = '"Team"."G/G"';
  660. $result = $connection->quoteIdentifier('Model.name as y');
  661. $expected = '"Model"."name" AS "y"';
  662. $this->assertEquals($expected, $result);
  663. }
  664. /**
  665. * Tests default return vale for logger() function
  666. *
  667. * @return void
  668. */
  669. public function testLoggerDefault()
  670. {
  671. $logger = $this->connection->logger();
  672. $this->assertInstanceOf('Cake\Database\Log\QueryLogger', $logger);
  673. $this->assertSame($logger, $this->connection->logger());
  674. }
  675. /**
  676. * Tests that a custom logger object can be set
  677. *
  678. * @return void
  679. */
  680. public function testSetLogger()
  681. {
  682. $logger = new \Cake\Database\Log\QueryLogger;
  683. $this->connection->logger($logger);
  684. $this->assertSame($logger, $this->connection->logger());
  685. }
  686. /**
  687. * Tests that statements are decorated with a logger when logQueries is set to true
  688. *
  689. * @return void
  690. */
  691. public function testLoggerDecorator()
  692. {
  693. $logger = new \Cake\Database\Log\QueryLogger;
  694. $this->connection->logQueries(true);
  695. $this->connection->logger($logger);
  696. $st = $this->connection->prepare('SELECT 1');
  697. $this->assertInstanceOf('Cake\Database\Log\LoggingStatement', $st);
  698. $this->assertSame($logger, $st->logger());
  699. $this->connection->logQueries(false);
  700. $st = $this->connection->prepare('SELECT 1');
  701. $this->assertNotInstanceOf('\Cake\Database\Log\LoggingStatement', $st);
  702. }
  703. /**
  704. * test logQueries method
  705. *
  706. * @return void
  707. */
  708. public function testLogQueries()
  709. {
  710. $this->connection->logQueries(true);
  711. $this->assertTrue($this->connection->logQueries());
  712. $this->connection->logQueries(false);
  713. $this->assertFalse($this->connection->logQueries());
  714. }
  715. /**
  716. * Tests that log() function logs to the configured query logger
  717. *
  718. * @return void
  719. */
  720. public function testLogFunction()
  721. {
  722. $logger = $this->getMockBuilder('\Cake\Database\Log\QueryLogger')->getMock();
  723. $this->connection->logger($logger);
  724. $logger->expects($this->once())->method('log')
  725. ->with($this->logicalAnd(
  726. $this->isInstanceOf('\Cake\Database\Log\LoggedQuery'),
  727. $this->attributeEqualTo('query', 'SELECT 1')
  728. ));
  729. $this->connection->log('SELECT 1');
  730. }
  731. /**
  732. * Tests that begin and rollback are also logged
  733. *
  734. * @return void
  735. */
  736. public function testLogBeginRollbackTransaction()
  737. {
  738. $connection = $this
  739. ->getMockBuilder('\Cake\Database\Connection')
  740. ->setMethods(['connect'])
  741. ->disableOriginalConstructor()
  742. ->getMock();
  743. $connection->logQueries(true);
  744. $driver = $this->getMockFormDriver();
  745. $connection->driver($driver);
  746. $logger = $this->getMockBuilder('\Cake\Database\Log\QueryLogger')->getMock();
  747. $connection->logger($logger);
  748. $logger->expects($this->at(0))->method('log')
  749. ->with($this->logicalAnd(
  750. $this->isInstanceOf('\Cake\Database\Log\LoggedQuery'),
  751. $this->attributeEqualTo('query', 'BEGIN')
  752. ));
  753. $logger->expects($this->at(1))->method('log')
  754. ->with($this->logicalAnd(
  755. $this->isInstanceOf('\Cake\Database\Log\LoggedQuery'),
  756. $this->attributeEqualTo('query', 'ROLLBACK')
  757. ));
  758. $connection->begin();
  759. $connection->begin(); //This one will not be logged
  760. $connection->rollback();
  761. }
  762. /**
  763. * Tests that commits are logged
  764. *
  765. * @return void
  766. */
  767. public function testLogCommitTransaction()
  768. {
  769. $driver = $this->getMockFormDriver();
  770. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  771. ->setMethods(['connect'])
  772. ->setConstructorArgs([['driver' => $driver]])
  773. ->getMock();
  774. $logger = $this->getMockBuilder('\Cake\Database\Log\QueryLogger')->getMock();
  775. $connection->logger($logger);
  776. $logger->expects($this->at(1))->method('log')
  777. ->with($this->logicalAnd(
  778. $this->isInstanceOf('\Cake\Database\Log\LoggedQuery'),
  779. $this->attributeEqualTo('query', 'COMMIT')
  780. ));
  781. $connection->logQueries(true);
  782. $connection->begin();
  783. $connection->commit();
  784. }
  785. /**
  786. * Tests that the transactional method will start and commit a transaction
  787. * around some arbitrary function passed as argument
  788. *
  789. * @return void
  790. */
  791. public function testTransactionalSuccess()
  792. {
  793. $driver = $this->getMockFormDriver();
  794. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  795. ->setMethods(['connect', 'commit', 'begin'])
  796. ->setConstructorArgs([['driver' => $driver]])
  797. ->getMock();
  798. $connection->expects($this->at(0))->method('begin');
  799. $connection->expects($this->at(1))->method('commit');
  800. $result = $connection->transactional(function ($conn) use ($connection) {
  801. $this->assertSame($connection, $conn);
  802. return 'thing';
  803. });
  804. $this->assertEquals('thing', $result);
  805. }
  806. /**
  807. * Tests that the transactional method will rollback the transaction if false
  808. * is returned from the callback
  809. *
  810. * @return void
  811. */
  812. public function testTransactionalFail()
  813. {
  814. $driver = $this->getMockFormDriver();
  815. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  816. ->setMethods(['connect', 'commit', 'begin', 'rollback'])
  817. ->setConstructorArgs([['driver' => $driver]])
  818. ->getMock();
  819. $connection->expects($this->at(0))->method('begin');
  820. $connection->expects($this->at(1))->method('rollback');
  821. $connection->expects($this->never())->method('commit');
  822. $result = $connection->transactional(function ($conn) use ($connection) {
  823. $this->assertSame($connection, $conn);
  824. return false;
  825. });
  826. $this->assertFalse($result);
  827. }
  828. /**
  829. * Tests that the transactional method will rollback the transaction
  830. * and throw the same exception if the callback raises one
  831. *
  832. * @expectedException \InvalidArgumentException
  833. * @return void
  834. * @throws \InvalidArgumentException
  835. */
  836. public function testTransactionalWithException()
  837. {
  838. $driver = $this->getMockFormDriver();
  839. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  840. ->setMethods(['connect', 'commit', 'begin', 'rollback'])
  841. ->setConstructorArgs([['driver' => $driver]])
  842. ->getMock();
  843. $connection->expects($this->at(0))->method('begin');
  844. $connection->expects($this->at(1))->method('rollback');
  845. $connection->expects($this->never())->method('commit');
  846. $connection->transactional(function ($conn) use ($connection) {
  847. $this->assertSame($connection, $conn);
  848. throw new \InvalidArgumentException;
  849. });
  850. }
  851. /**
  852. * Tests it is possible to set a schema collection object
  853. *
  854. * @return void
  855. */
  856. public function testSchemaCollection()
  857. {
  858. $driver = $this->getMockFormDriver();
  859. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  860. ->setMethods(['connect'])
  861. ->setConstructorArgs([['driver' => $driver]])
  862. ->getMock();
  863. $schema = $connection->schemaCollection();
  864. $this->assertInstanceOf('Cake\Database\Schema\Collection', $schema);
  865. $schema = $this->getMockBuilder('Cake\Database\Schema\Collection')
  866. ->setConstructorArgs([$connection])
  867. ->getMock();
  868. $connection->schemaCollection($schema);
  869. $this->assertSame($schema, $connection->schemaCollection());
  870. }
  871. /**
  872. * Tests that allowed nesting of commit/rollback operations doesn't
  873. * throw any exceptions.
  874. *
  875. * @return void
  876. */
  877. public function testNestedTransactionRollbackExceptionNotThrown()
  878. {
  879. $this->connection->transactional(function () {
  880. $this->connection->transactional(function () {
  881. return true;
  882. });
  883. return true;
  884. });
  885. $this->assertFalse($this->connection->inTransaction());
  886. $this->connection->transactional(function () {
  887. $this->connection->transactional(function () {
  888. return true;
  889. });
  890. return false;
  891. });
  892. $this->assertFalse($this->connection->inTransaction());
  893. $this->connection->transactional(function () {
  894. $this->connection->transactional(function () {
  895. return false;
  896. });
  897. return false;
  898. });
  899. $this->assertFalse($this->connection->inTransaction());
  900. }
  901. /**
  902. * Tests that not allowed nesting of commit/rollback operations throws
  903. * a NestedTransactionRollbackException.
  904. *
  905. * @return void
  906. * @expectedException \Cake\Database\Exception\NestedTransactionRollbackException
  907. * @expectedExceptionMessage Cannot commit transaction - rollback() has been already called in the nested transaction
  908. */
  909. public function testNestedTransactionRollbackExceptionThrown()
  910. {
  911. // ROLLBACK -> COMMIT
  912. $this->connection->transactional(function () {
  913. $this->connection->transactional(function () {
  914. return false;
  915. });
  916. return true;
  917. });
  918. }
  919. /**
  920. * Tests mor detail about that not allowed nesting of rollback/commit
  921. * operations throws a NestedTransactionRollbackException by trace.
  922. *
  923. * @return void
  924. */
  925. public function testNestedTransactionRollbackExceptionByTrace()
  926. {
  927. $rollbackSourceLine = -1;
  928. $nestedTransactionStates = [];
  929. $e = null;
  930. try {
  931. $this->connection->transactional(function () use (&$rollbackSourceLine, &$nestedTransactionStates) {
  932. $nestedTransactionStates[] = $this->connection->wasNestedTransactionRolledback();
  933. $this->connection->transactional(function () {
  934. return true;
  935. });
  936. $this->connection->transactional(function () use (&$rollbackSourceLine, &$nestedTransactionStates) {
  937. $nestedTransactionStates[] = $this->connection->wasNestedTransactionRolledback();
  938. $this->connection->transactional(function () {
  939. return false;
  940. });
  941. $rollbackSourceLine = __LINE__ - 1;
  942. $nestedTransactionStates[] = $this->connection->wasNestedTransactionRolledback();
  943. return true;
  944. });
  945. $this->connection->transactional(function () {
  946. return false;
  947. });
  948. $nestedTransactionStates[] = $this->connection->wasNestedTransactionRolledback();
  949. return true;
  950. });
  951. } catch (NestedTransactionRollbackException $e) {
  952. }
  953. $nestedTransactionStates[] = $this->connection->wasNestedTransactionRolledback();
  954. $this->assertInstanceOf(NestedTransactionRollbackException::class, $e);
  955. $this->assertSame([false, false, true, true, false], $nestedTransactionStates);
  956. $this->assertFalse($this->connection->inTransaction());
  957. $trace = $e->getTrace();
  958. $this->assertEquals(__FILE__, $trace[1]['file']);
  959. $this->assertEquals($rollbackSourceLine, $trace[1]['line']);
  960. }
  961. }