ConnectionTest.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389
  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. ->setMethods(['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. $result = $this->connection->execute('SELECT title, body FROM things');
  387. $row = $result->fetch(\PDO::FETCH_OBJ);
  388. $this->assertSame('a title', $row->title);
  389. $this->assertSame('a body', $row->body);
  390. }
  391. /**
  392. * Tests rows can be updated without specifying any conditions nor types
  393. *
  394. * @return void
  395. */
  396. public function testUpdateWithoutConditionsNorTypes()
  397. {
  398. $title = 'changed the title!';
  399. $body = 'changed the body!';
  400. $this->connection->update('things', ['title' => $title, 'body' => $body]);
  401. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  402. $this->assertCount(2, $result);
  403. $result->closeCursor();
  404. }
  405. /**
  406. * Tests it is possible to use key => value conditions for update
  407. *
  408. * @return void
  409. */
  410. public function testUpdateWithConditionsNoTypes()
  411. {
  412. $title = 'changed the title!';
  413. $body = 'changed the body!';
  414. $this->connection->update('things', ['title' => $title, 'body' => $body], ['id' => 2]);
  415. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  416. $this->assertCount(1, $result);
  417. $result->closeCursor();
  418. }
  419. /**
  420. * Tests it is possible to use key => value and string conditions for update
  421. *
  422. * @return void
  423. */
  424. public function testUpdateWithConditionsCombinedNoTypes()
  425. {
  426. $title = 'changed the title!';
  427. $body = 'changed the body!';
  428. $this->connection->update('things', ['title' => $title, 'body' => $body], ['id' => 2, 'body is not null']);
  429. $result = $this->connection->execute('SELECT * FROM things WHERE title = ? AND body = ?', [$title, $body]);
  430. $this->assertCount(1, $result);
  431. $result->closeCursor();
  432. }
  433. /**
  434. * Tests you can bind types to update values
  435. *
  436. * @return void
  437. */
  438. public function testUpdateWithTypes()
  439. {
  440. $title = 'changed the title!';
  441. $body = new \DateTime('2012-01-01');
  442. $values = compact('title', 'body');
  443. $this->connection->update('things', $values, [], ['body' => 'date']);
  444. $result = $this->connection->execute('SELECT * FROM things WHERE title = :title AND body = :body', $values, ['body' => 'date']);
  445. $this->assertCount(2, $result);
  446. $row = $result->fetch('assoc');
  447. $this->assertSame('2012-01-01', $row['body']);
  448. $row = $result->fetch('assoc');
  449. $this->assertSame('2012-01-01', $row['body']);
  450. $result->closeCursor();
  451. }
  452. /**
  453. * Tests you can bind types to update values
  454. *
  455. * @return void
  456. */
  457. public function testUpdateWithConditionsAndTypes()
  458. {
  459. $title = 'changed the title!';
  460. $body = new \DateTime('2012-01-01');
  461. $values = compact('title', 'body');
  462. $this->connection->update('things', $values, ['id' => '1'], ['body' => 'date', 'id' => 'integer']);
  463. $result = $this->connection->execute('SELECT * FROM things WHERE title = :title AND body = :body', $values, ['body' => 'date']);
  464. $this->assertCount(1, $result);
  465. $row = $result->fetch('assoc');
  466. $this->assertSame('2012-01-01', $row['body']);
  467. $result->closeCursor();
  468. }
  469. /**
  470. * Tests delete from table with no conditions
  471. *
  472. * @return void
  473. */
  474. public function testDeleteNoConditions()
  475. {
  476. $this->connection->delete('things');
  477. $result = $this->connection->execute('SELECT * FROM things');
  478. $this->assertCount(0, $result);
  479. $result->closeCursor();
  480. }
  481. /**
  482. * Tests delete from table with conditions
  483. *
  484. * @return void
  485. */
  486. public function testDeleteWithConditions()
  487. {
  488. $this->connection->delete('things', ['id' => '1'], ['id' => 'integer']);
  489. $result = $this->connection->execute('SELECT * FROM things');
  490. $this->assertCount(1, $result);
  491. $result->closeCursor();
  492. $this->connection->delete('things', ['id' => '1'], ['id' => 'integer']);
  493. $result = $this->connection->execute('SELECT * FROM things');
  494. $this->assertCount(1, $result);
  495. $result->closeCursor();
  496. $this->connection->delete('things', ['id' => '2'], ['id' => 'integer']);
  497. $result = $this->connection->execute('SELECT * FROM things');
  498. $this->assertCount(0, $result);
  499. $result->closeCursor();
  500. }
  501. /**
  502. * Tests that it is possible to use simple database transactions
  503. *
  504. * @return void
  505. */
  506. public function testSimpleTransactions()
  507. {
  508. $this->connection->begin();
  509. $this->connection->delete('things', ['id' => 1]);
  510. $this->connection->rollback();
  511. $result = $this->connection->execute('SELECT * FROM things');
  512. $this->assertCount(2, $result);
  513. $result->closeCursor();
  514. $this->connection->begin();
  515. $this->connection->delete('things', ['id' => 1]);
  516. $this->connection->commit();
  517. $result = $this->connection->execute('SELECT * FROM things');
  518. $this->assertCount(1, $result);
  519. }
  520. /**
  521. * Tests that the destructor of Connection generates a warning log
  522. * when transaction is not closed
  523. *
  524. * @return void
  525. */
  526. public function testDestructorWithUncommittedTransaction()
  527. {
  528. $driver = $this->getMockFormDriver();
  529. $connection = new Connection(['driver' => $driver]);
  530. $connection->begin();
  531. $this->assertTrue($connection->inTransaction());
  532. $logger = $this->createMock('Psr\Log\AbstractLogger');
  533. $logger->expects($this->once())
  534. ->method('log')
  535. ->with('warning', $this->stringContains('The connection is going to be closed'));
  536. Log::setConfig('error', $logger);
  537. // Destroy the connection
  538. unset($connection);
  539. }
  540. /**
  541. * Tests that it is possible to use virtualized nested transaction
  542. * with early rollback algorithm
  543. *
  544. * @return void
  545. */
  546. public function testVirtualNestedTransaction()
  547. {
  548. //starting 3 virtual transaction
  549. $this->connection->begin();
  550. $this->connection->begin();
  551. $this->connection->begin();
  552. $this->connection->delete('things', ['id' => 1]);
  553. $result = $this->connection->execute('SELECT * FROM things');
  554. $this->assertCount(1, $result);
  555. $this->connection->commit();
  556. $this->connection->rollback();
  557. $result = $this->connection->execute('SELECT * FROM things');
  558. $this->assertCount(2, $result);
  559. }
  560. /**
  561. * Tests that it is possible to use virtualized nested transaction
  562. * with early rollback algorithm
  563. *
  564. * @return void
  565. */
  566. public function testVirtualNestedTransaction2()
  567. {
  568. //starting 3 virtual transaction
  569. $this->connection->begin();
  570. $this->connection->begin();
  571. $this->connection->begin();
  572. $this->connection->delete('things', ['id' => 1]);
  573. $result = $this->connection->execute('SELECT * FROM things');
  574. $this->assertCount(1, $result);
  575. $this->connection->rollback();
  576. $result = $this->connection->execute('SELECT * FROM things');
  577. $this->assertCount(2, $result);
  578. }
  579. /**
  580. * Tests that it is possible to use virtualized nested transaction
  581. * with early rollback algorithm
  582. *
  583. * @return void
  584. */
  585. public function testVirtualNestedTransaction3()
  586. {
  587. //starting 3 virtual transaction
  588. $this->connection->begin();
  589. $this->connection->begin();
  590. $this->connection->begin();
  591. $this->connection->delete('things', ['id' => 1]);
  592. $result = $this->connection->execute('SELECT * FROM things');
  593. $this->assertCount(1, $result);
  594. $this->connection->commit();
  595. $this->connection->commit();
  596. $this->connection->commit();
  597. $result = $this->connection->execute('SELECT * FROM things');
  598. $this->assertCount(1, $result);
  599. }
  600. /**
  601. * Tests that it is possible to real use nested transactions
  602. *
  603. * @return void
  604. */
  605. public function testSavePoints()
  606. {
  607. $this->skipIf(!$this->connection->enableSavePoints(true));
  608. $this->connection->begin();
  609. $this->connection->delete('things', ['id' => 1]);
  610. $result = $this->connection->execute('SELECT * FROM things');
  611. $this->assertCount(1, $result);
  612. $this->connection->begin();
  613. $this->connection->delete('things', ['id' => 2]);
  614. $result = $this->connection->execute('SELECT * FROM things');
  615. $this->assertCount(0, $result);
  616. $this->connection->rollback();
  617. $result = $this->connection->execute('SELECT * FROM things');
  618. $this->assertCount(1, $result);
  619. $this->connection->rollback();
  620. $result = $this->connection->execute('SELECT * FROM things');
  621. $this->assertCount(2, $result);
  622. }
  623. /**
  624. * Tests that it is possible to real use nested transactions
  625. *
  626. * @return void
  627. */
  628. public function testSavePoints2()
  629. {
  630. $this->skipIf(!$this->connection->enableSavePoints(true));
  631. $this->connection->begin();
  632. $this->connection->delete('things', ['id' => 1]);
  633. $result = $this->connection->execute('SELECT * FROM things');
  634. $this->assertCount(1, $result);
  635. $this->connection->begin();
  636. $this->connection->delete('things', ['id' => 2]);
  637. $result = $this->connection->execute('SELECT * FROM things');
  638. $this->assertCount(0, $result);
  639. $this->connection->rollback();
  640. $result = $this->connection->execute('SELECT * FROM things');
  641. $this->assertCount(1, $result);
  642. $this->connection->commit();
  643. $result = $this->connection->execute('SELECT * FROM things');
  644. $this->assertCount(1, $result);
  645. }
  646. /**
  647. * Tests inTransaction()
  648. *
  649. * @return void
  650. */
  651. public function testInTransaction()
  652. {
  653. $this->connection->begin();
  654. $this->assertTrue($this->connection->inTransaction());
  655. $this->connection->begin();
  656. $this->assertTrue($this->connection->inTransaction());
  657. $this->connection->commit();
  658. $this->assertTrue($this->connection->inTransaction());
  659. $this->connection->commit();
  660. $this->assertFalse($this->connection->inTransaction());
  661. $this->connection->begin();
  662. $this->assertTrue($this->connection->inTransaction());
  663. $this->connection->begin();
  664. $this->connection->rollback();
  665. $this->assertFalse($this->connection->inTransaction());
  666. }
  667. /**
  668. * Tests inTransaction() with save points
  669. *
  670. * @return void
  671. */
  672. public function testInTransactionWithSavePoints()
  673. {
  674. $this->skipIf(
  675. $this->connection->getDriver() instanceof \Cake\Database\Driver\Sqlserver,
  676. 'SQLServer fails when this test is included.'
  677. );
  678. $this->skipIf(!$this->connection->enableSavePoints(true));
  679. $this->connection->begin();
  680. $this->assertTrue($this->connection->inTransaction());
  681. $this->connection->begin();
  682. $this->assertTrue($this->connection->inTransaction());
  683. $this->connection->commit();
  684. $this->assertTrue($this->connection->inTransaction());
  685. $this->connection->commit();
  686. $this->assertFalse($this->connection->inTransaction());
  687. $this->connection->begin();
  688. $this->assertTrue($this->connection->inTransaction());
  689. $this->connection->begin();
  690. $this->connection->rollback();
  691. $this->assertTrue($this->connection->inTransaction());
  692. $this->connection->rollback();
  693. $this->assertFalse($this->connection->inTransaction());
  694. }
  695. /**
  696. * Tests connection can quote values to be safely used in query strings
  697. *
  698. * @return void
  699. */
  700. public function testQuote()
  701. {
  702. $this->skipIf(!$this->connection->supportsQuoting());
  703. $expected = "'2012-01-01'";
  704. $result = $this->connection->quote(new \DateTime('2012-01-01'), 'date');
  705. $this->assertEquals($expected, $result);
  706. $expected = "'1'";
  707. $result = $this->connection->quote(1, 'string');
  708. $this->assertEquals($expected, $result);
  709. $expected = "'hello'";
  710. $result = $this->connection->quote('hello', 'string');
  711. $this->assertEquals($expected, $result);
  712. }
  713. /**
  714. * Tests identifier quoting
  715. *
  716. * @return void
  717. */
  718. public function testQuoteIdentifier()
  719. {
  720. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlite')
  721. ->setMethods(['enabled'])
  722. ->getMock();
  723. $driver->expects($this->once())
  724. ->method('enabled')
  725. ->will($this->returnValue(true));
  726. $connection = new Connection(['driver' => $driver]);
  727. $result = $connection->quoteIdentifier('name');
  728. $expected = '"name"';
  729. $this->assertEquals($expected, $result);
  730. $result = $connection->quoteIdentifier('Model.*');
  731. $expected = '"Model".*';
  732. $this->assertEquals($expected, $result);
  733. $result = $connection->quoteIdentifier('Items.No_ 2');
  734. $expected = '"Items"."No_ 2"';
  735. $this->assertEquals($expected, $result);
  736. $result = $connection->quoteIdentifier('Items.No_ 2 thing');
  737. $expected = '"Items"."No_ 2 thing"';
  738. $this->assertEquals($expected, $result);
  739. $result = $connection->quoteIdentifier('Items.No_ 2 thing AS thing');
  740. $expected = '"Items"."No_ 2 thing" AS "thing"';
  741. $this->assertEquals($expected, $result);
  742. $result = $connection->quoteIdentifier('Items.Item Category Code = :c1');
  743. $expected = '"Items"."Item Category Code" = :c1';
  744. $this->assertEquals($expected, $result);
  745. $result = $connection->quoteIdentifier('MTD()');
  746. $expected = 'MTD()';
  747. $this->assertEquals($expected, $result);
  748. $result = $connection->quoteIdentifier('(sm)');
  749. $expected = '(sm)';
  750. $this->assertEquals($expected, $result);
  751. $result = $connection->quoteIdentifier('name AS x');
  752. $expected = '"name" AS "x"';
  753. $this->assertEquals($expected, $result);
  754. $result = $connection->quoteIdentifier('Model.name AS x');
  755. $expected = '"Model"."name" AS "x"';
  756. $this->assertEquals($expected, $result);
  757. $result = $connection->quoteIdentifier('Function(Something.foo)');
  758. $expected = 'Function("Something"."foo")';
  759. $this->assertEquals($expected, $result);
  760. $result = $connection->quoteIdentifier('Function(SubFunction(Something.foo))');
  761. $expected = 'Function(SubFunction("Something"."foo"))';
  762. $this->assertEquals($expected, $result);
  763. $result = $connection->quoteIdentifier('Function(Something.foo) AS x');
  764. $expected = 'Function("Something"."foo") AS "x"';
  765. $this->assertEquals($expected, $result);
  766. $result = $connection->quoteIdentifier('name-with-minus');
  767. $expected = '"name-with-minus"';
  768. $this->assertEquals($expected, $result);
  769. $result = $connection->quoteIdentifier('my-name');
  770. $expected = '"my-name"';
  771. $this->assertEquals($expected, $result);
  772. $result = $connection->quoteIdentifier('Foo-Model.*');
  773. $expected = '"Foo-Model".*';
  774. $this->assertEquals($expected, $result);
  775. $result = $connection->quoteIdentifier('Team.P%');
  776. $expected = '"Team"."P%"';
  777. $this->assertEquals($expected, $result);
  778. $result = $connection->quoteIdentifier('Team.G/G');
  779. $expected = '"Team"."G/G"';
  780. $this->assertEquals($expected, $result);
  781. $result = $connection->quoteIdentifier('Model.name as y');
  782. $expected = '"Model"."name" AS "y"';
  783. $this->assertEquals($expected, $result);
  784. $result = $connection->quoteIdentifier('nämé');
  785. $expected = '"nämé"';
  786. $this->assertEquals($expected, $result);
  787. $result = $connection->quoteIdentifier('aßa.nämé');
  788. $expected = '"aßa"."nämé"';
  789. $this->assertEquals($expected, $result);
  790. $result = $connection->quoteIdentifier('aßa.*');
  791. $expected = '"aßa".*';
  792. $this->assertEquals($expected, $result);
  793. $result = $connection->quoteIdentifier('Modeß.nämé as y');
  794. $expected = '"Modeß"."nämé" AS "y"';
  795. $this->assertEquals($expected, $result);
  796. $result = $connection->quoteIdentifier('Model.näme Datum as y');
  797. $expected = '"Model"."näme Datum" AS "y"';
  798. $this->assertEquals($expected, $result);
  799. }
  800. /**
  801. * Tests default return vale for logger() function
  802. *
  803. * @return void
  804. */
  805. public function testGetLoggerDefault()
  806. {
  807. $logger = $this->connection->getLogger();
  808. $this->assertInstanceOf('Cake\Database\Log\QueryLogger', $logger);
  809. $this->assertSame($logger, $this->connection->getLogger());
  810. }
  811. /**
  812. * Tests setting and getting the logger object
  813. *
  814. * @return void
  815. */
  816. public function testGetAndSetLogger()
  817. {
  818. $logger = new QueryLogger();
  819. $this->connection->setLogger($logger);
  820. $this->assertSame($logger, $this->connection->getLogger());
  821. }
  822. /**
  823. * Tests that statements are decorated with a logger when logQueries is set to true
  824. *
  825. * @return void
  826. */
  827. public function testLoggerDecorator()
  828. {
  829. $logger = new QueryLogger();
  830. $this->connection->enableQueryLogging(true);
  831. $this->connection->setLogger($logger);
  832. $st = $this->connection->prepare('SELECT 1');
  833. $this->assertInstanceOf(LoggingStatement::class, $st);
  834. $this->assertSame($logger, $st->getLogger());
  835. $this->connection->enableQueryLogging(false);
  836. $st = $this->connection->prepare('SELECT 1');
  837. $this->assertNotInstanceOf('Cake\Database\Log\LoggingStatement', $st);
  838. }
  839. /**
  840. * test enableQueryLogging method
  841. *
  842. * @return void
  843. */
  844. public function testEnableQueryLogging()
  845. {
  846. $this->connection->enableQueryLogging(true);
  847. $this->assertTrue($this->connection->isQueryLoggingEnabled());
  848. $this->connection->disableQueryLogging();
  849. $this->assertFalse($this->connection->isQueryLoggingEnabled());
  850. }
  851. /**
  852. * Tests that log() function logs to the configured query logger
  853. *
  854. * @return void
  855. */
  856. public function testLogFunction()
  857. {
  858. Log::setConfig('queries', ['className' => 'Array']);
  859. $this->connection->enableQueryLogging();
  860. $this->connection->log('SELECT 1');
  861. $messages = Log::engine('queries')->read();
  862. $this->assertCount(1, $messages);
  863. $this->assertSame('debug connection=test duration=0 rows=0 SELECT 1', $messages[0]);
  864. }
  865. /**
  866. * Tests that begin and rollback are also logged
  867. *
  868. * @return void
  869. */
  870. public function testLogBeginRollbackTransaction()
  871. {
  872. Log::setConfig('queries', ['className' => 'Array']);
  873. $connection = $this
  874. ->getMockBuilder(Connection::class)
  875. ->setMethods(['connect'])
  876. ->disableOriginalConstructor()
  877. ->getMock();
  878. $connection->enableQueryLogging(true);
  879. $driver = $this->getMockFormDriver();
  880. $connection->setDriver($driver);
  881. $connection->begin();
  882. $connection->begin(); //This one will not be logged
  883. $connection->rollback();
  884. $messages = Log::engine('queries')->read();
  885. $this->assertCount(2, $messages);
  886. $this->assertSame('debug connection= duration=0 rows=0 BEGIN', $messages[0]);
  887. $this->assertSame('debug connection= duration=0 rows=0 ROLLBACK', $messages[1]);
  888. }
  889. /**
  890. * Tests that commits are logged
  891. *
  892. * @return void
  893. */
  894. public function testLogCommitTransaction()
  895. {
  896. $driver = $this->getMockFormDriver();
  897. $connection = $this->getMockBuilder(Connection::class)
  898. ->setMethods(['connect'])
  899. ->setConstructorArgs([['driver' => $driver]])
  900. ->getMock();
  901. Log::setConfig('queries', ['className' => 'Array']);
  902. $connection->enableQueryLogging(true);
  903. $connection->begin();
  904. $connection->commit();
  905. $messages = Log::engine('queries')->read();
  906. $this->assertCount(2, $messages);
  907. $this->assertSame('debug connection= duration=0 rows=0 BEGIN', $messages[0]);
  908. $this->assertSame('debug connection= duration=0 rows=0 COMMIT', $messages[1]);
  909. }
  910. /**
  911. * Tests setting and getting the cacher object
  912. *
  913. * @return void
  914. */
  915. public function testGetAndSetCacher()
  916. {
  917. $cacher = new NullEngine();
  918. $this->connection->setCacher($cacher);
  919. $this->assertSame($cacher, $this->connection->getCacher());
  920. }
  921. /**
  922. * Tests that the transactional method will start and commit a transaction
  923. * around some arbitrary function passed as argument
  924. *
  925. * @return void
  926. */
  927. public function testTransactionalSuccess()
  928. {
  929. $driver = $this->getMockFormDriver();
  930. $connection = $this->getMockBuilder(Connection::class)
  931. ->setMethods(['connect', 'commit', 'begin'])
  932. ->setConstructorArgs([['driver' => $driver]])
  933. ->getMock();
  934. $connection->expects($this->at(0))->method('begin');
  935. $connection->expects($this->at(1))->method('commit');
  936. $result = $connection->transactional(function ($conn) use ($connection) {
  937. $this->assertSame($connection, $conn);
  938. return 'thing';
  939. });
  940. $this->assertSame('thing', $result);
  941. }
  942. /**
  943. * Tests that the transactional method will rollback the transaction if false
  944. * is returned from the callback
  945. *
  946. * @return void
  947. */
  948. public function testTransactionalFail()
  949. {
  950. $driver = $this->getMockFormDriver();
  951. $connection = $this->getMockBuilder(Connection::class)
  952. ->setMethods(['connect', 'commit', 'begin', 'rollback'])
  953. ->setConstructorArgs([['driver' => $driver]])
  954. ->getMock();
  955. $connection->expects($this->at(0))->method('begin');
  956. $connection->expects($this->at(1))->method('rollback');
  957. $connection->expects($this->never())->method('commit');
  958. $result = $connection->transactional(function ($conn) use ($connection) {
  959. $this->assertSame($connection, $conn);
  960. return false;
  961. });
  962. $this->assertFalse($result);
  963. }
  964. /**
  965. * Tests that the transactional method will rollback the transaction
  966. * and throw the same exception if the callback raises one
  967. *
  968. * @return void
  969. * @throws \InvalidArgumentException
  970. */
  971. public function testTransactionalWithException()
  972. {
  973. $this->expectException(\InvalidArgumentException::class);
  974. $driver = $this->getMockFormDriver();
  975. $connection = $this->getMockBuilder(Connection::class)
  976. ->setMethods(['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. $connection->transactional(function ($conn) use ($connection) {
  983. $this->assertSame($connection, $conn);
  984. throw new \InvalidArgumentException();
  985. });
  986. }
  987. /**
  988. * Tests it is possible to set a schema collection object
  989. *
  990. * @return void
  991. */
  992. public function testSetSchemaCollection()
  993. {
  994. $driver = $this->getMockFormDriver();
  995. $connection = $this->getMockBuilder(Connection::class)
  996. ->setMethods(['connect'])
  997. ->setConstructorArgs([['driver' => $driver]])
  998. ->getMock();
  999. $schema = $connection->getSchemaCollection();
  1000. $this->assertInstanceOf('Cake\Database\Schema\Collection', $schema);
  1001. $schema = $this->getMockBuilder('Cake\Database\Schema\Collection')
  1002. ->setConstructorArgs([$connection])
  1003. ->getMock();
  1004. $connection->setSchemaCollection($schema);
  1005. $this->assertSame($schema, $connection->getSchemaCollection());
  1006. }
  1007. /**
  1008. * Test CachedCollection creation with default and custom cache key prefix.
  1009. *
  1010. * @return void
  1011. */
  1012. public function testGetCachedCollection()
  1013. {
  1014. $driver = $this->getMockFormDriver();
  1015. $connection = $this->getMockBuilder(Connection::class)
  1016. ->setMethods(['connect'])
  1017. ->setConstructorArgs([[
  1018. 'driver' => $driver,
  1019. 'name' => 'default',
  1020. 'cacheMetadata' => true,
  1021. ]])
  1022. ->getMock();
  1023. $schema = $connection->getSchemaCollection();
  1024. $this->assertInstanceOf(CachedCollection::class, $schema);
  1025. $this->assertSame('default_key', $schema->cacheKey('key'));
  1026. $driver = $this->getMockFormDriver();
  1027. $connection = $this->getMockBuilder(Connection::class)
  1028. ->setMethods(['connect'])
  1029. ->setConstructorArgs([[
  1030. 'driver' => $driver,
  1031. 'name' => 'default',
  1032. 'cacheMetadata' => true,
  1033. 'cacheKeyPrefix' => 'foo',
  1034. ]])
  1035. ->getMock();
  1036. $schema = $connection->getSchemaCollection();
  1037. $this->assertInstanceOf(CachedCollection::class, $schema);
  1038. $this->assertSame('foo_key', $schema->cacheKey('key'));
  1039. }
  1040. /**
  1041. * Tests that allowed nesting of commit/rollback operations doesn't
  1042. * throw any exceptions.
  1043. *
  1044. * @return void
  1045. */
  1046. public function testNestedTransactionRollbackExceptionNotThrown()
  1047. {
  1048. $this->connection->transactional(function () {
  1049. $this->connection->transactional(function () {
  1050. return true;
  1051. });
  1052. return true;
  1053. });
  1054. $this->assertFalse($this->connection->inTransaction());
  1055. $this->connection->transactional(function () {
  1056. $this->connection->transactional(function () {
  1057. return true;
  1058. });
  1059. return false;
  1060. });
  1061. $this->assertFalse($this->connection->inTransaction());
  1062. $this->connection->transactional(function () {
  1063. $this->connection->transactional(function () {
  1064. return false;
  1065. });
  1066. return false;
  1067. });
  1068. $this->assertFalse($this->connection->inTransaction());
  1069. }
  1070. /**
  1071. * Tests that not allowed nesting of commit/rollback operations throws
  1072. * a NestedTransactionRollbackException.
  1073. *
  1074. * @return void
  1075. */
  1076. public function testNestedTransactionRollbackExceptionThrown()
  1077. {
  1078. $this->rollbackSourceLine = -1;
  1079. $e = null;
  1080. try {
  1081. $this->connection->transactional(function () {
  1082. $this->connection->transactional(function () {
  1083. return false;
  1084. });
  1085. $this->rollbackSourceLine = __LINE__ - 1;
  1086. return true;
  1087. });
  1088. $this->fail('NestedTransactionRollbackException should be thrown');
  1089. } catch (NestedTransactionRollbackException $e) {
  1090. }
  1091. $trace = $e->getTrace();
  1092. $this->assertEquals(__FILE__, $trace[1]['file']);
  1093. $this->assertEquals($this->rollbackSourceLine, $trace[1]['line']);
  1094. }
  1095. /**
  1096. * Tests more detail about that not allowed nesting of rollback/commit
  1097. * operations throws a NestedTransactionRollbackException.
  1098. *
  1099. * @return void
  1100. */
  1101. public function testNestedTransactionStates()
  1102. {
  1103. $this->rollbackSourceLine = -1;
  1104. $this->nestedTransactionStates = [];
  1105. $e = null;
  1106. try {
  1107. $this->connection->transactional(function () {
  1108. $this->pushNestedTransactionState();
  1109. $this->connection->transactional(function () {
  1110. return true;
  1111. });
  1112. $this->connection->transactional(function () {
  1113. $this->pushNestedTransactionState();
  1114. $this->connection->transactional(function () {
  1115. return false;
  1116. });
  1117. $this->rollbackSourceLine = __LINE__ - 1;
  1118. $this->pushNestedTransactionState();
  1119. return true;
  1120. });
  1121. $this->connection->transactional(function () {
  1122. return false;
  1123. });
  1124. $this->pushNestedTransactionState();
  1125. return true;
  1126. });
  1127. $this->fail('NestedTransactionRollbackException should be thrown');
  1128. } catch (NestedTransactionRollbackException $e) {
  1129. }
  1130. $this->pushNestedTransactionState();
  1131. $this->assertSame([false, false, true, true, false], $this->nestedTransactionStates);
  1132. $this->assertFalse($this->connection->inTransaction());
  1133. $trace = $e->getTrace();
  1134. $this->assertEquals(__FILE__, $trace[1]['file']);
  1135. $this->assertEquals($this->rollbackSourceLine, $trace[1]['line']);
  1136. }
  1137. /**
  1138. * Helper method to trace nested transaction states.
  1139. *
  1140. * @return void
  1141. */
  1142. public function pushNestedTransactionState()
  1143. {
  1144. $method = new ReflectionMethod($this->connection, 'wasNestedTransactionRolledback');
  1145. $method->setAccessible(true);
  1146. $this->nestedTransactionStates[] = $method->invoke($this->connection);
  1147. }
  1148. /**
  1149. * Tests that the connection is restablished whenever it is interrupted
  1150. * after having used the connection at least once.
  1151. *
  1152. * @return void
  1153. */
  1154. public function testAutomaticReconnect()
  1155. {
  1156. $conn = clone $this->connection;
  1157. $statement = $conn->query('SELECT 1');
  1158. $statement->execute();
  1159. $statement->closeCursor();
  1160. $prop = new ReflectionProperty($conn, '_driver');
  1161. $prop->setAccessible(true);
  1162. $oldDriver = $prop->getValue($conn);
  1163. $newDriver = $this->getMockBuilder(Driver::class)->getMock();
  1164. $prop->setValue($conn, $newDriver);
  1165. $newDriver->expects($this->at(0))
  1166. ->method('prepare')
  1167. ->will($this->throwException(new Exception('server gone away')));
  1168. $newDriver->expects($this->at(1))->method('disconnect');
  1169. $newDriver->expects($this->at(2))->method('connect');
  1170. $newDriver->expects($this->at(3))
  1171. ->method('prepare')
  1172. ->will($this->returnValue($statement));
  1173. $res = $conn->query('SELECT 1');
  1174. $this->assertInstanceOf(StatementInterface::class, $res);
  1175. }
  1176. /**
  1177. * Tests that the connection is not restablished whenever it is interrupted
  1178. * inside a transaction.
  1179. *
  1180. * @return void
  1181. */
  1182. public function testNoAutomaticReconnect()
  1183. {
  1184. $conn = clone $this->connection;
  1185. $statement = $conn->query('SELECT 1');
  1186. $statement->execute();
  1187. $statement->closeCursor();
  1188. $conn->begin();
  1189. $prop = new ReflectionProperty($conn, '_driver');
  1190. $prop->setAccessible(true);
  1191. $oldDriver = $prop->getValue($conn);
  1192. $newDriver = $this->getMockBuilder(Driver::class)->getMock();
  1193. $prop->setValue($conn, $newDriver);
  1194. $newDriver->expects($this->once())
  1195. ->method('prepare')
  1196. ->will($this->throwException(new Exception('server gone away')));
  1197. $this->expectException(Exception::class);
  1198. $conn->query('SELECT 1');
  1199. }
  1200. }