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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for MSSQL unique constraint #4887

Merged
merged 5 commits into from Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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', constraint?: true|false }} indexName
*/
unique(columns, indexName) {
/** @type {string | undefined} */
let deferrable;
let constraint = false;
if (isObject(indexName)) {
({ indexName, deferrable } = indexName);
({ indexName, deferrable, constraint } = 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 (constraint) {
// 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,
constraint: 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', constraint: 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, constraint?: boolean}>): TableBuilder;
/** @deprecated */
unique(columnNames: readonly (string | Raw)[], indexName?: string): TableBuilder;
foreign(column: string, foreignKeyName?: string): ForeignConstraintBuilder;
Expand Down