ConnectionTest.php 45 KB

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