Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checks Constraints Support #4874

Merged
merged 10 commits into from Jan 6, 2022
19 changes: 19 additions & 0 deletions lib/dialects/cockroachdb/crdb-tablecompiler.js
Expand Up @@ -7,6 +7,25 @@ class TableCompiler_CRDB extends TableCompiler {
super(client, tableBuilder);
}

addColumns(columns, prefix, colCompilers) {
if (prefix === this.alterColumnsPrefix) {
// alter columns
for (const col of colCompilers) {
this.client.logger.warn(
'Experimental alter column in use, see issue: https://github.com/cockroachdb/cockroach/issues/49329'
);
this.pushQuery({
sql: 'SET enable_experimental_alter_column_type_general = true',
bindings: [],
});
super._addColumn(col);
}
} else {
// base class implementation for normal add
super.addColumns(columns, prefix);
}
}

dropUnique(columns, indexName) {
indexName = indexName
? this.formatter.wrap(indexName)
Expand Down
22 changes: 22 additions & 0 deletions lib/dialects/mssql/schema/mssql-columncompiler.js
Expand Up @@ -3,11 +3,13 @@
const ColumnCompiler = require('../../../schema/columncompiler');
const { toNumber } = require('../../../util/helpers');
const { formatDefault } = require('../../../formatter/formatterUtils');
const { operator: operator_ } = require('../../../formatter/wrappingFormatter');

class ColumnCompiler_MSSQL extends ColumnCompiler {
constructor(client, tableCompiler, columnBuilder) {
super(client, tableCompiler, columnBuilder);
this.modifiers = ['nullable', 'defaultTo', 'first', 'after', 'comment'];
this._addCheckModifiers();
}

// Types
Expand Down Expand Up @@ -127,6 +129,26 @@ class ColumnCompiler_MSSQL extends ColumnCompiler {
return '';
}

checkLength(operator, length, constraintName) {
return this._check(
`LEN(${this.formatter.wrap(this.getColumnName())}) ${operator_(
operator,
this.columnBuilder,
this.bindingsHolder
)} ${toNumber(length)}`,
constraintName
);
}

checkRegex(regex, constraintName) {
return this._check(
`${this.formatter.wrap(
this.getColumnName()
)} LIKE ${this.client._escapeBinding('%' + regex + '%')}`,
constraintName
);
}

increments(options = { primaryKey: true }) {
return (
'int identity(1,1) not null' +
Expand Down
1 change: 1 addition & 0 deletions lib/dialects/mssql/schema/mssql-tablecompiler.js
Expand Up @@ -28,6 +28,7 @@ class TableCompiler_MSSQL extends TableCompiler {
this.tableName() +
(this._formatting ? ' (\n ' : ' (') +
columns.sql.join(this._formatting ? ',\n ' : ', ') +
this._addChecks() +
')';
}

Expand Down
10 changes: 10 additions & 0 deletions lib/dialects/mysql/schema/mysql-columncompiler.js
Expand Up @@ -18,6 +18,7 @@ class ColumnCompiler_MySQL extends ColumnCompiler {
'first',
'after',
];
this._addCheckModifiers();
}

// Types
Expand Down Expand Up @@ -153,6 +154,15 @@ class ColumnCompiler_MySQL extends ColumnCompiler {
return collation && `collate '${collation}'`;
}

checkRegex(regex, constraintName) {
return this._check(
`${this.formatter.wrap(
this.getColumnName()
)} REGEXP ${this.client._escapeBinding(regex)}`,
constraintName
);
}

increments(options = { primaryKey: true }) {
return (
'int unsigned not null' +
Expand Down
2 changes: 1 addition & 1 deletion lib/dialects/mysql/schema/mysql-tablecompiler.js
Expand Up @@ -19,7 +19,7 @@ class TableCompiler_MySQL extends TableCompiler {
: 'create table ';
const { client } = this;
let conn = {};
const columnsSql = ' (' + columns.sql.join(', ') + ')';
const columnsSql = ' (' + columns.sql.join(', ') + this._addChecks() + ')';
let sql =
createStatement +
this.tableName() +
Expand Down
2 changes: 1 addition & 1 deletion lib/dialects/oracle/schema/oracle-tablecompiler.js
Expand Up @@ -57,7 +57,7 @@ class TableCompiler_Oracle extends TableCompiler {
const columnsSql =
like && this.tableNameLike()
? ' as (select * from ' + this.tableNameLike() + ' where 0=1)'
: ' (' + columns.sql.join(', ') + ')';
: ' (' + columns.sql.join(', ') + this._addChecks() + ')';
const sql = `create table ${this.tableName()}${columnsSql}`;

this.pushQuery({
Expand Down
11 changes: 11 additions & 0 deletions lib/dialects/oracledb/schema/oracledb-columncompiler.js
Expand Up @@ -4,6 +4,8 @@ const { isObject } = require('../../../util/is');
class ColumnCompiler_Oracledb extends ColumnCompiler_Oracle {
constructor() {
super(...arguments);
this.modifiers = ['defaultTo', 'nullable', 'comment'];
this._addCheckModifiers();
}

datetime(withoutTz) {
Expand All @@ -26,6 +28,15 @@ class ColumnCompiler_Oracledb extends ColumnCompiler_Oracle {
return useTz ? 'timestamp with local time zone' : 'timestamp';
}

checkRegex(regex, constraintName) {
return this._check(
`REGEXP_LIKE(${this.formatter.wrap(
this.getColumnName()
)},${this.client._escapeBinding(regex)})`,
constraintName
);
}

json() {
return `varchar2(4000) check (${this.formatter.columnize(
this.getColumnName()
Expand Down
10 changes: 10 additions & 0 deletions lib/dialects/postgres/schema/pg-columncompiler.js
Expand Up @@ -10,6 +10,7 @@ class ColumnCompiler_PG extends ColumnCompiler {
constructor(client, tableCompiler, columnBuilder) {
super(client, tableCompiler, columnBuilder);
this.modifiers = ['nullable', 'defaultTo', 'comment'];
this._addCheckModifiers();
}

// Types
Expand Down Expand Up @@ -64,6 +65,15 @@ class ColumnCompiler_PG extends ColumnCompiler {
return jsonColumn(this.client, true);
}

checkRegex(regex, constraintName) {
return this._check(
`${this.formatter.wrap(
this.getColumnName()
)} ~ ${this.client._escapeBinding(regex)}`,
constraintName
);
}

datetime(withoutTz = false, precision) {
let useTz;
if (isObject(withoutTz)) {
Expand Down
96 changes: 50 additions & 46 deletions lib/dialects/postgres/schema/pg-tablecompiler.js
Expand Up @@ -44,7 +44,7 @@ class TableCompiler_PG extends TableCompiler {
const createStatement = ifNot
? 'create table if not exists '
: 'create table ';
const columnsSql = ' (' + columns.sql.join(', ') + ')';
const columnsSql = ' (' + columns.sql.join(', ') + this._addChecks() + ')';
let sql =
createStatement +
this.tableName() +
Expand All @@ -69,58 +69,62 @@ class TableCompiler_PG extends TableCompiler {
if (prefix === this.alterColumnsPrefix) {
// alter columns
for (const col of colCompilers) {
const quotedTableName = this.tableName();
const type = col.getColumnType();
// We'd prefer to call this.formatter.wrapAsIdentifier here instead, however the context passed to
// `this` instance is not that of the column, but of the table. Thus, we unfortunately have to call
// `wrapIdentifier` here as well (it is already called once on the initial column operation) to give
// our `alter` operation the correct `queryContext`. Refer to issue #2606 and PR #2612.
const colName = this.client.wrapIdentifier(
col.getColumnName(),
col.columnBuilder.queryContext()
);

// To alter enum columns they must be cast to text first
const isEnum = col.type === 'enu';

this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} drop default`,
bindings: [],
});
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} drop not null`,
bindings: [],
});
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} type ${type} using (${colName}${
isEnum ? '::text::' : '::'
}${type})`,
bindings: [],
});

const defaultTo = col.modified['defaultTo'];
if (defaultTo) {
const modifier = col.defaultTo.apply(col, defaultTo);
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} set ${modifier}`,
bindings: [],
});
}

const nullable = col.modified['nullable'];
if (nullable && nullable[0] === false) {
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} set not null`,
bindings: [],
});
}
this._addColumn(col);
}
} else {
// base class implementation for normal add
super.addColumns(columns, prefix);
}
}

_addColumn(col) {
const quotedTableName = this.tableName();
const type = col.getColumnType();
// We'd prefer to call this.formatter.wrapAsIdentifier here instead, however the context passed to
// `this` instance is not that of the column, but of the table. Thus, we unfortunately have to call
// `wrapIdentifier` here as well (it is already called once on the initial column operation) to give
// our `alter` operation the correct `queryContext`. Refer to issue #2606 and PR #2612.
const colName = this.client.wrapIdentifier(
col.getColumnName(),
col.columnBuilder.queryContext()
);

// To alter enum columns they must be cast to text first
const isEnum = col.type === 'enu';

this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} drop default`,
bindings: [],
});
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} drop not null`,
bindings: [],
});
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} type ${type} using (${colName}${
isEnum ? '::text::' : '::'
}${type})`,
bindings: [],
});

const defaultTo = col.modified['defaultTo'];
if (defaultTo) {
const modifier = col.defaultTo.apply(col, defaultTo);
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} set ${modifier}`,
bindings: [],
});
}

