ConnectionTest.php 37 KB

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