SqliteSchemaTest.php 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  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\Table;
  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('Cake\Database\Schema\Table')
  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 \Cake\Database\Schema\Table('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' => Table::LENGTH_TINY, 'null' => false],
  460. '"body" VARCHAR(' . Table::LENGTH_TINY . ') NOT NULL'
  461. ],
  462. [
  463. 'body',
  464. ['type' => 'text', 'length' => Table::LENGTH_MEDIUM, 'null' => false],
  465. '"body" TEXT NOT NULL'
  466. ],
  467. [
  468. 'body',
  469. ['type' => 'text', 'length' => Table::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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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 Table('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. $pdo = PDO::class;
  975. if (version_compare(PHP_VERSION, '5.6', '<')) {
  976. $pdo = 'FakePdo';
  977. }
  978. $mock = $this->getMockBuilder($pdo)
  979. ->setMethods(['quote', 'prepare'])
  980. ->disableOriginalConstructor()
  981. ->getMock();
  982. $mock->expects($this->any())
  983. ->method('quote')
  984. ->will($this->returnCallback(function ($value) {
  985. return '"' . $value . '"';
  986. }));
  987. $driver->connection($mock);
  988. return $driver;
  989. }
  990. }