SqliteSchemaTest.php 32 KB

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