SqlserverSchemaDialect.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Database\Schema;
  17. /**
  18. * Schema management/reflection features for SQLServer.
  19. *
  20. * @internal
  21. */
  22. class SqlserverSchemaDialect extends SchemaDialect
  23. {
  24. /**
  25. * @var string
  26. */
  27. public const DEFAULT_SCHEMA_NAME = 'dbo';
  28. /**
  29. * Generate the SQL to list the tables and views.
  30. *
  31. * @param array<string, mixed> $config The connection configuration to use for
  32. * getting tables from.
  33. * @return array An array of (sql, params) to execute.
  34. */
  35. public function listTablesSql(array $config): array
  36. {
  37. $sql = "SELECT TABLE_NAME
  38. FROM INFORMATION_SCHEMA.TABLES
  39. WHERE TABLE_SCHEMA = ?
  40. AND (TABLE_TYPE = 'BASE TABLE' OR TABLE_TYPE = 'VIEW')
  41. ORDER BY TABLE_NAME";
  42. $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
  43. return [$sql, [$schema]];
  44. }
  45. /**
  46. * Generate the SQL to list the tables, excluding all views.
  47. *
  48. * @param array<string, mixed> $config The connection configuration to use for
  49. * getting tables from.
  50. * @return array<mixed> An array of (sql, params) to execute.
  51. */
  52. public function listTablesWithoutViewsSql(array $config): array
  53. {
  54. $sql = "SELECT TABLE_NAME
  55. FROM INFORMATION_SCHEMA.TABLES
  56. WHERE TABLE_SCHEMA = ?
  57. AND (TABLE_TYPE = 'BASE TABLE')
  58. ORDER BY TABLE_NAME";
  59. $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
  60. return [$sql, [$schema]];
  61. }
  62. /**
  63. * @inheritDoc
  64. */
  65. public function describeColumnSql(string $tableName, array $config): array
  66. {
  67. $sql = 'SELECT DISTINCT
  68. AC.column_id AS [column_id],
  69. AC.name AS [name],
  70. TY.name AS [type],
  71. AC.max_length AS [char_length],
  72. AC.precision AS [precision],
  73. AC.scale AS [scale],
  74. AC.is_identity AS [autoincrement],
  75. AC.is_nullable AS [null],
  76. OBJECT_DEFINITION(AC.default_object_id) AS [default],
  77. AC.collation_name AS [collation_name]
  78. FROM sys.[objects] T
  79. INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
  80. INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id]
  81. INNER JOIN sys.[types] TY ON TY.[user_type_id] = AC.[user_type_id]
  82. WHERE T.[name] = ? AND S.[name] = ?
  83. ORDER BY column_id';
  84. $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
  85. return [$sql, [$tableName, $schema]];
  86. }
  87. /**
  88. * Convert a column definition to the abstract types.
  89. *
  90. * The returned type will be a type that
  91. * Cake\Database\TypeFactory can handle.
  92. *
  93. * @param string $col The column type
  94. * @param int|null $length the column length
  95. * @param int|null $precision The column precision
  96. * @param int|null $scale The column scale
  97. * @return array<string, mixed> Array of column information.
  98. * @link https://technet.microsoft.com/en-us/library/ms187752.aspx
  99. */
  100. protected function _convertColumn(
  101. string $col,
  102. ?int $length = null,
  103. ?int $precision = null,
  104. ?int $scale = null
  105. ): array {
  106. $col = strtolower($col);
  107. $type = $this->_applyTypeSpecificColumnConversion(
  108. $col,
  109. compact('length', 'precision', 'scale')
  110. );
  111. if ($type !== null) {
  112. return $type;
  113. }
  114. if (in_array($col, ['date', 'time'])) {
  115. return ['type' => $col, 'length' => null];
  116. }
  117. if ($col === 'datetime') {
  118. // datetime cannot parse more than 3 digits of precision and isn't accurate
  119. return ['type' => TableSchemaInterface::TYPE_DATETIME, 'length' => null];
  120. }
  121. if (str_contains($col, 'datetime')) {
  122. $typeName = TableSchemaInterface::TYPE_DATETIME;
  123. if ($scale > 0) {
  124. $typeName = TableSchemaInterface::TYPE_DATETIME_FRACTIONAL;
  125. }
  126. return ['type' => $typeName, 'length' => null, 'precision' => $scale];
  127. }
  128. if ($col === 'char') {
  129. return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length];
  130. }
  131. if ($col === 'tinyint') {
  132. return ['type' => TableSchemaInterface::TYPE_TINYINTEGER, 'length' => $precision ?: 3];
  133. }
  134. if ($col === 'smallint') {
  135. return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => $precision ?: 5];
  136. }
  137. if ($col === 'int' || $col === 'integer') {
  138. return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => $precision ?: 10];
  139. }
  140. if ($col === 'bigint') {
  141. return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => $precision ?: 20];
  142. }
  143. if ($col === 'bit') {
  144. return ['type' => TableSchemaInterface::TYPE_BOOLEAN, 'length' => null];
  145. }
  146. if (
  147. str_contains($col, 'numeric') ||
  148. str_contains($col, 'money') ||
  149. str_contains($col, 'decimal')
  150. ) {
  151. return ['type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale];
  152. }
  153. if ($col === 'real' || $col === 'float') {
  154. return ['type' => TableSchemaInterface::TYPE_FLOAT, 'length' => null];
  155. }
  156. // SqlServer schema reflection returns double length for unicode
  157. // columns because internally it uses UTF16/UCS2
  158. if ($col === 'nvarchar' || $col === 'nchar' || $col === 'ntext') {
  159. $length /= 2;
  160. }
  161. if (str_contains($col, 'varchar') && $length < 0) {
  162. return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null];
  163. }
  164. if (str_contains($col, 'varchar')) {
  165. return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length ?: 255];
  166. }
  167. if (str_contains($col, 'char')) {
  168. return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length];
  169. }
  170. if (str_contains($col, 'text')) {
  171. return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null];
  172. }
  173. if ($col === 'image' || str_contains($col, 'binary')) {
  174. // -1 is the value for MAX which we treat as a 'long' binary
  175. if ($length == -1) {
  176. $length = TableSchema::LENGTH_LONG;
  177. }
  178. return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => $length];
  179. }
  180. if ($col === 'uniqueidentifier') {
  181. return ['type' => TableSchemaInterface::TYPE_UUID];
  182. }
  183. if ($col === 'geometry') {
  184. return ['type' => TableSchemaInterface::TYPE_GEOMETRY];
  185. }
  186. if ($col === 'geography') {
  187. // SQLserver only has one generic geometry type that
  188. // we map to point.
  189. return ['type' => TableSchemaInterface::TYPE_POINT];
  190. }
  191. return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => null];
  192. }
  193. /**
  194. * @inheritDoc
  195. */
  196. public function convertColumnDescription(TableSchema $schema, array $row): void
  197. {
  198. $field = $this->_convertColumn(
  199. $row['type'],
  200. $row['char_length'] !== null ? (int)$row['char_length'] : null,
  201. $row['precision'] !== null ? (int)$row['precision'] : null,
  202. $row['scale'] !== null ? (int)$row['scale'] : null
  203. );
  204. if (!empty($row['autoincrement'])) {
  205. $field['autoIncrement'] = true;
  206. }
  207. $field += [
  208. 'null' => $row['null'] === '1',
  209. 'default' => $this->_defaultValue($field['type'], $row['default']),
  210. 'collate' => $row['collation_name'],
  211. ];
  212. $schema->addColumn($row['name'], $field);
  213. }
  214. /**
  215. * Manipulate the default value.
  216. *
  217. * Removes () wrapping default values, extracts strings from
  218. * N'' wrappers and collation text and converts NULL strings.
  219. *
  220. * @param string $type The schema type
  221. * @param string|null $default The default value.
  222. * @return string|int|null
  223. */
  224. protected function _defaultValue(string $type, ?string $default): string|int|null
  225. {
  226. if ($default === null) {
  227. return null;
  228. }
  229. // remove () surrounding value (NULL) but leave () at the end of functions
  230. // integers might have two ((0)) wrapping value
  231. if (preg_match('/^\(+(.*?(\(\))?)\)+$/', $default, $matches)) {
  232. $default = $matches[1];
  233. }
  234. if ($default === 'NULL') {
  235. return null;
  236. }
  237. if ($type === TableSchemaInterface::TYPE_BOOLEAN) {
  238. return (int)$default;
  239. }
  240. // Remove quotes
  241. if (preg_match("/^\(?N?'(.*)'\)?/", $default, $matches)) {
  242. return str_replace("''", "'", $matches[1]);
  243. }
  244. return $default;
  245. }
  246. /**
  247. * @inheritDoc
  248. */
  249. public function describeIndexSql(string $tableName, array $config): array
  250. {
  251. $sql = "SELECT
  252. I.[name] AS [index_name],
  253. IC.[index_column_id] AS [index_order],
  254. AC.[name] AS [column_name],
  255. I.[is_unique], I.[is_primary_key],
  256. I.[is_unique_constraint]
  257. FROM sys.[tables] AS T
  258. INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
  259. INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
  260. INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id]
  261. INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]
  262. WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = ? AND S.[name] = ?
  263. ORDER BY I.[index_id], IC.[index_column_id]";
  264. $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
  265. return [$sql, [$tableName, $schema]];
  266. }
  267. /**
  268. * @inheritDoc
  269. */
  270. public function convertIndexDescription(TableSchema $schema, array $row): void
  271. {
  272. $type = TableSchema::INDEX_INDEX;
  273. $name = $row['index_name'];
  274. if ($row['is_primary_key']) {
  275. $name = $type = TableSchema::CONSTRAINT_PRIMARY;
  276. }
  277. if (($row['is_unique'] || $row['is_unique_constraint']) && $type === TableSchema::INDEX_INDEX) {
  278. $type = TableSchema::CONSTRAINT_UNIQUE;
  279. }
  280. if ($type === TableSchema::INDEX_INDEX) {
  281. $existing = $schema->getIndex($name);
  282. } else {
  283. $existing = $schema->getConstraint($name);
  284. }
  285. $columns = [$row['column_name']];
  286. if ($existing) {
  287. $columns = array_merge($existing['columns'], $columns);
  288. }
  289. if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
  290. $schema->addConstraint($name, [
  291. 'type' => $type,
  292. 'columns' => $columns,
  293. ]);
  294. return;
  295. }
  296. $schema->addIndex($name, [
  297. 'type' => $type,
  298. 'columns' => $columns,
  299. ]);
  300. }
  301. /**
  302. * @inheritDoc
  303. */
  304. public function describeForeignKeySql(string $tableName, array $config): array
  305. {
  306. // phpcs:disable Generic.Files.LineLength
  307. $sql = 'SELECT FK.[name] AS [foreign_key_name], FK.[delete_referential_action_desc] AS [delete_type],
  308. FK.[update_referential_action_desc] AS [update_type], C.name AS [column], RT.name AS [reference_table],
  309. RC.name AS [reference_column]
  310. FROM sys.foreign_keys FK
  311. INNER JOIN sys.foreign_key_columns FKC ON FKC.constraint_object_id = FK.object_id
  312. INNER JOIN sys.tables T ON T.object_id = FKC.parent_object_id
  313. INNER JOIN sys.tables RT ON RT.object_id = FKC.referenced_object_id
  314. INNER JOIN sys.schemas S ON S.schema_id = T.schema_id AND S.schema_id = RT.schema_id
  315. INNER JOIN sys.columns C ON C.column_id = FKC.parent_column_id AND C.object_id = FKC.parent_object_id
  316. INNER JOIN sys.columns RC ON RC.column_id = FKC.referenced_column_id AND RC.object_id = FKC.referenced_object_id
  317. WHERE FK.is_ms_shipped = 0 AND T.name = ? AND S.name = ?
  318. ORDER BY FKC.constraint_column_id';
  319. // phpcs:enable Generic.Files.LineLength
  320. $schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
  321. return [$sql, [$tableName, $schema]];
  322. }
  323. /**
  324. * @inheritDoc
  325. */
  326. public function convertForeignKeyDescription(TableSchema $schema, array $row): void
  327. {
  328. $data = [
  329. 'type' => TableSchema::CONSTRAINT_FOREIGN,
  330. 'columns' => [$row['column']],
  331. 'references' => [$row['reference_table'], $row['reference_column']],
  332. 'update' => $this->_convertOnClause($row['update_type']),
  333. 'delete' => $this->_convertOnClause($row['delete_type']),
  334. ];
  335. $name = $row['foreign_key_name'];
  336. $schema->addConstraint($name, $data);
  337. }
  338. /**
  339. * @inheritDoc
  340. */
  341. protected function _foreignOnClause(string $on): string
  342. {
  343. $parent = parent::_foreignOnClause($on);
  344. return $parent === 'RESTRICT' ? parent::_foreignOnClause(TableSchema::ACTION_NO_ACTION) : $parent;
  345. }
  346. /**
  347. * @inheritDoc
  348. */
  349. protected function _convertOnClause(string $clause): string
  350. {
  351. return match ($clause) {
  352. 'NO_ACTION' => TableSchema::ACTION_NO_ACTION,
  353. 'CASCADE' => TableSchema::ACTION_CASCADE,
  354. 'SET_NULL' => TableSchema::ACTION_SET_NULL,
  355. 'SET_DEFAULT' => TableSchema::ACTION_SET_DEFAULT,
  356. default => TableSchema::ACTION_SET_NULL,
  357. };
  358. }
  359. /**
  360. * @inheritDoc
  361. */
  362. public function columnSql(TableSchema $schema, string $name): string
  363. {
  364. $data = $schema->getColumn($name);
  365. assert($data !== null);
  366. $sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
  367. if ($sql !== null) {
  368. return $sql;
  369. }
  370. $out = $this->_driver->quoteIdentifier($name);
  371. $typeMap = [
  372. TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT',
  373. TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT',
  374. TableSchemaInterface::TYPE_INTEGER => ' INTEGER',
  375. TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT',
  376. TableSchemaInterface::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER',
  377. TableSchemaInterface::TYPE_BOOLEAN => ' BIT',
  378. TableSchemaInterface::TYPE_CHAR => ' NCHAR',
  379. TableSchemaInterface::TYPE_FLOAT => ' FLOAT',
  380. TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL',
  381. TableSchemaInterface::TYPE_DATE => ' DATE',
  382. TableSchemaInterface::TYPE_TIME => ' TIME',
  383. TableSchemaInterface::TYPE_DATETIME => ' DATETIME2',
  384. TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' DATETIME2',
  385. TableSchemaInterface::TYPE_TIMESTAMP => ' DATETIME2',
  386. TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2',
  387. TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2',
  388. TableSchemaInterface::TYPE_UUID => ' UNIQUEIDENTIFIER',
  389. TableSchemaInterface::TYPE_JSON => ' NVARCHAR(MAX)',
  390. TableSchemaInterface::TYPE_GEOMETRY => ' GEOMETRY',
  391. TableSchemaInterface::TYPE_POINT => ' GEOGRAPHY',
  392. TableSchemaInterface::TYPE_LINESTRING => ' GEOGRAPHY',
  393. TableSchemaInterface::TYPE_POLYGON => ' GEOGRAPHY',
  394. ];
  395. if (isset($typeMap[$data['type']])) {
  396. $out .= $typeMap[$data['type']];
  397. }
  398. $autoIncrementTypes = [
  399. TableSchemaInterface::TYPE_TINYINTEGER,
  400. TableSchemaInterface::TYPE_SMALLINTEGER,
  401. TableSchemaInterface::TYPE_INTEGER,
  402. TableSchemaInterface::TYPE_BIGINTEGER,
  403. ];
  404. if (
  405. in_array($data['type'], $autoIncrementTypes, true) &&
  406. (
  407. ($schema->getPrimaryKey() === [$name] && $name === 'id') || $data['autoIncrement']
  408. )
  409. ) {
  410. $out .= ' IDENTITY(1, 1)';
  411. unset($data['default']);
  412. }
  413. if ($data['type'] === TableSchemaInterface::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
  414. $out .= ' NVARCHAR(MAX)';
  415. }
  416. if ($data['type'] === TableSchemaInterface::TYPE_CHAR) {
  417. $out .= '(' . $data['length'] . ')';
  418. }
  419. if ($data['type'] === TableSchemaInterface::TYPE_BINARY) {
  420. if (
  421. !isset($data['length'])
  422. || in_array($data['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true)
  423. ) {
  424. $data['length'] = 'MAX';
  425. }
  426. if ($data['length'] === 1) {
  427. $out .= ' BINARY(1)';
  428. } else {
  429. $out .= ' VARBINARY';
  430. $out .= sprintf('(%s)', $data['length']);
  431. }
  432. }
  433. if (
  434. $data['type'] === TableSchemaInterface::TYPE_STRING ||
  435. (
  436. $data['type'] === TableSchemaInterface::TYPE_TEXT &&
  437. $data['length'] === TableSchema::LENGTH_TINY
  438. )
  439. ) {
  440. $type = ' NVARCHAR';
  441. $length = $data['length'] ?? TableSchema::LENGTH_TINY;
  442. $out .= sprintf('%s(%d)', $type, $length);
  443. }
  444. $hasCollate = [
  445. TableSchemaInterface::TYPE_TEXT,
  446. TableSchemaInterface::TYPE_STRING,
  447. TableSchemaInterface::TYPE_CHAR,
  448. ];
  449. if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
  450. $out .= ' COLLATE ' . $data['collate'];
  451. }
  452. $precisionTypes = [
  453. TableSchemaInterface::TYPE_FLOAT,
  454. TableSchemaInterface::TYPE_DATETIME,
  455. TableSchemaInterface::TYPE_DATETIME_FRACTIONAL,
  456. TableSchemaInterface::TYPE_TIMESTAMP,
  457. TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL,
  458. ];
  459. if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
  460. $out .= '(' . (int)$data['precision'] . ')';
  461. }
  462. if (
  463. $data['type'] === TableSchemaInterface::TYPE_DECIMAL &&
  464. (
  465. isset($data['length']) ||
  466. isset($data['precision'])
  467. )
  468. ) {
  469. $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
  470. }
  471. if (isset($data['null']) && $data['null'] === false) {
  472. $out .= ' NOT NULL';
  473. }
  474. $dateTimeTypes = [
  475. TableSchemaInterface::TYPE_DATETIME,
  476. TableSchemaInterface::TYPE_DATETIME_FRACTIONAL,
  477. TableSchemaInterface::TYPE_TIMESTAMP,
  478. TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL,
  479. ];
  480. $dateTimeDefaults = [
  481. 'current_timestamp',
  482. 'getdate()',
  483. 'getutcdate()',
  484. 'sysdatetime()',
  485. 'sysutcdatetime()',
  486. 'sysdatetimeoffset()',
  487. ];
  488. if (
  489. isset($data['default']) &&
  490. in_array($data['type'], $dateTimeTypes, true) &&
  491. in_array(strtolower($data['default']), $dateTimeDefaults, true)
  492. ) {
  493. $out .= ' DEFAULT ' . strtoupper($data['default']);
  494. } elseif (isset($data['default'])) {
  495. $default = is_bool($data['default'])
  496. ? (int)$data['default']
  497. : $this->_driver->schemaValue($data['default']);
  498. $out .= ' DEFAULT ' . $default;
  499. } elseif (isset($data['null']) && $data['null'] !== false) {
  500. $out .= ' DEFAULT NULL';
  501. }
  502. return $out;
  503. }
  504. /**
  505. * @inheritDoc
  506. */
  507. public function addConstraintSql(TableSchema $schema): array
  508. {
  509. $sqlPattern = 'ALTER TABLE %s ADD %s;';
  510. $sql = [];
  511. foreach ($schema->constraints() as $name) {
  512. $constraint = $schema->getConstraint($name);
  513. assert($constraint !== null);
  514. if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
  515. $tableName = $this->_driver->quoteIdentifier($schema->name());
  516. $sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
  517. }
  518. }
  519. return $sql;
  520. }
  521. /**
  522. * @inheritDoc
  523. */
  524. public function dropConstraintSql(TableSchema $schema): array
  525. {
  526. $sqlPattern = 'ALTER TABLE %s DROP CONSTRAINT %s;';
  527. $sql = [];
  528. foreach ($schema->constraints() as $name) {
  529. $constraint = $schema->getConstraint($name);
  530. assert($constraint !== null);
  531. if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
  532. $tableName = $this->_driver->quoteIdentifier($schema->name());
  533. $constraintName = $this->_driver->quoteIdentifier($name);
  534. $sql[] = sprintf($sqlPattern, $tableName, $constraintName);
  535. }
  536. }
  537. return $sql;
  538. }
  539. /**
  540. * @inheritDoc
  541. */
  542. public function indexSql(TableSchema $schema, string $name): string
  543. {
  544. $data = $schema->getIndex($name);
  545. assert($data !== null);
  546. $columns = array_map(
  547. [$this->_driver, 'quoteIdentifier'],
  548. $data['columns']
  549. );
  550. return sprintf(
  551. 'CREATE INDEX %s ON %s (%s)',
  552. $this->_driver->quoteIdentifier($name),
  553. $this->_driver->quoteIdentifier($schema->name()),
  554. implode(', ', $columns)
  555. );
  556. }
  557. /**
  558. * @inheritDoc
  559. */
  560. public function constraintSql(TableSchema $schema, string $name): string
  561. {
  562. $data = $schema->getConstraint($name);
  563. assert($data !== null);
  564. $out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
  565. if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
  566. $out = 'PRIMARY KEY';
  567. }
  568. if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
  569. $out .= ' UNIQUE';
  570. }
  571. return $this->_keySql($out, $data);
  572. }
  573. /**
  574. * Helper method for generating key SQL snippets.
  575. *
  576. * @param string $prefix The key prefix
  577. * @param array $data Key data.
  578. * @return string
  579. */
  580. protected function _keySql(string $prefix, array $data): string
  581. {
  582. $columns = array_map(
  583. [$this->_driver, 'quoteIdentifier'],
  584. $data['columns']
  585. );
  586. if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
  587. return $prefix . sprintf(
  588. ' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
  589. implode(', ', $columns),
  590. $this->_driver->quoteIdentifier($data['references'][0]),
  591. $this->_convertConstraintColumns($data['references'][1]),
  592. $this->_foreignOnClause($data['update']),
  593. $this->_foreignOnClause($data['delete'])
  594. );
  595. }
  596. return $prefix . ' (' . implode(', ', $columns) . ')';
  597. }
  598. /**
  599. * @inheritDoc
  600. */
  601. public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
  602. {
  603. $content = array_merge($columns, $constraints);
  604. $content = implode(",\n", array_filter($content));
  605. $tableName = $this->_driver->quoteIdentifier($schema->name());
  606. $out = [];
  607. $out[] = sprintf("CREATE TABLE %s (\n%s\n)", $tableName, $content);
  608. foreach ($indexes as $index) {
  609. $out[] = $index;
  610. }
  611. return $out;
  612. }
  613. /**
  614. * @inheritDoc
  615. */
  616. public function truncateTableSql(TableSchema $schema): array
  617. {
  618. $name = $this->_driver->quoteIdentifier($schema->name());
  619. $queries = [
  620. sprintf('DELETE FROM %s', $name),
  621. ];
  622. // Restart identity sequences
  623. $pk = $schema->getPrimaryKey();
  624. if (count($pk) === 1) {
  625. $column = $schema->getColumn($pk[0]);
  626. assert($column !== null);
  627. if (in_array($column['type'], ['integer', 'biginteger'])) {
  628. $queries[] = sprintf(
  629. "IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = '%s' AND " .
  630. "last_value IS NOT NULL) DBCC CHECKIDENT('%s', RESEED, 0)",
  631. $schema->name(),
  632. $schema->name()
  633. );
  634. }
  635. }
  636. return $queries;
  637. }
  638. }