PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php

http://github.com/doctrine/dbal
PHP | 1218 lines | 1080 code | 48 blank | 90 comment | 30 complexity | 423dd3acd86363dc6fae5813210ad9b1 MD5 | raw file
Possible License(s): Unlicense
  1. <?php
  2. namespace Doctrine\DBAL\Platforms;
  3. use Doctrine\DBAL\DBALException;
  4. use Doctrine\DBAL\Schema\Column;
  5. use Doctrine\DBAL\Schema\Constraint;
  6. use Doctrine\DBAL\Schema\ForeignKeyConstraint;
  7. use Doctrine\DBAL\Schema\Identifier;
  8. use Doctrine\DBAL\Schema\Index;
  9. use Doctrine\DBAL\Schema\Table;
  10. use Doctrine\DBAL\Schema\TableDiff;
  11. use Doctrine\DBAL\TransactionIsolationLevel;
  12. use Doctrine\DBAL\Types;
  13. use function array_merge;
  14. use function array_unique;
  15. use function array_values;
  16. use function implode;
  17. use function is_numeric;
  18. use function sprintf;
  19. use function sqrt;
  20. use function str_replace;
  21. use function strlen;
  22. use function strpos;
  23. use function strtolower;
  24. use function trim;
  25. /**
  26. * The SqlitePlatform class describes the specifics and dialects of the SQLite
  27. * database platform.
  28. *
  29. * @todo Rename: SQLitePlatform
  30. */
  31. class SqlitePlatform extends AbstractPlatform
  32. {
  33. /**
  34. * {@inheritDoc}
  35. */
  36. public function getRegexpExpression()
  37. {
  38. return 'REGEXP';
  39. }
  40. /**
  41. * {@inheritDoc}
  42. *
  43. * @deprecated Use application-generated UUIDs instead
  44. */
  45. public function getGuidExpression()
  46. {
  47. return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || "
  48. . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || "
  49. . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || "
  50. . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))";
  51. }
  52. /**
  53. * @param string $type
  54. *
  55. * @return string
  56. */
  57. public function getNowExpression($type = 'timestamp')
  58. {
  59. switch ($type) {
  60. case 'time':
  61. return 'time(\'now\')';
  62. case 'date':
  63. return 'date(\'now\')';
  64. case 'timestamp':
  65. default:
  66. return 'datetime(\'now\')';
  67. }
  68. }
  69. /**
  70. * {@inheritDoc}
  71. */
  72. public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
  73. {
  74. $trimChar = $char !== false ? ', ' . $char : '';
  75. switch ($pos) {
  76. case TrimMode::LEADING:
  77. $trimFn = 'LTRIM';
  78. break;
  79. case TrimMode::TRAILING:
  80. $trimFn = 'RTRIM';
  81. break;
  82. default:
  83. $trimFn = 'TRIM';
  84. }
  85. return $trimFn . '(' . $str . $trimChar . ')';
  86. }
  87. /**
  88. * {@inheritDoc}
  89. *
  90. * SQLite only supports the 2 parameter variant of this function
  91. */
  92. public function getSubstringExpression($value, $position, $length = null)
  93. {
  94. if ($length !== null) {
  95. return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')';
  96. }
  97. return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))';
  98. }
  99. /**
  100. * {@inheritDoc}
  101. */
  102. public function getLocateExpression($str, $substr, $startPos = false)
  103. {
  104. if ($startPos === false) {
  105. return 'LOCATE(' . $str . ', ' . $substr . ')';
  106. }
  107. return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')';
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
  113. {
  114. switch ($unit) {
  115. case DateIntervalUnit::SECOND:
  116. case DateIntervalUnit::MINUTE:
  117. case DateIntervalUnit::HOUR:
  118. return 'DATETIME(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')";
  119. }
  120. switch ($unit) {
  121. case DateIntervalUnit::WEEK:
  122. $interval *= 7;
  123. $unit = DateIntervalUnit::DAY;
  124. break;
  125. case DateIntervalUnit::QUARTER:
  126. $interval *= 3;
  127. $unit = DateIntervalUnit::MONTH;
  128. break;
  129. }
  130. if (! is_numeric($interval)) {
  131. $interval = "' || " . $interval . " || '";
  132. }
  133. return 'DATE(' . $date . ",'" . $operator . $interval . ' ' . $unit . "')";
  134. }
  135. /**
  136. * {@inheritDoc}
  137. */
  138. public function getDateDiffExpression($date1, $date2)
  139. {
  140. return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2);
  141. }
  142. /**
  143. * {@inheritDoc}
  144. */
  145. protected function _getTransactionIsolationLevelSQL($level)
  146. {
  147. switch ($level) {
  148. case TransactionIsolationLevel::READ_UNCOMMITTED:
  149. return '0';
  150. case TransactionIsolationLevel::READ_COMMITTED:
  151. case TransactionIsolationLevel::REPEATABLE_READ:
  152. case TransactionIsolationLevel::SERIALIZABLE:
  153. return '1';
  154. default:
  155. return parent::_getTransactionIsolationLevelSQL($level);
  156. }
  157. }
  158. /**
  159. * {@inheritDoc}
  160. */
  161. public function getSetTransactionIsolationSQL($level)
  162. {
  163. return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level);
  164. }
  165. /**
  166. * {@inheritDoc}
  167. */
  168. public function prefersIdentityColumns()
  169. {
  170. return true;
  171. }
  172. /**
  173. * {@inheritDoc}
  174. */
  175. public function getBooleanTypeDeclarationSQL(array $field)
  176. {
  177. return 'BOOLEAN';
  178. }
  179. /**
  180. * {@inheritDoc}
  181. */
  182. public function getIntegerTypeDeclarationSQL(array $field)
  183. {
  184. return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  185. }
  186. /**
  187. * {@inheritDoc}
  188. */
  189. public function getBigIntTypeDeclarationSQL(array $field)
  190. {
  191. // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
  192. if (! empty($field['autoincrement'])) {
  193. return $this->getIntegerTypeDeclarationSQL($field);
  194. }
  195. return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  196. }
  197. /**
  198. * @param array<string, mixed> $field
  199. *
  200. * @return string
  201. */
  202. public function getTinyIntTypeDeclarationSql(array $field)
  203. {
  204. // SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
  205. if (! empty($field['autoincrement'])) {
  206. return $this->getIntegerTypeDeclarationSQL($field);
  207. }
  208. return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  209. }
  210. /**
  211. * {@inheritDoc}
  212. */
  213. public function getSmallIntTypeDeclarationSQL(array $field)
  214. {
  215. // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
  216. if (! empty($field['autoincrement'])) {
  217. return $this->getIntegerTypeDeclarationSQL($field);
  218. }
  219. return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  220. }
  221. /**
  222. * @param array<string, mixed> $field
  223. *
  224. * @return string
  225. */
  226. public function getMediumIntTypeDeclarationSql(array $field)
  227. {
  228. // SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
  229. if (! empty($field['autoincrement'])) {
  230. return $this->getIntegerTypeDeclarationSQL($field);
  231. }
  232. return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
  233. }
  234. /**
  235. * {@inheritDoc}
  236. */
  237. public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
  238. {
  239. return 'DATETIME';
  240. }
  241. /**
  242. * {@inheritDoc}
  243. */
  244. public function getDateTypeDeclarationSQL(array $fieldDeclaration)
  245. {
  246. return 'DATE';
  247. }
  248. /**
  249. * {@inheritDoc}
  250. */
  251. public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
  252. {
  253. return 'TIME';
  254. }
  255. /**
  256. * {@inheritDoc}
  257. */
  258. protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
  259. {
  260. // sqlite autoincrement is only possible for the primary key
  261. if (! empty($columnDef['autoincrement'])) {
  262. return ' PRIMARY KEY AUTOINCREMENT';
  263. }
  264. return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : '';
  265. }
  266. /**
  267. * {@inheritDoc}
  268. */
  269. public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
  270. {
  271. return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint(
  272. $foreignKey->getQuotedLocalColumns($this),
  273. str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)),
  274. $foreignKey->getQuotedForeignColumns($this),
  275. $foreignKey->getName(),
  276. $foreignKey->getOptions()
  277. ));
  278. }
  279. /**
  280. * {@inheritDoc}
  281. */
  282. protected function _getCreateTableSQL($name, array $columns, array $options = [])
  283. {
  284. $name = str_replace('.', '__', $name);
  285. $queryFields = $this->getColumnDeclarationListSQL($columns);
  286. if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
  287. foreach ($options['uniqueConstraints'] as $name => $definition) {
  288. $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
  289. }
  290. }
  291. $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options);
  292. if (isset($options['foreignKeys'])) {
  293. foreach ($options['foreignKeys'] as $foreignKey) {
  294. $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey);
  295. }
  296. }
  297. $tableComment = '';
  298. if (isset($options['comment'])) {
  299. $comment = trim($options['comment'], " '");
  300. $tableComment = $this->getInlineTableCommentSQL($comment);
  301. }
  302. $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')'];
  303. if (isset($options['alter']) && $options['alter'] === true) {
  304. return $query;
  305. }
  306. if (isset($options['indexes']) && ! empty($options['indexes'])) {
  307. foreach ($options['indexes'] as $indexDef) {
  308. $query[] = $this->getCreateIndexSQL($indexDef, $name);
  309. }
  310. }
  311. if (isset($options['unique']) && ! empty($options['unique'])) {
  312. foreach ($options['unique'] as $indexDef) {
  313. $query[] = $this->getCreateIndexSQL($indexDef, $name);
  314. }
  315. }
  316. return $query;
  317. }
  318. /**
  319. * Generate a PRIMARY KEY definition if no autoincrement value is used
  320. *
  321. * @param mixed[][] $columns
  322. * @param mixed[] $options
  323. */
  324. private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options) : string
  325. {
  326. if (empty($options['primary'])) {
  327. return '';
  328. }
  329. $keyColumns = array_unique(array_values($options['primary']));
  330. foreach ($keyColumns as $keyColumn) {
  331. if (! empty($columns[$keyColumn]['autoincrement'])) {
  332. return '';
  333. }
  334. }
  335. return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
  336. }
  337. /**
  338. * {@inheritDoc}
  339. */
  340. protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
  341. {
  342. return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
  343. : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
  344. }
  345. /**
  346. * {@inheritdoc}
  347. */
  348. protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
  349. {
  350. return 'BLOB';
  351. }
  352. /**
  353. * {@inheritdoc}
  354. */
  355. public function getBinaryMaxLength()
  356. {
  357. return 0;
  358. }
  359. /**
  360. * {@inheritdoc}
  361. */
  362. public function getBinaryDefaultLength()
  363. {
  364. return 0;
  365. }
  366. /**
  367. * {@inheritDoc}
  368. */
  369. public function getClobTypeDeclarationSQL(array $field)
  370. {
  371. return 'CLOB';
  372. }
  373. /**
  374. * {@inheritDoc}
  375. */
  376. public function getListTableConstraintsSQL($table)
  377. {
  378. $table = str_replace('.', '__', $table);
  379. return sprintf(
  380. "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = %s AND sql NOT NULL ORDER BY name",
  381. $this->quoteStringLiteral($table)
  382. );
  383. }
  384. /**
  385. * {@inheritDoc}
  386. */
  387. public function getListTableColumnsSQL($table, $currentDatabase = null)
  388. {
  389. $table = str_replace('.', '__', $table);
  390. return sprintf('PRAGMA table_info(%s)', $this->quoteStringLiteral($table));
  391. }
  392. /**
  393. * {@inheritDoc}
  394. */
  395. public function getListTableIndexesSQL($table, $currentDatabase = null)
  396. {
  397. $table = str_replace('.', '__', $table);
  398. return sprintf('PRAGMA index_list(%s)', $this->quoteStringLiteral($table));
  399. }
  400. /**
  401. * {@inheritDoc}
  402. */
  403. public function getListTablesSQL()
  404. {
  405. return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' "
  406. . 'UNION ALL SELECT name FROM sqlite_temp_master '
  407. . "WHERE type = 'table' ORDER BY name";
  408. }
  409. /**
  410. * {@inheritDoc}
  411. */
  412. public function getListViewsSQL($database)
  413. {
  414. return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL";
  415. }
  416. /**
  417. * {@inheritDoc}
  418. */
  419. public function getCreateViewSQL($name, $sql)
  420. {
  421. return 'CREATE VIEW ' . $name . ' AS ' . $sql;
  422. }
  423. /**
  424. * {@inheritDoc}
  425. */
  426. public function getDropViewSQL($name)
  427. {
  428. return 'DROP VIEW ' . $name;
  429. }
  430. /**
  431. * {@inheritDoc}
  432. */
  433. public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
  434. {
  435. $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
  436. $query .= ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false ? ' ' : ' NOT ') . 'DEFERRABLE';
  437. $query .= ' INITIALLY ' . ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false ? 'DEFERRED' : 'IMMEDIATE');
  438. return $query;
  439. }
  440. /**
  441. * {@inheritDoc}
  442. */
  443. public function supportsIdentityColumns()
  444. {
  445. return true;
  446. }
  447. /**
  448. * {@inheritDoc}
  449. */
  450. public function supportsColumnCollation()
  451. {
  452. return true;
  453. }
  454. /**
  455. * {@inheritDoc}
  456. */
  457. public function supportsInlineColumnComments()
  458. {
  459. return true;
  460. }
  461. /**
  462. * {@inheritDoc}
  463. */
  464. public function getName()
  465. {
  466. return 'sqlite';
  467. }
  468. /**
  469. * {@inheritDoc}
  470. */
  471. public function getTruncateTableSQL($tableName, $cascade = false)
  472. {
  473. $tableIdentifier = new Identifier($tableName);
  474. $tableName = str_replace('.', '__', $tableIdentifier->getQuotedName($this));
  475. return 'DELETE FROM ' . $tableName;
  476. }
  477. /**
  478. * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction().
  479. *
  480. * @param int|float $value
  481. *
  482. * @return float
  483. */
  484. public static function udfSqrt($value)
  485. {
  486. return sqrt($value);
  487. }
  488. /**
  489. * User-defined function for Sqlite that implements MOD(a, b).
  490. *
  491. * @param int $a
  492. * @param int $b
  493. *
  494. * @return int
  495. */
  496. public static function udfMod($a, $b)
  497. {
  498. return $a % $b;
  499. }
  500. /**
  501. * @param string $str
  502. * @param string $substr
  503. * @param int $offset
  504. *
  505. * @return int
  506. */
  507. public static function udfLocate($str, $substr, $offset = 0)
  508. {
  509. // SQL's LOCATE function works on 1-based positions, while PHP's strpos works on 0-based positions.
  510. // So we have to make them compatible if an offset is given.
  511. if ($offset > 0) {
  512. $offset -= 1;
  513. }
  514. $pos = strpos($str, $substr, $offset);
  515. if ($pos !== false) {
  516. return $pos + 1;
  517. }
  518. return 0;
  519. }
  520. /**
  521. * {@inheritDoc}
  522. */
  523. public function getForUpdateSQL()
  524. {
  525. return '';
  526. }
  527. /**
  528. * {@inheritDoc}
  529. */
  530. public function getInlineColumnCommentSQL($comment)
  531. {
  532. return '--' . str_replace("\n", "\n--", $comment) . "\n";
  533. }
  534. private function getInlineTableCommentSQL(string $comment) : string
  535. {
  536. return $this->getInlineColumnCommentSQL($comment);
  537. }
  538. /**
  539. * {@inheritDoc}
  540. */
  541. protected function initializeDoctrineTypeMappings()
  542. {
  543. $this->doctrineTypeMapping = [
  544. 'boolean' => 'boolean',
  545. 'tinyint' => 'boolean',
  546. 'smallint' => 'smallint',
  547. 'mediumint' => 'integer',
  548. 'int' => 'integer',
  549. 'integer' => 'integer',
  550. 'serial' => 'integer',
  551. 'bigint' => 'bigint',
  552. 'bigserial' => 'bigint',
  553. 'clob' => 'text',
  554. 'tinytext' => 'text',
  555. 'mediumtext' => 'text',
  556. 'longtext' => 'text',
  557. 'text' => 'text',
  558. 'varchar' => 'string',
  559. 'longvarchar' => 'string',
  560. 'varchar2' => 'string',
  561. 'nvarchar' => 'string',
  562. 'image' => 'string',
  563. 'ntext' => 'string',
  564. 'char' => 'string',
  565. 'date' => 'date',
  566. 'datetime' => 'datetime',
  567. 'timestamp' => 'datetime',
  568. 'time' => 'time',
  569. 'float' => 'float',
  570. 'double' => 'float',
  571. 'double precision' => 'float',
  572. 'real' => 'float',
  573. 'decimal' => 'decimal',
  574. 'numeric' => 'decimal',
  575. 'blob' => 'blob',
  576. ];
  577. }
  578. /**
  579. * {@inheritDoc}
  580. */
  581. protected function getReservedKeywordsClass()
  582. {
  583. return Keywords\SQLiteKeywords::class;
  584. }
  585. /**
  586. * {@inheritDoc}
  587. */
  588. protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
  589. {
  590. if (! $diff->fromTable instanceof Table) {
  591. throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
  592. }
  593. $sql = [];
  594. foreach ($diff->fromTable->getIndexes() as $index) {
  595. if ($index->isPrimary()) {
  596. continue;
  597. }
  598. $sql[] = $this->getDropIndexSQL($index, $diff->name);
  599. }
  600. return $sql;
  601. }
  602. /**
  603. * {@inheritDoc}
  604. */
  605. protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
  606. {
  607. if (! $diff->fromTable instanceof Table) {
  608. throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
  609. }
  610. $sql = [];
  611. $tableName = $diff->getNewName();
  612. if ($tableName === false) {
  613. $tableName = $diff->getName($this);
  614. }
  615. foreach ($this->getIndexesInAlteredTable($diff) as $index) {
  616. if ($index->isPrimary()) {
  617. continue;
  618. }
  619. $sql[] = $this->getCreateIndexSQL($index, $tableName->getQuotedName($this));
  620. }
  621. return $sql;
  622. }
  623. /**
  624. * {@inheritDoc}
  625. */
  626. protected function doModifyLimitQuery($query, $limit, $offset)
  627. {
  628. if ($limit === null && $offset > 0) {
  629. return $query . ' LIMIT -1 OFFSET ' . $offset;
  630. }
  631. return parent::doModifyLimitQuery($query, $limit, $offset);
  632. }
  633. /**
  634. * {@inheritDoc}
  635. */
  636. public function getBlobTypeDeclarationSQL(array $field)
  637. {
  638. return 'BLOB';
  639. }
  640. /**
  641. * {@inheritDoc}
  642. */
  643. public function getTemporaryTableName($tableName)
  644. {
  645. $tableName = str_replace('.', '__', $tableName);
  646. return $tableName;
  647. }
  648. /**
  649. * {@inheritDoc}
  650. *
  651. * Sqlite Platform emulates schema by underscoring each dot and generating tables
  652. * into the default database.
  653. *
  654. * This hack is implemented to be able to use SQLite as testdriver when
  655. * using schema supporting databases.
  656. */
  657. public function canEmulateSchemas()
  658. {
  659. return true;
  660. }
  661. /**
  662. * {@inheritDoc}
  663. */
  664. public function supportsForeignKeyConstraints()
  665. {
  666. return true;
  667. }
  668. public function supportsCreateDropForeignKeyConstraints() : bool
  669. {
  670. return false;
  671. }
  672. /**
  673. * {@inheritDoc}
  674. */
  675. public function getCreatePrimaryKeySQL(Index $index, $table)
  676. {
  677. throw new DBALException('Sqlite platform does not support alter primary key.');
  678. }
  679. /**
  680. * {@inheritdoc}
  681. */
  682. public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
  683. {
  684. throw new DBALException('Sqlite platform does not support alter foreign key, the table must be fully recreated using getAlterTableSQL.');
  685. }
  686. /**
  687. * {@inheritdoc}
  688. */
  689. public function getDropForeignKeySQL($foreignKey, $table)
  690. {
  691. throw new DBALException('Sqlite platform does not support alter foreign key, the table must be fully recreated using getAlterTableSQL.');
  692. }
  693. /**
  694. * {@inheritDoc}
  695. */
  696. public function getCreateConstraintSQL(Constraint $constraint, $table)
  697. {
  698. throw new DBALException('Sqlite platform does not support alter constraint.');
  699. }
  700. /**
  701. * {@inheritDoc}
  702. *
  703. * @param int|null $createFlags
  704. */
  705. public function getCreateTableSQL(Table $table, $createFlags = null)
  706. {
  707. $createFlags = $createFlags ?? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS;
  708. return parent::getCreateTableSQL($table, $createFlags);
  709. }
  710. /**
  711. * @param string $table
  712. * @param string|null $database
  713. *
  714. * @return string
  715. */
  716. public function getListTableForeignKeysSQL($table, $database = null)
  717. {
  718. $table = str_replace('.', '__', $table);
  719. return sprintf('PRAGMA foreign_key_list(%s)', $this->quoteStringLiteral($table));
  720. }
  721. /**
  722. * {@inheritDoc}
  723. */
  724. public function getAlterTableSQL(TableDiff $diff)
  725. {
  726. $sql = $this->getSimpleAlterTableSQL($diff);
  727. if ($sql !== false) {
  728. return $sql;
  729. }
  730. $fromTable = $diff->fromTable;
  731. if (! $fromTable instanceof Table) {
  732. throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema');
  733. }
  734. $table = clone $fromTable;
  735. $columns = [];
  736. $oldColumnNames = [];
  737. $newColumnNames = [];
  738. $columnSql = [];
  739. foreach ($table->getColumns() as $columnName => $column) {
  740. $columnName = strtolower($columnName);
  741. $columns[$columnName] = $column;
  742. $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this);
  743. }
  744. foreach ($diff->removedColumns as $columnName => $column) {
  745. if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
  746. continue;
  747. }
  748. $columnName = strtolower($columnName);
  749. if (! isset($columns[$columnName])) {
  750. continue;
  751. }
  752. unset(
  753. $columns[$columnName],
  754. $oldColumnNames[$columnName],
  755. $newColumnNames[$columnName]
  756. );
  757. }
  758. foreach ($diff->renamedColumns as $oldColumnName => $column) {
  759. if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
  760. continue;
  761. }
  762. $oldColumnName = strtolower($oldColumnName);
  763. if (isset($columns[$oldColumnName])) {
  764. unset($columns[$oldColumnName]);
  765. }
  766. $columns[strtolower($column->getName())] = $column;
  767. if (! isset($newColumnNames[$oldColumnName])) {
  768. continue;
  769. }
  770. $newColumnNames[$oldColumnName] = $column->getQuotedName($this);
  771. }
  772. foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
  773. if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
  774. continue;
  775. }
  776. if (isset($columns[$oldColumnName])) {
  777. unset($columns[$oldColumnName]);
  778. }
  779. $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column;
  780. if (! isset($newColumnNames[$oldColumnName])) {
  781. continue;
  782. }
  783. $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this);
  784. }
  785. foreach ($diff->addedColumns as $columnName => $column) {
  786. if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
  787. continue;
  788. }
  789. $columns[strtolower($columnName)] = $column;
  790. }
  791. $sql = [];
  792. $tableSql = [];
  793. if (! $this->onSchemaAlterTable($diff, $tableSql)) {
  794. $dataTable = new Table('__temp__' . $table->getName());
  795. $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
  796. $newTable->addOption('alter', true);
  797. $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
  798. //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0));
  799. $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this));
  800. $sql[] = $this->getDropTableSQL($fromTable);
  801. $sql = array_merge($sql, $this->getCreateTableSQL($newTable));
  802. $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this));
  803. $sql[] = $this->getDropTableSQL($dataTable);
  804. $newName = $diff->getNewName();
  805. if ($newName !== false) {
  806. $sql[] = sprintf(
  807. 'ALTER TABLE %s RENAME TO %s',
  808. $newTable->getQuotedName($this),
  809. $newName->getQuotedName($this)
  810. );
  811. }
  812. $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff));
  813. }
  814. return array_merge($sql, $tableSql, $columnSql);
  815. }
  816. /**
  817. * @return string[]|false
  818. */
  819. private function getSimpleAlterTableSQL(TableDiff $diff)
  820. {
  821. // Suppress changes on integer type autoincrement columns.
  822. foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
  823. if (! $columnDiff->fromColumn instanceof Column ||
  824. ! $columnDiff->column instanceof Column ||
  825. ! $columnDiff->column->getAutoincrement() ||
  826. ! $columnDiff->column->getType() instanceof Types\IntegerType
  827. ) {
  828. continue;
  829. }
  830. if (! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
  831. unset($diff->changedColumns[$oldColumnName]);
  832. continue;
  833. }
  834. $fromColumnType = $columnDiff->fromColumn->getType();
  835. if (! ($fromColumnType instanceof Types\SmallIntType) && ! ($fromColumnType instanceof Types\BigIntType)) {
  836. continue;
  837. }
  838. unset($diff->changedColumns[$oldColumnName]);
  839. }
  840. if (! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
  841. || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
  842. || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
  843. || ! empty($diff->renamedIndexes)
  844. ) {
  845. return false;
  846. }
  847. $table = new Table($diff->name);
  848. $sql = [];
  849. $tableSql = [];
  850. $columnSql = [];
  851. foreach ($diff->addedColumns as $column) {
  852. if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
  853. continue;
  854. }
  855. $field = array_merge(['unique' => null, 'autoincrement' => null, 'default' => null], $column->toArray());
  856. $type = $field['type'];
  857. switch (true) {
  858. case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']:
  859. case $type instanceof Types\DateTimeType && $field['default'] === $this->getCurrentTimestampSQL():
  860. case $type instanceof Types\DateType && $field['default'] === $this->getCurrentDateSQL():
  861. case $type instanceof Types\TimeType && $field['default'] === $this->getCurrentTimeSQL():
  862. return false;
  863. }
  864. $field['name'] = $column->getQuotedName($this);
  865. if ($type instanceof Types\StringType && $field['length'] === null) {
  866. $field['length'] = 255;
  867. }
  868. $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' . $this->getColumnDeclarationSQL($field['name'], $field);
  869. }
  870. if (! $this->onSchemaAlterTable($diff, $tableSql)) {
  871. if ($diff->newName !== false) {
  872. $newTable = new Identifier($diff->newName);
  873. $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' RENAME TO ' . $newTable->getQuotedName($this);
  874. }
  875. }
  876. return array_merge($sql, $tableSql, $columnSql);
  877. }
  878. /**
  879. * @return string[]
  880. */
  881. private function getColumnNamesInAlteredTable(TableDiff $diff)
  882. {
  883. $columns = [];
  884. foreach ($diff->fromTable->getColumns() as $columnName => $column) {
  885. $columns[strtolower($columnName)] = $column->getName();
  886. }
  887. foreach ($diff->removedColumns as $columnName => $column) {
  888. $columnName = strtolower($columnName);
  889. if (! isset($columns[$columnName])) {
  890. continue;
  891. }
  892. unset($columns[$columnName]);
  893. }
  894. foreach ($diff->renamedColumns as $oldColumnName => $column) {
  895. $columnName = $column->getName();
  896. $columns[strtolower($oldColumnName)] = $columnName;
  897. $columns[strtolower($columnName)] = $columnName;
  898. }
  899. foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
  900. $columnName = $columnDiff->column->getName();
  901. $columns[strtolower($oldColumnName)] = $columnName;
  902. $columns[strtolower($columnName)] = $columnName;
  903. }
  904. foreach ($diff->addedColumns as $column) {
  905. $columnName = $column->getName();
  906. $columns[strtolower($columnName)] = $columnName;
  907. }
  908. return $columns;
  909. }
  910. /**
  911. * @return Index[]
  912. */
  913. private function getIndexesInAlteredTable(TableDiff $diff)
  914. {
  915. $indexes = $diff->fromTable->getIndexes();
  916. $columnNames = $this->getColumnNamesInAlteredTable($diff);
  917. foreach ($indexes as $key => $index) {
  918. foreach ($diff->renamedIndexes as $oldIndexName => $renamedIndex) {
  919. if (strtolower($key) !== strtolower($oldIndexName)) {
  920. continue;
  921. }
  922. unset($indexes[$key]);
  923. }
  924. $changed = false;
  925. $indexColumns = [];
  926. foreach ($index->getColumns() as $columnName) {
  927. $normalizedColumnName = strtolower($columnName);
  928. if (! isset($columnNames[$normalizedColumnName])) {
  929. unset($indexes[$key]);
  930. continue 2;
  931. }
  932. $indexColumns[] = $columnNames[$normalizedColumnName];
  933. if ($columnName === $columnNames[$normalizedColumnName]) {
  934. continue;
  935. }
  936. $changed = true;
  937. }
  938. if (! $changed) {
  939. continue;
  940. }
  941. $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags());
  942. }
  943. foreach ($diff->removedIndexes as $index) {
  944. $indexName = strtolower($index->getName());
  945. if (! strlen($indexName) || ! isset($indexes[$indexName])) {
  946. continue;
  947. }
  948. unset($indexes[$indexName]);
  949. }
  950. foreach (array_merge($diff->changedIndexes, $diff->addedIndexes, $diff->renamedIndexes) as $index) {
  951. $indexName = strtolower($index->getName());
  952. if (strlen($indexName)) {
  953. $indexes[$indexName] = $index;
  954. } else {
  955. $indexes[] = $index;
  956. }
  957. }
  958. return $indexes;
  959. }
  960. /**
  961. * @return ForeignKeyConstraint[]
  962. */
  963. private function getForeignKeysInAlteredTable(TableDiff $diff)
  964. {
  965. $foreignKeys = $diff->fromTable->getForeignKeys();
  966. $columnNames = $this->getColumnNamesInAlteredTable($diff);
  967. foreach ($foreignKeys as $key => $constraint) {
  968. $changed = false;
  969. $localColumns = [];
  970. foreach ($constraint->getLocalColumns() as $columnName) {
  971. $normalizedColumnName = strtolower($columnName);
  972. if (! isset($columnNames[$normalizedColumnName])) {
  973. unset($foreignKeys[$key]);
  974. continue 2;
  975. }
  976. $localColumns[] = $columnNames[$normalizedColumnName];
  977. if ($columnName === $columnNames[$normalizedColumnName]) {
  978. continue;
  979. }
  980. $changed = true;
  981. }
  982. if (! $changed) {
  983. continue;
  984. }
  985. $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions());
  986. }
  987. foreach ($diff->removedForeignKeys as $constraint) {
  988. if (! $constraint instanceof ForeignKeyConstraint) {
  989. $constraint = new Identifier($constraint);
  990. }
  991. $constraintName = strtolower($constraint->getName());
  992. if (! strlen($constraintName) || ! isset($foreignKeys[$constraintName])) {
  993. continue;
  994. }
  995. unset($foreignKeys[$constraintName]);
  996. }
  997. foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) {
  998. $constraintName = strtolower($constraint->getName());
  999. if (strlen($constraintName)) {
  1000. $foreignKeys[$constraintName] = $constraint;
  1001. } else {
  1002. $foreignKeys[] = $constraint;
  1003. }
  1004. }
  1005. return $foreignKeys;
  1006. }
  1007. /**
  1008. * @return Index[]
  1009. */
  1010. private function getPrimaryIndexInAlteredTable(TableDiff $diff)
  1011. {
  1012. $primaryIndex = [];
  1013. foreach ($this->getIndexesInAlteredTable($diff) as $index) {
  1014. if (! $index->isPrimary()) {
  1015. continue;
  1016. }
  1017. $primaryIndex = [$index->getName() => $index];
  1018. }
  1019. return $primaryIndex;
  1020. }
  1021. }