Implement partial index support (#4768)

This commit is contained in:
Olivier Cavadenti 2021-10-25 23:37:26 +02:00 committed by GitHub
parent 821e8494fd
commit ace439d5c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 228 additions and 26 deletions

View File

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

View File

@ -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
// ------
@ -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

View File

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

View File

@ -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.'
);

View File

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

View File

@ -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',

View File

@ -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', () => {

View File

@ -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()

View File

@ -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();

View File

@ -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);
@ -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()

View File

@ -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()

32
types/index.d.ts vendored
View File

@ -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;
@ -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;
@ -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