ConnectionTest.php 46 KB

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