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

Commit e40f6f45 by Mickael Burguet Committed by Sushant

feat: MySQL JSON support (#7106)

1 parent 9f8ac7f9
......@@ -141,7 +141,7 @@ Sequelize.BOOLEAN // TINYINT(1)
Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only.
Sequelize.JSON // JSON column. PostgreSQL only.
Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB // JSONB column. PostgreSQL only.
Sequelize.BLOB // BLOB (bytea for PostgreSQL)
......
......@@ -453,6 +453,12 @@ HSTORE.prototype.validate = function validate(value) {
return true;
};
/**
* A JSON string column. Available in postgres, sqlite and MySQL.
*
* @function JSON
* @memberof DataTypes
*/
function JSONTYPE() {
if (!(this instanceof JSONTYPE)) return new JSONTYPE();
}
......
......@@ -910,7 +910,7 @@ const QueryGenerator = {
// Users shouldn't have to worry about these args - just give them a function that takes a single arg
const simpleEscape = _.partialRight(SqlString.escape, this.options.timezone, this.dialect);
value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone });
value = field.type.stringify(value, { escape: simpleEscape, field, timezone: this.options.timezone, operation: options.operation });
if (field.type.escape === false) {
// The data-type already did the required escaping
......@@ -2211,7 +2211,7 @@ const QueryGenerator = {
if (_.isPlainObject(item)) {
Utils.getOperators(item).forEach(op => {
const value = item[op];
const value = this._toJSONValue(item[op]);
items.push(this.whereItemQuery(this._castKey(pathKey, value, cast), {[op]: value}));
});
_.forOwn(item, (value, itemProp) => {
......@@ -2221,13 +2221,18 @@ const QueryGenerator = {
return;
}
item = this._toJSONValue(item);
items.push(this.whereItemQuery(this._castKey(pathKey, item, cast), {[Op.eq]: item}));
},
_castKey(key, value, cast) {
_toJSONValue(value) {
return value;
},
_castKey(key, value, cast, json) {
cast = cast || this._getJsonCast(Array.isArray(value) ? value[0] : value);
if (cast) {
return new Utils.Literal(this.handleSequelizeMethod(new Utils.Cast(new Utils.Literal(key), cast)));
return new Utils.Literal(this.handleSequelizeMethod(new Utils.Cast(new Utils.Literal(key), cast, json)));
}
return new Utils.Literal(key);
......
......@@ -34,6 +34,7 @@ module.exports = BaseTypes => {
BaseTypes.REAL.types.mysql = ['DOUBLE'];
BaseTypes.DOUBLE.types.mysql = ['DOUBLE'];
BaseTypes.GEOMETRY.types.mysql = ['GEOMETRY'];
BaseTypes.JSON.types.mysql = ['JSON'];
function BLOB(length) {
if (!(this instanceof BLOB)) return new BLOB(length);
......@@ -177,6 +178,16 @@ module.exports = BaseTypes => {
return 'ENUM(' + _.map(this.values, value => options.escape(value)).join(', ') + ')';
};
function JSONTYPE() {
if (!(this instanceof JSONTYPE)) return new JSONTYPE();
BaseTypes.JSON.apply(this, arguments);
}
inherits(JSONTYPE, BaseTypes.JSON);
JSONTYPE.prototype._stringify = function _stringify(value, options) {
return options.operation === 'where' && typeof value === 'string' ? value : JSON.stringify(value);
};
const exports = {
ENUM,
DATE,
......@@ -184,7 +195,8 @@ module.exports = BaseTypes => {
UUID,
GEOMETRY,
DECIMAL,
BLOB
BLOB,
JSON: JSONTYPE
};
_.forIn(exports, (DataType, key) => {
......
......@@ -42,6 +42,7 @@ MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.
indexViaAlter: true,
NUMERIC: true,
GEOMETRY: true,
JSON: true,
REGEXP: true
});
......
......@@ -3,6 +3,7 @@
const _ = require('lodash');
const Utils = require('../../utils');
const AbstractQueryGenerator = require('../abstract/query-generator');
const util = require('util');
const Op = require('../../operators');
const QueryGenerator = {
......@@ -158,6 +159,79 @@ const QueryGenerator = {
return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`;
},
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
// Parse nested object
if (smth.conditions) {
const conditions = _.map(this.parseConditionObject(smth.conditions), condition =>
`${this.quoteIdentifier(_.first(condition.path))}->>'\$.${_.tail(condition.path).join('.')}' = '${condition.value}'`
);
return conditions.join(' and ');
} else if (smth.path) {
let str;
// Allow specifying conditions using the sqlite json functions
if (this._checkValidJsonStatement(smth.path)) {
str = smth.path;
} else {
// Also support json dot notation
let path = smth.path;
let startWithDot = true;
// Convert .number. to [number].
path = path.replace(/\.(\d+)\./g, '[$1].');
// Convert .number$ to [number]
path = path.replace(/\.(\d+)$/, '[$1]');
path = path.split('.');
let columnName = path.shift();
const match = columnName.match(/\[\d+\]$/);
// If columnName ends with [\d+]
if (match !== null) {
path.unshift(columnName.substr(match.index));
columnName = columnName.substr(0, match.index);
startWithDot = false;
}
str = `${this.quoteIdentifier(columnName)}->>'\$${startWithDot ? '.' : ''}${path.join('.')}'`;
}
if (smth.value) {
str += util.format(' = %s', this.escape(smth.value));
}
return str;
}
} else if (smth instanceof Utils.Cast) {
if (/timestamp/i.test(smth.type)) {
smth.type = 'datetime';
} else if (smth.json && /boolean/i.test(smth.type)) {
// true or false cannot be casted as booleans within a JSON structure
smth.type = 'char';
} else if (/double precision/i.test(smth.type) || /boolean/i.test(smth.type) || /integer/i.test(smth.type)) {
smth.type = 'decimal';
} else if (/text/i.test(smth.type)) {
smth.type = 'char';
}
}
return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
},
_toJSONValue(value) {
// true/false are stored as strings in mysql
if (typeof value === 'boolean') {
return value.toString();
}
// null is stored as a string in mysql
if (value === null) {
return 'null';
}
return value;
},
upsertQuery(tableName, insertValues, updateValues, where, model, options) {
options.onDuplicate = 'UPDATE ';
......@@ -169,7 +243,7 @@ const QueryGenerator = {
return this.insertQuery(tableName, insertValues, model.rawAttributes, options);
},
deleteQuery(tableName, where, options) {
deleteQuery(tableName, where, options, model) {
options = options || {};
const table = this.quoteTable(tableName);
......@@ -178,7 +252,7 @@ const QueryGenerator = {
return 'TRUNCATE ' + table;
}
where = this.getWhereConditions(where);
where = this.getWhereConditions(where, null, model, options);
let limit = '';
if (_.isUndefined(options.limit)) {
......@@ -312,6 +386,89 @@ const QueryGenerator = {
},
/**
* Check whether the statmement is json function or simple path
*
* @param {String} stmt The statement to validate
* @returns {Boolean} true if the given statement is json function
* @throws {Error} throw if the statement looks like json function but has invalid token
* @private
*/
_checkValidJsonStatement(stmt) {
if (!_.isString(stmt)) {
return false;
}
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
let currentIndex = 0;
let openingBrackets = 0;
let closingBrackets = 0;
let hasJsonFunction = false;
let hasInvalidToken = false;
while (currentIndex < stmt.length) {
const string = stmt.substr(currentIndex);
const functionMatches = jsonFunctionRegex.exec(string);
if (functionMatches) {
currentIndex += functionMatches[0].indexOf('(');
hasJsonFunction = true;
continue;
}
const operatorMatches = jsonOperatorRegex.exec(string);
if (operatorMatches) {
currentIndex += operatorMatches[0].length;
hasJsonFunction = true;
continue;
}
const tokenMatches = tokenCaptureRegex.exec(string);
if (tokenMatches) {
const capturedToken = tokenMatches[1];
if (capturedToken === '(') {
openingBrackets++;
} else if (capturedToken === ')') {
closingBrackets++;
} else if (capturedToken === ';') {
hasInvalidToken = true;
break;
}
currentIndex += tokenMatches[0].length;
continue;
}
break;
}
// Check invalid json statement
hasInvalidToken |= openingBrackets !== closingBrackets;
if (hasJsonFunction && hasInvalidToken) {
throw new Error('Invalid json statement: ' + stmt);
}
// return true if the statement has valid json function
return hasJsonFunction;
},
/**
* Generates an SQL query that extract JSON property of given path.
*
* @param {String} column The JSON column
* @param {String|Array<String>} [path] The path to extract (optional)
* @returns {String} The generated sql query
* @private
*/
jsonPathExtractionQuery(column, path) {
const paths = _.toPath(path);
const pathStr = `${['$'].concat(paths).join('.')}`;
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `(${quotedColumn}->>'${pathStr}')`;
},
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {String} tableName The name of the table.
......
......@@ -132,7 +132,7 @@ const QueryGenerator = {
* @returns {Boolean} true if the given statement is json function
* @throws {Error} throw if the statement looks like json function but has invalid token
*/
checkValidJsonStatement(stmt) {
_checkValidJsonStatement(stmt) {
if (!_.isString(stmt)) {
return false;
}
......@@ -220,7 +220,7 @@ const QueryGenerator = {
let str;
// Allow specifying conditions using the postgres json syntax
if (this.checkValidJsonStatement(smth.path)) {
if (this._checkValidJsonStatement(smth.path)) {
str = smth.path;
} else {
// Also support json property accessors
......
......@@ -86,7 +86,7 @@ const QueryGenerator = {
* @returns {Boolean} true if the given statement is json function
* @throws {Error} throw if the statement looks like json function but has invalid token
*/
checkValidJsonStatement(stmt) {
_checkValidJsonStatement(stmt) {
if (!_.isString(stmt)) {
return false;
}
......@@ -158,40 +158,13 @@ const QueryGenerator = {
},
//sqlite can't cast to datetime so we need to convert date values to their ISO strings
_traverseJSON(items, baseKey, prop, item, path) {
let cast;
if (path[path.length - 1].indexOf('::') > -1) {
const tmp = path[path.length - 1].split('::');
cast = tmp[1];
path[path.length - 1] = tmp[0];
}
const pathKey = this.jsonPathExtractionQuery(baseKey, path);
if (_.isPlainObject(item)) {
Utils.getOperators(item).forEach(op => {
let value = item[op];
_toJSONValue(value) {
if (value instanceof Date) {
value = value.toISOString();
return value.toISOString();
} else if (Array.isArray(value) && value[0] instanceof Date) {
value = value.map(val => val.toISOString());
return value.map(val => val.toISOString());
}
items.push(this.whereItemQuery(this._castKey(pathKey, value, cast), {[op]: value}));
});
_.forOwn(item, (value, itemProp) => {
this._traverseJSON(items, baseKey, itemProp, value, path.concat([itemProp]));
});
return;
}
if (item instanceof Date) {
item = item.toISOString();
} else if (Array.isArray(item) && item[0] instanceof Date) {
item = item.map(val => val.toISOString());
}
items.push(this.whereItemQuery(this._castKey(pathKey, item, cast), {$eq: item}));
return value;
},
......@@ -208,7 +181,7 @@ const QueryGenerator = {
let str;
// Allow specifying conditions using the sqlite json functions
if (this.checkValidJsonStatement(smth.path)) {
if (this._checkValidJsonStatement(smth.path)) {
str = smth.path;
} else {
// Also support json property accessors
......@@ -228,6 +201,7 @@ const QueryGenerator = {
smth.type = 'datetime';
}
}
return AbstractQueryGenerator.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
},
......
......@@ -874,11 +874,11 @@ class Sequelize {
}
/**
* Creates an object representing nested where conditions for postgres's json data-type.
* @see {@link Model.findAll}
* Creates an object representing nested where conditions for postgres/sqlite/mysql json data-type.
* @see {@link Model#findAll}
*
* @method json
* @param {String|Object} conditions A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres json syntax.
* @param {String|Object} conditions A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres/sqlite/mysql json syntax.
* @param {String|Number|Boolean} [value] An optional value to compare against. Produces a string of the form "<json path> = '<value>'".
* @memberof Sequelize
* @return {Sequelize.json}
......
......@@ -497,10 +497,11 @@ class Col extends SequelizeMethod {
exports.Col = Col;
class Cast extends SequelizeMethod {
constructor(val, type) {
constructor(val, type, json) {
super();
this.val = val;
this.type = (type || '').trim();
this.json = json || false;
}
}
exports.Cast = Cast;
......
......@@ -34,8 +34,13 @@ describe('model', () => {
fields: ['id', 'username', 'document', 'emergency_contact'],
logging: sql => {
const expected = '\'{"name":"joe","phones":[1337,42]}\'';
const expectedEscaped = '\'{\\"name\\":\\"joe\\",\\"phones\\":[1337,42]}\'';
if (sql.indexOf(expected) === -1) {
expect(sql.indexOf(expectedEscaped)).not.to.equal(-1);
} else {
expect(sql.indexOf(expected)).not.to.equal(-1);
}
}
});
});
......@@ -235,6 +240,22 @@ describe('model', () => {
expect(user.username).to.equal('swen');
});
});
// JSONB Supports this, but not JSON in postgres/mysql
if (current.dialect.name === 'sqlite') {
it('should be able to find with just string', function() {
return this.User.create({
username: 'swen123',
emergency_contact: 'Unknown',
}).then(() => {
return this.User.find({where: {
emergency_contact: 'Unknown',
}});
}).then(user => {
expect(user.username).to.equal('swen123');
});
});
}
});
}
});
......@@ -251,9 +251,9 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
it('should be possible to query dates with array operators', function() {
const now = moment().toDate();
const before = moment().subtract(1, 'day').toDate();
const after = moment().add(1, 'day').toDate();
const now = moment().milliseconds(0).toDate();
const before = moment().milliseconds(0).subtract(1, 'day').toDate();
const after = moment().milliseconds(0).add(1, 'day').toDate();
return Promise.join(
this.Event.create({
json: {
......
......@@ -102,7 +102,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
});
if (current.dialect.supports.JSON) {
if (current.dialect.supports.JSONB) {
describe('JSONB', () => {
before(function() {
this.Model = this.sequelize.define('Model', {
......
......@@ -9,14 +9,14 @@ const Support = require(__dirname + '/../support'),
sql = current.dialect.QueryGenerator;
// Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation
if (current.dialect.supports.JSON) {
suite(Support.getTestDialectTeaser('SQL'), () => {
suite('JSON', () => {
suite('escape', () => {
test('plain string', () => {
expectsql(sql.escape('string', { type: new DataTypes.JSON() }), {
default: '\'"string"\''
default: '\'"string"\'',
mysql: '\'\\"string\\"\''
});
});
......@@ -46,7 +46,8 @@ if (current.dialect.supports.JSON) {
test('nested object', () => {
expectsql(sql.escape({ some: 'nested', more: { nested: true }, answer: 42 }, { type: new DataTypes.JSON() }), {
default: '\'{"some":"nested","more":{"nested":true},"answer":42}\''
default: '\'{"some":"nested","more":{"nested":true},"answer":42}\'',
mysql: '\'{\\"some\\":\\"nested\\",\\"more\\":{\\"nested\\":true},\\"answer\\":42}\''
});
});
......@@ -79,35 +80,40 @@ if (current.dialect.supports.JSON) {
test('condition object', () => {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ id: 1 })), {
postgres: '("id"#>>\'{}\') = \'1\'',
sqlite: "json_extract(`id`, '$') = '1'"
sqlite: "json_extract(`id`, '$') = '1'",
mysql: "`id`->>'$.' = '1'"
});
});
test('nested condition object', () => {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ profile: { id: 1 } })), {
postgres: '("profile"#>>\'{id}\') = \'1\'',
sqlite: "json_extract(`profile`, '$.id') = '1'"
sqlite: "json_extract(`profile`, '$.id') = '1'",
mysql: "`profile`->>'$.id' = '1'"
});
});
test('multiple condition object', () => {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: { value: 1 }, another: { value: 'string' } })), {
postgres: '("property"#>>\'{value}\') = \'1\' AND ("another"#>>\'{value}\') = \'string\'',
sqlite: "json_extract(`property`, '$.value') = '1' AND json_extract(`another`, '$.value') = 'string'"
sqlite: "json_extract(`property`, '$.value') = '1' AND json_extract(`another`, '$.value') = 'string'",
mysql: "`property`->>'$.value' = '1' and `another`->>'$.value' = 'string'"
});
});
test('dot notaion', () => {
test('dot notation', () => {
expectsql(sql.whereItemQuery(Sequelize.json('profile.id'), '1'), {
postgres: '("profile"#>>\'{id}\') = \'1\'',
sqlite: "json_extract(`profile`, '$.id') = '1'"
sqlite: "json_extract(`profile`, '$.id') = '1'",
mysql: "`profile`->>'$.id' = '1'"
});
});
test('column named "json"', () => {
expectsql(sql.whereItemQuery(Sequelize.json('json'), '{}'), {
postgres: '("json"#>>\'{}\') = \'{}\'',
sqlite: "json_extract(`json`, '$') = '{}'"
sqlite: "json_extract(`json`, '$') = '{}'",
mysql: "`json`->>'$.' = '{}'"
});
});
});
......
......@@ -769,7 +769,16 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
test('sequelize.json("profile.id"), sequelize.cast(2, \'text\')")', function() {
expectsql(sql.whereItemQuery(undefined, this.sequelize.json('profile.id', this.sequelize.cast('12346-78912', 'text'))), {
postgres: "(\"profile\"#>>'{id}') = CAST('12346-78912' AS TEXT)",
sqlite: "json_extract(`profile`, '$.id') = CAST('12346-78912' AS TEXT)"
sqlite: "json_extract(`profile`, '$.id') = CAST('12346-78912' AS TEXT)",
mysql: "`profile`->>'$.id' = CAST('12346-78912' AS CHAR)"
});
});
test('sequelize.json({profile: {id: "12346-78912", name: "test"}})', function () {
expectsql(sql.whereItemQuery(undefined, this.sequelize.json({profile: {id: '12346-78912', name: 'test'}})), {
postgres: "(\"profile\"#>>'{id}') = '12346-78912' AND (\"profile\"#>>'{name}') = 'test'",
sqlite: "json_extract(`profile`, '$.id') = '12346-78912' AND json_extract(`profile`, '$.name') = 'test'",
mysql: "`profile`->>'$.id' = '12346-78912' and `profile`->>'$.name' = 'test'"
});
});
......@@ -783,6 +792,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
},
prefix: 'User'
}, {
mysql: "(`User`.`data`->>'$.nested.attribute') = 'value'",
postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'"
});
......@@ -796,6 +806,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "CAST((`data`->>'$.nested') AS DECIMAL) IN (1, 2)",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)"
});
......@@ -809,6 +820,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "CAST((`data`->>'$.nested') AS DECIMAL) BETWEEN 1 AND 2",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
});
......@@ -824,8 +836,9 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
field: {
type: new DataTypes.JSONB()
},
prefix: 'User'
prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {tableName: 'User'}))
}, {
mysql: "((`User`.`data`->>'$.nested.attribute') = 'value' AND (`User`.`data`->>'$.nested.prop') != 'None')",
postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')"
});
......@@ -843,6 +856,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
},
prefix: 'User'
}, {
mysql: "((`User`.`data`->>'$.name.last') = 'Simpson' AND (`User`.`data`->>'$.employment') != 'None')",
postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
});
......@@ -855,6 +869,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "(CAST((`data`->>'$.price') AS DECIMAL) = 5 AND (`data`->>'$.name') = 'Product')",
postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 5 AND (\"data\"#>>'{name}') = 'Product')",
sqlite: "(CAST(json_extract(`data`, '$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`, '$.name') = 'Product')"
});
......@@ -868,6 +883,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mysql: "(`data`->>'$.nested.attribute') = 'value'",
postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
});
......@@ -881,6 +897,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) = 4",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4"
});
......@@ -896,6 +913,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) IN (3, 7)",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) IN (3, 7)",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)"
});
......@@ -911,6 +929,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2"
});
......@@ -926,6 +945,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2"
});
......@@ -942,6 +962,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DATETIME) > "+sql.escape(dt),
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS TIMESTAMPTZ) > "+sql.escape(dt),
sqlite: "json_extract(`data`, '$.nested.attribute') > " + sql.escape(dt.toISOString())
});
......@@ -955,6 +976,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mysql: "(`data`->>'$.nested.attribute') = 'true'",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1"
});
......@@ -970,6 +992,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mysql: "(`meta_data`->>'$.nested.attribute') = 'value'",
postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`meta_data`, '$.nested.attribute') = 'value'"
});
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!