DriverTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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.2.12
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Database;
  17. use Cake\Database\Driver\Sqlserver;
  18. use Cake\Database\Exception\MissingConnectionException;
  19. use Cake\Database\Exception\QueryException;
  20. use Cake\Database\Log\QueryLogger;
  21. use Cake\Database\Query;
  22. use Cake\Database\QueryCompiler;
  23. use Cake\Database\Schema\TableSchema;
  24. use Cake\Database\Statement\Statement;
  25. use Cake\Database\ValueBinder;
  26. use Cake\Datasource\ConnectionManager;
  27. use Cake\Log\Log;
  28. use Cake\TestSuite\TestCase;
  29. use DateTime;
  30. use Exception;
  31. use PDO;
  32. use PDOException;
  33. use PDOStatement;
  34. use PHPUnit\Framework\Attributes\DataProvider;
  35. use TestApp\Database\Driver\RetryDriver;
  36. use TestApp\Database\Driver\StubDriver;
  37. /**
  38. * Tests Driver class
  39. */
  40. class DriverTest extends TestCase
  41. {
  42. /**
  43. * @var \Cake\Database\Driver|\PHPUnit\Framework\MockObject\MockObject
  44. */
  45. protected $driver;
  46. /**
  47. * Setup.
  48. */
  49. public function setUp(): void
  50. {
  51. parent::setUp();
  52. Log::setConfig('queries', [
  53. 'className' => 'Array',
  54. 'scopes' => ['queriesLog'],
  55. ]);
  56. $this->driver = $this->getMockBuilder(StubDriver::class)
  57. ->onlyMethods(['createPdo', 'prepare'])
  58. ->getMock();
  59. }
  60. public function tearDown(): void
  61. {
  62. parent::tearDown();
  63. Log::drop('queries');
  64. }
  65. /**
  66. * Test if building the object throws an exception if we're not passing
  67. * required config data.
  68. */
  69. public function testConstructorException(): void
  70. {
  71. try {
  72. new StubDriver(['login' => 'Bear']);
  73. } catch (Exception $e) {
  74. $this->assertStringContainsString(
  75. 'Please pass "username" instead of "login" for connecting to the database',
  76. $e->getMessage()
  77. );
  78. }
  79. }
  80. /**
  81. * Test the constructor.
  82. */
  83. public function testConstructor(): void
  84. {
  85. $driver = new StubDriver(['quoteIdentifiers' => true]);
  86. $this->assertTrue($driver->isAutoQuotingEnabled());
  87. $driver = new StubDriver(['username' => 'GummyBear']);
  88. $this->assertFalse($driver->isAutoQuotingEnabled());
  89. }
  90. /**
  91. * Test schemaValue().
  92. * Uses a provider for all the different values we can pass to the method.
  93. *
  94. * @param mixed $input
  95. */
  96. #[DataProvider('schemaValueProvider')]
  97. public function testSchemaValue($input, string $expected): void
  98. {
  99. $result = $this->driver->schemaValue($input);
  100. $this->assertSame($expected, $result);
  101. }
  102. /**
  103. * Test schemaValue().
  104. * Asserting that quote() is being called because none of the conditions were met before.
  105. */
  106. public function testSchemaValueConnectionQuoting(): void
  107. {
  108. $value = 'string';
  109. $connection = $this->getMockBuilder(PDO::class)
  110. ->disableOriginalConstructor()
  111. ->onlyMethods(['quote'])
  112. ->getMock();
  113. $connection
  114. ->expects($this->once())
  115. ->method('quote')
  116. ->with($value, PDO::PARAM_STR)
  117. ->willReturn('string');
  118. $this->driver->expects($this->any())
  119. ->method('createPdo')
  120. ->willReturn($connection);
  121. $this->driver->schemaValue($value);
  122. }
  123. /**
  124. * Test lastInsertId().
  125. */
  126. public function testLastInsertId(): void
  127. {
  128. $connection = $this->getMockBuilder(PDO::class)
  129. ->disableOriginalConstructor()
  130. ->onlyMethods(['lastInsertId'])
  131. ->getMock();
  132. $connection
  133. ->expects($this->once())
  134. ->method('lastInsertId')
  135. ->willReturn('all-the-bears');
  136. $this->driver->expects($this->any())
  137. ->method('createPdo')
  138. ->willReturn($connection);
  139. $this->assertSame('all-the-bears', $this->driver->lastInsertId());
  140. }
  141. /**
  142. * Test isConnected().
  143. */
  144. public function testIsConnected(): void
  145. {
  146. $this->assertFalse($this->driver->isConnected());
  147. $connection = $this->getMockBuilder(PDO::class)
  148. ->disableOriginalConstructor()
  149. ->onlyMethods(['query'])
  150. ->getMock();
  151. $connection
  152. ->expects($this->once())
  153. ->method('query')
  154. ->willReturn(new PDOStatement());
  155. $this->driver->expects($this->any())
  156. ->method('createPdo')
  157. ->willReturn($connection);
  158. $this->driver->connect();
  159. $this->assertTrue($this->driver->isConnected());
  160. }
  161. /**
  162. * test autoQuoting().
  163. */
  164. public function testAutoQuoting(): void
  165. {
  166. $this->assertFalse($this->driver->isAutoQuotingEnabled());
  167. $this->assertSame($this->driver, $this->driver->enableAutoQuoting(true));
  168. $this->assertTrue($this->driver->isAutoQuotingEnabled());
  169. $this->driver->disableAutoQuoting();
  170. $this->assertFalse($this->driver->isAutoQuotingEnabled());
  171. }
  172. /**
  173. * Test compileQuery().
  174. */
  175. public function testCompileQuery(): void
  176. {
  177. $compiler = $this->getMockBuilder(QueryCompiler::class)
  178. ->onlyMethods(['compile'])
  179. ->getMock();
  180. $compiler
  181. ->expects($this->once())
  182. ->method('compile')
  183. ->willReturn('1');
  184. $driver = $this->getMockBuilder(StubDriver::class)
  185. ->onlyMethods(['newCompiler', 'transformQuery'])
  186. ->getMock();
  187. $driver
  188. ->expects($this->once())
  189. ->method('newCompiler')
  190. ->willReturn($compiler);
  191. $query = $this->getMockBuilder(Query::class)
  192. ->disableOriginalConstructor()
  193. ->getMock();
  194. $query->method('type')->willReturn('select');
  195. $driver
  196. ->expects($this->once())
  197. ->method('transformQuery')
  198. ->willReturn($query);
  199. $result = $driver->compileQuery($query, new ValueBinder());
  200. $this->assertSame('1', $result);
  201. }
  202. /**
  203. * Test newCompiler().
  204. */
  205. public function testNewCompiler(): void
  206. {
  207. $this->assertInstanceOf(QueryCompiler::class, $this->driver->newCompiler());
  208. }
  209. /**
  210. * Test newTableSchema().
  211. */
  212. public function testNewTableSchema(): void
  213. {
  214. $tableName = 'articles';
  215. $actual = $this->driver->newTableSchema($tableName);
  216. $this->assertInstanceOf(TableSchema::class, $actual);
  217. $this->assertSame($tableName, $actual->name());
  218. }
  219. public function testConnectRetry(): void
  220. {
  221. $this->skipIf(!ConnectionManager::get('test')->getDriver() instanceof Sqlserver);
  222. $driver = new RetryDriver();
  223. try {
  224. $driver->connect();
  225. } catch (MissingConnectionException) {
  226. }
  227. $this->assertSame(4, $driver->getConnectRetries());
  228. }
  229. /**
  230. * Test __destruct().
  231. */
  232. public function testDestructor(): void
  233. {
  234. $this->driver->__destruct();
  235. $this->assertFalse($this->driver->__debugInfo()['connected']);
  236. }
  237. /**
  238. * Data provider for testSchemaValue().
  239. *
  240. * @return array
  241. */
  242. public static function schemaValueProvider(): array
  243. {
  244. return [
  245. [null, 'NULL'],
  246. [false, 'FALSE'],
  247. [true, 'TRUE'],
  248. [1, '1'],
  249. ['0', '0'],
  250. ['42', '42'],
  251. ];
  252. }
  253. /**
  254. * Tests that queries are logged when executed without params
  255. */
  256. public function testExecuteNoParams(): void
  257. {
  258. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  259. $statement = $this->getMockBuilder(Statement::class)
  260. ->setConstructorArgs([$inner, $this->driver])
  261. ->onlyMethods(['queryString','rowCount','execute'])
  262. ->getMock();
  263. $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
  264. $statement->method('rowCount')->willReturn(3);
  265. $statement->method('execute')->willReturn(true);
  266. $this->driver->expects($this->any())
  267. ->method('prepare')
  268. ->willReturn($statement);
  269. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  270. $this->driver->execute('SELECT bar FROM foo');
  271. $messages = Log::engine('queries')->read();
  272. $this->assertCount(1, $messages);
  273. $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
  274. }
  275. /**
  276. * Tests that queries are logged when executed with bound params
  277. */
  278. public function testExecuteWithBinding(): void
  279. {
  280. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  281. $statement = $this->getMockBuilder(Statement::class)
  282. ->setConstructorArgs([$inner, $this->driver])
  283. ->onlyMethods(['queryString','rowCount','execute'])
  284. ->getMock();
  285. $statement->method('rowCount')->willReturn(3);
  286. $statement->method('execute')->willReturn(true);
  287. $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo WHERE a=:a AND b=:b');
  288. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  289. $this->driver->expects($this->any())
  290. ->method('prepare')
  291. ->willReturn($statement);
  292. $this->driver->execute(
  293. 'SELECT bar FROM foo WHERE a=:a AND b=:b',
  294. [
  295. 'a' => 1,
  296. 'b' => new DateTime('2013-01-01'),
  297. ],
  298. ['b' => 'date']
  299. );
  300. $messages = Log::engine('queries')->read();
  301. $this->assertCount(1, $messages);
  302. $this->assertMatchesRegularExpression("/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
  303. }
  304. /**
  305. * Tests that queries are logged despite database errors
  306. */
  307. public function testExecuteWithError(): void
  308. {
  309. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  310. $statement = $this->getMockBuilder(Statement::class)
  311. ->setConstructorArgs([$inner, $this->driver])
  312. ->onlyMethods(['queryString','rowCount','execute'])
  313. ->getMock();
  314. $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
  315. $statement->method('rowCount')->willReturn(0);
  316. $statement->method('execute')->will($this->throwException(new PDOException()));
  317. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  318. $this->driver->expects($this->any())
  319. ->method('prepare')
  320. ->willReturn($statement);
  321. try {
  322. $this->driver->execute('SELECT foo FROM bar');
  323. } catch (PDOException) {
  324. }
  325. $messages = Log::engine('queries')->read();
  326. $this->assertCount(1, $messages);
  327. $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
  328. }
  329. public function testGetLoggerDefault(): void
  330. {
  331. $driver = $this->getMockBuilder(StubDriver::class)
  332. ->onlyMethods(['createPdo', 'prepare'])
  333. ->getMock();
  334. $this->assertNull($driver->getLogger());
  335. $driver = $this->getMockBuilder(StubDriver::class)
  336. ->setConstructorArgs([['log' => true]])
  337. ->onlyMethods(['createPdo'])
  338. ->getMock();
  339. $logger = $driver->getLogger();
  340. $this->assertInstanceOf(QueryLogger::class, $logger);
  341. }
  342. public function testSetLogger(): void
  343. {
  344. $logger = new QueryLogger();
  345. $this->driver->setLogger($logger);
  346. $this->assertSame($logger, $this->driver->getLogger());
  347. }
  348. public function testLogTransaction(): void
  349. {
  350. $pdo = $this->getMockBuilder(PDO::class)
  351. ->disableOriginalConstructor()
  352. ->onlyMethods(['beginTransaction', 'commit', 'rollback', 'inTransaction'])
  353. ->getMock();
  354. $pdo
  355. ->expects($this->any())
  356. ->method('beginTransaction')
  357. ->willReturn(true);
  358. $pdo
  359. ->expects($this->any())
  360. ->method('commit')
  361. ->willReturn(true);
  362. $pdo
  363. ->expects($this->any())
  364. ->method('rollBack')
  365. ->willReturn(true);
  366. $pdo->expects($this->exactly(5))
  367. ->method('inTransaction')
  368. ->willReturn(
  369. false,
  370. true,
  371. true,
  372. false,
  373. true,
  374. );
  375. $driver = $this->getMockBuilder(StubDriver::class)
  376. ->setConstructorArgs([['log' => true]])
  377. ->onlyMethods(['getPdo'])
  378. ->getMock();
  379. $driver->expects($this->any())
  380. ->method('getPdo')
  381. ->willReturn($pdo);
  382. $driver->beginTransaction();
  383. $driver->beginTransaction(); //This one will not be logged
  384. $driver->rollbackTransaction();
  385. $driver->beginTransaction();
  386. $driver->commitTransaction();
  387. $messages = Log::engine('queries')->read();
  388. $this->assertCount(4, $messages);
  389. $this->assertSame('debug: connection= role= duration=0 rows=0 BEGIN', $messages[0]);
  390. $this->assertSame('debug: connection= role= duration=0 rows=0 ROLLBACK', $messages[1]);
  391. $this->assertSame('debug: connection= role= duration=0 rows=0 BEGIN', $messages[2]);
  392. $this->assertSame('debug: connection= role= duration=0 rows=0 COMMIT', $messages[3]);
  393. }
  394. public function testQueryException(): void
  395. {
  396. $this->expectException(QueryException::class);
  397. ConnectionManager::get('default')->execute('SELECT * FROM non_existent_table');
  398. }
  399. public function testQueryExceptionStatementExecute(): void
  400. {
  401. $this->expectException(QueryException::class);
  402. ConnectionManager::get('default')->getDriver()
  403. ->execute('SELECT * FROM :foo', ['foo' => 'bar']);
  404. }
  405. /**
  406. * Tests that queries are logged when executed without params
  407. */
  408. public function testDisableQueryLogging(): void
  409. {
  410. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  411. $statement = $this->getMockBuilder(Statement::class)
  412. ->setConstructorArgs([$inner, $this->driver])
  413. ->onlyMethods(['queryString','rowCount','execute'])
  414. ->getMock();
  415. $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
  416. $statement->method('rowCount')->willReturn(3);
  417. $statement->method('execute')->willReturn(true);
  418. $this->driver->expects($this->any())
  419. ->method('prepare')
  420. ->willReturn($statement);
  421. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  422. $this->driver->execute('SELECT bar FROM foo');
  423. $messages = Log::engine('queries')->read();
  424. $this->driver->disableQueryLogging();
  425. $this->driver->execute('SELECT bar FROM foo');
  426. $this->assertCount(1, $messages);
  427. $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
  428. }
  429. }