| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660 |
- <?php
- namespace Doctrine\DBAL\Platforms;
- use Doctrine\DBAL\Exception\InvalidLockMode;
- use Doctrine\DBAL\LockMode;
- use Doctrine\DBAL\Schema\Column;
- use Doctrine\DBAL\Schema\ColumnDiff;
- use Doctrine\DBAL\Schema\ForeignKeyConstraint;
- use Doctrine\DBAL\Schema\Identifier;
- use Doctrine\DBAL\Schema\Index;
- use Doctrine\DBAL\Schema\Sequence;
- use Doctrine\DBAL\Schema\Table;
- use Doctrine\DBAL\Schema\TableDiff;
- use Doctrine\Deprecations\Deprecation;
- use InvalidArgumentException;
- use function array_merge;
- use function array_unique;
- use function array_values;
- use function count;
- use function crc32;
- use function dechex;
- use function explode;
- use function func_get_args;
- use function implode;
- use function is_array;
- use function is_bool;
- use function is_numeric;
- use function is_string;
- use function preg_match;
- use function preg_match_all;
- use function sprintf;
- use function str_replace;
- use function strpos;
- use function strtoupper;
- use function substr_count;
- use const PREG_OFFSET_CAPTURE;
- /**
- * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform
- * of the oldest supported version.
- */
- class SQLServerPlatform extends AbstractPlatform
- {
- /**
- * {@inheritdoc}
- */
- public function getCurrentDateSQL()
- {
- return $this->getConvertExpression('date', 'GETDATE()');
- }
- /**
- * {@inheritdoc}
- */
- public function getCurrentTimeSQL()
- {
- return $this->getConvertExpression('time', 'GETDATE()');
- }
- /**
- * Returns an expression that converts an expression of one data type to another.
- *
- * @param string $dataType The target native data type. Alias data types cannot be used.
- * @param string $expression The SQL expression to convert.
- */
- private function getConvertExpression($dataType, $expression): string
- {
- return sprintf('CONVERT(%s, %s)', $dataType, $expression);
- }
- /**
- * {@inheritdoc}
- */
- protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
- {
- $factorClause = '';
- if ($operator === '-') {
- $factorClause = '-1 * ';
- }
- return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateDiffExpression($date1, $date2)
- {
- return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')';
- }
- /**
- * {@inheritDoc}
- *
- * Microsoft SQL Server prefers "autoincrement" identity columns
- * since sequences can only be emulated with a table.
- *
- * @deprecated
- */
- public function prefersIdentityColumns()
- {
- Deprecation::trigger(
- 'doctrine/dbal',
- 'https://github.com/doctrine/dbal/pulls/1519',
- 'SQLServerPlatform::prefersIdentityColumns() is deprecated.'
- );
- return true;
- }
- /**
- * {@inheritDoc}
- *
- * Microsoft SQL Server supports this through AUTO_INCREMENT columns.
- */
- public function supportsIdentityColumns()
- {
- return true;
- }
- /**
- * {@inheritDoc}
- */
- public function supportsReleaseSavepoints()
- {
- return false;
- }
- /**
- * {@inheritdoc}
- */
- public function supportsSchemas()
- {
- return true;
- }
- /**
- * {@inheritdoc}
- */
- public function getDefaultSchemaName()
- {
- return 'dbo';
- }
- /**
- * {@inheritDoc}
- */
- public function supportsColumnCollation()
- {
- return true;
- }
- public function supportsSequences(): bool
- {
- return true;
- }
- public function getAlterSequenceSQL(Sequence $sequence): string
- {
- return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
- ' INCREMENT BY ' . $sequence->getAllocationSize();
- }
- public function getCreateSequenceSQL(Sequence $sequence): string
- {
- return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
- ' START WITH ' . $sequence->getInitialValue() .
- ' INCREMENT BY ' . $sequence->getAllocationSize() .
- ' MINVALUE ' . $sequence->getInitialValue();
- }
- /**
- * {@inheritdoc}
- */
- public function getListSequencesSQL($database)
- {
- return 'SELECT seq.name,
- CAST(
- seq.increment AS VARCHAR(MAX)
- ) AS increment, -- CAST avoids driver error for sql_variant type
- CAST(
- seq.start_value AS VARCHAR(MAX)
- ) AS start_value -- CAST avoids driver error for sql_variant type
- FROM sys.sequences AS seq';
- }
- /**
- * {@inheritdoc}
- */
- public function getSequenceNextValSQL($sequence)
- {
- return 'SELECT NEXT VALUE FOR ' . $sequence;
- }
- /**
- * {@inheritDoc}
- */
- public function hasNativeGuidType()
- {
- return true;
- }
- /**
- * {@inheritDoc}
- */
- public function getDropForeignKeySQL($foreignKey, $table)
- {
- if (! $foreignKey instanceof ForeignKeyConstraint) {
- $foreignKey = new Identifier($foreignKey);
- }
- if (! $table instanceof Table) {
- $table = new Identifier($table);
- }
- $foreignKey = $foreignKey->getQuotedName($this);
- $table = $table->getQuotedName($this);
- return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
- }
- /**
- * {@inheritDoc}
- */
- public function getDropIndexSQL($index, $table = null)
- {
- if ($index instanceof Index) {
- $index = $index->getQuotedName($this);
- } elseif (! is_string($index)) {
- throw new InvalidArgumentException(
- __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.'
- );
- }
- if ($table instanceof Table) {
- $table = $table->getQuotedName($this);
- } elseif (! is_string($table)) {
- throw new InvalidArgumentException(
- __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.'
- );
- }
- return 'DROP INDEX ' . $index . ' ON ' . $table;
- }
- /**
- * {@inheritDoc}
- */
- protected function _getCreateTableSQL($name, array $columns, array $options = [])
- {
- $defaultConstraintsSql = [];
- $commentsSql = [];
- $tableComment = $options['comment'] ?? null;
- if ($tableComment !== null) {
- $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment);
- }
- // @todo does other code breaks because of this?
- // force primary keys to be not null
- foreach ($columns as &$column) {
- if (! empty($column['primary'])) {
- $column['notnull'] = true;
- }
- // Build default constraints SQL statements.
- if (isset($column['default'])) {
- $defaultConstraintsSql[] = 'ALTER TABLE ' . $name .
- ' ADD' . $this->getDefaultConstraintDeclarationSQL($name, $column);
- }
- if (empty($column['comment']) && ! is_numeric($column['comment'])) {
- continue;
- }
- $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']);
- }
- $columnListSql = $this->getColumnDeclarationListSQL($columns);
- if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
- foreach ($options['uniqueConstraints'] as $constraintName => $definition) {
- $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($constraintName, $definition);
- }
- }
- if (isset($options['primary']) && ! empty($options['primary'])) {
- $flags = '';
- if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) {
- $flags = ' NONCLUSTERED';
- }
- $columnListSql .= ', PRIMARY KEY' . $flags
- . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')';
- }
- $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql;
- $check = $this->getCheckDeclarationSQL($columns);
- if (! empty($check)) {
- $query .= ', ' . $check;
- }
- $query .= ')';
- $sql = [$query];
- if (isset($options['indexes']) && ! empty($options['indexes'])) {
- foreach ($options['indexes'] as $index) {
- $sql[] = $this->getCreateIndexSQL($index, $name);
- }
- }
- if (isset($options['foreignKeys'])) {
- foreach ((array) $options['foreignKeys'] as $definition) {
- $sql[] = $this->getCreateForeignKeySQL($definition, $name);
- }
- }
- return array_merge($sql, $commentsSql, $defaultConstraintsSql);
- }
- /**
- * {@inheritDoc}
- */
- public function getCreatePrimaryKeySQL(Index $index, $table)
- {
- if ($table instanceof Table) {
- $identifier = $table->getQuotedName($this);
- } else {
- $identifier = $table;
- }
- $sql = 'ALTER TABLE ' . $identifier . ' ADD PRIMARY KEY';
- if ($index->hasFlag('nonclustered')) {
- $sql .= ' NONCLUSTERED';
- }
- return $sql . ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')';
- }
- /**
- * Returns the SQL statement for creating a column comment.
- *
- * SQL Server does not support native column comments,
- * therefore the extended properties functionality is used
- * as a workaround to store them.
- * The property name used to store column comments is "MS_Description"
- * which provides compatibility with SQL Server Management Studio,
- * as column comments are stored in the same property there when
- * specifying a column's "Description" attribute.
- *
- * @param string $tableName The quoted table name to which the column belongs.
- * @param string $columnName The quoted column name to create the comment for.
- * @param string|null $comment The column's comment.
- *
- * @return string
- */
- protected function getCreateColumnCommentSQL($tableName, $columnName, $comment)
- {
- if (strpos($tableName, '.') !== false) {
- [$schemaSQL, $tableSQL] = explode('.', $tableName);
- $schemaSQL = $this->quoteStringLiteral($schemaSQL);
- $tableSQL = $this->quoteStringLiteral($tableSQL);
- } else {
- $schemaSQL = "'dbo'";
- $tableSQL = $this->quoteStringLiteral($tableName);
- }
- return $this->getAddExtendedPropertySQL(
- 'MS_Description',
- $comment,
- 'SCHEMA',
- $schemaSQL,
- 'TABLE',
- $tableSQL,
- 'COLUMN',
- $columnName
- );
- }
- /**
- * Returns the SQL snippet for declaring a default constraint.
- *
- * @internal The method should be only used from within the SQLServerPlatform class hierarchy.
- *
- * @param string $table Name of the table to return the default constraint declaration for.
- * @param mixed[] $column Column definition.
- *
- * @return string
- *
- * @throws InvalidArgumentException
- */
- public function getDefaultConstraintDeclarationSQL($table, array $column)
- {
- if (! isset($column['default'])) {
- throw new InvalidArgumentException("Incomplete column definition. 'default' required.");
- }
- $columnName = new Identifier($column['name']);
- return ' CONSTRAINT ' .
- $this->generateDefaultConstraintName($table, $column['name']) .
- $this->getDefaultValueDeclarationSQL($column) .
- ' FOR ' . $columnName->getQuotedName($this);
- }
- /**
- * {@inheritDoc}
- */
- public function getCreateIndexSQL(Index $index, $table)
- {
- $constraint = parent::getCreateIndexSQL($index, $table);
- if ($index->isUnique() && ! $index->isPrimary()) {
- $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
- }
- return $constraint;
- }
- /**
- * {@inheritDoc}
- */
- protected function getCreateIndexSQLFlags(Index $index)
- {
- $type = '';
- if ($index->isUnique()) {
- $type .= 'UNIQUE ';
- }
- if ($index->hasFlag('clustered')) {
- $type .= 'CLUSTERED ';
- } elseif ($index->hasFlag('nonclustered')) {
- $type .= 'NONCLUSTERED ';
- }
- return $type;
- }
- /**
- * Extend unique key constraint with required filters
- *
- * @param string $sql
- */
- private function _appendUniqueConstraintDefinition($sql, Index $index): string
- {
- $fields = [];
- foreach ($index->getQuotedColumns($this) as $field) {
- $fields[] = $field . ' IS NOT NULL';
- }
- return $sql . ' WHERE ' . implode(' AND ', $fields);
- }
- /**
- * {@inheritDoc}
- */
- public function getAlterTableSQL(TableDiff $diff)
- {
- $queryParts = [];
- $sql = [];
- $columnSql = [];
- $commentsSql = [];
- foreach ($diff->addedColumns as $column) {
- if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
- continue;
- }
- $columnDef = $column->toArray();
- $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
- if (isset($columnDef['default'])) {
- $addColumnSql .= ' CONSTRAINT ' .
- $this->generateDefaultConstraintName($diff->name, $column->getQuotedName($this)) .
- $this->getDefaultValueDeclarationSQL($columnDef);
- }
- $queryParts[] = $addColumnSql;
- $comment = $this->getColumnComment($column);
- if (empty($comment) && ! is_numeric($comment)) {
- continue;
- }
- $commentsSql[] = $this->getCreateColumnCommentSQL(
- $diff->name,
- $column->getQuotedName($this),
- $comment
- );
- }
- foreach ($diff->removedColumns as $column) {
- if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
- continue;
- }
- $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
- }
- foreach ($diff->changedColumns as $columnDiff) {
- if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
- continue;
- }
- $column = $columnDiff->column;
- $comment = $this->getColumnComment($column);
- $hasComment = ! empty($comment) || is_numeric($comment);
- if ($columnDiff->fromColumn instanceof Column) {
- $fromComment = $this->getColumnComment($columnDiff->fromColumn);
- $hasFromComment = ! empty($fromComment) || is_numeric($fromComment);
- if ($hasFromComment && $hasComment && $fromComment !== $comment) {
- $commentsSql[] = $this->getAlterColumnCommentSQL(
- $diff->name,
- $column->getQuotedName($this),
- $comment
- );
- } elseif ($hasFromComment && ! $hasComment) {
- $commentsSql[] = $this->getDropColumnCommentSQL($diff->name, $column->getQuotedName($this));
- } elseif (! $hasFromComment && $hasComment) {
- $commentsSql[] = $this->getCreateColumnCommentSQL(
- $diff->name,
- $column->getQuotedName($this),
- $comment
- );
- }
- }
- // Do not add query part if only comment has changed.
- if ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1) {
- continue;
- }
- $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff);
- if ($requireDropDefaultConstraint) {
- $queryParts[] = $this->getAlterTableDropDefaultConstraintClause(
- $diff->name,
- $columnDiff->oldColumnName
- );
- }
- $columnDef = $column->toArray();
- $queryParts[] = 'ALTER COLUMN ' .
- $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
- if (
- ! isset($columnDef['default'])
- || (! $requireDropDefaultConstraint && ! $columnDiff->hasChanged('default'))
- ) {
- continue;
- }
- $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column);
- }
- foreach ($diff->renamedColumns as $oldColumnName => $column) {
- if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
- continue;
- }
- $oldColumnName = new Identifier($oldColumnName);
- $sql[] = "sp_rename '" .
- $diff->getName($this)->getQuotedName($this) . '.' . $oldColumnName->getQuotedName($this) .
- "', '" . $column->getQuotedName($this) . "', 'COLUMN'";
- // Recreate default constraint with new column name if necessary (for future reference).
- if ($column->getDefault() === null) {
- continue;
- }
- $queryParts[] = $this->getAlterTableDropDefaultConstraintClause(
- $diff->name,
- $oldColumnName->getQuotedName($this)
- );
- $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column);
- }
- $tableSql = [];
- if ($this->onSchemaAlterTable($diff, $tableSql)) {
- return array_merge($tableSql, $columnSql);
- }
- foreach ($queryParts as $query) {
- $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
- }
- $sql = array_merge($sql, $commentsSql);
- $newName = $diff->getNewName();
- if ($newName !== false) {
- $sql[] = "sp_rename '" . $diff->getName($this)->getQuotedName($this) . "', '" . $newName->getName() . "'";
- /**
- * Rename table's default constraints names
- * to match the new table name.
- * This is necessary to ensure that the default
- * constraints can be referenced in future table
- * alterations as the table name is encoded in
- * default constraints' names.
- */
- $sql[] = "DECLARE @sql NVARCHAR(MAX) = N''; " .
- "SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' " .
- "+ REPLACE(dc.name, '" . $this->generateIdentifierName($diff->name) . "', " .
- "'" . $this->generateIdentifierName($newName->getName()) . "') + ''', ''OBJECT'';' " .
- 'FROM sys.default_constraints dc ' .
- 'JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id ' .
- "WHERE tbl.name = '" . $newName->getName() . "';" .
- 'EXEC sp_executesql @sql';
- }
- $sql = array_merge(
- $this->getPreAlterTableIndexForeignKeySQL($diff),
- $sql,
- $this->getPostAlterTableIndexForeignKeySQL($diff)
- );
- return array_merge($sql, $tableSql, $columnSql);
- }
- /**
- * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement.
- *
- * @param string $tableName The name of the table to generate the clause for.
- * @param Column $column The column to generate the clause for.
- */
- private function getAlterTableAddDefaultConstraintClause($tableName, Column $column): string
- {
- $columnDef = $column->toArray();
- $columnDef['name'] = $column->getQuotedName($this);
- return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef);
- }
- /**
- * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement.
- *
- * @param string $tableName The name of the table to generate the clause for.
- * @param string $columnName The name of the column to generate the clause for.
- */
- private function getAlterTableDropDefaultConstraintClause($tableName, $columnName): string
- {
- return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName);
- }
- /**
- * Checks whether a column alteration requires dropping its default constraint first.
- *
- * Different to other database vendors SQL Server implements column default values
- * as constraints and therefore changes in a column's default value as well as changes
- * in a column's type require dropping the default constraint first before being to
- * alter the particular column to the new definition.
- */
- private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool
- {
- // We can only decide whether to drop an existing default constraint
- // if we know the original default value.
- if (! $columnDiff->fromColumn instanceof Column) {
- return false;
- }
- // We only need to drop an existing default constraint if we know the
- // column was defined with a default value before.
- if ($columnDiff->fromColumn->getDefault() === null) {
- return false;
- }
- // We need to drop an existing default constraint if the column was
- // defined with a default value before and it has changed.
- if ($columnDiff->hasChanged('default')) {
- return true;
- }
- // We need to drop an existing default constraint if the column was
- // defined with a default value before and the native column type has changed.
- return $columnDiff->hasChanged('type') || $columnDiff->hasChanged('fixed');
- }
- /**
- * Returns the SQL statement for altering a column comment.
- *
- * SQL Server does not support native column comments,
- * therefore the extended properties functionality is used
- * as a workaround to store them.
- * The property name used to store column comments is "MS_Description"
- * which provides compatibility with SQL Server Management Studio,
- * as column comments are stored in the same property there when
- * specifying a column's "Description" attribute.
- *
- * @param string $tableName The quoted table name to which the column belongs.
- * @param string $columnName The quoted column name to alter the comment for.
- * @param string|null $comment The column's comment.
- *
- * @return string
- */
- protected function getAlterColumnCommentSQL($tableName, $columnName, $comment)
- {
- if (strpos($tableName, '.') !== false) {
- [$schemaSQL, $tableSQL] = explode('.', $tableName);
- $schemaSQL = $this->quoteStringLiteral($schemaSQL);
- $tableSQL = $this->quoteStringLiteral($tableSQL);
- } else {
- $schemaSQL = "'dbo'";
- $tableSQL = $this->quoteStringLiteral($tableName);
- }
- return $this->getUpdateExtendedPropertySQL(
- 'MS_Description',
- $comment,
- 'SCHEMA',
- $schemaSQL,
- 'TABLE',
- $tableSQL,
- 'COLUMN',
- $columnName
- );
- }
- /**
- * Returns the SQL statement for dropping a column comment.
- *
- * SQL Server does not support native column comments,
- * therefore the extended properties functionality is used
- * as a workaround to store them.
- * The property name used to store column comments is "MS_Description"
- * which provides compatibility with SQL Server Management Studio,
- * as column comments are stored in the same property there when
- * specifying a column's "Description" attribute.
- *
- * @param string $tableName The quoted table name to which the column belongs.
- * @param string $columnName The quoted column name to drop the comment for.
- *
- * @return string
- */
- protected function getDropColumnCommentSQL($tableName, $columnName)
- {
- if (strpos($tableName, '.') !== false) {
- [$schemaSQL, $tableSQL] = explode('.', $tableName);
- $schemaSQL = $this->quoteStringLiteral($schemaSQL);
- $tableSQL = $this->quoteStringLiteral($tableSQL);
- } else {
- $schemaSQL = "'dbo'";
- $tableSQL = $this->quoteStringLiteral($tableName);
- }
- return $this->getDropExtendedPropertySQL(
- 'MS_Description',
- 'SCHEMA',
- $schemaSQL,
- 'TABLE',
- $tableSQL,
- 'COLUMN',
- $columnName
- );
- }
- /**
- * {@inheritdoc}
- */
- protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
- {
- return [sprintf(
- "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'",
- $tableName,
- $oldIndexName,
- $index->getQuotedName($this)
- ),
- ];
- }
- /**
- * Returns the SQL statement for adding an extended property to a database object.
- *
- * @internal The method should be only used from within the SQLServerPlatform class hierarchy.
- *
- * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx
- *
- * @param string $name The name of the property to add.
- * @param string|null $value The value of the property to add.
- * @param string|null $level0Type The type of the object at level 0 the property belongs to.
- * @param string|null $level0Name The name of the object at level 0 the property belongs to.
- * @param string|null $level1Type The type of the object at level 1 the property belongs to.
- * @param string|null $level1Name The name of the object at level 1 the property belongs to.
- * @param string|null $level2Type The type of the object at level 2 the property belongs to.
- * @param string|null $level2Name The name of the object at level 2 the property belongs to.
- *
- * @return string
- */
- public function getAddExtendedPropertySQL(
- $name,
- $value = null,
- $level0Type = null,
- $level0Name = null,
- $level1Type = null,
- $level1Name = null,
- $level2Type = null,
- $level2Name = null
- ) {
- return 'EXEC sp_addextendedproperty ' .
- 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name;
- }
- /**
- * Returns the SQL statement for dropping an extended property from a database object.
- *
- * @internal The method should be only used from within the SQLServerPlatform class hierarchy.
- *
- * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx
- *
- * @param string $name The name of the property to drop.
- * @param string|null $level0Type The type of the object at level 0 the property belongs to.
- * @param string|null $level0Name The name of the object at level 0 the property belongs to.
- * @param string|null $level1Type The type of the object at level 1 the property belongs to.
- * @param string|null $level1Name The name of the object at level 1 the property belongs to.
- * @param string|null $level2Type The type of the object at level 2 the property belongs to.
- * @param string|null $level2Name The name of the object at level 2 the property belongs to.
- *
- * @return string
- */
- public function getDropExtendedPropertySQL(
- $name,
- $level0Type = null,
- $level0Name = null,
- $level1Type = null,
- $level1Name = null,
- $level2Type = null,
- $level2Name = null
- ) {
- return 'EXEC sp_dropextendedproperty ' .
- 'N' . $this->quoteStringLiteral($name) . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name;
- }
- /**
- * Returns the SQL statement for updating an extended property of a database object.
- *
- * @internal The method should be only used from within the SQLServerPlatform class hierarchy.
- *
- * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx
- *
- * @param string $name The name of the property to update.
- * @param string|null $value The value of the property to update.
- * @param string|null $level0Type The type of the object at level 0 the property belongs to.
- * @param string|null $level0Name The name of the object at level 0 the property belongs to.
- * @param string|null $level1Type The type of the object at level 1 the property belongs to.
- * @param string|null $level1Name The name of the object at level 1 the property belongs to.
- * @param string|null $level2Type The type of the object at level 2 the property belongs to.
- * @param string|null $level2Name The name of the object at level 2 the property belongs to.
- *
- * @return string
- */
- public function getUpdateExtendedPropertySQL(
- $name,
- $value = null,
- $level0Type = null,
- $level0Name = null,
- $level1Type = null,
- $level1Name = null,
- $level2Type = null,
- $level2Name = null
- ) {
- return 'EXEC sp_updateextendedproperty ' .
- 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral((string) $value) . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level0Type) . ', ' . $level0Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level1Type) . ', ' . $level1Name . ', ' .
- 'N' . $this->quoteStringLiteral((string) $level2Type) . ', ' . $level2Name;
- }
- /**
- * {@inheritDoc}
- */
- public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
- {
- return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
- }
- /**
- * {@inheritDoc}
- */
- public function getListTablesSQL()
- {
- // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
- // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication
- return 'SELECT name, SCHEMA_NAME (uid) AS schema_name FROM sysobjects'
- . " WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name";
- }
- /**
- * {@inheritDoc}
- */
- public function getListTableColumnsSQL($table, $database = null)
- {
- return "SELECT col.name,
- type.name AS type,
- col.max_length AS length,
- ~col.is_nullable AS notnull,
- def.definition AS [default],
- col.scale,
- col.precision,
- col.is_identity AS autoincrement,
- col.collation_name AS collation,
- CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type
- FROM sys.columns AS col
- JOIN sys.types AS type
- ON col.user_type_id = type.user_type_id
- JOIN sys.objects AS obj
- ON col.object_id = obj.object_id
- JOIN sys.schemas AS scm
- ON obj.schema_id = scm.schema_id
- LEFT JOIN sys.default_constraints def
- ON col.default_object_id = def.object_id
- AND col.object_id = def.parent_object_id
- LEFT JOIN sys.extended_properties AS prop
- ON obj.object_id = prop.major_id
- AND col.column_id = prop.minor_id
- AND prop.name = 'MS_Description'
- WHERE obj.type = 'U'
- AND " . $this->getTableWhereClause($table, 'scm.name', 'obj.name');
- }
- /**
- * @param string $table
- * @param string|null $database
- *
- * @return string
- */
- public function getListTableForeignKeysSQL($table, $database = null)
- {
- return 'SELECT f.name AS ForeignKey,
- SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
- OBJECT_NAME (f.parent_object_id) AS TableName,
- COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
- SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
- OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
- COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
- f.delete_referential_action_desc,
- f.update_referential_action_desc
- FROM sys.foreign_keys AS f
- INNER JOIN sys.foreign_key_columns AS fc
- INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
- ON f.OBJECT_ID = fc.constraint_object_id
- WHERE ' .
- $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)') .
- ' ORDER BY fc.constraint_column_id';
- }
- /**
- * {@inheritDoc}
- */
- public function getListTableIndexesSQL($table, $database = null)
- {
- return "SELECT idx.name AS key_name,
- col.name AS column_name,
- ~idx.is_unique AS non_unique,
- idx.is_primary_key AS [primary],
- CASE idx.type
- WHEN '1' THEN 'clustered'
- WHEN '2' THEN 'nonclustered'
- ELSE NULL
- END AS flags
- FROM sys.tables AS tbl
- JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id
- JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id
- JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id
- JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id
- WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . '
- ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC';
- }
- /**
- * {@inheritDoc}
- */
- public function getListViewsSQL($database)
- {
- return "SELECT name, definition FROM sysobjects
- INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id
- WHERE type = 'V' ORDER BY name";
- }
- /**
- * Returns the where clause to filter schema and table name in a query.
- *
- * @param string $table The full qualified name of the table.
- * @param string $schemaColumn The name of the column to compare the schema to in the where clause.
- * @param string $tableColumn The name of the column to compare the table to in the where clause.
- */
- private function getTableWhereClause($table, $schemaColumn, $tableColumn): string
- {
- if (strpos($table, '.') !== false) {
- [$schema, $table] = explode('.', $table);
- $schema = $this->quoteStringLiteral($schema);
- $table = $this->quoteStringLiteral($table);
- } else {
- $schema = 'SCHEMA_NAME()';
- $table = $this->quoteStringLiteral($table);
- }
- return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema);
- }
- /**
- * {@inheritDoc}
- */
- public function getLocateExpression($str, $substr, $startPos = false)
- {
- if ($startPos === false) {
- return 'CHARINDEX(' . $substr . ', ' . $str . ')';
- }
- return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')';
- }
- /**
- * {@inheritDoc}
- */
- public function getModExpression($expression1, $expression2)
- {
- return $expression1 . ' % ' . $expression2;
- }
- /**
- * {@inheritDoc}
- */
- public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false)
- {
- if ($char === false) {
- switch ($mode) {
- case TrimMode::LEADING:
- $trimFn = 'LTRIM';
- break;
- case TrimMode::TRAILING:
- $trimFn = 'RTRIM';
- break;
- default:
- return 'LTRIM(RTRIM(' . $str . '))';
- }
- return $trimFn . '(' . $str . ')';
- }
- $pattern = "'%[^' + " . $char . " + ']%'";
- if ($mode === TrimMode::LEADING) {
- return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)';
- }
- if ($mode === TrimMode::TRAILING) {
- return 'reverse(stuff(reverse(' . $str . '), 1, '
- . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))';
- }
- return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, '
- . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str
- . ') - 1, null))) - 1, null))';
- }
- /**
- * {@inheritDoc}
- */
- public function getConcatExpression()
- {
- return sprintf('CONCAT(%s)', implode(', ', func_get_args()));
- }
- /**
- * {@inheritDoc}
- */
- public function getListDatabasesSQL()
- {
- return 'SELECT * FROM sys.databases';
- }
- /**
- * {@inheritDoc}
- *
- * @deprecated Use {@see SQLServerSchemaManager::listSchemaNames()} instead.
- */
- public function getListNamespacesSQL()
- {
- Deprecation::triggerIfCalledFromOutside(
- 'doctrine/dbal',
- 'https://github.com/doctrine/dbal/issues/4503',
- 'SQLServerPlatform::getListNamespacesSQL() is deprecated,'
- . ' use SQLServerSchemaManager::listSchemaNames() instead.'
- );
- return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')";
- }
- /**
- * {@inheritDoc}
- */
- public function getSubstringExpression($string, $start, $length = null)
- {
- if ($length !== null) {
- return 'SUBSTRING(' . $string . ', ' . $start . ', ' . $length . ')';
- }
- return 'SUBSTRING(' . $string . ', ' . $start . ', LEN(' . $string . ') - ' . $start . ' + 1)';
- }
- /**
- * {@inheritDoc}
- */
- public function getLengthExpression($column)
- {
- return 'LEN(' . $column . ')';
- }
- public function getCurrentDatabaseExpression(): string
- {
- return 'DB_NAME()';
- }
- /**
- * {@inheritDoc}
- */
- public function getSetTransactionIsolationSQL($level)
- {
- return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
- }
- /**
- * {@inheritDoc}
- */
- public function getIntegerTypeDeclarationSQL(array $column)
- {
- return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
- }
- /**
- * {@inheritDoc}
- */
- public function getBigIntTypeDeclarationSQL(array $column)
- {
- return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
- }
- /**
- * {@inheritDoc}
- */
- public function getSmallIntTypeDeclarationSQL(array $column)
- {
- return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column);
- }
- /**
- * {@inheritDoc}
- */
- public function getGuidTypeDeclarationSQL(array $column)
- {
- return 'UNIQUEIDENTIFIER';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateTimeTzTypeDeclarationSQL(array $column)
- {
- return 'DATETIMEOFFSET(6)';
- }
- /**
- * {@inheritDoc}
- */
- public function getAsciiStringTypeDeclarationSQL(array $column): string
- {
- $length = $column['length'] ?? null;
- if (! isset($column['fixed'])) {
- return sprintf('VARCHAR(%d)', $length ?? 255);
- }
- return sprintf('CHAR(%d)', $length ?? 255);
- }
- /**
- * {@inheritDoc}
- */
- protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
- {
- return $fixed
- ? ($length > 0 ? 'NCHAR(' . $length . ')' : 'CHAR(255)')
- : ($length > 0 ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)');
- }
- /**
- * {@inheritdoc}
- */
- protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
- {
- return $fixed
- ? 'BINARY(' . ($length > 0 ? $length : 255) . ')'
- : 'VARBINARY(' . ($length > 0 ? $length : 255) . ')';
- }
- /**
- * {@inheritdoc}
- */
- public function getBinaryMaxLength()
- {
- return 8000;
- }
- /**
- * {@inheritDoc}
- */
- public function getClobTypeDeclarationSQL(array $column)
- {
- return 'VARCHAR(MAX)';
- }
- /**
- * {@inheritDoc}
- */
- protected function _getCommonIntegerTypeDeclarationSQL(array $column)
- {
- return ! empty($column['autoincrement']) ? ' IDENTITY' : '';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateTimeTypeDeclarationSQL(array $column)
- {
- // 3 - microseconds precision length
- // http://msdn.microsoft.com/en-us/library/ms187819.aspx
- return 'DATETIME2(6)';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateTypeDeclarationSQL(array $column)
- {
- return 'DATE';
- }
- /**
- * {@inheritDoc}
- */
- public function getTimeTypeDeclarationSQL(array $column)
- {
- return 'TIME(0)';
- }
- /**
- * {@inheritDoc}
- */
- public function getBooleanTypeDeclarationSQL(array $column)
- {
- return 'BIT';
- }
- /**
- * {@inheritDoc}
- */
- protected function doModifyLimitQuery($query, $limit, $offset)
- {
- if ($limit === null && $offset <= 0) {
- return $query;
- }
- if ($this->shouldAddOrderBy($query)) {
- if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) {
- // SQL Server won't let us order by a non-selected column in a DISTINCT query,
- // so we have to do this madness. This says, order by the first column in the
- // result. SQL Server's docs say that a nonordered query's result order is non-
- // deterministic anyway, so this won't do anything that a bunch of update and
- // deletes to the table wouldn't do anyway.
- $query .= ' ORDER BY 1';
- } else {
- // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you
- // use constant expressions in the order by list.
- $query .= ' ORDER BY (SELECT 0)';
- }
- }
- // This looks somewhat like MYSQL, but limit/offset are in inverse positions
- // Supposedly SQL:2008 core standard.
- // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS.
- $query .= sprintf(' OFFSET %d ROWS', $offset);
- if ($limit !== null) {
- $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit);
- }
- return $query;
- }
- /**
- * {@inheritDoc}
- */
- public function convertBooleans($item)
- {
- if (is_array($item)) {
- foreach ($item as $key => $value) {
- if (! is_bool($value) && ! is_numeric($value)) {
- continue;
- }
- $item[$key] = (int) (bool) $value;
- }
- } elseif (is_bool($item) || is_numeric($item)) {
- $item = (int) (bool) $item;
- }
- return $item;
- }
- /**
- * {@inheritDoc}
- */
- public function getCreateTemporaryTableSnippetSQL()
- {
- return 'CREATE TABLE';
- }
- /**
- * {@inheritDoc}
- */
- public function getTemporaryTableName($tableName)
- {
- return '#' . $tableName;
- }
- /**
- * {@inheritDoc}
- */
- public function getDateTimeFormatString()
- {
- return 'Y-m-d H:i:s.u';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateFormatString()
- {
- return 'Y-m-d';
- }
- /**
- * {@inheritDoc}
- */
- public function getTimeFormatString()
- {
- return 'H:i:s';
- }
- /**
- * {@inheritDoc}
- */
- public function getDateTimeTzFormatString()
- {
- return 'Y-m-d H:i:s.u P';
- }
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return 'mssql';
- }
- /**
- * {@inheritDoc}
- */
- protected function initializeDoctrineTypeMappings()
- {
- $this->doctrineTypeMapping = [
- 'bigint' => 'bigint',
- 'binary' => 'binary',
- 'bit' => 'boolean',
- 'blob' => 'blob',
- 'char' => 'string',
- 'date' => 'date',
- 'datetime' => 'datetime',
- 'datetime2' => 'datetime',
- 'datetimeoffset' => 'datetimetz',
- 'decimal' => 'decimal',
- 'double' => 'float',
- 'double precision' => 'float',
- 'float' => 'float',
- 'image' => 'blob',
- 'int' => 'integer',
- 'money' => 'integer',
- 'nchar' => 'string',
- 'ntext' => 'text',
- 'numeric' => 'decimal',
- 'nvarchar' => 'string',
- 'real' => 'float',
- 'smalldatetime' => 'datetime',
- 'smallint' => 'smallint',
- 'smallmoney' => 'integer',
- 'text' => 'text',
- 'time' => 'time',
- 'tinyint' => 'smallint',
- 'uniqueidentifier' => 'guid',
- 'varbinary' => 'binary',
- 'varchar' => 'string',
- ];
- }
- /**
- * {@inheritDoc}
- */
- public function createSavePoint($savepoint)
- {
- return 'SAVE TRANSACTION ' . $savepoint;
- }
- /**
- * {@inheritDoc}
- */
- public function releaseSavePoint($savepoint)
- {
- return '';
- }
- /**
- * {@inheritDoc}
- */
- public function rollbackSavePoint($savepoint)
- {
- return 'ROLLBACK TRANSACTION ' . $savepoint;
- }
- /**
- * {@inheritdoc}
- */
- public function getForeignKeyReferentialActionSQL($action)
- {
- // RESTRICT is not supported, therefore falling back to NO ACTION.
- if (strtoupper($action) === 'RESTRICT') {
- return 'NO ACTION';
- }
- return parent::getForeignKeyReferentialActionSQL($action);
- }
- public function appendLockHint(string $fromClause, int $lockMode): string
- {
- switch ($lockMode) {
- case LockMode::NONE:
- case LockMode::OPTIMISTIC:
- return $fromClause;
- case LockMode::PESSIMISTIC_READ:
- return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)';
- case LockMode::PESSIMISTIC_WRITE:
- return $fromClause . ' WITH (UPDLOCK, ROWLOCK)';
- default:
- throw InvalidLockMode::fromLockMode($lockMode);
- }
- }
- /**
- * {@inheritDoc}
- */
- public function getForUpdateSQL()
- {
- return ' ';
- }
- /**
- * {@inheritDoc}
- *
- * @deprecated Implement {@see createReservedKeywordsList()} instead.
- */
- protected function getReservedKeywordsClass()
- {
- Deprecation::triggerIfCalledFromOutside(
- 'doctrine/dbal',
- 'https://github.com/doctrine/dbal/issues/4510',
- 'SQLServerPlatform::getReservedKeywordsClass() is deprecated,'
- . ' use SQLServerPlatform::createReservedKeywordsList() instead.'
- );
- return Keywords\SQLServer2012Keywords::class;
- }
- /**
- * {@inheritDoc}
- */
- public function quoteSingleIdentifier($str)
- {
- return '[' . str_replace(']', ']]', $str) . ']';
- }
- /**
- * {@inheritDoc}
- */
- public function getTruncateTableSQL($tableName, $cascade = false)
- {
- $tableIdentifier = new Identifier($tableName);
- return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
- }
- /**
- * {@inheritDoc}
- */
- public function getBlobTypeDeclarationSQL(array $column)
- {
- return 'VARBINARY(MAX)';
- }
- /**
- * {@inheritdoc}
- *
- * Modifies column declaration order as it differs in Microsoft SQL Server.
- */
- public function getColumnDeclarationSQL($name, array $column)
- {
- if (isset($column['columnDefinition'])) {
- $columnDef = $this->getCustomTypeDeclarationSQL($column);
- } else {
- $collation = ! empty($column['collation']) ?
- ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : '';
- $notnull = ! empty($column['notnull']) ? ' NOT NULL' : '';
- $unique = ! empty($column['unique']) ?
- ' ' . $this->getUniqueFieldDeclarationSQL() : '';
- $check = ! empty($column['check']) ?
- ' ' . $column['check'] : '';
- $typeDecl = $column['type']->getSQLDeclaration($column, $this);
- $columnDef = $typeDecl . $collation . $notnull . $unique . $check;
- }
- return $name . ' ' . $columnDef;
- }
- public function columnsEqual(Column $column1, Column $column2): bool
- {
- if (! parent::columnsEqual($column1, $column2)) {
- return false;
- }
- return $this->getDefaultValueDeclarationSQL($column1->toArray())
- === $this->getDefaultValueDeclarationSQL($column2->toArray());
- }
- protected function getLikeWildcardCharacters(): string
- {
- return parent::getLikeWildcardCharacters() . '[]^';
- }
- /**
- * Returns a unique default constraint name for a table and column.
- *
- * @param string $table Name of the table to generate the unique default constraint name for.
- * @param string $column Name of the column in the table to generate the unique default constraint name for.
- */
- private function generateDefaultConstraintName($table, $column): string
- {
- return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column);
- }
- /**
- * Returns a hash value for a given identifier.
- *
- * @param string $identifier Identifier to generate a hash value for.
- */
- private function generateIdentifierName($identifier): string
- {
- // Always generate name for unquoted identifiers to ensure consistency.
- $identifier = new Identifier($identifier);
- return strtoupper(dechex(crc32($identifier->getName())));
- }
- protected function getCommentOnTableSQL(string $tableName, ?string $comment): string
- {
- return sprintf(
- <<<'SQL'
- EXEC sys.sp_addextendedproperty @name=N'MS_Description',
- @value=N%s, @level0type=N'SCHEMA', @level0name=N'dbo',
- @level1type=N'TABLE', @level1name=N%s
- SQL
- ,
- $this->quoteStringLiteral((string) $comment),
- $this->quoteStringLiteral($tableName)
- );
- }
- public function getListTableMetadataSQL(string $table): string
- {
- return sprintf(
- <<<'SQL'
- SELECT
- p.value AS [table_comment]
- FROM
- sys.tables AS tbl
- INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1
- WHERE
- (tbl.name=N%s and SCHEMA_NAME(tbl.schema_id)=N'dbo' and p.name=N'MS_Description')
- SQL
- ,
- $this->quoteStringLiteral($table)
- );
- }
- /**
- * @param string $query
- */
- private function shouldAddOrderBy($query): bool
- {
- // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement
- // but can be in a newline
- $matches = [];
- $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE);
- if ($matchesCount === 0) {
- return true;
- }
- // ORDER BY instance may be in a subquery after ORDER BY
- // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2)
- // if in the searched query ORDER BY clause was found where
- // number of open parentheses after the occurrence of the clause is equal to
- // number of closed brackets after the occurrence of the clause,
- // it means that ORDER BY is included in the query being checked
- while ($matchesCount > 0) {
- $orderByPos = $matches[0][--$matchesCount][1];
- $openBracketsCount = substr_count($query, '(', $orderByPos);
- $closedBracketsCount = substr_count($query, ')', $orderByPos);
- if ($openBracketsCount === $closedBracketsCount) {
- return false;
- }
- }
- return true;
- }
- }
|