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

Commit 315b4cf7 by Mick Hansen

feat: JSONB GIN indexing

1 parent 41a9e502
...@@ -429,8 +429,8 @@ module.exports = (function() { ...@@ -429,8 +429,8 @@ module.exports = (function() {
nameIndexes: function (indexes, rawTablename) { nameIndexes: function (indexes, rawTablename) {
return Utils._.map(indexes, function (index) { return Utils._.map(indexes, function (index) {
if (!index.hasOwnProperty('name')) { if (!index.hasOwnProperty('name')) {
var onlyAttributeNames = index.fields.map(function(attribute) { var onlyAttributeNames = index.fields.map(function(field) {
return (typeof attribute === 'string') ? attribute : attribute.attribute; return (typeof field === 'string') ? field : (field.name || field.attribute);
}.bind(this)); }.bind(this));
index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_')); index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_'));
...@@ -444,45 +444,68 @@ module.exports = (function() { ...@@ -444,45 +444,68 @@ module.exports = (function() {
Returns an add index query. Returns an add index query.
Parameters: Parameters:
- tableName -> Name of an existing table, possibly with schema. - 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: - options:
- indicesType: UNIQUE|FULLTEXT|SPATIAL - type: UNIQUE|FULLTEXT|SPATIAL
- indexName: The name of the index. Default is <tableName>_<attrName1>_<attrName2> - 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 - parser
- rawTablename, the name of the table, without schema. Used to create the name of the index - rawTablename, the name of the table, without schema. Used to create the name of the index
*/ */
addIndexQuery: function(tableName, attributes, options, rawTablename) { addIndexQuery: function(tableName, attributes, options, rawTablename) {
var fieldsSql;
options = options || {}; options = options || {};
var transformedAttributes = attributes.map(function(attribute) { if (!Array.isArray(attributes)) {
if (typeof attribute === 'string') { options = attributes;
return this.quoteIdentifier(attribute); attributes = undefined;
} else if (attribute._isSequelizeMethod) { } else {
return this.handleSequelizeMethod(attribute); 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 { } else {
var result = ''; var result = '';
if (!attribute.attribute) { if (field.attribute) {
throw new Error('The following index attribute has no attribute: ' + util.inspect(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) { if (this._dialect.supports.index.collate && field.collate) {
result += ' COLLATE ' + this.quoteIdentifier(attribute.collate); result += ' COLLATE ' + this.quoteIdentifier(field.collate);
} }
if (this._dialect.supports.index.length && attribute.length) { if (this._dialect.supports.index.length && field.length) {
result += '(' + attribute.length + ')'; result += '(' + field.length + ')';
} }
if (attribute.order) { if (field.order) {
result += ' ' + attribute.order; result += ' ' + field.order;
} }
return result; return result;
...@@ -492,37 +515,37 @@ module.exports = (function() { ...@@ -492,37 +515,37 @@ module.exports = (function() {
if (!options.name) { if (!options.name) {
// Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations) // 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 // All calls that go through sequelize should already have a name
options.fields = options.fields || attributes; options = this.nameIndexes([options], options.prefix)[0];
options = this.nameIndexes([options], rawTablename)[0];
} }
options = Utils._.defaults(options, { options = Utils._.defaults(options, {
type: '', type: '',
indicesType: options.type || '',
indexType: options.method || undefined,
indexName: options.name,
parser: null parser: null
}); });
if (options.indicesType.toLowerCase() === 'unique') { if (options.type.toLowerCase() === 'unique') {
options.unique = true; options.unique = true;
delete options.indicesType; delete options.type;
} }
if (!this._dialect.supports.index.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([ return Utils._.compact([
'CREATE', 'CREATE',
options.unique ? 'UNIQUE' : '', options.unique ? 'UNIQUE' : '',
options.indicesType, 'INDEX', options.type, 'INDEX',
this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined, this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined,
this.quoteIdentifiers(options.indexName), this.quoteIdentifiers(options.name),
this._dialect.supports.index.using === 1 && options.indexType ? 'USING ' + options.indexType : '', this._dialect.supports.index.using === 1 && options.using ? 'USING ' + options.using : '',
'ON', this.quoteIdentifiers(tableName), 'ON', this.quoteIdentifiers(tableName),
this._dialect.supports.index.using === 2 && options.indexType ? 'USING ' + options.indexType : '', this._dialect.supports.index.using === 2 && options.using ? 'USING ' + options.using : '',
'(' + transformedAttributes.join(', ') + ')', '(' + fieldsSql.join(', ') + (options.operator ? ' '+options.operator : '') + ')',
(this._dialect.supports.index.parser && options.parser ? 'WITH PARSER ' + options.parser : undefined) (this._dialect.supports.index.parser && options.parser ? 'WITH PARSER ' + options.parser : undefined)
]).join(' '); ]).join(' ');
}, },
......
...@@ -38,7 +38,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp ...@@ -38,7 +38,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp
concurrently: true, concurrently: true,
using: 2, using: 2,
}, },
JSON: true, JSON: true
}); });
PostgresDialect.prototype.Query = Query; PostgresDialect.prototype.Query = Query;
......
...@@ -36,7 +36,8 @@ module.exports = (function() { ...@@ -36,7 +36,8 @@ module.exports = (function() {
schemaDelimiter: '', schemaDelimiter: '',
defaultScope: null, defaultScope: null,
scopes: null, scopes: null,
hooks: {} hooks: {},
indexes: []
}, options || {}); }, options || {});
this.associations = {}; this.associations = {};
...@@ -169,7 +170,6 @@ module.exports = (function() { ...@@ -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].msg = self.options.uniqueKeys[idxName].msg || options.unique.msg || null;
self.options.uniqueKeys[idxName].name = idxName || false; self.options.uniqueKeys[idxName].name = idxName || false;
} }
} }
if (options.primaryKey === true) { if (options.primaryKey === true) {
...@@ -327,6 +327,15 @@ module.exports = (function() { ...@@ -327,6 +327,15 @@ module.exports = (function() {
if (definition.hasOwnProperty('validate')) { if (definition.hasOwnProperty('validate')) {
self.Instance.prototype.validators[name] = definition.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; this._hasBooleanAttributes = !!this._booleanAttributes.length;
...@@ -415,7 +424,7 @@ module.exports = (function() { ...@@ -415,7 +424,7 @@ module.exports = (function() {
}); });
return Promise.map(indexes, function (index) { 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); }).return(this);
}; };
......
...@@ -441,7 +441,8 @@ module.exports = (function() { ...@@ -441,7 +441,8 @@ module.exports = (function() {
} }
options = options || {}; 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}); return this.sequelize.query(sql, null, {logging: options.hasOwnProperty('logging') ? options.logging : this.sequelize.options.logging});
}; };
......
...@@ -868,7 +868,7 @@ if (dialect.match(/^postgres/)) { ...@@ -868,7 +868,7 @@ if (dialect.match(/^postgres/)) {
], ],
expectation: 'CREATE INDEX \"user_username_is_admin\" ON \"User\" (\"username\" ASC, \"isAdmin\")' 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\")' expectation: 'CREATE INDEX \"bar\" ON \"User\" (\"username\", \"isAdmin\")'
}, { }, {
arguments: ['mySchema.User', ['username', 'isAdmin'], {}, 'User'], arguments: ['mySchema.User', ['username', 'isAdmin'], {}, 'User'],
...@@ -905,7 +905,7 @@ if (dialect.match(/^postgres/)) { ...@@ -905,7 +905,7 @@ if (dialect.match(/^postgres/)) {
expectation: 'CREATE INDEX user_username_is_admin ON User (username ASC, isAdmin)', expectation: 'CREATE INDEX user_username_is_admin ON User (username ASC, isAdmin)',
context: {options: {quoteIdentifiers: false}} 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)', expectation: 'CREATE INDEX bar ON User (username, isAdmin)',
context: {options: {quoteIdentifiers: false}} 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() { ...@@ -14,6 +14,14 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), { expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), {
default: 'CREATE INDEX [table_column1_column2] ON [table] ([column1], [column2])' 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 () { test('POJO field', function () {
...@@ -30,5 +38,28 @@ suite(Support.getTestDialectTeaser('SQL'), function() { ...@@ -30,5 +38,28 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
default: 'CREATE INDEX [myindex] ON [table] (UPPER([test]))' 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!