const nullable = col.modified['nullable'];
if (nullable && nullable[0] === false) {
this.pushQuery({
sql: `alter table ${quotedTableName} alter column ${colName} set not null`,
bindings: [],
});
}
}

// Compiles the comment on the table.
comment(comment) {
this.pushQuery(
Expand Down
2 changes: 1 addition & 1 deletion lib/dialects/redshift/schema/redshift-tablecompiler.js
Expand Up @@ -30,7 +30,7 @@ class TableCompiler_Redshift extends TableCompiler_PG {
const createStatement = ifNot
? 'create table if not exists '
: 'create table ';
const columnsSql = ' (' + columns.sql.join(', ') + ')';
const columnsSql = ' (' + columns.sql.join(', ') + this._addChecks() + ')';
let sql =
createStatement +
this.tableName() +
Expand Down
16 changes: 16 additions & 0 deletions lib/dialects/sqlite3/schema/sqlite-columncompiler.js
Expand Up @@ -7,6 +7,7 @@ class ColumnCompiler_SQLite3 extends ColumnCompiler {
constructor() {
super(...arguments);
this.modifiers = ['nullable', 'defaultTo'];
this._addCheckModifiers();
}

// Types
Expand All @@ -17,6 +18,21 @@ class ColumnCompiler_SQLite3 extends ColumnCompiler {
this.args[0]
)} in ('${allowed.join("', '")}'))`;
}

_pushAlterCheckQuery(checkPredicate, constraintName) {
throw new Error(
`Alter table with to add constraints is not permitted in SQLite`
);
}

checkRegex(regexes, constraintName) {
return this._check(
`${this.formatter.wrap(
this.getColumnName()
)} REGEXP ${this.client._escapeBinding(regexes)}`,
constraintName
);
}
}

ColumnCompiler_SQLite3.prototype.json = 'json';
Expand Down
1 change: 1 addition & 0 deletions lib/dialects/sqlite3/schema/sqlite-tablecompiler.js
Expand Up @@ -27,6 +27,7 @@ class TableCompiler_SQLite3 extends TableCompiler {
sql += ' (' + columns.sql.join(', ');
sql += this.foreignKeys() || '';
sql += this.primaryKeys() || '';
sql += this._addChecks();
sql += ')';
}
this.pushQuery(sql);
Expand Down
8 changes: 8 additions & 0 deletions lib/schema/columnbuilder.js
Expand Up @@ -42,6 +42,14 @@ const modifiers = [
'after',
'comment',
'collate',
'check',
'checkPositive',
'checkNegative',
'checkIn',
'checkNotIn',
'checkBetween',
'checkLength',
'checkRegex',
];

// Aliases for convenience.
Expand Down