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

Commit 0af04063 by Yoni Jah Committed by Jan Aagaard Meier

fix(query-generator) Simplify where item query (#8068)

* refactor(abstruct.query-generator): Reduce complexity of abstruct query-generator whereItemQuery by

whereItemQuery had cyclic complexity of 85 reduced to 26 to make logic a bit easier to follow.
Logic is almost identical but at places where logic looked like an obvoius error it was tweeked, like -
	_traverseJSON had weird logic if the item was plain object -
		cast wast set by either path (ok) or the first value of the first property of an object (doesn't make sense)
		where value passed into whereItemQuery was in the main function scope causing it to accumulate properties on each iteration (doesn't make sense)
All test are passing with no issues but since there were minor logic changes there might be some edge cases needed to be addressed.
Though it's more resonable to assume changes will fix bugs related to those edge cases than cause them

* fix(abstract.query-generator): Fix discrepancies between postgres and sqlite casting and handling of

postgres and sqlite handle casting and JSONB formats a bit diffrently yet some previous commits
didn't took this changes into consideration when handling casting and didn't test casting properly

* fix(abstract.query-generator): Fix issue where not properly treating  plain js object values for JSON column

Fixes #3824 (again)
1 parent 0f8417a2
......@@ -7,6 +7,11 @@ const QueryGenerator = {
__proto__: AbstractQueryGenerator,
dialect: 'mysql',
OperatorMap: Object.assign({}, AbstractQueryGenerator.OperatorMap, {
$regexp: 'REGEXP',
$notRegexp: 'NOT REGEXP'
}),
createSchema() {
return 'SHOW TABLES';
},
......
......@@ -157,6 +157,44 @@ const QueryGenerator = {
return `json_extract(${quotedColumn}, ${pathStr})`;
},
//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)) {
_.forOwn(item, (value, itemProp) => {
if (itemProp.indexOf('$') === 0) {
if (value instanceof Date) {
value = value.toISOString();
} else if (Array.isArray(value) && value[0] instanceof Date) {
value = value.map(val => val.toISOString());
}
items.push(this.whereItemQuery(this._castKey(pathKey, value, cast), {[itemProp]: value}));
return;
}
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}));
},
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
// Parse nested object
......
......@@ -3,6 +3,7 @@
const chai = require('chai'),
Sequelize = require('../../../index'),
Promise = Sequelize.Promise,
moment = require('moment'),
expect = chai.expect,
Support = require(__dirname + '/../support'),
DataTypes = require(__dirname + '/../../../lib/data-types'),
......@@ -249,6 +250,96 @@ 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();
return Promise.join(
this.Event.create({
json: {
user: 'Homer',
lastLogin: now
}
})
).then(() => {
return this.Event.findAll({
where: {
json: {
lastLogin: now
}
}
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('json')).to.eql({
user: 'Homer',
lastLogin: now.toISOString()
});
});
}).then(() => {
return this.Event.findAll({
where: {
json: {
lastLogin: {$between: [before, after]}
}
},
logging: console.log.bind(console)
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('json')).to.eql({
user: 'Homer',
lastLogin: now.toISOString()
});
});
});
});
it('should be possible to query a boolean with array operators', function() {
return Promise.join(
this.Event.create({
json: {
user: 'Homer',
active: true
}
})
).then(() => {
return this.Event.findAll({
where: {
json: {
active: true
}
}
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('json')).to.eql({
user: 'Homer',
active: true
});
});
}).then(() => {
return this.Event.findAll({
where: {
json: {
active: {$in: [true, false]}
}
}
}).then(events => {
const event = events[0];
expect(events.length).to.equal(1);
expect(event.get('json')).to.eql({
user: 'Homer',
active: true
});
});
});
});
it('should be possible to query a nested integer value', function() {
return Promise.join(
this.Event.create({
......
......@@ -789,6 +789,32 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
testsql('data', {
nested: {
$in: [1, 2]
}
}, {
field: {
type: new DataTypes.JSONB()
}
}, {
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)"
});
testsql('data', {
nested: {
$between: [1, 2]
}
}, {
field: {
type: new DataTypes.JSONB()
}
}, {
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
});
testsql('data', {
nested: {
attribute: 'value',
prop: {
$ne: 'None'
......@@ -821,6 +847,18 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
});
testsql('data', {
price: 5,
name: 'Product'
}, {
field: {
type: new DataTypes.JSONB()
}
}, {
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')"
});
testsql('data.nested.attribute', 'value', {
model: {
rawAttributes: {
......@@ -858,8 +896,8 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
postgres: "(\"data\"#>>'{nested,attribute}') IN (3, 7)",
sqlite: "json_extract(`data`, '$.nested.attribute') 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)"
});
testsql('data', {
......@@ -905,7 +943,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}
}, {
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS TIMESTAMPTZ) > "+sql.escape(dt),
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DATETIME) > "+sql.escape(dt)
sqlite: "json_extract(`data`, '$.nested.attribute') > " + sql.escape(dt.toISOString())
});
testsql('data', {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!