不要怂,就是干,撸起袖子干!

Commit 315b4cf7 by Mick Hansen

feat: JSONB GIN indexing

1 parent 41a9e502
......@@ -429,8 +429,8 @@ module.exports = (function() {
nameIndexes: function (indexes, rawTablename) {
return Utils._.map(indexes, function (index) {
if (!index.hasOwnProperty('name')) {
var onlyAttributeNames = index.fields.map(function(attribute) {
return (typeof attribute === 'string') ? attribute : attribute.attribute;
var onlyAttributeNames = index.fields.map(function(field) {
return (typeof field === 'string') ? field : (field.name || field.attribute);
}.bind(this));
index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_'));
......@@ -444,45 +444,68 @@ module.exports = (function() {
Returns an add index query.
Parameters:
- tableName -> Name of an existing table, possibly with schema.
- attributes:
An array of attributes as string or as hash.
If the attribute is a hash, it must have the following content:
- attribute: The name of the attribute/column
- length: An integer. Optional
- order: 'ASC' or 'DESC'. Optional
- options:
- indicesType: UNIQUE|FULLTEXT|SPATIAL
- indexName: The name of the index. Default is <tableName>_<attrName1>_<attrName2>
- type: UNIQUE|FULLTEXT|SPATIAL
- name: The name of the index. Default is <table>_<attr1>_<attr2>
- fields: An array of attributes as string or as hash.
If the attribute is a hash, it must have the following content:
- name: The name of the attribute/column
- length: An integer. Optional
- order: 'ASC' or 'DESC'. Optional
- parser
- rawTablename, the name of the table, without schema. Used to create the name of the index
*/
addIndexQuery: function(tableName, attributes, options, rawTablename) {
var fieldsSql;
options = options || {};
var transformedAttributes = attributes.map(function(attribute) {
if (typeof attribute === 'string') {
return this.quoteIdentifier(attribute);
} else if (attribute._isSequelizeMethod) {
return this.handleSequelizeMethod(attribute);
if (!Array.isArray(attributes)) {
options = attributes;
attributes = undefined;
} else {
options.fields = attributes;
}
// Backwards compatability
if (options.indexName) {
options.name = options.indexName;
}
options.prefix = options.prefix || rawTablename || tableName;
if (options.prefix) {
options.prefix = options.prefix.replace(/\./g, '_');
options.prefix = options.prefix.replace(/\"\'/g, '');
}
fieldsSql = options.fields.map(function(field) {
if (typeof field === 'string') {
return this.quoteIdentifier(field);
} else if (field._isSequelizeMethod) {
return this.handleSequelizeMethod(field);
} else {
var result = '';
if (!attribute.attribute) {
throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute));
if (field.attribute) {
field.name = field.attribute;
}
if (!field.name) {
throw new Error('The following index field has no name: ' + util.inspect(field));
}
result += this.quoteIdentifier(attribute.attribute);
result += this.quoteIdentifier(field.name);
if (this._dialect.supports.index.collate && attribute.collate) {
result += ' COLLATE ' + this.quoteIdentifier(attribute.collate);
if (this._dialect.supports.index.collate && field.collate) {
result += ' COLLATE ' + this.quoteIdentifier(field.collate);
}
if (this._dialect.supports.index.length && attribute.length) {
result += '(' + attribute.length + ')';
if (this._dialect.supports.index.length && field.length) {
result += '(' + field.length + ')';
}
if (attribute.order) {
result += ' ' + attribute.order;
if (field.order) {
result += ' ' + field.order;
}
return result;
......@@ -492,37 +515,37 @@ module.exports = (function() {
if (!options.name) {
// Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations)
// All calls that go through sequelize should already have a name
options.fields = options.fields || attributes;
options = this.nameIndexes([options], rawTablename)[0];
options = this.nameIndexes([options], options.prefix)[0];
}
options = Utils._.defaults(options, {
type: '',
indicesType: options.type || '',
indexType: options.method || undefined,
indexName: options.name,
parser: null
});
if (options.indicesType.toLowerCase() === 'unique') {
if (options.type.toLowerCase() === 'unique') {
options.unique = true;
delete options.indicesType;
delete options.type;
}
if (!this._dialect.supports.index.type) {
delete options.indicesType;
delete options.type;
}
if (options.indexType || options.method) {
options.using = options.indexType || options.method;
}
return Utils._.compact([
'CREATE',
options.unique ? 'UNIQUE' : '',
options.indicesType, 'INDEX',
options.type, 'INDEX',
this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined,
this.quoteIdentifiers(options.indexName),
this._dialect.supports.index.using === 1 && options.indexType ? 'USING ' + options.indexType : '',
this.quoteIdentifiers(options.name),
this._dialect.supports.index.using === 1 && options.using ? 'USING ' + options.using : '',
'ON', this.quoteIdentifiers(tableName),
this._dialect.supports.index.using === 2 && options.indexType ? 'USING ' + options.indexType : '',
'(' + transformedAttributes.join(', ') + ')',
this._dialect.supports.index.using === 2 && options.using ? 'USING ' + options.using : '',
'(' + fieldsSql.join(', ') + (options.operator ? ' '+options.operator : '') + ')',
(this._dialect.supports.index.parser && options.parser ? 'WITH PARSER ' + options.parser : undefined)
]).join(' ');
},
......
......@@ -38,7 +38,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp
concurrently: true,
using: 2,
},
JSON: true,
JSON: true
});
PostgresDialect.prototype.Query = Query;
......
......@@ -36,7 +36,8 @@ module.exports = (function() {
schemaDelimiter: '',
defaultScope: null,
scopes: null,
hooks: {}
hooks: {},
indexes: []
}, options || {});
this.associations = {};
......@@ -169,7 +170,6 @@ module.exports = (function() {
self.options.uniqueKeys[idxName].msg = self.options.uniqueKeys[idxName].msg || options.unique.msg || null;
self.options.uniqueKeys[idxName].name = idxName || false;
}
}
if (options.primaryKey === true) {
......@@ -327,6 +327,15 @@ module.exports = (function() {
if (definition.hasOwnProperty('validate')) {
self.Instance.prototype.validators[name] = definition.validate;
}
if (definition.index === true && definition.type instanceof DataTypes.JSONB) {
self.options.indexes.push({
fields: [definition.field || name],
using: 'gin'
});
delete definition.index;
}
});
this._hasBooleanAttributes = !!this._booleanAttributes.length;
......@@ -415,7 +424,7 @@ module.exports = (function() {
});
return Promise.map(indexes, function (index) {
return self.QueryInterface.addIndex(self.getTableName(options), index.fields, index, self.tableName);
return self.QueryInterface.addIndex(self.getTableName(options), index, self.tableName);
});
}).return(this);
};
......
......@@ -441,7 +441,8 @@ module.exports = (function() {
}
options = options || {};
var sql = this.QueryGenerator.addIndexQuery(tableName, attributes, options, rawTablename);
options.fields = attributes;
var sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename);
return this.sequelize.query(sql, null, {logging: options.hasOwnProperty('logging') ? options.logging : this.sequelize.options.logging});
};
......
......@@ -868,7 +868,7 @@ if (dialect.match(/^postgres/)) {
],
expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\" ASC, \"isAdmin\")'
}, {
arguments: ['User', ['username', 'isAdmin'], { indexName: 'bar'}, {}, 'User'],
arguments: ['User', ['username', 'isAdmin'], { indexName: 'bar'}, 'User'],
expectation: 'CREATE INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")'
}, {
arguments: ['mySchema.User', ['username', 'isAdmin'], {}, 'User'],
......@@ -905,7 +905,7 @@ if (dialect.match(/^postgres/)) {
expectation: 'CREATE INDEX user_username_is_admin ON User (username ASC, isAdmin)',
context: {options: {quoteIdentifiers: false}}
}, {
arguments: ['User', ['username', 'isAdmin'], { indexName: 'bar'}, {}, 'User'],
arguments: ['User', ['username', 'isAdmin'], { indexName: 'bar'}, 'User'],
expectation: 'CREATE INDEX bar ON User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}}
}, {
......
'use strict';
/* jshint -W030 */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, current = Support.sequelize
, DataTypes = require(__dirname + '/../../../lib/data-types');
describe(Support.getTestDialectTeaser('Model'), function() {
describe('indexes', function () {
it('should automatically set a gin index for JSONB indexes', function () {
var Model = current.define('event', {
eventData: {
type: DataTypes.JSONB,
index: true,
field: 'data'
}
});
expect(Model.rawAttributes.eventData.index).not.to.equal(true);
expect(Model.options.indexes.length).to.equal(1);
expect(Model.options.indexes[0].fields).to.eql(['data']);
expect(Model.options.indexes[0].using).to.equal('gin');
});
});
});
\ No newline at end of file
......@@ -14,6 +14,14 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), {
default: 'CREATE INDEX [table_column1_column2] ON [table] ([column1], [column2])'
});
expectsql(sql.addIndexQuery('schema.table', ['column1', 'column2'], {}), {
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])'
});
expectsql(sql.addIndexQuery('"schema"."table"', ['column1', 'column2'], {}), {
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])'
});
});
test('POJO field', function () {
......@@ -30,5 +38,28 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
default: 'CREATE INDEX [myindex] ON [table] (UPPER([test]))'
});
});
if (current.dialect.supports.index.using === 2) {
test('USING', function () {
expectsql(sql.addIndexQuery('table', {
fields: ['event'],
using: 'gin'
}), {
postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event")'
});
});
}
if (current.dialect.supports.JSON) {
test('operator', function () {
expectsql(sql.addIndexQuery('table', {
fields: ['event'],
using: 'gin',
operator: 'jsonb_path_ops'
}), {
postgres: 'CREATE INDEX "table_event" ON "table" USING gin ("event" jsonb_path_ops)'
});
});
}
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!