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

Commit b0bd34df by Sushant Committed by GitHub

fix(mysql/json): properly quote JSON path to be valid ECMAScript name (#8793)

1 parent d4acd7cb
...@@ -461,7 +461,12 @@ const QueryGenerator = { ...@@ -461,7 +461,12 @@ const QueryGenerator = {
* @private * @private
*/ */
jsonPathExtractionQuery(column, path) { jsonPathExtractionQuery(column, path) {
const paths = _.toPath(path); /**
* Sub paths need to be quoted as ECMAScript identifiers
*
* https://bugs.mysql.com/bug.php?id=81896
*/
const paths = _.toPath(path).map(subPath => Utils.addTicks(subPath, '"'));
const pathStr = `${['$'].concat(paths).join('.')}`; const pathStr = `${['$'].concat(paths).join('.')}`;
const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column); const quotedColumn = this.isIdentifierQuoted(column) ? column : this.quoteIdentifier(column);
return `(${quotedColumn}->>'${pathStr}')`; return `(${quotedColumn}->>'${pathStr}')`;
......
...@@ -423,6 +423,60 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -423,6 +423,60 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
}); });
it('should be possible to query for nested fields with hyphens/dashes, #8718', function() {
return Promise.join(
this.Event.create({
data: {
name: {
first: 'Homer',
last: 'Simpson'
},
status_report: {
'red-indicator': {
'level$$level': true
}
},
employment: 'Nuclear Safety Inspector'
}
}),
this.Event.create({
data: {
name: {
first: 'Marge',
last: 'Simpson'
},
employment: null
}
})
).then(() => {
return this.Event.findAll({
where: {
data: {
status_report: {
'red-indicator': {
'level$$level': true
}
}
}
}
}).then(events => {
expect(events.length).to.equal(1);
expect(events[0].get('data')).to.eql({
name: {
first: 'Homer',
last: 'Simpson'
},
status_report: {
'red-indicator': {
'level$$level': true
}
},
employment: 'Nuclear Safety Inspector'
});
});
});
});
it('should be possible to query multiple nested values', function() { it('should be possible to query multiple nested values', function() {
return this.Event.create({ return this.Event.create({
data: { data: {
......
...@@ -102,15 +102,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -102,15 +102,15 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}); });
}); });
if (current.dialect.supports.JSONB) { if (current.dialect.supports.JSON) {
describe('JSONB', () => { describe('JSON', () => {
before(function() { before(function() {
this.Model = this.sequelize.define('Model', { this.Model = this.sequelize.define('Model', {
name: { name: {
type: DataTypes.STRING type: DataTypes.STRING
}, },
data: { data: {
type: DataTypes.JSONB type: DataTypes.JSON
}, },
deletedAt: { deletedAt: {
type: DataTypes.DATE, type: DataTypes.DATE,
...@@ -128,7 +128,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ...@@ -128,7 +128,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return this.Model.sync({ force: true }); return this.Model.sync({ force: true });
}); });
it('should soft delete with JSONB condition', function() { it('should soft delete with JSON condition', function() {
return this.Model.bulkCreate([{ return this.Model.bulkCreate([{
name: 'One', name: 'One',
data: { data: {
......
...@@ -792,7 +792,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -792,7 +792,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}, },
prefix: 'User' prefix: 'User'
}, { }, {
mysql: "(`User`.`data`->>'$.nested.attribute') = 'value'", mysql: "(`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'", postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'" sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'"
}); });
...@@ -806,7 +806,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -806,7 +806,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested') AS DECIMAL) IN (1, 2)", mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) IN (1, 2)",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) 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)" sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)"
}); });
...@@ -820,7 +820,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -820,7 +820,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested') AS DECIMAL) BETWEEN 1 AND 2", mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) BETWEEN 1 AND 2",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) 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" sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
}); });
...@@ -838,7 +838,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -838,7 +838,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}, },
prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {tableName: 'User'})) prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {tableName: 'User'}))
}, { }, {
mysql: "((`User`.`data`->>'$.nested.attribute') = 'value' AND (`User`.`data`->>'$.nested.prop') != 'None')", mysql: "((`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value' AND (`User`.`data`->>'$.\"nested\".\"prop\"') != 'None')",
postgres: "((\"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')" sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')"
}); });
...@@ -856,7 +856,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -856,7 +856,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
}, },
prefix: 'User' prefix: 'User'
}, { }, {
mysql: "((`User`.`data`->>'$.name.last') = 'Simpson' AND (`User`.`data`->>'$.employment') != 'None')", mysql: "((`User`.`data`->>'$.\"name\".\"last\"') = 'Simpson' AND (`User`.`data`->>'$.\"employment\"') != 'None')",
postgres: "((\"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')" sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
}); });
...@@ -869,7 +869,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -869,7 +869,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "(CAST((`data`->>'$.price') AS DECIMAL) = 5 AND (`data`->>'$.name') = 'Product')", mysql: "(CAST((`data`->>'$.\"price\"') AS DECIMAL) = 5 AND (`data`->>'$.\"name\"') = 'Product')",
postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 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')" sqlite: "(CAST(json_extract(`data`, '$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`, '$.name') = 'Product')"
}); });
...@@ -883,7 +883,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -883,7 +883,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
} }
} }
}, { }, {
mysql: "(`data`->>'$.nested.attribute') = 'value'", mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"data\"#>>'{nested,attribute}') = 'value'", postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'" sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
}); });
...@@ -897,7 +897,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -897,7 +897,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
} }
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) = 4", mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) = 4",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4" sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4"
}); });
...@@ -913,7 +913,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -913,7 +913,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
} }
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) IN (3, 7)", mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) IN (3, 7)",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) 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)" sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)"
}); });
...@@ -929,7 +929,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -929,7 +929,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) > 2", mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2" sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2"
}); });
...@@ -945,7 +945,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -945,7 +945,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DECIMAL) > 2", mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2" sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2"
}); });
...@@ -962,7 +962,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -962,7 +962,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "CAST((`data`->>'$.nested.attribute') AS DATETIME) > "+sql.escape(dt), mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DATETIME) > "+sql.escape(dt),
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS TIMESTAMPTZ) > "+sql.escape(dt), postgres: "CAST((\"data\"#>>'{nested,attribute}') AS TIMESTAMPTZ) > "+sql.escape(dt),
sqlite: "json_extract(`data`, '$.nested.attribute') > " + sql.escape(dt.toISOString()) sqlite: "json_extract(`data`, '$.nested.attribute') > " + sql.escape(dt.toISOString())
}); });
...@@ -976,7 +976,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -976,7 +976,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB() type: new DataTypes.JSONB()
} }
}, { }, {
mysql: "(`data`->>'$.nested.attribute') = 'true'", mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'true'",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true", postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1" sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1"
}); });
...@@ -992,7 +992,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => { ...@@ -992,7 +992,7 @@ suite(Support.getTestDialectTeaser('SQL'), () => {
} }
} }
}, { }, {
mysql: "(`meta_data`->>'$.nested.attribute') = 'value'", mysql: "(`meta_data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'", postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`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!