Skip to content

Commit

Permalink
feat: support JSON_VALUE(j, '$.a' RETURNING CHAR(50))
Browse files Browse the repository at this point in the history
  • Loading branch information
cyjake committed Dec 7, 2023
1 parent 55539b6 commit 2409445
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 2 deletions.
65 changes: 65 additions & 0 deletions src/expr.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ const PRECEDENCES = [
['or']
];

const RETURNING_TYPES = [
'float',
'double',
'decimal',
'signed',
'unsigned',
'date',
'time',
'datetime',
'year', // mysql >= 8.0.22
'char',
'json',
];

/**
* Compare the precedence of two operators. Operators with lower indices have higher priorities.
* @example
Expand Down Expand Up @@ -163,7 +177,58 @@ function parseExprList(str, ...values) {
return { type: 'literal', value };
}

function dataType() {
let value = '';
while (chr && /[a-z0-9$_.]/i.test(chr)) {
value += chr;
next();
}
value = value.toLowerCase();
if (!RETURNING_TYPES.includes(value)) {
throw new Error(`Unexpected RETURNING type of JSON_VALUE(): ${value}`);

Check warning on line 188 in src/expr.js

View check run for this annotation

Codecov / codecov/patch

src/expr.js#L188

Added line #L188 was not covered by tests
}
if (chr === '(') {
let length = '';
next();
while (chr && chr !== ')') {
length += chr;
next();
}
next();
return { type: 'dataType', value, length };
}
return { type: 'dataType', value };

Check warning on line 200 in src/expr.js

View check run for this annotation

Codecov / codecov/patch

src/expr.js#L200

Added line #L200 was not covered by tests
}

// JSON_VALUE(json_doc, path [RETURNING type] [on_empty] [on_error])\
// JSON_VALUE(j, '$.id' RETURNING UNSIGNED)
function jsonValue(name) {
const args = [];
next();
args.push(expr());
next();
space();
args.push(string());
space();
const result = { type: 'func', name, args };
while (chr !== ')') {
let value = '';
while (chr && /[a-z0-9$_.]/i.test(chr)) {
value += chr;
next();
}
if (value.toLowerCase() === 'returning') {
space();
result.dataType = dataType();
}
}
// the trailing ')'
next();
return result;
}

function func(name) {
if (name === 'json_value') return jsonValue(name);
const args = [];
do {
next();
Expand Down
14 changes: 13 additions & 1 deletion src/expr_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,29 @@ function formatIdentifier(spell, ast) {
return escapeId(column);
}

function formatDataType(spell, ast) {
const { value, length } = ast;
if (length) return `${value.toUpperCase()}(${length})`;
return value.toUpperCase();

Check warning on line 45 in src/expr_formatter.js

View check run for this annotation

Codecov / codecov/patch

src/expr_formatter.js#L45

Added line #L45 was not covered by tests
}

const extractFieldNames = ['year', 'month', 'day'];

function formatFuncExpr(spell, ast) {
const { name, args } = ast;
const { name, args, dataType } = ast;
const { type } = spell.Model.driver;

// https://www.postgresql.org/docs/9.1/static/functions-datetime.html
if (type === 'postgres' && extractFieldNames.includes(name)) {
return `EXTRACT(${name.toUpperCase()} FROM ${args.map(arg => formatExpr(spell, arg)).join(', ')})`;
}

// https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#function_json-value
// https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-21.html#mysqld-8-0-21-json
if (name === 'json_value' && dataType) {
return `${name.toUpperCase()}(${args.map(arg => formatExpr(spell, arg)).join(', ')} RETURNING ${formatDataType(spell, dataType)})`;
}

return `${name.toUpperCase()}(${args.map(arg => formatExpr(spell, arg)).join(', ')})`;
}

Expand Down
19 changes: 18 additions & 1 deletion test/unit/expr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,10 +462,27 @@ describe('=> parse arithmetic operators', function() {
});
});

describe('parse malformed expression', function() {
describe('=> parse malformed expression', function() {
it('parse )', function() {
assert.throws(function() {
parseExpr(')');
});
}, /unexpected token/i);
});

describe('=> parse func with modifiers', function() {
it('parse JSON_VALUE()', function() {
assertExpr(
"JSON_VALUE(extra, '$.foo.bar' RETURNING CHAR(32))",
{
type: 'func',
name: 'json_value',
dataType: { length: '32', type: 'dataType', value: 'char' },
args: [
{ type: 'id', value: 'extra' },
{ type: 'literal', value: '$.foo.bar' },
],
}
);
});
});
7 changes: 7 additions & 0 deletions test/unit/expr_formatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,11 @@ describe('=> formatExpr', function() {
"SELECT * FROM `articles` WHERE `settings` LIKE '%foo%' AND `gmt_deleted` IS NULL"
);
});

it("should be able to process JSON_VALUE(j, '$.a' RETURNING CHAR(32))", async function() {
assert.equal(
Post.where({ "JSON_VALUE(extra, '$.a' RETURNING CHAR(32))": 'hello' }).toSqlString(),
"SELECT * FROM `articles` WHERE JSON_VALUE(`extra`, '$.a' RETURNING CHAR(32)) = 'hello' AND `gmt_deleted` IS NULL"
);
});
});

0 comments on commit 2409445

Please sign in to comment.