CVE-2019-10752 (published 2019-10-17T19:15Z)

Sequelize, all versions prior to version 4.44.3 and 5.15.1, is vulnerable to SQL Injection due to sequelize.json() helper function not escaping values properly when formatting sub paths for JSON queries for MySQL, MariaDB and SQLite.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-89
cwe_name: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/sequelize/sequelize
commit_details: repo_url: https://github.com/sequelize/sequelize
author: Sushant
author_date: 2019-08-18 17:32:02+05:30
msg: fix(sequelize.json.fn): use common path extraction for mysql/mariadb/sqlite (#11329)
repository: repo_url: https://github.com/sequelize/sequelize
repo_name: sequelize/sequelize
description: Feature-rich ORM for modern Node.js and TypeScript, it supports PostgreSQL (with JSON and JSONB support), MySQL, MariaDB, SQLite, MS SQL Server, Snowflake, Oracle DB (v6), DB2 and DB2 for IBM i.
repo_language: TypeScript
owner: sequelize
file_changes: filename: query-generator.js
change_type: ModificationType.MODIFY
diff: @@ -1068,24 +1068,30 @@ class QueryGenerator {
 
     switch (this.dialect) {
       case 'mysql':
+      case 'mariadb':
+      case 'sqlite':
         /**
-         * Sub paths need to be quoted as ECMAScript identifiers
+         * Non digit sub paths need to be quoted as ECMAScript identifiers
          * https://bugs.mysql.com/bug.php?id=81896
          */
-        paths = paths.map(subPath => Utils.addTicks(subPath, '"'));
-        pathStr = this.escape(['$'].concat(paths).join('.'));
-        return `(${quotedColumn}->>${pathStr})`;
-
-      case 'mariadb':
-        pathStr = this.escape(['$'].concat(paths).join('.'));
-        return `json_unquote(json_extract(${quotedColumn},${pathStr}))`;
+        if (this.dialect === 'mysql') {
+          paths = paths.map(subPath => {
+            return /\D/.test(subPath)
+              ? Utils.addTicks(subPath, '"')
+              : subPath;
+          });
+        }
 
-      case 'sqlite':
         pathStr = this.escape(['$']
           .concat(paths)
           .join('.')
-          .replace(/\.(\d+)(?:(?=\.)|$)/g, (_, digit) => `[${digit}]`));
-        return `json_extract(${quotedColumn}, ${pathStr})`;
+          .replace(/\.(\d+)(?:(?=\.)|$)/g, (__, digit) => `[${digit}]`));
+
+        if (this.dialect === 'sqlite') {
+          return `json_extract(${quotedColumn},${pathStr})`;
+        }
+
+        return `json_unquote(json_extract(${quotedColumn},${pathStr}))`;
 
       case 'postgres':
         pathStr = this.escape(`{${paths.join(',')}}`);

programming_language: JavaScript
filename: query-generator.js
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,8 @@
 'use strict';
 
-const _ = require('lodash');
-const Utils = require('../../utils');
 const MySQLQueryGenerator = require('../mysql/query-generator');
-const util = require('util');
 
 class MariaDBQueryGenerator extends MySQLQueryGenerator {
-
   createSchema(schema, options) {
     options = Object.assign({
       charset: null,
@@ -31,74 +27,6 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator {
   showTablesQuery() {
     return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\') AND TABLE_TYPE = \'BASE TABLE\'';
   }
-
-  handleSequelizeMethod(smth, tableName, factory, options, prepend) {
-    if (smth instanceof Utils.Json) {
-      // Parse nested object
-      if (smth.conditions) {
-        const conditions = this.parseConditionObject(smth.conditions).map(
-          condition =>
-            `json_unquote(json_extract(${this.quoteIdentifier(
-              condition.path[0])},'$.${_.tail(condition.path).join(
-              '.')}')) = '${condition.value}'`
-        );
-
-        return conditions.join(' and ');
-      }
-      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 = `json_unquote(json_extract(${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) {
-      const lowType = smth.type.toLowerCase();
-      if (lowType.includes('timestamp')) {
-        smth.type = 'datetime';
-      } else if (smth.json && lowType.includes('boolean')) {
-        // true or false cannot be casted as booleans within a JSON structure
-        smth.type = 'char';
-      } else if (lowType.includes('double precision') || lowType.includes('boolean') || lowType.includes('integer')) {
-        smth.type = 'decimal';
-      } else if (lowType.includes('text')) {
-        smth.type = 'char';
-      }
-    }
-
-    return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
-  }
-
 }
 
 module.exports = MariaDBQueryGenerator;

programming_language: SQL
filename: query-generator.js
change_type: ModificationType.MODIFY
diff: @@ -209,10 +209,10 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
       // Parse nested object
       if (smth.conditions) {
         const conditions = this.parseConditionObject(smth.conditions).map(condition =>
-          `${this.quoteIdentifier(condition.path[0])}->>'$.${_.tail(condition.path).join('.')}' = '${condition.value}'`
+          `${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'`
         );
 
-        return conditions.join(' and ');
+        return conditions.join(' AND ');
       }
       if (smth.path) {
         let str;
@@ -221,27 +221,10 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
         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('.')}'`;
+          // Also support json property accessors
+          const paths = _.toPath(smth.path);
+          const column = paths.shift();
+          str = this.jsonPathExtractionQuery(column, paths);
         }
 
         if (smth.value) {

programming_language: JavaScript
filename: query-generator.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,6 @@
 'use strict';
 
 const Utils = require('../../utils');
-const util = require('util');
 const Transaction = require('../../transaction');
 const _ = require('lodash');
 const MySqlQueryGenerator = require('../mysql/query-generator');
@@ -149,34 +148,10 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator {
 
   handleSequelizeMethod(smth, tableName, factory, options, prepend) {
     if (smth instanceof Utils.Json) {
-      // Parse nested object
-      if (smth.conditions) {
-        const conditions = this.parseConditionObject(smth.conditions).map(condition =>
-          `${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'`
-        );
-
-        return conditions.join(' AND ');
-      }
-      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 property accessors
-          const paths = _.toPath(smth.path);
-          const column = paths.shift();
-          str = this.jsonPathExtractionQuery(column, paths);
-        }
-
-        if (smth.value) {
-          str += util.format(' = %s', this.escape(smth.value));
-        }
+      return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
+    }
 
-        return str;
-      }
-    } else if (smth instanceof Utils.Cast) {
+    if (smth instanceof Utils.Cast) {
       if (/timestamp/i.test(smth.type)) {
         smth.type = 'datetime';
       }

programming_language: JavaScript
filename: json.test.js
change_type: ModificationType.MODIFY
diff: @@ -697,6 +697,14 @@ describe(Support.getTestDialectTeaser('Model'), () => {
           });
         });
 
+        it('should properly escape path keys with sequelize.json', function() {
+          return this.Model.findAll({
+            raw: true,
+            attributes: ['id'],
+            where: this.sequelize.json("data.id')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ", '1')
+          });
+        });
+
         it('should properly escape the single quotes in array', function() {
           return this.Model.create({
             data: {

programming_language: TypeScript
filename: json.test.js
change_type: ModificationType.MODIFY
diff: @@ -82,63 +82,63 @@ if (current.dialect.supports.JSON) {
         it('condition object', () => {
           expectsql(sql.whereItemQuery(undefined, Sequelize.json({ id: 1 })), {
             postgres: '("id"#>>\'{}\') = \'1\'',
-            sqlite: "json_extract(`id`, '$') = '1'",
-            mariadb: "json_unquote(json_extract(`id`,'$.')) = '1'",
-            mysql: "`id`->>'$.' = '1'"
+            sqlite: "json_extract(`id`,'$') = '1'",
+            mariadb: "json_unquote(json_extract(`id`,'$')) = '1'",
+            mysql: "json_unquote(json_extract(`id`,'$')) = '1'"
           });
         });
 
         it('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'",
             mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'",
-            mysql: "`profile`->>'$.id' = '1'"
+            mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '1'"
           });
         });
 
         it('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'",
-            mariadb: "json_unquote(json_extract(`property`,'$.value')) = '1' and json_unquote(json_extract(`another`,'$.value')) = 'string'",
-            mysql: "`property`->>'$.value' = '1' and `another`->>'$.value' = 'string'"
+            sqlite: "json_extract(`property`,'$.value') = '1' AND json_extract(`another`,'$.value') = 'string'",
+            mariadb: "json_unquote(json_extract(`property`,'$.value')) = '1' AND json_unquote(json_extract(`another`,'$.value')) = 'string'",
+            mysql: "json_unquote(json_extract(`property`,'$.\\\"value\\\"')) = '1' AND json_unquote(json_extract(`another`,'$.\\\"value\\\"')) = 'string'"
           });
         });
 
         it('property array object', () => {
           expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: [[4, 6], [8]] })), {
             postgres: '("property"#>>\'{0,0}\') = \'4\' AND ("property"#>>\'{0,1}\') = \'6\' AND ("property"#>>\'{1,0}\') = \'8\'',
-            sqlite: "json_extract(`property`, '$[0][0]') = '4' AND json_extract(`property`, '$[0][1]') = '6' AND json_extract(`property`, '$[1][0]') = '8'",
-            mariadb: "json_unquote(json_extract(`property`,'$.0.0')) = '4' and json_unquote(json_extract(`property`,'$.0.1')) = '6' and json_unquote(json_extract(`property`,'$.1.0')) = '8'",
-            mysql: "`property`->>'$.0.0' = '4' and `property`->>'$.0.1' = '6' and `property`->>'$.1.0' = '8'"
+            sqlite: "json_extract(`property`,'$[0][0]') = '4' AND json_extract(`property`,'$[0][1]') = '6' AND json_extract(`property`,'$[1][0]') = '8'",
+            mariadb: "json_unquote(json_extract(`property`,'$[0][0]')) = '4' AND json_unquote(json_extract(`property`,'$[0][1]')) = '6' AND json_unquote(json_extract(`property`,'$[1][0]')) = '8'",
+            mysql: "json_unquote(json_extract(`property`,'$[0][0]')) = '4' AND json_unquote(json_extract(`property`,'$[0][1]')) = '6' AND json_unquote(json_extract(`property`,'$[1][0]')) = '8'"
           });
         });
 
         it('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'",
             mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'",
-            mysql: "`profile`->>'$.id' = '1'"
+            mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '1'"
           });
         });
 
         it('item dot notation array', () => {
           expectsql(sql.whereItemQuery(Sequelize.json('profile.id.0.1'), '1'), {
             postgres: '("profile"#>>\'{id,0,1}\') = \'1\'',
-            sqlite: "json_extract(`profile`, '$.id[0][1]') = '1'",
+            sqlite: "json_extract(`profile`,'$.id[0][1]') = '1'",
             mariadb: "json_unquote(json_extract(`profile`,'$.id[0][1]')) = '1'",
-            mysql: "`profile`->>'$.id[0][1]' = '1'"
+            mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"[0][1]')) = '1'"
           });
         });
 
         it('column named "json"', () => {
           expectsql(sql.whereItemQuery(Sequelize.json('json'), '{}'), {
             postgres: '("json"#>>\'{}\') = \'{}\'',
-            sqlite: "json_extract(`json`, '$') = '{}'",
-            mariadb: "json_unquote(json_extract(`json`,'$.')) = '{}'",
-            mysql: "`json`->>'$.' = '{}'"
+            sqlite: "json_extract(`json`,'$') = '{}'",
+            mariadb: "json_unquote(json_extract(`json`,'$')) = '{}'",
+            mysql: "json_unquote(json_extract(`json`,'$')) = '{}'"
           });
         });
       });

