Skip to content

Commit

Permalink
Implement partial index support (#4768)
Browse files Browse the repository at this point in the history
  • Loading branch information
OlivierCavadenti committed Oct 25, 2021
1 parent 821e849 commit ace439d
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 26 deletions.
12 changes: 10 additions & 2 deletions lib/dialects/mssql/schema/mssql-tablecompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,22 @@ class TableCompiler_MSSQL extends TableCompiler {
);
}

index(columns, indexName) {
index(columns, indexName, options) {
indexName = indexName
? this.formatter.wrap(indexName)
: this._indexCommand('index', this.tableNameRaw, columns);

let predicate;
if (isObject(options)) {
({ predicate } = options);
}
const predicateQuery = predicate
? ' ' + this.client.queryCompiler(predicate).where()
: '';
this.pushQuery(
`CREATE INDEX ${indexName} ON ${this.tableName()} (${this.formatter.columnize(
columns
)})`
)})${predicateQuery}`
);
}

Expand Down
12 changes: 8 additions & 4 deletions lib/dialects/mysql/schema/mysql-tablecompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// MySQL Table Builder & Compiler
// -------
const TableCompiler = require('../../../schema/tablecompiler');
const { isObject } = require('../../../util/is');
const { isObject, isString } = require('../../../util/is');

// Table Compiler
// ------
Expand Down Expand Up @@ -211,10 +211,14 @@ class TableCompiler_MySQL extends TableCompiler {
);
}

index(columns, indexName, indexType) {
index(columns, indexName, options) {
let storageEngineIndexType;
if (isObject(indexName)) {
({ indexName, storageEngineIndexType } = indexName);
let indexType;

if (isString(options)) {
indexType = options;
} else if (isObject(options)) {
({ indexType, storageEngineIndexType } = options);
}

indexName = indexName
Expand Down
21 changes: 18 additions & 3 deletions lib/dialects/postgres/schema/pg-tablecompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

const has = require('lodash/has');
const TableCompiler = require('../../../schema/tablecompiler');
const { isObject } = require('../../../util/is');
const { isObject, isString } = require('../../../util/is');

class TableCompiler_PG extends TableCompiler {
constructor(client, tableBuilder) {
Expand Down Expand Up @@ -161,17 +161,32 @@ class TableCompiler_PG extends TableCompiler {
);
}

index(columns, indexName, indexType) {
index(columns, indexName, options) {
indexName = indexName
? this.formatter.wrap(indexName)
: this._indexCommand('index', this.tableNameRaw, columns);

let predicate;
let indexType;

if (isString(options)) {
indexType = options;
} else if (isObject(options)) {
({ indexType, predicate } = options);
}

const predicateQuery = predicate
? ' ' + this.client.queryCompiler(predicate).where()
: '';

this.pushQuery(
`create index ${indexName} on ${this.tableName()}${
(indexType && ` using ${indexType}`) || ''
}` +
' (' +
this.formatter.columnize(columns) +
')'
')' +
`${predicateQuery}`
);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/dialects/redshift/schema/redshift-tablecompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TableCompiler_Redshift extends TableCompiler_PG {
super(...arguments);
}

index(columns, indexName, indexType) {
index(columns, indexName, options) {
this.client.logger.warn(
'Redshift does not support the creation of indexes.'
);
Expand Down
12 changes: 10 additions & 2 deletions lib/dialects/sqlite3/schema/sqlite-tablecompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,21 @@ class TableCompiler_SQLite3 extends TableCompiler {
}

// Compile a plain index key command.
index(columns, indexName) {
index(columns, indexName, options) {
indexName = indexName
? this.formatter.wrap(indexName)
: this._indexCommand('index', this.tableNameRaw, columns);
columns = this.formatter.columnize(columns);

let predicate;
if (isObject(options)) {
({ predicate } = options);
}
const predicateQuery = predicate
? ' ' + this.client.queryCompiler(predicate).where()
: '';
this.pushQuery(
`create index ${indexName} on ${this.tableName()} (${columns})`
`create index ${indexName} on ${this.tableName()} (${columns})${predicateQuery}`
);
}

Expand Down
9 changes: 4 additions & 5 deletions test/integration2/schema/alter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ describe('Schema', () => {
}
await knex.schema
.alterTable('alter_table', (table) => {
table.index(
['column_string', 'column_datetime'],
{ indexName: 'idx_1', storageEngineIndexType: 'BTREE' },
'FULLTEXT'
);
table.index(['column_string', 'column_datetime'], 'idx_1', {
indexType: 'FULLTEXT',
storageEngineIndexType: 'BTREE',
});
table.unique('column_notNullable', {
indexName: 'idx_2',
storageEngineIndexType: 'HASH',
Expand Down
37 changes: 37 additions & 0 deletions test/integration2/schema/misc.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,43 @@ describe('Schema (misc)', () => {
knex.schema.table('test_table_one', (t) => {
t.dropIndex('first_name');
}));

describe('supports partial indexes - postgres, sqlite, and mssql', function () {
it('allows creating indexes with predicate', async function () {
if (!(isPostgreSQL(knex) || isMssql(knex) || isSQLite(knex))) {
return this.skip();
}

await knex.schema.table('test_table_one', function (t) {
t.index('first_name', 'first_name_idx', {
predicate: knex.whereRaw("first_name = 'brandon'"),
});
t.index('phone', 'phone_idx', {
predicate: knex.whereNotNull('phone'),
});
});
});

it('actually stores the predicate in the Postgres server', async function () {
if (!isPostgreSQL(knex)) {
return this.skip();
}
await knex.schema.table('test_table_one', function (t) {
t.index('phone', 'phone_idx_2', {
predicate: knex.whereNotNull('phone'),
});
});
const results = await knex
.from('pg_class')
.innerJoin('pg_index', 'pg_index.indexrelid', 'pg_class.oid')
.where({
relname: 'phone_idx_2',
indisvalid: true,
})
.whereNotNull('indpred');
expect(results).to.not.be.empty;
});
});
});

describe('hasTable', () => {
Expand Down
30 changes: 30 additions & 0 deletions test/unit/schema-builder/mssql.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,36 @@ describe('MSSQL SchemaBuilder', function () {
);
});

it('test adding index with a predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereRaw('email = "foo@bar"'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'CREATE INDEX [baz] ON [users] ([foo], [bar]) where email = "foo@bar"'
);
});

it('test adding index with a where not null predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereNotNull('email'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'CREATE INDEX [baz] ON [users] ([foo], [bar]) where [email] is not null'
);
});

it('test adding foreign key', function () {
tableSql = client
.schemaBuilder()
Expand Down
9 changes: 4 additions & 5 deletions test/unit/schema-builder/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,10 @@ module.exports = function (dialect) {
tableSql = client
.schemaBuilder()
.table('users', function () {
this.index(
['foo', 'bar'],
{ indexName: 'baz', storageEngineIndexType: 'BTREE' },
'UNIQUE'
);
this.index(['foo', 'bar'], 'baz', {
indexType: 'UNIQUE',
storageEngineIndexType: 'BTREE',
});
})
.toSQL();

Expand Down
48 changes: 47 additions & 1 deletion test/unit/schema-builder/postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ describe('PostgreSQL SchemaBuilder', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.string('name').index(null, 'gist');
table.string('name').index(null, { indexType: 'gist' });
})
.toSQL();
equal(2, tableSql.length);
Expand All @@ -803,6 +803,52 @@ describe('PostgreSQL SchemaBuilder', function () {
);
});

it('adding index with a predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereRaw('email = "foo@bar"'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create index "baz" on "users" ("foo", "bar") where email = "foo@bar"'
);
});

it('adding index with an index type and a predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
indexType: 'gist',
predicate: client.queryBuilder().whereRaw('email = "foo@bar"'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create index "baz" on "users" using gist ("foo", "bar") where email = "foo@bar"'
);
});

it('adding index with a where not null predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereNotNull('email'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create index "baz" on "users" ("foo", "bar") where "email" is not null'
);
});

it('adding incrementing id', function () {
tableSql = client
.schemaBuilder()
Expand Down
30 changes: 30 additions & 0 deletions test/unit/schema-builder/sqlite3.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,36 @@ describe('SQLite SchemaBuilder', function () {
equal(tableSql[0].sql, 'create index `baz` on `users` (`foo`, `bar`)');
});

it('adding index with a predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereRaw('email = "foo@bar"'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create index `baz` on `users` (`foo`, `bar`) where email = "foo@bar"'
);
});

it('adding index with a where not null predicate', function () {
tableSql = client
.schemaBuilder()
.table('users', function (table) {
table.index(['foo', 'bar'], 'baz', {
predicate: client.queryBuilder().whereNotNull('email'),
});
})
.toSQL();
equal(1, tableSql.length);
expect(tableSql[0].sql).to.equal(
'create index `baz` on `users` (`foo`, `bar`) where `email` is not null'
);
});

it('adding incrementing id', function () {
tableSql = client
.schemaBuilder()
Expand Down
32 changes: 29 additions & 3 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2045,11 +2045,11 @@ export declare namespace Knex {
index(
columnNames: string | readonly (string | Raw)[],
indexName?: string,
indexType?: string
options?: Readonly<{indexType?: string, storageEngineIndexType?: storageEngineIndexType, predicate?: QueryBuilder}>
): TableBuilder;
setNullable(column: string): TableBuilder;
dropNullable(column: string): TableBuilder;
unique(columnNames: readonly (string | Raw)[], options?: Readonly<{indexName?: string, deferrable?: deferrableType}>): TableBuilder;
unique(columnNames: readonly (string | Raw)[], options?: Readonly<{indexName?: string, storageEngineIndexType?: string, deferrable?: deferrableType}>): TableBuilder;
/** @deprecated */
unique(columnNames: readonly (string | Raw)[], indexName?: string): TableBuilder;
foreign(column: string, foreignKeyName?: string): ForeignConstraintBuilder;
Expand Down Expand Up @@ -2093,6 +2093,8 @@ export declare namespace Knex {
}

type deferrableType = 'not deferrable' | 'immediate' | 'deferred';
type storageEngineIndexType = 'hash' | 'btree';

interface ColumnBuilder {
index(indexName?: string): ColumnBuilder;
primary(options?: Readonly<{constraintName?: string, deferrable?: deferrableType}>): ColumnBuilder;
Expand Down Expand Up @@ -2122,7 +2124,31 @@ export declare namespace Knex {
}

interface PostgreSqlColumnBuilder extends ColumnBuilder {
index(indexName?: string, indexType?: string): ColumnBuilder;
index(
indexName?: string,
options?: Readonly<{indexType?: string, predicate?: QueryBuilder}>
): ColumnBuilder;
}

interface SqlLiteColumnBuilder extends ColumnBuilder {
index(
indexName?: string,
options?: Readonly<{predicate?: QueryBuilder}>
): ColumnBuilder;
}

interface MsSqlColumnBuilder extends ColumnBuilder {
index(
indexName?: string,
options?: Readonly<{predicate?: QueryBuilder}>
): ColumnBuilder;
}

interface MySqlColumnBuilder extends ColumnBuilder {
index(
indexName?: string,
options?: Readonly<{indexType?: string, storageEngineIndexType?: storageEngineIndexType}>
): ColumnBuilder;
}

// patched ColumnBuilder methods to return ReferencingColumnBuilder with new methods
Expand Down

0 comments on commit ace439d

Please sign in to comment.