ConnectionTest.php 38 KB

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