PostgresTest.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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\Driver;
  17. use Cake\Database\Connection;
  18. use Cake\Database\Driver\Postgres;
  19. use Cake\Database\DriverFeatureEnum;
  20. use Cake\Database\Query\SelectQuery;
  21. use Cake\Datasource\ConnectionManager;
  22. use Cake\TestSuite\TestCase;
  23. use PDO;
  24. /**
  25. * Tests Postgres driver
  26. */
  27. class PostgresTest extends TestCase
  28. {
  29. /**
  30. * Test connecting to Postgres with default configuration
  31. */
  32. public function testConnectionConfigDefault(): void
  33. {
  34. $driver = $this->getMockBuilder(Postgres::class)
  35. ->onlyMethods(['createPdo'])
  36. ->getMock();
  37. $dsn = 'pgsql:host=localhost;port=5432;dbname=cake';
  38. $expected = [
  39. 'persistent' => true,
  40. 'host' => 'localhost',
  41. 'username' => 'root',
  42. 'password' => '',
  43. 'database' => 'cake',
  44. 'schema' => 'public',
  45. 'port' => 5432,
  46. 'encoding' => 'utf8',
  47. 'timezone' => null,
  48. 'flags' => [],
  49. 'init' => [],
  50. 'log' => false,
  51. ];
  52. $expected['flags'] += [
  53. PDO::ATTR_PERSISTENT => true,
  54. PDO::ATTR_EMULATE_PREPARES => false,
  55. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  56. ];
  57. $connection = $this->getMockBuilder('PDO')
  58. ->disableOriginalConstructor()
  59. ->onlyMethods(['exec', 'quote'])
  60. ->getMock();
  61. $connection->expects($this->any())
  62. ->method('quote')
  63. ->willReturnArgument(0);
  64. $connection->expects($this->exactly(2))
  65. ->method('exec')
  66. ->with(
  67. ...self::withConsecutive(
  68. ['SET NAMES utf8'],
  69. ['SET search_path TO public']
  70. )
  71. );
  72. $driver->expects($this->once())->method('createPdo')
  73. ->with($dsn, $expected)
  74. ->willReturn($connection);
  75. $driver->connect();
  76. }
  77. /**
  78. * Test connecting to Postgres with custom configuration
  79. */
  80. public function testConnectionConfigCustom(): void
  81. {
  82. $config = [
  83. 'persistent' => false,
  84. 'host' => 'foo',
  85. 'database' => 'bar',
  86. 'username' => 'user',
  87. 'password' => 'pass',
  88. 'port' => 3440,
  89. 'flags' => [1 => true, 2 => false],
  90. 'encoding' => 'a-language',
  91. 'timezone' => 'Antarctica',
  92. 'schema' => 'fooblic',
  93. 'init' => ['Execute this', 'this too'],
  94. 'log' => false,
  95. ];
  96. $driver = $this->getMockBuilder(Postgres::class)
  97. ->onlyMethods(['createPdo'])
  98. ->setConstructorArgs([$config])
  99. ->getMock();
  100. $dsn = 'pgsql:host=foo;port=3440;dbname=bar';
  101. $expected = $config;
  102. $expected['flags'] += [
  103. PDO::ATTR_PERSISTENT => false,
  104. PDO::ATTR_EMULATE_PREPARES => false,
  105. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  106. ];
  107. $connection = $this->getMockBuilder('PDO')
  108. ->disableOriginalConstructor()
  109. ->onlyMethods(['exec', 'quote'])
  110. ->getMock();
  111. $connection->expects($this->any())
  112. ->method('quote')
  113. ->willReturnArgument(0);
  114. $connection->expects($this->exactly(5))
  115. ->method('exec')
  116. ->with(
  117. ...self::withConsecutive(
  118. ['SET NAMES a-language'],
  119. ['SET search_path TO fooblic'],
  120. ['Execute this'],
  121. ['this too'],
  122. ['SET timezone = Antarctica']
  123. )
  124. );
  125. $driver->expects($this->once())->method('createPdo')
  126. ->with($dsn, $expected)
  127. ->willReturn($connection);
  128. $driver->connect();
  129. }
  130. /**
  131. * Tests that insert queries get a "RETURNING *" string at the end
  132. */
  133. public function testInsertReturning(): void
  134. {
  135. $driver = $this->getMockBuilder(Postgres::class)
  136. ->onlyMethods(['createPdo', 'getPdo', 'connect', 'enabled'])
  137. ->setConstructorArgs([[]])
  138. ->getMock();
  139. $driver->method('enabled')->willReturn(true);
  140. $connection = new Connection(['driver' => $driver, 'log' => false]);
  141. $query = $connection->insertQuery('articles', ['id' => 1, 'title' => 'foo']);
  142. $this->assertStringEndsWith(' RETURNING *', $query->sql());
  143. $query = $connection->insertQuery('articles', ['id' => 1, 'title' => 'foo']);
  144. $query->epilog('FOO');
  145. $this->assertStringEndsWith(' FOO', $query->sql());
  146. }
  147. /**
  148. * Test that having queries replace the aggregated alias field.
  149. */
  150. public function testHavingReplacesAlias(): void
  151. {
  152. $driver = $this->getMockBuilder(Postgres::class)
  153. ->onlyMethods(['connect', 'getPdo', 'version', 'enabled'])
  154. ->setConstructorArgs([[]])
  155. ->getMock();
  156. $driver->method('enabled')
  157. ->willReturn(true);
  158. $connection = new Connection(['driver' => $driver, 'log' => false]);
  159. $query = new SelectQuery($connection);
  160. $query
  161. ->select([
  162. 'posts.author_id',
  163. 'post_count' => $query->func()->count('posts.id'),
  164. ])
  165. ->groupBy(['posts.author_id'])
  166. ->having([$query->newExpr()->gte('post_count', 2, 'integer')]);
  167. $expected = 'SELECT posts.author_id, (COUNT(posts.id)) AS "post_count" ' .
  168. 'GROUP BY posts.author_id HAVING COUNT(posts.id) >= :c0';
  169. $this->assertSame($expected, $query->sql());
  170. }
  171. /**
  172. * Test that having queries replaces nothing if no alias is used.
  173. */
  174. public function testHavingWhenNoAliasIsUsed(): void
  175. {
  176. $driver = $this->getMockBuilder(Postgres::class)
  177. ->onlyMethods(['connect', 'getPdo', 'version', 'enabled'])
  178. ->setConstructorArgs([[]])
  179. ->getMock();
  180. $driver->method('enabled')
  181. ->willReturn(true);
  182. $connection = new Connection(['driver' => $driver, 'log' => false]);
  183. $query = new SelectQuery($connection);
  184. $query
  185. ->select([
  186. 'posts.author_id',
  187. 'post_count' => $query->func()->count('posts.id'),
  188. ])
  189. ->groupBy(['posts.author_id'])
  190. ->having([$query->newExpr()->gte('posts.author_id', 2, 'integer')]);
  191. $expected = 'SELECT posts.author_id, (COUNT(posts.id)) AS "post_count" ' .
  192. 'GROUP BY posts.author_id HAVING posts.author_id >= :c0';
  193. $this->assertSame($expected, $query->sql());
  194. }
  195. /**
  196. * Tests driver-specific feature support check.
  197. */
  198. public function testSupports(): void
  199. {
  200. $driver = ConnectionManager::get('test')->getDriver();
  201. $this->skipIf(!$driver instanceof Postgres);
  202. $this->assertTrue($driver->supports(DriverFeatureEnum::CTE));
  203. $this->assertTrue($driver->supports(DriverFeatureEnum::JSON));
  204. $this->assertTrue($driver->supports(DriverFeatureEnum::SAVEPOINT));
  205. $this->assertTrue($driver->supports(DriverFeatureEnum::TRUNCATE_WITH_CONSTRAINTS));
  206. $this->assertTrue($driver->supports(DriverFeatureEnum::WINDOW));
  207. $this->assertTrue($driver->supports(DriverFeatureEnum::INTERSECT));
  208. $this->assertTrue($driver->supports(DriverFeatureEnum::INTERSECT_ALL));
  209. $this->assertTrue($driver->supports(DriverFeatureEnum::SET_OPERATIONS_ORDER_BY));
  210. $this->assertFalse($driver->supports(DriverFeatureEnum::DISABLE_CONSTRAINT_WITHOUT_TRANSACTION));
  211. }
  212. }