Skip to content

Commit

Permalink
Checks Constraints Support (#4874)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Zhuravlev <intech@users.noreply.github.com>
  • Loading branch information
OlivierCavadenti and intech committed Jan 6, 2022
1 parent b6775d9 commit 4494113
Show file tree
Hide file tree
Showing 25 changed files with 1,559 additions and 50 deletions.
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

0 comments on commit 4494113

Please sign in to comment.