SqliteSchemaTest.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Database\Schema;
  16. use Cake\Database\Driver\Sqlite;
  17. use Cake\Database\Schema\Collection as SchemaCollection;
  18. use Cake\Database\Schema\SqliteSchema;
  19. use Cake\Database\Schema\TableSchema;
  20. use Cake\Datasource\ConnectionManager;
  21. use Cake\TestSuite\TestCase;
  22. use PDO;
  23. /**
  24. * Test case for Sqlite Schema Dialect.
  25. */
  26. class SqliteSchemaTest extends TestCase
  27. {
  28. /**
  29. * Helper method for skipping tests that need a real connection.
  30. *
  31. * @return void
  32. */
  33. protected function _needsConnection()
  34. {
  35. $config = ConnectionManager::config('test');
  36. $this->skipIf(strpos($config['driver'], 'Sqlite') === false, 'Not using Sqlite for test config');
  37. }
  38. /**
  39. * Data provider for convert column testing
  40. *
  41. * @return array
  42. */
  43. public static function convertColumnProvider()
  44. {
  45. return [
  46. [
  47. 'DATETIME',
  48. ['type' => 'datetime', 'length' => null]
  49. ],
  50. [
  51. 'DATE',
  52. ['type' => 'date', 'length' => null]
  53. ],
  54. [
  55. 'TIME',
  56. ['type' => 'time', 'length' => null]
  57. ],
  58. [
  59. 'BOOLEAN',
  60. ['type' => 'boolean', 'length' => null]
  61. ],
  62. [
  63. 'BIGINT',
  64. ['type' => 'biginteger', 'length' => null, 'unsigned' => false]
  65. ],
  66. [
  67. 'UNSIGNED BIGINT',
  68. ['type' => 'biginteger', 'length' => null, 'unsigned' => true]
  69. ],
  70. [
  71. 'VARCHAR(255)',
  72. ['type' => 'string', 'length' => 255]
  73. ],
  74. [
  75. 'CHAR(25)',
  76. ['type' => 'string', 'fixed' => true, 'length' => 25]
  77. ],
  78. [
  79. 'CHAR(36)',
  80. ['type' => 'uuid', 'length' => null]
  81. ],
  82. [
  83. 'BLOB',
  84. ['type' => 'binary', 'length' => null]
  85. ],
  86. [
  87. 'INTEGER(11)',
  88. ['type' => 'integer', 'length' => 11, 'unsigned' => false]
  89. ],
  90. [
  91. 'UNSIGNED INTEGER(11)',
  92. ['type' => 'integer', 'length' => 11, 'unsigned' => true]
  93. ],
  94. [
  95. 'TINYINT(5)',
  96. ['type' => 'integer', 'length' => 5, 'unsigned' => false]
  97. ],
  98. [
  99. 'MEDIUMINT(10)',
  100. ['type' => 'integer', 'length' => 10, 'unsigned' => false]
  101. ],
  102. [
  103. 'FLOAT',
  104. ['type' => 'float', 'length' => null, 'unsigned' => false]
  105. ],
  106. [
  107. 'DOUBLE',
  108. ['type' => 'float', 'length' => null, 'unsigned' => false]
  109. ],
  110. [
  111. 'UNSIGNED DOUBLE',
  112. ['type' => 'float', 'length' => null, 'unsigned' => true]
  113. ],
  114. [
  115. 'REAL',
  116. ['type' => 'float', 'length' => null, 'unsigned' => false]
  117. ],
  118. [
  119. 'DECIMAL(11,2)',
  120. ['type' => 'decimal', 'length' => null, 'unsigned' => false]
  121. ],
  122. [
  123. 'UNSIGNED DECIMAL(11,2)',
  124. ['type' => 'decimal', 'length' => null, 'unsigned' => true]
  125. ],
  126. ];
  127. }
  128. /**
  129. * Test parsing SQLite column types from field description.
  130. *
  131. * @dataProvider convertColumnProvider
  132. * @return void
  133. */
  134. public function testConvertColumn($type, $expected)
  135. {
  136. $field = [
  137. 'pk' => false,
  138. 'name' => 'field',
  139. 'type' => $type,
  140. 'notnull' => false,
  141. 'dflt_value' => 'Default value',
  142. ];
  143. $expected += [
  144. 'null' => true,
  145. 'default' => 'Default value',
  146. ];
  147. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlite')->getMock();
  148. $dialect = new SqliteSchema($driver);
  149. $table = $this->getMockBuilder(TableSchema::class)
  150. ->setConstructorArgs(['table'])
  151. ->getMock();
  152. $table->expects($this->at(1))->method('addColumn')->with('field', $expected);
  153. $dialect->convertColumnDescription($table, $field);
  154. }
  155. /**
  156. * Tests converting multiple rows into a primary constraint with multiple
  157. * columns
  158. *
  159. * @return void
  160. */
  161. public function testConvertCompositePrimaryKey()
  162. {
  163. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlite')->getMock();
  164. $dialect = new SqliteSchema($driver);
  165. $field1 = [
  166. 'pk' => true,
  167. 'name' => 'field1',
  168. 'type' => 'INTEGER(11)',
  169. 'notnull' => false,
  170. 'dflt_value' => 0,
  171. ];
  172. $field2 = [
  173. 'pk' => true,
  174. 'name' => 'field2',
  175. 'type' => 'INTEGER(11)',
  176. 'notnull' => false,
  177. 'dflt_value' => 1,
  178. ];
  179. $table = new TableSchema('table');
  180. $dialect->convertColumnDescription($table, $field1);
  181. $dialect->convertColumnDescription($table, $field2);
  182. $this->assertEquals(['field1', 'field2'], $table->primaryKey());
  183. }
  184. /**
  185. * Creates tables for testing listTables/describe()
  186. *
  187. * @param \Cake\Database\Connection $connection
  188. * @return void
  189. */
  190. protected function _createTables($connection)
  191. {
  192. $this->_needsConnection();
  193. $schema = new SchemaCollection($connection);
  194. $result = $schema->listTables();
  195. if (in_array('schema_articles', $result) &&
  196. in_array('schema_authors', $result)
  197. ) {
  198. return;
  199. }
  200. $table = <<<SQL
  201. CREATE TABLE schema_authors (
  202. id INTEGER PRIMARY KEY AUTOINCREMENT,
  203. name VARCHAR(50),
  204. bio TEXT,
  205. created DATETIME
  206. )
  207. SQL;
  208. $connection->execute($table);
  209. $table = <<<SQL
  210. CREATE TABLE schema_articles (
  211. id INTEGER PRIMARY KEY AUTOINCREMENT,
  212. title VARCHAR(20) DEFAULT 'Let ''em eat cake',
  213. body TEXT,
  214. author_id INT(11) NOT NULL,
  215. published BOOLEAN DEFAULT 0,
  216. created DATETIME,
  217. field1 VARCHAR(10) DEFAULT NULL,
  218. field2 VARCHAR(10) DEFAULT 'NULL',
  219. CONSTRAINT "title_idx" UNIQUE ("title", "body")
  220. CONSTRAINT "author_idx" FOREIGN KEY ("author_id") REFERENCES "schema_authors" ("id") ON UPDATE CASCADE ON DELETE RESTRICT
  221. );
  222. SQL;
  223. $connection->execute($table);
  224. $connection->execute('CREATE INDEX "created_idx" ON "schema_articles" ("created")');
  225. $sql = <<<SQL
  226. CREATE TABLE schema_composite (
  227. "id" INTEGER NOT NULL,
  228. "site_id" INTEGER NOT NULL,
  229. "name" VARCHAR(255),
  230. PRIMARY KEY("id", "site_id")
  231. );
  232. SQL;
  233. $connection->execute($sql);
  234. }
  235. /**
  236. * Test SchemaCollection listing tables with Sqlite
  237. *
  238. * @return void
  239. */
  240. public function testListTables()
  241. {
  242. $connection = ConnectionManager::get('test');
  243. $this->_createTables($connection);
  244. $schema = new SchemaCollection($connection);
  245. $result = $schema->listTables();
  246. $this->assertInternalType('array', $result);
  247. $this->assertContains('schema_articles', $result);
  248. $this->assertContains('schema_authors', $result);
  249. }
  250. /**
  251. * Test describing a table with Sqlite
  252. *
  253. * @return void
  254. */
  255. public function testDescribeTable()
  256. {
  257. $connection = ConnectionManager::get('test');
  258. $this->_createTables($connection);
  259. $schema = new SchemaCollection($connection);
  260. $result = $schema->describe('schema_articles');
  261. $expected = [
  262. 'id' => [
  263. 'type' => 'integer',
  264. 'null' => false,
  265. 'default' => null,
  266. 'length' => null,
  267. 'precision' => null,
  268. 'comment' => null,
  269. 'unsigned' => false,
  270. 'autoIncrement' => true,
  271. ],
  272. 'title' => [
  273. 'type' => 'string',
  274. 'null' => true,
  275. 'default' => 'Let \'em eat cake',
  276. 'length' => 20,
  277. 'precision' => null,
  278. 'fixed' => null,
  279. 'comment' => null,
  280. 'collate' => null,
  281. ],
  282. 'body' => [
  283. 'type' => 'text',
  284. 'null' => true,
  285. 'default' => null,
  286. 'length' => null,
  287. 'precision' => null,
  288. 'comment' => null,
  289. 'collate' => null,
  290. ],
  291. 'author_id' => [
  292. 'type' => 'integer',
  293. 'null' => false,
  294. 'default' => null,
  295. 'length' => 11,
  296. 'unsigned' => false,
  297. 'precision' => null,
  298. 'comment' => null,
  299. 'autoIncrement' => null,
  300. ],
  301. 'published' => [
  302. 'type' => 'boolean',
  303. 'null' => true,
  304. 'default' => 0,
  305. 'length' => null,
  306. 'precision' => null,
  307. 'comment' => null,
  308. ],
  309. 'created' => [
  310. 'type' => 'datetime',
  311. 'null' => true,
  312. 'default' => null,
  313. 'length' => null,
  314. 'precision' => null,
  315. 'comment' => null,
  316. ],
  317. 'field1' => [
  318. 'type' => 'string',
  319. 'null' => true,
  320. 'default' => null,
  321. 'length' => 10,
  322. 'precision' => null,
  323. 'fixed' => null,
  324. 'comment' => null,
  325. 'collate' => null,
  326. ],
  327. 'field2' => [
  328. 'type' => 'string',
  329. 'null' => true,
  330. 'default' => 'NULL',
  331. 'length' => 10,
  332. 'precision' => null,
  333. 'fixed' => null,
  334. 'comment' => null,
  335. 'collate' => null,
  336. ],
  337. ];
  338. $this->assertInstanceOf('Cake\Database\Schema\Table', $result);
  339. $this->assertEquals(['id'], $result->primaryKey());
  340. foreach ($expected as $field => $definition) {
  341. $this->assertEquals($definition, $result->column($field));
  342. }
  343. }
  344. /**
  345. * Test describing a table with Sqlite and composite keys
  346. *
  347. * Composite keys in SQLite are never autoincrement, and shouldn't be marked
  348. * as such.
  349. *
  350. * @return void
  351. */
  352. public function testDescribeTableCompositeKey()
  353. {
  354. $connection = ConnectionManager::get('test');
  355. $this->_createTables($connection);
  356. $schema = new SchemaCollection($connection);
  357. $result = $schema->describe('schema_composite');
  358. $this->assertEquals(['id', 'site_id'], $result->primaryKey());
  359. $this->assertNull($result->column('site_id')['autoIncrement'], 'site_id should not be autoincrement');
  360. $this->assertNull($result->column('id')['autoIncrement'], 'id should not be autoincrement');
  361. }
  362. /**
  363. * Test describing a table with indexes
  364. *
  365. * @return void
  366. */
  367. public function testDescribeTableIndexes()
  368. {
  369. $connection = ConnectionManager::get('test');
  370. $this->_createTables($connection);
  371. $schema = new SchemaCollection($connection);
  372. $result = $schema->describe('schema_articles');
  373. $this->assertInstanceOf('Cake\Database\Schema\Table', $result);
  374. $expected = [
  375. 'primary' => [
  376. 'type' => 'primary',
  377. 'columns' => ['id'],
  378. 'length' => []
  379. ],
  380. 'sqlite_autoindex_schema_articles_1' => [
  381. 'type' => 'unique',
  382. 'columns' => ['title', 'body'],
  383. 'length' => []
  384. ],
  385. 'author_id_fk' => [
  386. 'type' => 'foreign',
  387. 'columns' => ['author_id'],
  388. 'references' => ['schema_authors', 'id'],
  389. 'length' => [],
  390. 'update' => 'cascade',
  391. 'delete' => 'restrict',
  392. ]
  393. ];
  394. $this->assertCount(3, $result->constraints());
  395. $this->assertEquals($expected['primary'], $result->constraint('primary'));
  396. $this->assertEquals(
  397. $expected['sqlite_autoindex_schema_articles_1'],
  398. $result->constraint('sqlite_autoindex_schema_articles_1')
  399. );
  400. $this->assertEquals(
  401. $expected['author_id_fk'],
  402. $result->constraint('author_id_fk')
  403. );
  404. $this->assertCount(1, $result->indexes());
  405. $expected = [
  406. 'type' => 'index',
  407. 'columns' => ['created'],
  408. 'length' => []
  409. ];
  410. $this->assertEquals($expected, $result->index('created_idx'));
  411. }
  412. /**
  413. * Column provider for creating column sql
  414. *
  415. * @return array
  416. */
  417. public static function columnSqlProvider()
  418. {
  419. return [
  420. // strings
  421. [
  422. 'title',
  423. ['type' => 'string', 'length' => 25, 'null' => false],
  424. '"title" VARCHAR(25) NOT NULL'
  425. ],
  426. [
  427. 'title',
  428. ['type' => 'string', 'length' => 25, 'null' => true, 'default' => 'ignored'],
  429. '"title" VARCHAR(25) DEFAULT "ignored"',
  430. ],
  431. [
  432. 'id',
  433. ['type' => 'string', 'length' => 32, 'fixed' => true, 'null' => false],
  434. '"id" VARCHAR(32) NOT NULL'
  435. ],
  436. [
  437. 'role',
  438. ['type' => 'string', 'length' => 10, 'null' => false, 'default' => 'admin'],
  439. '"role" VARCHAR(10) NOT NULL DEFAULT "admin"'
  440. ],
  441. [
  442. 'title',
  443. ['type' => 'string'],
  444. '"title" VARCHAR'
  445. ],
  446. [
  447. 'id',
  448. ['type' => 'uuid'],
  449. '"id" CHAR(36)'
  450. ],
  451. // Text
  452. [
  453. 'body',
  454. ['type' => 'text', 'null' => false],
  455. '"body" TEXT NOT NULL'
  456. ],
  457. [
  458. 'body',
  459. ['type' => 'text', 'length' => TableSchema::LENGTH_TINY, 'null' => false],
  460. '"body" VARCHAR(' . TableSchema::LENGTH_TINY . ') NOT NULL'
  461. ],
  462. [
  463. 'body',
  464. ['type' => 'text', 'length' => TableSchema::LENGTH_MEDIUM, 'null' => false],
  465. '"body" TEXT NOT NULL'
  466. ],
  467. [
  468. 'body',
  469. ['type' => 'text', 'length' => TableSchema::LENGTH_LONG, 'null' => false],
  470. '"body" TEXT NOT NULL'
  471. ],
  472. // Integers
  473. [
  474. 'post_id',
  475. ['type' => 'integer', 'length' => 11, 'unsigned' => false],
  476. '"post_id" INTEGER(11)'
  477. ],
  478. [
  479. 'post_id',
  480. ['type' => 'biginteger', 'length' => 20, 'unsigned' => false],
  481. '"post_id" BIGINT'
  482. ],
  483. [
  484. 'post_id',
  485. ['type' => 'biginteger', 'length' => 20, 'unsigned' => true],
  486. '"post_id" UNSIGNED BIGINT'
  487. ],
  488. // Decimal
  489. [
  490. 'value',
  491. ['type' => 'decimal', 'unsigned' => false],
  492. '"value" DECIMAL'
  493. ],
  494. [
  495. 'value',
  496. ['type' => 'decimal', 'length' => 11, 'unsigned' => false],
  497. '"value" DECIMAL(11,0)'
  498. ],
  499. [
  500. 'value',
  501. ['type' => 'decimal', 'length' => 11, 'unsigned' => true],
  502. '"value" UNSIGNED DECIMAL(11,0)'
  503. ],
  504. [
  505. 'value',
  506. ['type' => 'decimal', 'length' => 12, 'precision' => 5, 'unsigned' => false],
  507. '"value" DECIMAL(12,5)'
  508. ],
  509. // Float
  510. [
  511. 'value',
  512. ['type' => 'float'],
  513. '"value" FLOAT'
  514. ],
  515. [
  516. 'value',
  517. ['type' => 'float', 'length' => 11, 'precision' => 3, 'unsigned' => false],
  518. '"value" FLOAT(11,3)'
  519. ],
  520. [
  521. 'value',
  522. ['type' => 'float', 'length' => 11, 'precision' => 3, 'unsigned' => true],
  523. '"value" UNSIGNED FLOAT(11,3)'
  524. ],
  525. // Boolean
  526. [
  527. 'checked',
  528. ['type' => 'boolean', 'null' => true, 'default' => false],
  529. '"checked" BOOLEAN DEFAULT FALSE'
  530. ],
  531. [
  532. 'checked',
  533. ['type' => 'boolean', 'default' => true, 'null' => false],
  534. '"checked" BOOLEAN NOT NULL DEFAULT TRUE'
  535. ],
  536. // datetimes
  537. [
  538. 'created',
  539. ['type' => 'datetime'],
  540. '"created" DATETIME'
  541. ],
  542. [
  543. 'open_date',
  544. ['type' => 'datetime', 'null' => false, 'default' => '2016-12-07 23:04:00'],
  545. '"open_date" DATETIME NOT NULL DEFAULT "2016-12-07 23:04:00"'
  546. ],
  547. // Date & Time
  548. [
  549. 'start_date',
  550. ['type' => 'date'],
  551. '"start_date" DATE'
  552. ],
  553. [
  554. 'start_time',
  555. ['type' => 'time'],
  556. '"start_time" TIME'
  557. ],
  558. // timestamps
  559. [
  560. 'created',
  561. ['type' => 'timestamp', 'null' => true],
  562. '"created" TIMESTAMP DEFAULT NULL'
  563. ],
  564. ];
  565. }
  566. /**
  567. * Test the addConstraintSql method.
  568. *
  569. * @return void
  570. */
  571. public function testAddConstraintSql()
  572. {
  573. $driver = $this->_getMockedDriver();
  574. $connection = $this->getMockBuilder('Cake\Database\Connection')
  575. ->disableOriginalConstructor()
  576. ->getMock();
  577. $connection->expects($this->any())->method('driver')
  578. ->will($this->returnValue($driver));
  579. $table = new TableSchema('posts');
  580. $result = $table->addConstraintSql($connection);
  581. $this->assertEmpty($result);
  582. }
  583. /**
  584. * Test the dropConstraintSql method.
  585. *
  586. * @return void
  587. */
  588. public function testDropConstraintSql()
  589. {
  590. $driver = $this->_getMockedDriver();
  591. $connection = $this->getMockBuilder('Cake\Database\Connection')
  592. ->disableOriginalConstructor()
  593. ->getMock();
  594. $connection->expects($this->any())->method('driver')
  595. ->will($this->returnValue($driver));
  596. $table = new TableSchema('posts');
  597. $result = $table->dropConstraintSql($connection);
  598. $this->assertEmpty($result);
  599. }
  600. /**
  601. * Test generating column definitions
  602. *
  603. * @dataProvider columnSqlProvider
  604. * @return void
  605. */
  606. public function testColumnSql($name, $data, $expected)
  607. {
  608. $driver = $this->_getMockedDriver();
  609. $schema = new SqliteSchema($driver);
  610. $table = (new TableSchema('articles'))->addColumn($name, $data);
  611. $this->assertEquals($expected, $schema->columnSql($table, $name));
  612. }
  613. /**
  614. * Test generating a column that is a primary key.
  615. *
  616. * @return void
  617. */
  618. public function testColumnSqlPrimaryKey()
  619. {
  620. $driver = $this->_getMockedDriver();
  621. $schema = new SqliteSchema($driver);
  622. $table = new TableSchema('articles');
  623. $table->addColumn('id', [
  624. 'type' => 'integer',
  625. 'null' => false,
  626. 'length' => 11,
  627. 'unsigned' => true
  628. ])
  629. ->addConstraint('primary', [
  630. 'type' => 'primary',
  631. 'columns' => ['id']
  632. ]);
  633. $result = $schema->columnSql($table, 'id');
  634. $this->assertEquals($result, '"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT');
  635. $result = $schema->constraintSql($table, 'primary');
  636. $this->assertEquals('', $result, 'Integer primary keys are special in sqlite.');
  637. }
  638. /**
  639. * Test generating a bigint column that is a primary key.
  640. *
  641. * @return void
  642. */
  643. public function testColumnSqlPrimaryKeyBigInt()
  644. {
  645. $driver = $this->_getMockedDriver();
  646. $schema = new SqliteSchema($driver);
  647. $table = new TableSchema('articles');
  648. $table->addColumn('id', [
  649. 'type' => 'biginteger',
  650. 'null' => false
  651. ])
  652. ->addConstraint('primary', [
  653. 'type' => 'primary',
  654. 'columns' => ['id']
  655. ]);
  656. $result = $schema->columnSql($table, 'id');
  657. $this->assertEquals($result, '"id" BIGINT NOT NULL');
  658. $result = $schema->constraintSql($table, 'primary');
  659. $this->assertEquals('CONSTRAINT "primary" PRIMARY KEY ("id")', $result, 'Bigint primary keys are not special.');
  660. }
  661. /**
  662. * Provide data for testing constraintSql
  663. *
  664. * @return array
  665. */
  666. public static function constraintSqlProvider()
  667. {
  668. return [
  669. [
  670. 'primary',
  671. ['type' => 'primary', 'columns' => ['title']],
  672. 'CONSTRAINT "primary" PRIMARY KEY ("title")'
  673. ],
  674. [
  675. 'unique_idx',
  676. ['type' => 'unique', 'columns' => ['title', 'author_id']],
  677. 'CONSTRAINT "unique_idx" UNIQUE ("title", "author_id")'
  678. ],
  679. [
  680. 'author_id_idx',
  681. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id']],
  682. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  683. 'REFERENCES "authors" ("id") ON UPDATE RESTRICT ON DELETE RESTRICT'
  684. ],
  685. [
  686. 'author_id_idx',
  687. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'cascade'],
  688. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  689. 'REFERENCES "authors" ("id") ON UPDATE CASCADE ON DELETE RESTRICT'
  690. ],
  691. [
  692. 'author_id_idx',
  693. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'restrict'],
  694. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  695. 'REFERENCES "authors" ("id") ON UPDATE RESTRICT ON DELETE RESTRICT'
  696. ],
  697. [
  698. 'author_id_idx',
  699. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'setNull'],
  700. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  701. 'REFERENCES "authors" ("id") ON UPDATE SET NULL ON DELETE RESTRICT'
  702. ],
  703. [
  704. 'author_id_idx',
  705. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'noAction'],
  706. 'CONSTRAINT "author_id_idx" FOREIGN KEY ("author_id") ' .
  707. 'REFERENCES "authors" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT'
  708. ],
  709. ];
  710. }
  711. /**
  712. * Test the constraintSql method.
  713. *
  714. * @dataProvider constraintSqlProvider
  715. */
  716. public function testConstraintSql($name, $data, $expected)
  717. {
  718. $driver = $this->_getMockedDriver();
  719. $schema = new SqliteSchema($driver);
  720. $table = (new TableSchema('articles'))->addColumn('title', [
  721. 'type' => 'string',
  722. 'length' => 255
  723. ])->addColumn('author_id', [
  724. 'type' => 'integer',
  725. ])->addConstraint($name, $data);
  726. $this->assertEquals($expected, $schema->constraintSql($table, $name));
  727. }
  728. /**
  729. * Provide data for testing indexSql
  730. *
  731. * @return array
  732. */
  733. public static function indexSqlProvider()
  734. {
  735. return [
  736. [
  737. 'author_idx',
  738. ['type' => 'index', 'columns' => ['title', 'author_id']],
  739. 'CREATE INDEX "author_idx" ON "articles" ("title", "author_id")'
  740. ],
  741. ];
  742. }
  743. /**
  744. * Test the indexSql method.
  745. *
  746. * @dataProvider indexSqlProvider
  747. */
  748. public function testIndexSql($name, $data, $expected)
  749. {
  750. $driver = $this->_getMockedDriver();
  751. $schema = new SqliteSchema($driver);
  752. $table = (new TableSchema('articles'))->addColumn('title', [
  753. 'type' => 'string',
  754. 'length' => 255
  755. ])->addColumn('author_id', [
  756. 'type' => 'integer',
  757. ])->addIndex($name, $data);
  758. $this->assertEquals($expected, $schema->indexSql($table, $name));
  759. }
  760. /**
  761. * Integration test for converting a Schema\Table into MySQL table creates.
  762. *
  763. * @return void
  764. */
  765. public function testCreateSql()
  766. {
  767. $driver = $this->_getMockedDriver();
  768. $connection = $this->getMockBuilder('Cake\Database\Connection')
  769. ->disableOriginalConstructor()
  770. ->getMock();
  771. $connection->expects($this->any())->method('driver')
  772. ->will($this->returnValue($driver));
  773. $table = (new TableSchema('articles'))->addColumn('id', [
  774. 'type' => 'integer',
  775. 'null' => false
  776. ])
  777. ->addColumn('title', [
  778. 'type' => 'string',
  779. 'null' => false,
  780. ])
  781. ->addColumn('body', ['type' => 'text'])
  782. ->addColumn('data', ['type' => 'json'])
  783. ->addColumn('created', 'datetime')
  784. ->addConstraint('primary', [
  785. 'type' => 'primary',
  786. 'columns' => ['id']
  787. ])
  788. ->addIndex('title_idx', [
  789. 'type' => 'index',
  790. 'columns' => ['title']
  791. ]);
  792. $expected = <<<SQL
  793. CREATE TABLE "articles" (
  794. "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  795. "title" VARCHAR NOT NULL,
  796. "body" TEXT,
  797. "data" TEXT,
  798. "created" DATETIME
  799. )
  800. SQL;
  801. $result = $table->createSql($connection);
  802. $this->assertCount(2, $result);
  803. $this->assertTextEquals($expected, $result[0]);
  804. $this->assertEquals(
  805. 'CREATE INDEX "title_idx" ON "articles" ("title")',
  806. $result[1]
  807. );
  808. }
  809. /**
  810. * Tests creating temporary tables
  811. *
  812. * @return void
  813. */
  814. public function testCreateTemporary()
  815. {
  816. $driver = $this->_getMockedDriver();
  817. $connection = $this->getMockBuilder('Cake\Database\Connection')
  818. ->disableOriginalConstructor()
  819. ->getMock();
  820. $connection->expects($this->any())->method('driver')
  821. ->will($this->returnValue($driver));
  822. $table = (new TableSchema('schema_articles'))->addColumn('id', [
  823. 'type' => 'integer',
  824. 'null' => false
  825. ]);
  826. $table->temporary(true);
  827. $sql = $table->createSql($connection);
  828. $this->assertContains('CREATE TEMPORARY TABLE', $sql[0]);
  829. }
  830. /**
  831. * Test primary key generation & auto-increment.
  832. *
  833. * @return void
  834. */
  835. public function testCreateSqlCompositeIntegerKey()
  836. {
  837. $driver = $this->_getMockedDriver();
  838. $connection = $this->getMockBuilder('Cake\Database\Connection')
  839. ->disableOriginalConstructor()
  840. ->getMock();
  841. $connection->expects($this->any())->method('driver')
  842. ->will($this->returnValue($driver));
  843. $table = (new TableSchema('articles_tags'))
  844. ->addColumn('article_id', [
  845. 'type' => 'integer',
  846. 'null' => false
  847. ])
  848. ->addColumn('tag_id', [
  849. 'type' => 'integer',
  850. 'null' => false,
  851. ])
  852. ->addConstraint('primary', [
  853. 'type' => 'primary',
  854. 'columns' => ['article_id', 'tag_id']
  855. ]);
  856. $expected = <<<SQL
  857. CREATE TABLE "articles_tags" (
  858. "article_id" INTEGER NOT NULL,
  859. "tag_id" INTEGER NOT NULL,
  860. CONSTRAINT "primary" PRIMARY KEY ("article_id", "tag_id")
  861. )
  862. SQL;
  863. $result = $table->createSql($connection);
  864. $this->assertCount(1, $result);
  865. $this->assertTextEquals($expected, $result[0]);
  866. // Sqlite only supports AUTO_INCREMENT on single column primary
  867. // keys. Ensure that schema data follows the limitations of Sqlite.
  868. $table = (new TableSchema('composite_key'))
  869. ->addColumn('id', [
  870. 'type' => 'integer',
  871. 'null' => false,
  872. 'autoIncrement' => true
  873. ])
  874. ->addColumn('account_id', [
  875. 'type' => 'integer',
  876. 'null' => false,
  877. ])
  878. ->addConstraint('primary', [
  879. 'type' => 'primary',
  880. 'columns' => ['id', 'account_id']
  881. ]);
  882. $expected = <<<SQL
  883. CREATE TABLE "composite_key" (
  884. "id" INTEGER NOT NULL,
  885. "account_id" INTEGER NOT NULL,
  886. CONSTRAINT "primary" PRIMARY KEY ("id", "account_id")
  887. )
  888. SQL;
  889. $result = $table->createSql($connection);
  890. $this->assertCount(1, $result);
  891. $this->assertTextEquals($expected, $result[0]);
  892. }
  893. /**
  894. * test dropSql
  895. *
  896. * @return void
  897. */
  898. public function testDropSql()
  899. {
  900. $driver = $this->_getMockedDriver();
  901. $connection = $this->getMockBuilder('Cake\Database\Connection')
  902. ->disableOriginalConstructor()
  903. ->getMock();
  904. $connection->expects($this->any())->method('driver')
  905. ->will($this->returnValue($driver));
  906. $table = new TableSchema('articles');
  907. $result = $table->dropSql($connection);
  908. $this->assertCount(1, $result);
  909. $this->assertEquals('DROP TABLE "articles"', $result[0]);
  910. }
  911. /**
  912. * Test truncateSql()
  913. *
  914. * @return void
  915. */
  916. public function testTruncateSql()
  917. {
  918. $driver = $this->_getMockedDriver();
  919. $connection = $this->getMockBuilder('Cake\Database\Connection')
  920. ->disableOriginalConstructor()
  921. ->getMock();
  922. $connection->expects($this->any())->method('driver')
  923. ->will($this->returnValue($driver));
  924. $statement = $this->getMockBuilder('\PDOStatement')
  925. ->setMethods(['execute', 'rowCount', 'closeCursor', 'fetch'])
  926. ->getMock();
  927. $driver->connection()->expects($this->once())->method('prepare')
  928. ->with('SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"')
  929. ->will($this->returnValue($statement));
  930. $statement->expects($this->at(0))->method('fetch')
  931. ->will($this->returnValue(['1']));
  932. $statement->expects($this->at(2))->method('fetch')
  933. ->will($this->returnValue(false));
  934. $table = new TableSchema('articles');
  935. $result = $table->truncateSql($connection);
  936. $this->assertCount(2, $result);
  937. $this->assertEquals('DELETE FROM sqlite_sequence WHERE name="articles"', $result[0]);
  938. $this->assertEquals('DELETE FROM "articles"', $result[1]);
  939. }
  940. /**
  941. * Test truncateSql() with no sequences
  942. *
  943. * @return void
  944. */
  945. public function testTruncateSqlNoSequences()
  946. {
  947. $driver = $this->_getMockedDriver();
  948. $connection = $this->getMockBuilder('Cake\Database\Connection')
  949. ->disableOriginalConstructor()
  950. ->getMock();
  951. $connection->expects($this->any())->method('driver')
  952. ->will($this->returnValue($driver));
  953. $statement = $this->getMockBuilder('\PDOStatement')
  954. ->setMethods(['execute', 'rowCount', 'closeCursor', 'fetch'])
  955. ->getMock();
  956. $driver->connection()->expects($this->once())->method('prepare')
  957. ->with('SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"')
  958. ->will($this->returnValue($statement));
  959. $statement->expects($this->once())->method('fetch')
  960. ->will($this->returnValue(false));
  961. $table = new TableSchema('articles');
  962. $result = $table->truncateSql($connection);
  963. $this->assertCount(1, $result);
  964. $this->assertEquals('DELETE FROM "articles"', $result[0]);
  965. }
  966. /**
  967. * Get a schema instance with a mocked driver/pdo instances
  968. *
  969. * @return \Cake\Database\Driver
  970. */
  971. protected function _getMockedDriver()
  972. {
  973. $driver = new Sqlite();
  974. $mock = $this->getMockBuilder(PDO::class)
  975. ->setMethods(['quote', 'prepare'])
  976. ->disableOriginalConstructor()
  977. ->getMock();
  978. $mock->expects($this->any())
  979. ->method('quote')
  980. ->will($this->returnCallback(function ($value) {
  981. return '"' . $value . '"';
  982. }));
  983. $driver->connection($mock);
  984. return $driver;
  985. }
  986. }