PostgresSchemaTest.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401
  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\Schema;
  17. use Cake\Database\Driver\Postgres;
  18. use Cake\Database\Schema\Collection as SchemaCollection;
  19. use Cake\Database\Schema\PostgresSchema;
  20. use Cake\Database\Schema\TableSchema;
  21. use Cake\Datasource\ConnectionManager;
  22. use Cake\TestSuite\TestCase;
  23. use PDO;
  24. /**
  25. * Postgres schema test case.
  26. */
  27. class PostgresSchemaTest extends TestCase
  28. {
  29. /**
  30. * Helper method for skipping tests that need a real connection.
  31. *
  32. * @return void
  33. */
  34. protected function _needsConnection()
  35. {
  36. $config = ConnectionManager::getConfig('test');
  37. $this->skipIf(strpos($config['driver'], 'Postgres') === false, 'Not using Postgres for test config');
  38. }
  39. /**
  40. * Helper method for testing methods.
  41. *
  42. * @param \Cake\Datasource\ConnectionInterface $connection
  43. * @return void
  44. */
  45. protected function _createTables($connection)
  46. {
  47. $this->_needsConnection();
  48. $connection->execute('DROP TABLE IF EXISTS schema_articles');
  49. $connection->execute('DROP TABLE IF EXISTS schema_authors');
  50. $table = <<<SQL
  51. CREATE TABLE schema_authors (
  52. id SERIAL,
  53. name VARCHAR(50) DEFAULT 'bob',
  54. bio DATE,
  55. position INT DEFAULT 1,
  56. created TIMESTAMP,
  57. PRIMARY KEY (id),
  58. CONSTRAINT "unique_position" UNIQUE ("position")
  59. )
  60. SQL;
  61. $connection->execute($table);
  62. $table = <<<SQL
  63. CREATE TABLE schema_articles (
  64. id BIGINT PRIMARY KEY,
  65. title VARCHAR(20),
  66. body TEXT,
  67. author_id INTEGER NOT NULL,
  68. published BOOLEAN DEFAULT false,
  69. views SMALLINT DEFAULT 0,
  70. readingtime TIME,
  71. data JSONB,
  72. average_note DECIMAL(4,2),
  73. average_income NUMERIC(10,2),
  74. created TIMESTAMP,
  75. created_without_precision TIMESTAMP(0),
  76. created_with_precision TIMESTAMP(3),
  77. created_with_timezone TIMESTAMPTZ(3),
  78. CONSTRAINT "content_idx" UNIQUE ("title", "body"),
  79. CONSTRAINT "author_idx" FOREIGN KEY ("author_id") REFERENCES "schema_authors" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
  80. )
  81. SQL;
  82. $connection->execute($table);
  83. $connection->execute('COMMENT ON COLUMN "schema_articles"."title" IS \'a title\'');
  84. $connection->execute('CREATE INDEX "author_idx" ON "schema_articles" ("author_id")');
  85. }
  86. /**
  87. * Data provider for convert column testing
  88. *
  89. * @return array
  90. */
  91. public static function convertColumnProvider()
  92. {
  93. return [
  94. // Timestamp
  95. [
  96. ['type' => 'TIMESTAMP', 'datetime_precision' => 6],
  97. ['type' => 'timestampfractional', 'length' => null, 'precision' => 6],
  98. ],
  99. [
  100. ['type' => 'TIMESTAMP', 'datetime_precision' => 0],
  101. ['type' => 'timestamp', 'length' => null, 'precision' => 0],
  102. ],
  103. [
  104. ['type' => 'TIMESTAMP WITHOUT TIME ZONE', 'datetime_precision' => 6],
  105. ['type' => 'timestampfractional', 'length' => null, 'precision' => 6],
  106. ],
  107. [
  108. ['type' => 'TIMESTAMP WITH TIME ZONE', 'datetime_precision' => 6],
  109. ['type' => 'timestamptimezone', 'length' => null, 'precision' => 6],
  110. ],
  111. [
  112. ['type' => 'TIMESTAMPTZ', 'datetime_precision' => 6],
  113. ['type' => 'timestamptimezone', 'length' => null, 'precision' => 6],
  114. ],
  115. // Date & time
  116. [
  117. ['type' => 'DATE'],
  118. ['type' => 'date', 'length' => null],
  119. ],
  120. [
  121. ['type' => 'TIME'],
  122. ['type' => 'time', 'length' => null],
  123. ],
  124. [
  125. ['type' => 'TIME WITHOUT TIME ZONE'],
  126. ['type' => 'time', 'length' => null],
  127. ],
  128. // Integer
  129. [
  130. ['type' => 'SMALLINT'],
  131. ['type' => 'smallinteger', 'length' => 5],
  132. ],
  133. [
  134. ['type' => 'INTEGER'],
  135. ['type' => 'integer', 'length' => 10],
  136. ],
  137. [
  138. ['type' => 'SERIAL'],
  139. ['type' => 'integer', 'length' => 10],
  140. ],
  141. [
  142. ['type' => 'BIGINT'],
  143. ['type' => 'biginteger', 'length' => 20],
  144. ],
  145. [
  146. ['type' => 'BIGSERIAL'],
  147. ['type' => 'biginteger', 'length' => 20],
  148. ],
  149. // Decimal
  150. [
  151. ['type' => 'NUMERIC'],
  152. ['type' => 'decimal', 'length' => null, 'precision' => null],
  153. ],
  154. [
  155. ['type' => 'NUMERIC', 'default' => 'NULL::numeric'],
  156. ['type' => 'decimal', 'length' => null, 'precision' => null, 'default' => null],
  157. ],
  158. [
  159. ['type' => 'DECIMAL(10,2)', 'column_precision' => 10, 'column_scale' => 2],
  160. ['type' => 'decimal', 'length' => 10, 'precision' => 2],
  161. ],
  162. // String
  163. [
  164. ['type' => 'VARCHAR'],
  165. ['type' => 'string', 'length' => null, 'collate' => 'ja_JP.utf8'],
  166. ],
  167. [
  168. ['type' => 'VARCHAR(10)'],
  169. ['type' => 'string', 'length' => 10, 'collate' => 'ja_JP.utf8'],
  170. ],
  171. [
  172. ['type' => 'CHARACTER VARYING'],
  173. ['type' => 'string', 'length' => null, 'collate' => 'ja_JP.utf8'],
  174. ],
  175. [
  176. ['type' => 'CHARACTER VARYING(10)'],
  177. ['type' => 'string', 'length' => 10, 'collate' => 'ja_JP.utf8'],
  178. ],
  179. [
  180. ['type' => 'CHARACTER VARYING(255)', 'default' => 'NULL::character varying'],
  181. ['type' => 'string', 'length' => 255, 'default' => null, 'collate' => 'ja_JP.utf8'],
  182. ],
  183. [
  184. ['type' => 'CHAR(10)'],
  185. ['type' => 'char', 'length' => 10, 'collate' => 'ja_JP.utf8'],
  186. ],
  187. [
  188. ['type' => 'CHAR(36)'],
  189. ['type' => 'char', 'length' => 36, 'collate' => 'ja_JP.utf8'],
  190. ],
  191. [
  192. ['type' => 'CHARACTER(10)'],
  193. ['type' => 'string', 'length' => 10, 'collate' => 'ja_JP.utf8'],
  194. ],
  195. [
  196. ['type' => 'MONEY'],
  197. ['type' => 'string', 'length' => null],
  198. ],
  199. // UUID
  200. [
  201. ['type' => 'UUID'],
  202. ['type' => 'uuid', 'length' => null],
  203. ],
  204. [
  205. ['type' => 'INET'],
  206. ['type' => 'string', 'length' => 39],
  207. ],
  208. // Text
  209. [
  210. ['type' => 'TEXT'],
  211. ['type' => 'text', 'length' => null, 'collate' => 'ja_JP.utf8'],
  212. ],
  213. // Blob
  214. [
  215. ['type' => 'BYTEA'],
  216. ['type' => 'binary', 'length' => null],
  217. ],
  218. // Float
  219. [
  220. ['type' => 'REAL'],
  221. ['type' => 'float', 'length' => null],
  222. ],
  223. [
  224. ['type' => 'DOUBLE PRECISION'],
  225. ['type' => 'float', 'length' => null],
  226. ],
  227. // Json
  228. [
  229. ['type' => 'JSON'],
  230. ['type' => 'json', 'length' => null],
  231. ],
  232. [
  233. ['type' => 'JSONB'],
  234. ['type' => 'json', 'length' => null],
  235. ],
  236. ];
  237. }
  238. /**
  239. * Test parsing Postgres column types from field description.
  240. *
  241. * @dataProvider convertColumnProvider
  242. * @return void
  243. */
  244. public function testConvertColumn($field, $expected)
  245. {
  246. $field += [
  247. 'name' => 'field',
  248. 'null' => 'YES',
  249. 'default' => 'Default value',
  250. 'comment' => 'Comment section',
  251. 'char_length' => null,
  252. 'column_precision' => null,
  253. 'column_scale' => null,
  254. 'collation_name' => 'ja_JP.utf8',
  255. ];
  256. $expected += [
  257. 'null' => true,
  258. 'default' => 'Default value',
  259. 'comment' => 'Comment section',
  260. ];
  261. $driver = $this->getMockBuilder('Cake\Database\Driver\Postgres')->getMock();
  262. $dialect = new PostgresSchema($driver);
  263. $table = new TableSchema('table');
  264. $dialect->convertColumnDescription($table, $field);
  265. $actual = array_intersect_key($table->getColumn('field'), $expected);
  266. ksort($expected);
  267. ksort($actual);
  268. $this->assertSame($expected, $actual);
  269. }
  270. /**
  271. * Test listing tables with Postgres
  272. *
  273. * @return void
  274. */
  275. public function testListTables()
  276. {
  277. $connection = ConnectionManager::get('test');
  278. $this->_createTables($connection);
  279. $schema = new SchemaCollection($connection);
  280. $result = $schema->listTables();
  281. $this->assertIsArray($result);
  282. $this->assertContains('schema_articles', $result);
  283. $this->assertContains('schema_authors', $result);
  284. }
  285. /**
  286. * Test that describe accepts tablenames containing `schema.table`.
  287. *
  288. * @return void
  289. */
  290. public function testDescribeWithSchemaName()
  291. {
  292. $connection = ConnectionManager::get('test');
  293. $this->_createTables($connection);
  294. $schema = new SchemaCollection($connection);
  295. $result = $schema->describe('public.schema_articles');
  296. $this->assertEquals(['id'], $result->getPrimaryKey());
  297. $this->assertSame('schema_articles', $result->name());
  298. }
  299. /**
  300. * Test describing a table with Postgres
  301. *
  302. * @return void
  303. */
  304. public function testDescribeTable()
  305. {
  306. $connection = ConnectionManager::get('test');
  307. $this->_createTables($connection);
  308. $schema = new SchemaCollection($connection);
  309. $result = $schema->describe('schema_articles');
  310. $expected = [
  311. 'id' => [
  312. 'type' => 'biginteger',
  313. 'null' => false,
  314. 'default' => null,
  315. 'length' => 20,
  316. 'precision' => null,
  317. 'unsigned' => null,
  318. 'comment' => null,
  319. 'autoIncrement' => false,
  320. ],
  321. 'title' => [
  322. 'type' => 'string',
  323. 'null' => true,
  324. 'default' => null,
  325. 'length' => 20,
  326. 'precision' => null,
  327. 'comment' => 'a title',
  328. 'collate' => null,
  329. ],
  330. 'body' => [
  331. 'type' => 'text',
  332. 'null' => true,
  333. 'default' => null,
  334. 'length' => null,
  335. 'precision' => null,
  336. 'comment' => null,
  337. 'collate' => null,
  338. ],
  339. 'author_id' => [
  340. 'type' => 'integer',
  341. 'null' => false,
  342. 'default' => null,
  343. 'length' => 10,
  344. 'precision' => null,
  345. 'unsigned' => null,
  346. 'comment' => null,
  347. 'autoIncrement' => null,
  348. ],
  349. 'published' => [
  350. 'type' => 'boolean',
  351. 'null' => true,
  352. 'default' => 0,
  353. 'length' => null,
  354. 'precision' => null,
  355. 'comment' => null,
  356. ],
  357. 'views' => [
  358. 'type' => 'smallinteger',
  359. 'null' => true,
  360. 'default' => 0,
  361. 'length' => 5,
  362. 'precision' => null,
  363. 'unsigned' => null,
  364. 'comment' => null,
  365. ],
  366. 'readingtime' => [
  367. 'type' => 'time',
  368. 'null' => true,
  369. 'default' => null,
  370. 'length' => null,
  371. 'precision' => null,
  372. 'comment' => null,
  373. ],
  374. 'data' => [
  375. 'type' => 'json',
  376. 'null' => true,
  377. 'default' => null,
  378. 'length' => null,
  379. 'precision' => null,
  380. 'comment' => null,
  381. ],
  382. 'average_note' => [
  383. 'type' => 'decimal',
  384. 'null' => true,
  385. 'default' => null,
  386. 'length' => 4,
  387. 'precision' => 2,
  388. 'unsigned' => null,
  389. 'comment' => null,
  390. ],
  391. 'average_income' => [
  392. 'type' => 'decimal',
  393. 'null' => true,
  394. 'default' => null,
  395. 'length' => 10,
  396. 'precision' => 2,
  397. 'unsigned' => null,
  398. 'comment' => null,
  399. ],
  400. 'created' => [
  401. 'type' => 'timestampfractional',
  402. 'null' => true,
  403. 'default' => null,
  404. 'length' => null,
  405. 'precision' => 6,
  406. 'comment' => null,
  407. ],
  408. 'created_without_precision' => [
  409. 'type' => 'timestamp',
  410. 'null' => true,
  411. 'default' => null,
  412. 'length' => null,
  413. 'precision' => 0,
  414. 'comment' => null,
  415. ],
  416. 'created_with_precision' => [
  417. 'type' => 'timestampfractional',
  418. 'null' => true,
  419. 'default' => null,
  420. 'length' => null,
  421. 'precision' => 3,
  422. 'comment' => null,
  423. ],
  424. 'created_with_timezone' => [
  425. 'type' => 'timestamptimezone',
  426. 'null' => true,
  427. 'default' => null,
  428. 'length' => null,
  429. 'precision' => 3,
  430. 'comment' => null,
  431. ],
  432. ];
  433. $this->assertEquals(['id'], $result->getPrimaryKey());
  434. foreach ($expected as $field => $definition) {
  435. $this->assertEquals($definition, $result->getColumn($field));
  436. }
  437. }
  438. /**
  439. * Test describing a table with postgres and composite keys
  440. *
  441. * @return void
  442. */
  443. public function testDescribeTableCompositeKey()
  444. {
  445. $this->_needsConnection();
  446. $connection = ConnectionManager::get('test');
  447. $sql = <<<SQL
  448. CREATE TABLE schema_composite (
  449. "id" SERIAL,
  450. "site_id" INTEGER NOT NULL,
  451. "name" VARCHAR(255),
  452. PRIMARY KEY("id", "site_id")
  453. );
  454. SQL;
  455. $connection->execute($sql);
  456. $schema = new SchemaCollection($connection);
  457. $result = $schema->describe('schema_composite');
  458. $connection->execute('DROP TABLE schema_composite');
  459. $this->assertEquals(['id', 'site_id'], $result->getPrimaryKey());
  460. $this->assertTrue($result->getColumn('id')['autoIncrement'], 'id should be autoincrement');
  461. $this->assertNull($result->getColumn('site_id')['autoIncrement'], 'site_id should not be autoincrement');
  462. }
  463. /**
  464. * Test describing a table containing defaults with Postgres
  465. *
  466. * @return void
  467. */
  468. public function testDescribeTableWithDefaults()
  469. {
  470. $connection = ConnectionManager::get('test');
  471. $this->_createTables($connection);
  472. $schema = new SchemaCollection($connection);
  473. $result = $schema->describe('schema_authors');
  474. $expected = [
  475. 'id' => [
  476. 'type' => 'integer',
  477. 'null' => false,
  478. 'default' => null,
  479. 'length' => 10,
  480. 'precision' => null,
  481. 'unsigned' => null,
  482. 'comment' => null,
  483. 'autoIncrement' => true,
  484. ],
  485. 'name' => [
  486. 'type' => 'string',
  487. 'null' => true,
  488. 'default' => 'bob',
  489. 'length' => 50,
  490. 'precision' => null,
  491. 'comment' => null,
  492. 'collate' => null,
  493. ],
  494. 'bio' => [
  495. 'type' => 'date',
  496. 'null' => true,
  497. 'default' => null,
  498. 'length' => null,
  499. 'precision' => null,
  500. 'comment' => null,
  501. ],
  502. 'position' => [
  503. 'type' => 'integer',
  504. 'null' => true,
  505. 'default' => '1',
  506. 'length' => 10,
  507. 'precision' => null,
  508. 'comment' => null,
  509. 'unsigned' => null,
  510. 'autoIncrement' => null,
  511. ],
  512. 'created' => [
  513. 'type' => 'timestampfractional',
  514. 'null' => true,
  515. 'default' => null,
  516. 'length' => null,
  517. 'precision' => 6,
  518. 'comment' => null,
  519. ],
  520. ];
  521. $this->assertEquals(['id'], $result->getPrimaryKey());
  522. foreach ($expected as $field => $definition) {
  523. $this->assertEquals($definition, $result->getColumn($field), "Mismatch in $field column");
  524. }
  525. }
  526. /**
  527. * Test describing a table with containing keywords
  528. *
  529. * @return void
  530. */
  531. public function testDescribeTableConstraintsWithKeywords()
  532. {
  533. $connection = ConnectionManager::get('test');
  534. $this->_createTables($connection);
  535. $schema = new SchemaCollection($connection);
  536. $result = $schema->describe('schema_authors');
  537. $this->assertInstanceOf('Cake\Database\Schema\TableSchema', $result);
  538. $expected = [
  539. 'primary' => [
  540. 'type' => 'primary',
  541. 'columns' => ['id'],
  542. 'length' => [],
  543. ],
  544. 'unique_position' => [
  545. 'type' => 'unique',
  546. 'columns' => ['position'],
  547. 'length' => [],
  548. ],
  549. ];
  550. $this->assertCount(2, $result->constraints());
  551. $this->assertEquals($expected['primary'], $result->getConstraint('primary'));
  552. $this->assertEquals($expected['unique_position'], $result->getConstraint('unique_position'));
  553. }
  554. /**
  555. * Test describing a table with indexes
  556. *
  557. * @return void
  558. */
  559. public function testDescribeTableIndexes()
  560. {
  561. $connection = ConnectionManager::get('test');
  562. $this->_createTables($connection);
  563. $schema = new SchemaCollection($connection);
  564. $result = $schema->describe('schema_articles');
  565. $this->assertInstanceOf('Cake\Database\Schema\TableSchema', $result);
  566. $expected = [
  567. 'primary' => [
  568. 'type' => 'primary',
  569. 'columns' => ['id'],
  570. 'length' => [],
  571. ],
  572. 'content_idx' => [
  573. 'type' => 'unique',
  574. 'columns' => ['title', 'body'],
  575. 'length' => [],
  576. ],
  577. ];
  578. $this->assertCount(3, $result->constraints());
  579. $expected = [
  580. 'primary' => [
  581. 'type' => 'primary',
  582. 'columns' => ['id'],
  583. 'length' => [],
  584. ],
  585. 'content_idx' => [
  586. 'type' => 'unique',
  587. 'columns' => ['title', 'body'],
  588. 'length' => [],
  589. ],
  590. 'author_idx' => [
  591. 'type' => 'foreign',
  592. 'columns' => ['author_id'],
  593. 'references' => ['schema_authors', 'id'],
  594. 'length' => [],
  595. 'update' => 'cascade',
  596. 'delete' => 'restrict',
  597. ],
  598. ];
  599. $this->assertEquals($expected['primary'], $result->getConstraint('primary'));
  600. $this->assertEquals($expected['content_idx'], $result->getConstraint('content_idx'));
  601. $this->assertEquals($expected['author_idx'], $result->getConstraint('author_idx'));
  602. $this->assertCount(1, $result->indexes());
  603. $expected = [
  604. 'type' => 'index',
  605. 'columns' => ['author_id'],
  606. 'length' => [],
  607. ];
  608. $this->assertEquals($expected, $result->getIndex('author_idx'));
  609. }
  610. /**
  611. * Test describing a table with indexes with nulls first
  612. *
  613. * @return void
  614. */
  615. public function testDescribeTableIndexesNullsFirst()
  616. {
  617. $this->_needsConnection();
  618. $connection = ConnectionManager::get('test');
  619. $connection->execute('DROP TABLE IF EXISTS schema_index');
  620. $table = <<<SQL
  621. CREATE TABLE schema_index (
  622. id serial NOT NULL,
  623. user_id integer NOT NULL,
  624. group_id integer NOT NULL,
  625. grade double precision
  626. )
  627. WITH (
  628. OIDS=FALSE
  629. )
  630. SQL;
  631. $connection->execute($table);
  632. $index = <<<SQL
  633. CREATE INDEX schema_index_nulls
  634. ON schema_index
  635. USING btree
  636. (group_id, grade DESC NULLS FIRST);
  637. SQL;
  638. $connection->execute($index);
  639. $schema = new SchemaCollection($connection);
  640. $result = $schema->describe('schema_index');
  641. $this->assertCount(1, $result->indexes());
  642. $expected = [
  643. 'type' => 'index',
  644. 'columns' => ['group_id', 'grade'],
  645. 'length' => [],
  646. ];
  647. $this->assertEquals($expected, $result->getIndex('schema_index_nulls'));
  648. $connection->execute('DROP TABLE schema_index');
  649. }
  650. /**
  651. * Test describing a table with postgres function defaults
  652. *
  653. * @return void
  654. */
  655. public function testDescribeTableFunctionDefaultValue()
  656. {
  657. $this->_needsConnection();
  658. $connection = ConnectionManager::get('test');
  659. $sql = <<<SQL
  660. CREATE TABLE schema_function_defaults (
  661. "id" SERIAL,
  662. year INT DEFAULT DATE_PART('year'::text, NOW()),
  663. PRIMARY KEY("id")
  664. );
  665. SQL;
  666. $connection->execute($sql);
  667. $schema = new SchemaCollection($connection);
  668. $result = $schema->describe('schema_function_defaults');
  669. $connection->execute('DROP TABLE schema_function_defaults');
  670. $expected = [
  671. 'type' => 'integer',
  672. 'default' => "date_part('year'::text, now())",
  673. 'null' => true,
  674. 'precision' => null,
  675. 'length' => 10,
  676. 'comment' => null,
  677. 'unsigned' => null,
  678. 'autoIncrement' => null,
  679. ];
  680. $this->assertEquals($expected, $result->getColumn('year'));
  681. }
  682. /**
  683. * Column provider for creating column sql
  684. *
  685. * @return array
  686. */
  687. public static function columnSqlProvider()
  688. {
  689. return [
  690. // strings
  691. [
  692. 'title',
  693. ['type' => 'string', 'length' => 25, 'null' => false],
  694. '"title" VARCHAR(25) NOT NULL',
  695. ],
  696. [
  697. 'title',
  698. ['type' => 'string', 'length' => 25, 'null' => true, 'default' => 'ignored'],
  699. '"title" VARCHAR(25) DEFAULT \'ignored\'',
  700. ],
  701. [
  702. 'id',
  703. ['type' => 'char', 'length' => 32, 'null' => false],
  704. '"id" CHAR(32) NOT NULL',
  705. ],
  706. [
  707. 'title',
  708. ['type' => 'string', 'length' => 36, 'null' => false],
  709. '"title" VARCHAR(36) NOT NULL',
  710. ],
  711. [
  712. 'id',
  713. ['type' => 'uuid', 'length' => 36, 'null' => false],
  714. '"id" UUID NOT NULL',
  715. ],
  716. [
  717. 'id',
  718. ['type' => 'binaryuuid', 'length' => null, 'null' => false],
  719. '"id" UUID NOT NULL',
  720. ],
  721. [
  722. 'role',
  723. ['type' => 'string', 'length' => 10, 'null' => false, 'default' => 'admin'],
  724. '"role" VARCHAR(10) NOT NULL DEFAULT \'admin\'',
  725. ],
  726. [
  727. 'title',
  728. ['type' => 'string'],
  729. '"title" VARCHAR',
  730. ],
  731. [
  732. 'title',
  733. ['type' => 'string', 'length' => 36],
  734. '"title" VARCHAR(36)',
  735. ],
  736. [
  737. 'title',
  738. ['type' => 'string', 'length' => 255, 'null' => false, 'collate' => 'C'],
  739. '"title" VARCHAR(255) COLLATE "C" NOT NULL',
  740. ],
  741. // Text
  742. [
  743. 'body',
  744. ['type' => 'text', 'null' => false],
  745. '"body" TEXT NOT NULL',
  746. ],
  747. [
  748. 'body',
  749. ['type' => 'text', 'length' => TableSchema::LENGTH_TINY, 'null' => false],
  750. sprintf('"body" VARCHAR(%s) NOT NULL', TableSchema::LENGTH_TINY),
  751. ],
  752. [
  753. 'body',
  754. ['type' => 'text', 'length' => TableSchema::LENGTH_MEDIUM, 'null' => false],
  755. '"body" TEXT NOT NULL',
  756. ],
  757. [
  758. 'body',
  759. ['type' => 'text', 'length' => TableSchema::LENGTH_LONG, 'null' => false],
  760. '"body" TEXT NOT NULL',
  761. ],
  762. [
  763. 'body',
  764. ['type' => 'text', 'null' => false, 'collate' => 'C'],
  765. '"body" TEXT COLLATE "C" NOT NULL',
  766. ],
  767. // Integers
  768. [
  769. 'post_id',
  770. ['type' => 'tinyinteger', 'length' => 11],
  771. '"post_id" SMALLINT',
  772. ],
  773. [
  774. 'post_id',
  775. ['type' => 'smallinteger', 'length' => 11],
  776. '"post_id" SMALLINT',
  777. ],
  778. [
  779. 'post_id',
  780. ['type' => 'integer', 'length' => 11],
  781. '"post_id" INTEGER',
  782. ],
  783. [
  784. 'post_id',
  785. ['type' => 'biginteger', 'length' => 20],
  786. '"post_id" BIGINT',
  787. ],
  788. [
  789. 'post_id',
  790. ['type' => 'integer', 'autoIncrement' => true, 'length' => 11],
  791. '"post_id" SERIAL',
  792. ],
  793. [
  794. 'post_id',
  795. ['type' => 'biginteger', 'autoIncrement' => true, 'length' => 20],
  796. '"post_id" BIGSERIAL',
  797. ],
  798. // Decimal
  799. [
  800. 'value',
  801. ['type' => 'decimal'],
  802. '"value" DECIMAL',
  803. ],
  804. [
  805. 'value',
  806. ['type' => 'decimal', 'length' => 11],
  807. '"value" DECIMAL(11,0)',
  808. ],
  809. [
  810. 'value',
  811. ['type' => 'decimal', 'length' => 12, 'precision' => 5],
  812. '"value" DECIMAL(12,5)',
  813. ],
  814. // Float
  815. [
  816. 'value',
  817. ['type' => 'float'],
  818. '"value" FLOAT',
  819. ],
  820. [
  821. 'value',
  822. ['type' => 'float', 'length' => 11, 'precision' => 3],
  823. '"value" FLOAT(3)',
  824. ],
  825. // Binary
  826. [
  827. 'img',
  828. ['type' => 'binary'],
  829. '"img" BYTEA',
  830. ],
  831. // Boolean
  832. [
  833. 'checked',
  834. ['type' => 'boolean', 'default' => false],
  835. '"checked" BOOLEAN DEFAULT FALSE',
  836. ],
  837. [
  838. 'checked',
  839. ['type' => 'boolean', 'default' => true, 'null' => false],
  840. '"checked" BOOLEAN NOT NULL DEFAULT TRUE',
  841. ],
  842. // Boolean
  843. [
  844. 'checked',
  845. ['type' => 'boolean', 'default' => 0],
  846. '"checked" BOOLEAN DEFAULT FALSE',
  847. ],
  848. [
  849. 'checked',
  850. ['type' => 'boolean', 'default' => 1, 'null' => false],
  851. '"checked" BOOLEAN NOT NULL DEFAULT TRUE',
  852. ],
  853. // Date & Time
  854. [
  855. 'start_date',
  856. ['type' => 'date'],
  857. '"start_date" DATE',
  858. ],
  859. [
  860. 'start_time',
  861. ['type' => 'time'],
  862. '"start_time" TIME',
  863. ],
  864. // Datetime
  865. [
  866. 'created',
  867. ['type' => 'datetime', 'null' => true],
  868. '"created" TIMESTAMP DEFAULT NULL',
  869. ],
  870. [
  871. 'created_without_precision',
  872. ['type' => 'datetime', 'precision' => 0],
  873. '"created_without_precision" TIMESTAMP(0)',
  874. ],
  875. [
  876. 'created_without_precision',
  877. ['type' => 'datetimefractional', 'precision' => 0],
  878. '"created_without_precision" TIMESTAMP(0)',
  879. ],
  880. [
  881. 'created_with_precision',
  882. ['type' => 'datetimefractional', 'precision' => 3],
  883. '"created_with_precision" TIMESTAMP(3)',
  884. ],
  885. // Timestamp
  886. [
  887. 'created',
  888. ['type' => 'timestamp', 'null' => true],
  889. '"created" TIMESTAMP DEFAULT NULL',
  890. ],
  891. [
  892. 'created_without_precision',
  893. ['type' => 'timestamp', 'precision' => 0],
  894. '"created_without_precision" TIMESTAMP(0)',
  895. ],
  896. [
  897. 'created_without_precision',
  898. ['type' => 'timestampfractional', 'precision' => 0],
  899. '"created_without_precision" TIMESTAMP(0)',
  900. ],
  901. [
  902. 'created_with_precision',
  903. ['type' => 'timestampfractional', 'precision' => 3],
  904. '"created_with_precision" TIMESTAMP(3)',
  905. ],
  906. [
  907. 'open_date',
  908. ['type' => 'timestampfractional', 'null' => false, 'default' => '2016-12-07 23:04:00'],
  909. '"open_date" TIMESTAMP NOT NULL DEFAULT \'2016-12-07 23:04:00\'',
  910. ],
  911. [
  912. 'null_date',
  913. ['type' => 'timestampfractional', 'null' => true],
  914. '"null_date" TIMESTAMP DEFAULT NULL',
  915. ],
  916. [
  917. 'current_timestamp',
  918. ['type' => 'timestamp', 'null' => false, 'default' => 'CURRENT_TIMESTAMP'],
  919. '"current_timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
  920. ],
  921. [
  922. 'current_timestamp_fractional',
  923. ['type' => 'timestampfractional', 'null' => false, 'default' => 'CURRENT_TIMESTAMP'],
  924. '"current_timestamp_fractional" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP',
  925. ],
  926. ];
  927. }
  928. /**
  929. * Test generating column definitions
  930. *
  931. * @dataProvider columnSqlProvider
  932. * @return void
  933. */
  934. public function testColumnSql($name, $data, $expected)
  935. {
  936. $driver = $this->_getMockedDriver();
  937. $schema = new PostgresSchema($driver);
  938. $table = (new TableSchema('schema_articles'))->addColumn($name, $data);
  939. $this->assertEquals($expected, $schema->columnSql($table, $name));
  940. }
  941. /**
  942. * Test generating a column that is a primary key.
  943. *
  944. * @return void
  945. */
  946. public function testColumnSqlPrimaryKey()
  947. {
  948. $driver = $this->_getMockedDriver();
  949. $schema = new PostgresSchema($driver);
  950. $table = new TableSchema('schema_articles');
  951. $table->addColumn('id', [
  952. 'type' => 'integer',
  953. 'null' => false,
  954. ])
  955. ->addConstraint('primary', [
  956. 'type' => 'primary',
  957. 'columns' => ['id'],
  958. ]);
  959. $result = $schema->columnSql($table, 'id');
  960. $this->assertEquals($result, '"id" SERIAL');
  961. }
  962. /**
  963. * Provide data for testing constraintSql
  964. *
  965. * @return array
  966. */
  967. public static function constraintSqlProvider()
  968. {
  969. return [
  970. [
  971. 'primary',
  972. ['type' => 'primary', 'columns' => ['title']],
  973. 'PRIMARY KEY ("title")',
  974. ],
  975. [
  976. 'unique_idx',
  977. ['type' => 'unique', 'columns' => ['title', 'author_id']],
  978. 'CONSTRAINT "unique_idx" UNIQUE ("title", "author_id")',
  979. ],
  980. [
  981. 'author_id_idx',
  982. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id']],
  983. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  984. 'REFERENCES "authors" ("id") ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE',
  985. ],
  986. [
  987. 'author_id_idx',
  988. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'cascade'],
  989. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  990. 'REFERENCES "authors" ("id") ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE',
  991. ],
  992. [
  993. 'author_id_idx',
  994. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'restrict'],
  995. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  996. 'REFERENCES "authors" ("id") ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE',
  997. ],
  998. [
  999. 'author_id_idx',
  1000. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'setNull'],
  1001. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  1002. 'REFERENCES "authors" ("id") ON UPDATE SET NULL ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE',
  1003. ],
  1004. [
  1005. 'author_id_idx',
  1006. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'noAction'],
  1007. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  1008. 'REFERENCES "authors" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE',
  1009. ],
  1010. ];
  1011. }
  1012. /**
  1013. * Test the constraintSql method.
  1014. *
  1015. * @dataProvider constraintSqlProvider
  1016. */
  1017. public function testConstraintSql($name, $data, $expected)
  1018. {
  1019. $driver = $this->_getMockedDriver();
  1020. $schema = new PostgresSchema($driver);
  1021. $table = (new TableSchema('schema_articles'))->addColumn('title', [
  1022. 'type' => 'string',
  1023. 'length' => 255,
  1024. ])->addColumn('author_id', [
  1025. 'type' => 'integer',
  1026. ])->addConstraint($name, $data);
  1027. $this->assertTextEquals($expected, $schema->constraintSql($table, $name));
  1028. }
  1029. /**
  1030. * Test the addConstraintSql method.
  1031. *
  1032. * @return void
  1033. */
  1034. public function testAddConstraintSql()
  1035. {
  1036. $driver = $this->_getMockedDriver();
  1037. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1038. ->disableOriginalConstructor()
  1039. ->getMock();
  1040. $connection->expects($this->any())->method('getDriver')
  1041. ->will($this->returnValue($driver));
  1042. $table = (new TableSchema('posts'))
  1043. ->addColumn('author_id', [
  1044. 'type' => 'integer',
  1045. 'null' => false,
  1046. ])
  1047. ->addColumn('category_id', [
  1048. 'type' => 'integer',
  1049. 'null' => false,
  1050. ])
  1051. ->addColumn('category_name', [
  1052. 'type' => 'integer',
  1053. 'null' => false,
  1054. ])
  1055. ->addConstraint('author_fk', [
  1056. 'type' => 'foreign',
  1057. 'columns' => ['author_id'],
  1058. 'references' => ['authors', 'id'],
  1059. 'update' => 'cascade',
  1060. 'delete' => 'cascade',
  1061. ])
  1062. ->addConstraint('category_fk', [
  1063. 'type' => 'foreign',
  1064. 'columns' => ['category_id', 'category_name'],
  1065. 'references' => ['categories', ['id', 'name']],
  1066. 'update' => 'cascade',
  1067. 'delete' => 'cascade',
  1068. ]);
  1069. $expected = [
  1070. 'ALTER TABLE "posts" ADD CONSTRAINT "author_fk" FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE;',
  1071. 'ALTER TABLE "posts" ADD CONSTRAINT "category_fk" FOREIGN KEY ("category_id", "category_name") REFERENCES "categories" ("id", "name") ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE;',
  1072. ];
  1073. $result = $table->addConstraintSql($connection);
  1074. $this->assertCount(2, $result);
  1075. $this->assertEquals($expected, $result);
  1076. }
  1077. /**
  1078. * Test the dropConstraintSql method.
  1079. *
  1080. * @return void
  1081. */
  1082. public function testDropConstraintSql()
  1083. {
  1084. $driver = $this->_getMockedDriver();
  1085. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1086. ->disableOriginalConstructor()
  1087. ->getMock();
  1088. $connection->expects($this->any())->method('getDriver')
  1089. ->will($this->returnValue($driver));
  1090. $table = (new TableSchema('posts'))
  1091. ->addColumn('author_id', [
  1092. 'type' => 'integer',
  1093. 'null' => false,
  1094. ])
  1095. ->addColumn('category_id', [
  1096. 'type' => 'integer',
  1097. 'null' => false,
  1098. ])
  1099. ->addColumn('category_name', [
  1100. 'type' => 'integer',
  1101. 'null' => false,
  1102. ])
  1103. ->addConstraint('author_fk', [
  1104. 'type' => 'foreign',
  1105. 'columns' => ['author_id'],
  1106. 'references' => ['authors', 'id'],
  1107. 'update' => 'cascade',
  1108. 'delete' => 'cascade',
  1109. ])
  1110. ->addConstraint('category_fk', [
  1111. 'type' => 'foreign',
  1112. 'columns' => ['category_id', 'category_name'],
  1113. 'references' => ['categories', ['id', 'name']],
  1114. 'update' => 'cascade',
  1115. 'delete' => 'cascade',
  1116. ]);
  1117. $expected = [
  1118. 'ALTER TABLE "posts" DROP CONSTRAINT "author_fk";',
  1119. 'ALTER TABLE "posts" DROP CONSTRAINT "category_fk";',
  1120. ];
  1121. $result = $table->dropConstraintSql($connection);
  1122. $this->assertCount(2, $result);
  1123. $this->assertEquals($expected, $result);
  1124. }
  1125. /**
  1126. * Integration test for converting a Schema\Table into MySQL table creates.
  1127. *
  1128. * @return void
  1129. */
  1130. public function testCreateSql()
  1131. {
  1132. $driver = $this->_getMockedDriver();
  1133. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1134. ->disableOriginalConstructor()
  1135. ->getMock();
  1136. $connection->expects($this->any())->method('getDriver')
  1137. ->will($this->returnValue($driver));
  1138. $table = (new TableSchema('schema_articles'))->addColumn('id', [
  1139. 'type' => 'integer',
  1140. 'null' => false,
  1141. ])
  1142. ->addColumn('title', [
  1143. 'type' => 'string',
  1144. 'null' => false,
  1145. 'comment' => 'This is the title',
  1146. ])
  1147. ->addColumn('body', ['type' => 'text'])
  1148. ->addColumn('data', ['type' => 'json'])
  1149. ->addColumn('hash', [
  1150. 'type' => 'char',
  1151. 'length' => 40,
  1152. 'collate' => 'C',
  1153. 'null' => false,
  1154. ])
  1155. ->addColumn('created', 'timestamp')
  1156. ->addColumn('created_without_precision', ['type' => 'timestamp', 'precision' => 0])
  1157. ->addColumn('created_with_precision', ['type' => 'timestampfractional', 'precision' => 6])
  1158. ->addColumn('created_with_timezone', ['type' => 'timestamptimezone', 'precision' => 6])
  1159. ->addConstraint('primary', [
  1160. 'type' => 'primary',
  1161. 'columns' => ['id'],
  1162. ])
  1163. ->addIndex('title_idx', [
  1164. 'type' => 'index',
  1165. 'columns' => ['title'],
  1166. ]);
  1167. $expected = <<<SQL
  1168. CREATE TABLE "schema_articles" (
  1169. "id" SERIAL,
  1170. "title" VARCHAR NOT NULL,
  1171. "body" TEXT,
  1172. "data" JSONB,
  1173. "hash" CHAR(40) COLLATE "C" NOT NULL,
  1174. "created" TIMESTAMP,
  1175. "created_without_precision" TIMESTAMP(0),
  1176. "created_with_precision" TIMESTAMP(6),
  1177. "created_with_timezone" TIMESTAMPTZ(6),
  1178. PRIMARY KEY ("id")
  1179. )
  1180. SQL;
  1181. $result = $table->createSql($connection);
  1182. $this->assertCount(3, $result);
  1183. $this->assertTextEquals($expected, $result[0]);
  1184. $this->assertEquals(
  1185. 'CREATE INDEX "title_idx" ON "schema_articles" ("title")',
  1186. $result[1]
  1187. );
  1188. $this->assertEquals(
  1189. 'COMMENT ON COLUMN "schema_articles"."title" IS \'This is the title\'',
  1190. $result[2]
  1191. );
  1192. }
  1193. /**
  1194. * Tests creating temporary tables
  1195. *
  1196. * @return void
  1197. */
  1198. public function testCreateTemporary()
  1199. {
  1200. $driver = $this->_getMockedDriver();
  1201. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1202. ->disableOriginalConstructor()
  1203. ->getMock();
  1204. $connection->expects($this->any())->method('getDriver')
  1205. ->will($this->returnValue($driver));
  1206. $table = (new TableSchema('schema_articles'))->addColumn('id', [
  1207. 'type' => 'integer',
  1208. 'null' => false,
  1209. ]);
  1210. $table->setTemporary(true);
  1211. $sql = $table->createSql($connection);
  1212. $this->assertStringContainsString('CREATE TEMPORARY TABLE', $sql[0]);
  1213. }
  1214. /**
  1215. * Test primary key generation & auto-increment.
  1216. *
  1217. * @return void
  1218. */
  1219. public function testCreateSqlCompositeIntegerKey()
  1220. {
  1221. $driver = $this->_getMockedDriver();
  1222. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1223. ->disableOriginalConstructor()
  1224. ->getMock();
  1225. $connection->expects($this->any())->method('getDriver')
  1226. ->will($this->returnValue($driver));
  1227. $table = (new TableSchema('articles_tags'))
  1228. ->addColumn('article_id', [
  1229. 'type' => 'integer',
  1230. 'null' => false,
  1231. ])
  1232. ->addColumn('tag_id', [
  1233. 'type' => 'integer',
  1234. 'null' => false,
  1235. ])
  1236. ->addConstraint('primary', [
  1237. 'type' => 'primary',
  1238. 'columns' => ['article_id', 'tag_id'],
  1239. ]);
  1240. $expected = <<<SQL
  1241. CREATE TABLE "articles_tags" (
  1242. "article_id" INTEGER NOT NULL,
  1243. "tag_id" INTEGER NOT NULL,
  1244. PRIMARY KEY ("article_id", "tag_id")
  1245. )
  1246. SQL;
  1247. $result = $table->createSql($connection);
  1248. $this->assertCount(1, $result);
  1249. $this->assertTextEquals($expected, $result[0]);
  1250. $table = (new TableSchema('composite_key'))
  1251. ->addColumn('id', [
  1252. 'type' => 'integer',
  1253. 'null' => false,
  1254. 'autoIncrement' => true,
  1255. ])
  1256. ->addColumn('account_id', [
  1257. 'type' => 'integer',
  1258. 'null' => false,
  1259. ])
  1260. ->addConstraint('primary', [
  1261. 'type' => 'primary',
  1262. 'columns' => ['id', 'account_id'],
  1263. ]);
  1264. $expected = <<<SQL
  1265. CREATE TABLE "composite_key" (
  1266. "id" SERIAL,
  1267. "account_id" INTEGER NOT NULL,
  1268. PRIMARY KEY ("id", "account_id")
  1269. )
  1270. SQL;
  1271. $result = $table->createSql($connection);
  1272. $this->assertCount(1, $result);
  1273. $this->assertTextEquals($expected, $result[0]);
  1274. }
  1275. /**
  1276. * test dropSql
  1277. *
  1278. * @return void
  1279. */
  1280. public function testDropSql()
  1281. {
  1282. $driver = $this->_getMockedDriver();
  1283. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1284. ->disableOriginalConstructor()
  1285. ->getMock();
  1286. $connection->expects($this->any())->method('getDriver')
  1287. ->will($this->returnValue($driver));
  1288. $table = new TableSchema('schema_articles');
  1289. $result = $table->dropSql($connection);
  1290. $this->assertCount(1, $result);
  1291. $this->assertSame('DROP TABLE "schema_articles" CASCADE', $result[0]);
  1292. }
  1293. /**
  1294. * Test truncateSql()
  1295. *
  1296. * @return void
  1297. */
  1298. public function testTruncateSql()
  1299. {
  1300. $driver = $this->_getMockedDriver();
  1301. $connection = $this->getMockBuilder('Cake\Database\Connection')
  1302. ->disableOriginalConstructor()
  1303. ->getMock();
  1304. $connection->expects($this->any())->method('getDriver')
  1305. ->will($this->returnValue($driver));
  1306. $table = new TableSchema('schema_articles');
  1307. $table->addColumn('id', 'integer')
  1308. ->addConstraint('primary', [
  1309. 'type' => 'primary',
  1310. 'columns' => ['id'],
  1311. ]);
  1312. $result = $table->truncateSql($connection);
  1313. $this->assertCount(1, $result);
  1314. $this->assertSame('TRUNCATE "schema_articles" RESTART IDENTITY CASCADE', $result[0]);
  1315. }
  1316. /**
  1317. * Get a schema instance with a mocked driver/pdo instances
  1318. *
  1319. * @return \Cake\Database\Driver
  1320. */
  1321. protected function _getMockedDriver()
  1322. {
  1323. $driver = new Postgres();
  1324. $mock = $this->getMockBuilder(PDO::class)
  1325. ->setMethods(['quote'])
  1326. ->disableOriginalConstructor()
  1327. ->getMock();
  1328. $mock->expects($this->any())
  1329. ->method('quote')
  1330. ->will($this->returnCallback(function ($value) {
  1331. return "'$value'";
  1332. }));
  1333. $driver->setConnection($mock);
  1334. return $driver;
  1335. }
  1336. }