2016-05-17 01:01:34 +10:00
/* eslint max-len:0 */
2016-03-02 17:07:05 +01:00
// MSSQL Table Builder & Compiler
// -------
2019-06-04 00:37:17 +02:00
const TableCompiler = require ( '../../../schema/tablecompiler' ) ;
2020-12-28 16:55:08 +02:00
const helpers = require ( '../../../util/helpers' ) ;
2021-10-10 15:47:32 -04:00
const { isObject } = require ( '../../../util/is' ) ;
2016-03-02 17:07:05 +01:00
// Table Compiler
// ------
2021-01-01 18:46:16 +02:00
class TableCompiler _MSSQL extends TableCompiler {
2021-02-04 15:54:26 +02:00
constructor ( client , tableBuilder ) {
super ( client , tableBuilder ) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
2021-10-15 15:57:46 +02:00
createQuery ( columns , ifNot , like ) {
let createStatement = ifNot
? ` if object_id(' ${ this . tableName ( ) } ', 'U') is null `
: '' ;
if ( like ) {
// This query copy only columns and not all indexes and keys like other databases.
createStatement += ` SELECT * INTO ${ this . tableName ( ) } FROM ${ this . tableNameLike ( ) } WHERE 0=1 ` ;
} else {
createStatement +=
'CREATE TABLE ' +
this . tableName ( ) +
( this . _formatting ? ' (\n ' : ' (' ) +
columns . sql . join ( this . _formatting ? ',\n ' : ', ' ) +
2022-01-06 14:44:16 +01:00
this . _addChecks ( ) +
2021-10-15 15:57:46 +02:00
')' ;
}
this . pushQuery ( createStatement ) ;
2021-08-21 14:29:55 -04:00
2016-03-02 17:07:05 +01:00
if ( this . single . comment ) {
2021-08-21 14:29:55 -04:00
this . comment ( this . single . comment ) ;
2016-03-02 17:07:05 +01:00
}
2021-11-10 21:24:34 +01:00
if ( like ) {
this . addColumns ( columns , this . addColumnsPrefix ) ;
}
2021-08-21 14:29:55 -04:00
}
2016-03-02 17:07:05 +01:00
2021-08-21 14:29:55 -04:00
comment ( /** @type {string} */ comment ) {
if ( ! comment ) {
return ;
}
// XXX: This is a byte limit, not character, so we cannot definitively say they'll exceed the limit without server collation info.
// When I checked in SQL Server 2019, the ctext column in sys.syscomments is defined as a varbinary(8000), so it doesn't even have its own defined collation.
if ( comment . length > 7500 / 2 ) {
this . client . logger . warn (
'Your comment might be longer than the max comment length for MSSQL of 7,500 bytes.'
) ;
}
// See: https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-addextendedproperty-transact-sql?view=sql-server-ver15#f-adding-an-extended-property-to-a-table
const value = this . formatter . escapingStringDelimiters ( comment ) ;
const level0name = this . formatter . escapingStringDelimiters (
this . schemaNameRaw || 'dbo'
) ;
const level1name = this . formatter . escapingStringDelimiters (
this . tableNameRaw
) ;
const args = ` N'MS_Description', N' ${ value } ', N'Schema', N' ${ level0name } ', N'Table', N' ${ level1name } ' ` ;
const isAlreadyDefined = ` EXISTS(SELECT * FROM sys.fn_listextendedproperty(N'MS_Description', N'Schema', N' ${ level0name } ', N'Table', N' ${ level1name } ', NULL, NULL)) ` ;
this . pushQuery (
` IF ${ isAlreadyDefined } \n EXEC sys.sp_updateextendedproperty ${ args } \n ELSE \n EXEC sys.sp_addextendedproperty ${ args } `
) ;
2021-01-01 18:46:16 +02:00
}
2017-02-16 20:34:59 +02:00
2016-05-12 10:24:29 +01:00
// Compiles column add. Multiple columns need only one ADD clause (not one ADD per column) so core addColumns doesn't work. #1348
2018-07-09 08:10:34 -04:00
addColumns ( columns , prefix ) {
2017-02-16 20:34:59 +02:00
prefix = prefix || this . addColumnsPrefix ;
2016-05-12 10:24:29 +01:00
if ( columns . sql . length > 0 ) {
this . pushQuery ( {
2018-07-09 08:10:34 -04:00
sql :
( this . lowerCase ? 'alter table ' : 'ALTER TABLE ' ) +
this . tableName ( ) +
' ' +
prefix +
columns . sql . join ( ', ' ) ,
bindings : columns . bindings ,
2016-05-12 10:24:29 +01:00
} ) ;
}
2021-01-01 18:46:16 +02:00
}
2016-05-12 10:24:29 +01:00
2021-02-25 17:34:22 +00:00
alterColumns ( columns , colBuilder ) {
2021-02-28 17:27:48 +00:00
for ( let i = 0 , l = colBuilder . length ; i < l ; i ++ ) {
const builder = colBuilder [ i ] ;
if ( builder . modified . defaultTo ) {
const schema = this . schemaNameRaw || 'dbo' ;
const baseQuery = `
DECLARE @ constraint varchar ( 100 ) = ( SELECT default _constraints . name
FROM sys . all _columns
INNER JOIN sys . tables
ON all _columns . object _id = tables . object _id
INNER JOIN sys . schemas
ON tables . schema _id = schemas . schema _id
INNER JOIN sys . default _constraints
ON all _columns . default _object _id = default _constraints . object _id
WHERE schemas . name = '${schema}'
AND tables . name = ' $ {
this . tableNameRaw
} '
AND all _columns . name = '${builder.getColumnName()}' )
IF @ constraint IS NOT NULL EXEC ( ' ALTER TABLE $ {
this . tableNameRaw
} DROP CONSTRAINT ' + @ constraint ) ` ;
this . pushQuery ( baseQuery ) ;
}
}
2021-02-25 17:34:22 +00:00
// in SQL server only one column can be altered at a time
columns . sql . forEach ( ( sql ) => {
this . pushQuery ( {
sql :
( this . lowerCase ? 'alter table ' : 'ALTER TABLE ' ) +
this . tableName ( ) +
' ' +
2021-02-28 17:27:48 +00:00
( this . lowerCase
? this . alterColumnPrefix . toLowerCase ( )
: this . alterColumnPrefix ) +
2021-02-25 17:34:22 +00:00
sql ,
bindings : columns . bindings ,
2021-02-28 17:27:48 +00:00
} ) ;
2021-02-25 17:34:22 +00:00
} ) ;
}
2016-05-12 10:24:29 +01:00
// Compiles column drop. Multiple columns need only one DROP clause (not one DROP per column) so core dropColumn doesn't work. #1348
2018-07-09 08:10:34 -04:00
dropColumn ( ) {
2016-05-17 01:01:34 +10:00
const _this2 = this ;
const columns = helpers . normalizeArr . apply ( null , arguments ) ;
2020-05-15 11:28:34 +02:00
const columnsArray = Array . isArray ( columns ) ? columns : [ columns ] ;
const drops = columnsArray . map ( ( column ) => _this2 . formatter . wrap ( column ) ) ;
const schema = this . schemaNameRaw || 'dbo' ;
for ( const column of columns ) {
const baseQuery = `
DECLARE @ constraint varchar ( 100 ) = ( SELECT default _constraints . name
FROM sys . all _columns
INNER JOIN sys . tables
ON all _columns . object _id = tables . object _id
INNER JOIN sys . schemas
ON tables . schema _id = schemas . schema _id
INNER JOIN sys . default _constraints
ON all _columns . default _object _id = default _constraints . object _id
WHERE schemas . name = '${schema}'
AND tables . name = '${this.tableNameRaw}'
AND all _columns . name = '${column}' )
IF @ constraint IS NOT NULL EXEC ( 'ALTER TABLE ${this.tableNameRaw} DROP CONSTRAINT ' + @ constraint ) ` ;
this . pushQuery ( baseQuery ) ;
}
2018-07-09 08:10:34 -04:00
this . pushQuery (
( this . lowerCase ? 'alter table ' : 'ALTER TABLE ' ) +
this . tableName ( ) +
' ' +
this . dropColumnPrefix +
drops . join ( ', ' )
) ;
2021-01-01 18:46:16 +02:00
}
2016-05-12 10:24:29 +01:00
2021-01-01 18:46:16 +02:00
changeType ( ) { }
2016-03-02 17:07:05 +01:00
// Renames a column on the table.
2018-07-09 08:10:34 -04:00
renameColumn ( from , to ) {
this . pushQuery (
2021-02-04 15:54:26 +02:00
` exec sp_rename ${ this . client . parameter (
this . tableName ( ) + '.' + from ,
this . tableBuilder ,
this . bindingsHolder
) } , $ { this . client . parameter (
to ,
this . tableBuilder ,
this . bindingsHolder
) } , 'COLUMN' `
2018-07-09 08:10:34 -04:00
) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
2018-07-09 08:10:34 -04:00
dropFKRefs ( runner , refs ) {
2018-02-01 23:41:01 +01:00
const formatter = this . client . formatter ( this . tableBuilder ) ;
2018-07-09 08:10:34 -04:00
return Promise . all (
2020-04-19 00:40:23 +02:00
refs . map ( function ( ref ) {
2018-07-09 08:10:34 -04:00
const constraintName = formatter . wrap ( ref . CONSTRAINT _NAME ) ;
const tableName = formatter . wrap ( ref . TABLE _NAME ) ;
return runner . query ( {
sql : ` ALTER TABLE ${ tableName } DROP CONSTRAINT ${ constraintName } ` ,
} ) ;
} )
) ;
2021-01-01 18:46:16 +02:00
}
2018-07-09 08:10:34 -04:00
createFKRefs ( runner , refs ) {
2018-02-01 23:41:01 +01:00
const formatter = this . client . formatter ( this . tableBuilder ) ;
2016-03-02 17:07:05 +01:00
2018-07-09 08:10:34 -04:00
return Promise . all (
2020-04-19 00:40:23 +02:00
refs . map ( function ( ref ) {
2018-07-09 08:10:34 -04:00
const tableName = formatter . wrap ( ref . TABLE _NAME ) ;
const keyName = formatter . wrap ( ref . CONSTRAINT _NAME ) ;
const column = formatter . columnize ( ref . COLUMN _NAME ) ;
const references = formatter . columnize ( ref . REFERENCED _COLUMN _NAME ) ;
const inTable = formatter . wrap ( ref . REFERENCED _TABLE _NAME ) ;
const onUpdate = ` ON UPDATE ${ ref . UPDATE _RULE } ` ;
const onDelete = ` ON DELETE ${ ref . DELETE _RULE } ` ;
return runner . query ( {
sql :
` ALTER TABLE ${ tableName } ADD CONSTRAINT ${ keyName } ` +
' FOREIGN KEY (' +
column +
') REFERENCES ' +
inTable +
' (' +
references +
')' +
onUpdate +
onDelete ,
} ) ;
} )
) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
2021-10-25 23:37:26 +02:00
index ( columns , indexName , options ) {
2018-07-09 08:10:34 -04:00
indexName = indexName
? this . formatter . wrap ( indexName )
: this . _indexCommand ( 'index' , this . tableNameRaw , columns ) ;
2021-10-25 23:37:26 +02:00
let predicate ;
if ( isObject ( options ) ) {
( { predicate } = options ) ;
}
const predicateQuery = predicate
? ' ' + this . client . queryCompiler ( predicate ) . where ( )
: '' ;
2018-07-09 08:10:34 -04:00
this . pushQuery (
` CREATE INDEX ${ indexName } ON ${ this . tableName ( ) } ( ${ this . formatter . columnize (
columns
2021-10-25 23:37:26 +02:00
) } ) $ { predicateQuery } `
2018-07-09 08:10:34 -04:00
) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
2021-10-10 15:47:32 -04:00
/ * *
* Create a primary key .
*
* @ param { undefined | string | string [ ] } columns
* @ param { string | { constraintName : string , deferrable ? : 'not deferrable' | 'deferred' | 'immediate' } } constraintName
* /
2018-07-09 08:10:34 -04:00
primary ( columns , constraintName ) {
2021-10-10 15:47:32 -04:00
let deferrable ;
if ( isObject ( constraintName ) ) {
( { constraintName , deferrable } = constraintName ) ;
}
if ( deferrable && deferrable !== 'not deferrable' ) {
this . client . logger . warn (
` mssql: primary key constraint [ ${ constraintName } ] will not be deferrable ${ deferrable } because mssql does not support deferred constraints. `
) ;
}
2018-07-09 08:10:34 -04:00
constraintName = constraintName
? this . formatter . wrap ( constraintName )
: this . formatter . wrap ( ` ${ this . tableNameRaw } _pkey ` ) ;
2016-03-02 17:07:05 +01:00
if ( ! this . forCreate ) {
2018-07-09 08:10:34 -04:00
this . pushQuery (
` ALTER TABLE ${ this . tableName ( ) } ADD CONSTRAINT ${ constraintName } PRIMARY KEY ( ${ this . formatter . columnize (
columns
) } ) `
) ;
2016-03-02 17:07:05 +01:00
} else {
2018-07-09 08:10:34 -04:00
this . pushQuery (
` CONSTRAINT ${ constraintName } PRIMARY KEY ( ${ this . formatter . columnize (
columns
) } ) `
) ;
2016-03-02 17:07:05 +01:00
}
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
2021-10-10 15:47:32 -04:00
/ * *
* Create a unique index .
*
* @ param { string | string [ ] } columns
2021-12-13 20:41:46 +11:00
* @ param { string | { indexName : undefined | string , deferrable ? : 'not deferrable' | 'deferred' | 'immediate' , useConstraint ? : true | false } } indexName
2021-10-10 15:47:32 -04:00
* /
2018-07-09 08:10:34 -04:00
unique ( columns , indexName ) {
2021-10-10 15:47:32 -04:00
/** @type {string | undefined} */
let deferrable ;
2021-12-13 20:41:46 +11:00
let useConstraint = false ;
2021-10-10 15:47:32 -04:00
if ( isObject ( indexName ) ) {
2021-12-13 20:41:46 +11:00
( { indexName , deferrable , useConstraint } = indexName ) ;
2021-10-10 15:47:32 -04:00
}
if ( deferrable && deferrable !== 'not deferrable' ) {
this . client . logger . warn (
` mssql: unique index [ ${ indexName } ] will not be deferrable ${ deferrable } because mssql does not support deferred constraints. `
) ;
}
2018-07-09 08:10:34 -04:00
indexName = indexName
? this . formatter . wrap ( indexName )
: this . _indexCommand ( 'unique' , this . tableNameRaw , columns ) ;
2018-06-29 10:47:06 +03:00
if ( ! Array . isArray ( columns ) ) {
columns = [ columns ] ;
2016-03-02 17:07:05 +01:00
}
2018-06-29 10:47:06 +03:00
2018-07-09 08:10:34 -04:00
const whereAllTheColumnsAreNotNull = columns
. map ( ( column ) => this . formatter . columnize ( column ) + ' IS NOT NULL' )
. join ( ' AND ' ) ;
2018-06-29 10:47:06 +03:00
2021-12-13 20:41:46 +11:00
if ( useConstraint ) {
// mssql supports unique indexes and unique constraints.
// unique indexes cannot be used with foreign key relationships hence unique constraints are used instead.
this . pushQuery (
` ALTER TABLE ${ this . tableName ( ) } ADD CONSTRAINT ${ indexName } UNIQUE ( ${ this . formatter . columnize (
columns
) } ) `
) ;
} else {
// make unique constraint that allows null https://stackoverflow.com/a/767702/360060
// to be more or less compatible with other DBs (if any of the columns is NULL then "duplicates" are allowed)
this . pushQuery (
` CREATE UNIQUE INDEX ${ indexName } ON ${ this . tableName ( ) } ( ${ this . formatter . columnize (
columns
) } ) WHERE $ { whereAllTheColumnsAreNotNull } `
) ;
}
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
// Compile a drop index command.
2018-07-09 08:10:34 -04:00
dropIndex ( columns , indexName ) {
indexName = indexName
? this . formatter . wrap ( indexName )
: this . _indexCommand ( 'index' , this . tableNameRaw , columns ) ;
2016-05-17 01:01:34 +10:00
this . pushQuery ( ` DROP INDEX ${ indexName } ON ${ this . tableName ( ) } ` ) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
// Compile a drop foreign key command.
2018-07-09 08:10:34 -04:00
dropForeign ( columns , indexName ) {
indexName = indexName
? this . formatter . wrap ( indexName )
: this . _indexCommand ( 'foreign' , this . tableNameRaw , columns ) ;
this . pushQuery (
` ALTER TABLE ${ this . tableName ( ) } DROP CONSTRAINT ${ indexName } `
) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
// Compile a drop primary key command.
2018-07-09 08:10:34 -04:00
dropPrimary ( constraintName ) {
constraintName = constraintName
? this . formatter . wrap ( constraintName )
: this . formatter . wrap ( ` ${ this . tableNameRaw } _pkey ` ) ;
this . pushQuery (
` ALTER TABLE ${ this . tableName ( ) } DROP CONSTRAINT ${ constraintName } `
) ;
2021-01-01 18:46:16 +02:00
}
2016-03-02 17:07:05 +01:00
// Compile a drop unique key command.
2018-07-09 08:10:34 -04:00
dropUnique ( column , indexName ) {
indexName = indexName
? this . formatter . wrap ( indexName )
: this . _indexCommand ( 'unique' , this . tableNameRaw , column ) ;
2018-06-29 10:47:06 +03:00
this . pushQuery ( ` DROP INDEX ${ indexName } ON ${ this . tableName ( ) } ` ) ;
2021-01-01 18:46:16 +02:00
}
}
TableCompiler _MSSQL . prototype . createAlterTableMethods = [ 'foreign' , 'primary' ] ;
TableCompiler _MSSQL . prototype . lowerCase = false ;
TableCompiler _MSSQL . prototype . addColumnsPrefix = 'ADD ' ;
TableCompiler _MSSQL . prototype . dropColumnPrefix = 'DROP COLUMN ' ;
TableCompiler _MSSQL . prototype . alterColumnPrefix = 'ALTER COLUMN ' ;
2016-03-02 17:07:05 +01:00
2019-06-04 00:37:17 +02:00
module . exports = TableCompiler _MSSQL ;