Skip to content

Commit

Permalink
Added support for MSSQL unique constraint (#4887)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlevit committed Dec 13, 2021
1 parent 2763e47 commit b612cdc
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 12 deletions.
29 changes: 20 additions & 9 deletions lib/dialects/mssql/schema/mssql-tablecompiler.js
Expand Up @@ -279,13 +279,14 @@ class TableCompiler_MSSQL extends TableCompiler {
* Create a unique index.
*
* @param {string | string[]} columns
* @param {string | {indexName: undefined | string, deferrable?: 'not deferrable'|'deferred'|'immediate' }} indexName
* @param {string | {indexName: undefined | string, deferrable?: 'not deferrable'|'deferred'|'immediate', useConstraint?: true|false }} indexName
*/
unique(columns, indexName) {
/** @type {string | undefined} */
let deferrable;
let useConstraint = false;
if (isObject(indexName)) {
({ indexName, deferrable } = indexName);
({ indexName, deferrable, useConstraint } = indexName);
}
if (deferrable && deferrable !== 'not deferrable') {
this.client.logger.warn(
Expand All @@ -304,13 +305,23 @@ class TableCompiler_MSSQL extends TableCompiler {
.map((column) => this.formatter.columnize(column) + ' IS NOT NULL')
.join(' AND ');

// 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}`
);
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}`
);
}
}

// Compile a drop index command.
Expand Down
36 changes: 34 additions & 2 deletions test/integration2/dialects/mssql.spec.js
Expand Up @@ -170,7 +170,7 @@ describe('MSSQL dialect', () => {
});
});

describe('unique table constraint with options object', () => {
describe('unique table index with options object', () => {
const tableName = 'test_unique_index_options';
before(async () => {
await knex.schema.createTable(tableName, function () {
Expand All @@ -188,7 +188,39 @@ describe('MSSQL dialect', () => {
await knex.schema.alterTable(tableName, function () {
this.unique(['x', 'y'], { indexName });
});
expect(
await expect(
knex
.insert([
{ x: 1, y: 1 },
{ x: 1, y: 1 },
])
.into(tableName)
).to.eventually.be.rejectedWith(new RegExp(indexName));
});
});

describe('unique table constraint with options object', () => {
const tableName = 'test_unique_constraint_options';
before(async () => {
await knex.schema.createTable(tableName, function () {
this.integer('x').notNull();
this.integer('y').notNull();
});
});

after(async () => {
await knex.schema.dropTable(tableName);
});

it('accepts indexName and constraint in options object', async () => {
const indexName = `UK_${tableName}_x_y`;
await knex.schema.alterTable(tableName, function () {
this.unique(['x', 'y'], {
indexName: indexName,
useConstraint: true,
});
});
await expect(
knex
.insert([
{ x: 1, y: 1 },
Expand Down
14 changes: 14 additions & 0 deletions test/unit/schema-builder/mssql.js
Expand Up @@ -570,6 +570,20 @@ describe('MSSQL SchemaBuilder', function () {
);
});

it('test adding unique constraint', function () {
tableSql = client
.schemaBuilder()
.table('users', function () {
this.unique('foo', { indexName: 'bar', useConstraint: true });
})
.toSQL();

equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'ALTER TABLE [users] ADD CONSTRAINT [bar] UNIQUE ([foo])'
);
});

it('test adding index', function () {
tableSql = client
.schemaBuilder()
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Expand Up @@ -2075,7 +2075,7 @@ export declare namespace Knex {
): TableBuilder;
setNullable(column: string): TableBuilder;
dropNullable(column: string): TableBuilder;
unique(columnNames: readonly (string | Raw)[], options?: Readonly<{indexName?: string, storageEngineIndexType?: string, deferrable?: deferrableType}>): TableBuilder;
unique(columnNames: readonly (string | Raw)[], options?: Readonly<{indexName?: string, storageEngineIndexType?: string, deferrable?: deferrableType, useConstraint?: boolean}>): TableBuilder;
/** @deprecated */
unique(columnNames: readonly (string | Raw)[], indexName?: string): TableBuilder;
foreign(column: string, foreignKeyName?: string): ForeignConstraintBuilder;
Expand Down

0 comments on commit b612cdc

Please sign in to comment.