ConnectionTest.php 41 KB

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