programming_language: JavaScript
filename: where.test.js
change_type: ModificationType.MODIFY
diff: @@ -825,18 +825,18 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
         it('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)",
             mariadb: "json_unquote(json_extract(`profile`,'$.id')) = CAST('12346-78912' AS CHAR)",
-            mysql: "`profile`->>'$.id' = CAST('12346-78912' AS CHAR)"
+            mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = CAST('12346-78912' AS CHAR)"
           });
         });
 
         it('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'",
-            mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '12346-78912' and json_unquote(json_extract(`profile`,'$.name')) = 'test'",
-            mysql: "`profile`->>'$.id' = '12346-78912' and `profile`->>'$.name' = 'test'"
+            sqlite: "json_extract(`profile`,'$.id') = '12346-78912' AND json_extract(`profile`,'$.name') = 'test'",
+            mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '12346-78912' AND json_unquote(json_extract(`profile`,'$.name')) = 'test'",
+            mysql: "json_unquote(json_extract(`profile`,'$.\\\"id\\\"')) = '12346-78912' AND json_unquote(json_extract(`profile`,'$.\\\"name\\\"')) = 'test'"
           });
         });
 
@@ -851,9 +851,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           prefix: 'User'
         }, {
           mariadb: "json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value'",
-          mysql: "(`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
+          mysql: "json_unquote(json_extract(`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'"
         });
 
         testsql('data', {
@@ -866,9 +866,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) IN (1, 2)",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) IN (1, 2)",
+          mysql: "CAST(json_unquote(json_extract(`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)"
+          sqlite: "CAST(json_extract(`data`,'$.nested') AS DOUBLE PRECISION) IN (1, 2)"
         });
 
         testsql('data', {
@@ -881,9 +881,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) BETWEEN 1 AND 2",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\"') AS DECIMAL) BETWEEN 1 AND 2",
+          mysql: "CAST(json_unquote(json_extract(`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"
+          sqlite: "CAST(json_extract(`data`,'$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
         });
 
         testsql('data', {
@@ -900,9 +900,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { tableName: 'User' }))
         }, {
           mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')",
-          mysql: "((`User`.`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value' AND (`User`.`data`->>'$.\\\"nested\\\".\\\"prop\\\"') != 'None')",
+          mysql: "(json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value' AND json_unquote(json_extract(`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')"
         });
 
         testsql('data', {
@@ -919,9 +919,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           prefix: 'User'
         }, {
           mariadb: "(json_unquote(json_extract(`User`.`data`,'$.name.last')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.employment')) != 'None')",
-          mysql: "((`User`.`data`->>'$.\\\"name\\\".\\\"last\\\"') = 'Simpson' AND (`User`.`data`->>'$.\\\"employment\\\"') != 'None')",
+          mysql: "(json_unquote(json_extract(`User`.`data`,'$.\\\"name\\\".\\\"last\\\"')) = 'Simpson' AND json_unquote(json_extract(`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')"
         });
 
         testsql('data', {
@@ -933,9 +933,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "(CAST(json_unquote(json_extract(`data`,'$.price')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.name')) = 'Product')",
-          mysql: "(CAST((`data`->>'$.\\\"price\\\"') AS DECIMAL) = 5 AND (`data`->>'$.\\\"name\\\"') = 'Product')",
+          mysql: "(CAST(json_unquote(json_extract(`data`,'$.\\\"price\\\"')) AS DECIMAL) = 5 AND json_unquote(json_extract(`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')"
         });
 
         testsql('data.nested.attribute', 'value', {
@@ -948,9 +948,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'value'",
-          mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
+          mysql: "json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value'",
           postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
-          sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
+          sqlite: "json_extract(`data`,'$.nested.attribute') = 'value'"
         });
 
         testsql('data.nested.attribute', 4, {
@@ -963,9 +963,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) = 4",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) = 4",
+          mysql: "CAST(json_unquote(json_extract(`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"
+          sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) = 4"
         });
 
         testsql('data.nested.attribute', {
@@ -980,9 +980,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) IN (3, 7)",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) IN (3, 7)",
+          mysql: "CAST(json_unquote(json_extract(`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)"
+          sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)"
         });
 
         testsql('data', {
@@ -997,9 +997,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2",
+          mysql: "CAST(json_unquote(json_extract(`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"
+          sqlite: "CAST(json_extract(`data`,'$.nested.attribute') AS DOUBLE PRECISION) > 2"
         });
 
         testsql('data', {
@@ -1014,9 +1014,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
-          mysql: "CAST((`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') AS DECIMAL) > 2",
+          mysql: "CAST(json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) AS DECIMAL) > 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"
         });
 
         const dt = new Date();
@@ -1032,9 +1032,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: `CAST(json_unquote(json_extract(\`data\`,'$.nested.attribute')) AS DATETIME) > ${sql.escape(dt)}`,
-          mysql: `CAST((\`data\`->>'$.\\"nested\\".\\"attribute\\"') AS DATETIME) > ${sql.escape(dt)}`,
+          mysql: `CAST(json_unquote(json_extract(\`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())}`
+          sqlite: `json_extract(\`data\`,'$.nested.attribute') > ${sql.escape(dt.toISOString())}`
         });
 
         testsql('data', {
@@ -1047,9 +1047,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'true'",
-          mysql: "(`data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'true'",
+          mysql: "json_unquote(json_extract(`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = '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"
         });
 
         testsql('metaData.nested.attribute', 'value', {
@@ -1064,9 +1064,9 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
           }
         }, {
           mariadb: "json_unquote(json_extract(`meta_data`,'$.nested.attribute')) = 'value'",
-          mysql: "(`meta_data`->>'$.\\\"nested\\\".\\\"attribute\\\"') = 'value'",
+          mysql: "json_unquote(json_extract(`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'"
         });
       });
     }

programming_language: JavaScript