ConnectionTest.php 37 KB

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