orange-orm
Version:
Object Relational Mapper
351 lines (328 loc) • 13.1 kB
JavaScript
const getSessionSingleton = require('../getSessionSingleton');
var newParameterized = require('../query/newParameterized');
const isJsonUpdateSupported = require('../isJsonUpdateSupported');
function newUpdateCommandCore(context, table, columns, row, concurrencyState) {
const quote = getSessionSingleton(context, 'quote');
const engine = getSessionSingleton(context, 'engine');
var command = newParameterized('UPDATE ' + quote(table._dbName) + ' SET');
var separator = ' ';
addColumns();
addWhereId();
addDiscriminators();
addConcurrencyChecks();
function addColumns() {
for (var alias in columns) {
var column = columns[alias];
const columnSql = quote(column._dbName);
const jsonUpdate = row._jsonUpdateState && row._jsonUpdateState[alias];
if (jsonUpdate && jsonUpdate.patches && jsonUpdate.patches.length) {
const updated = buildJsonUpdateExpression(columnSql, jsonUpdate.patches, column);
command = command.append(separator + columnSql + '=').append(updated);
}
else {
var encoded = column.encode(context, row[alias]);
command = command.append(separator + columnSql + '=').append(encoded);
}
separator = ',';
}
}
function addWhereId() {
separator = ' WHERE ';
var columns = table._primaryColumns;
for (var i = 0; i < columns.length; i++) {
var column = columns[i];
var value = row[column.alias];
var encoded = column.encode(context, value);
command = command.append(separator + quote(column._dbName) + '=').append(encoded);
separator = ' AND ';
}
}
function addDiscriminators() {
var discriminators = table._columnDiscriminators;
if (discriminators.length === 0)
return;
discriminators = separator + discriminators.join(' AND ');
command = command.append(discriminators);
}
function addConcurrencyChecks() {
const columnsState = concurrencyState && concurrencyState.columns;
if (!columnsState)
return;
for (let alias in columnsState) {
const state = columnsState[alias];
if (!state || state.concurrency === 'overwrite')
continue;
const column = table[alias];
if (!column)
continue;
if (state.paths && state.paths.length) {
for (let i = 0; i < state.paths.length; i++) {
const pathState = state.paths[i];
const encoded = encodeJsonValue(pathState.oldValue, column);
const jsonPath = buildJsonPath(pathState.path);
const columnExpr = buildJsonExtractExpression(quote(column._dbName), jsonPath, pathState.oldValue);
command = appendJsonPathComparison(columnExpr, encoded);
}
}
else {
const encoded = (engine === 'mysql' && column.tsType === 'JSONColumn')
? encodeJsonValue(state.oldValue, column)
: column.encode(context, state.oldValue);
command = appendNullSafeComparison(column, encoded);
}
}
}
function appendNullSafeComparison(column, encoded) {
const columnSql = quote(column._dbName);
if (engine === 'pg') {
command = command.append(separator + columnSql + ' IS NOT DISTINCT FROM ').append(encoded);
}
else if (engine === 'mysql') {
command = command.append(separator + columnSql + ' <=> ').append(encoded);
}
else if (engine === 'sqlite') {
command = command.append(separator + columnSql + ' IS ').append(encoded);
}
else if (engine === 'sap' && column.tsType === 'JSONColumn') {
if (encoded.sql() === 'null') {
command = command.append(separator + columnSql + ' IS NULL');
}
else {
const casted = newParameterized('CONVERT(VARCHAR(16384), ' + encoded.sql() + ')', encoded.parameters);
command = command.append(separator + 'CONVERT(VARCHAR(16384), ' + columnSql + ')=') .append(casted);
}
}
else if (engine === 'oracle' && column.tsType === 'JSONColumn') {
if (encoded.sql() === 'null') {
command = command.append(separator + columnSql + ' IS NULL');
}
else {
const jsonValue = newParameterized('JSON(' + encoded.sql() + ')', encoded.parameters);
const compare = newParameterized('JSON_EQUAL(' + columnSql + ', ' + jsonValue.sql() + ')', jsonValue.parameters);
command = command.append(separator).append(compare);
}
}
else {
if (encoded.sql() === 'null')
command = command.append(separator + columnSql + ' IS NULL');
else
command = command.append(separator + columnSql + '=').append(encoded);
}
separator = ' AND ';
return command;
}
function appendJsonPathComparison(columnExpr, encoded) {
if (engine === 'pg') {
command = command.append(separator).append(columnExpr).append(' IS NOT DISTINCT FROM ').append(encoded);
}
else if (engine === 'mysql') {
command = command.append(separator).append(columnExpr).append(' <=> ').append(encoded);
}
else if (engine === 'sqlite') {
command = command.append(separator).append(columnExpr).append(' IS ').append(encoded);
}
else if (engine === 'oracle') {
const isJsonQuery = columnExpr.sql().indexOf('JSON_QUERY(') === 0;
if (encoded.sql() === 'null') {
command = command.append(separator).append(columnExpr).append(' IS NULL');
}
else if (isJsonQuery) {
const jsonValue = newParameterized('JSON(' + encoded.sql() + ')', encoded.parameters);
const compare = newParameterized('JSON_EQUAL(' + columnExpr.sql() + ', ' + jsonValue.sql() + ')', columnExpr.parameters.concat(jsonValue.parameters));
command = command.append(separator).append(compare);
}
else {
command = command.append(separator).append(columnExpr).append('=').append(encoded);
}
}
else {
if (encoded.sql() === 'null')
command = command.append(separator).append(columnExpr).append(' IS NULL');
else
command = command.append(separator).append(columnExpr).append('=').append(encoded);
}
separator = ' AND ';
return command;
}
function buildJsonUpdateExpression(columnSql, patches, column) {
if (!isJsonUpdateSupported(engine))
return column.encode(context, row[column.alias]);
let expr = newParameterized(columnSql);
for (let i = 0; i < patches.length; i++) {
const patch = patches[i];
expr = applyJsonPatchExpression(expr, patch, column);
}
return expr;
}
function applyJsonPatchExpression(expr, patch, column) {
const path = patch.path || [];
const jsonPath = buildJsonPath(path);
if (patch.op === 'remove')
return buildJsonRemoveExpression(expr, jsonPath);
return buildJsonSetExpression(expr, jsonPath, patch.value, column);
}
function buildJsonSetExpression(expr, jsonPath, value, column) {
if (engine === 'pg') {
const pathLiteral = buildPgPathLiteral(jsonPath.tokens);
const jsonValue = JSON.stringify(value === undefined ? null : value);
const sql = 'jsonb_set(' + expr.sql() + ', ' + pathLiteral + ', ?::jsonb, true)';
return newParameterized(sql, expr.parameters.concat([jsonValue]));
}
if (engine === 'mysql') {
const jsonValue = JSON.stringify(value === undefined ? null : value);
const sql = 'JSON_SET(' + expr.sql() + ', ' + jsonPath.sql + ', CAST(? AS JSON))';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters, [jsonValue]));
}
if (engine === 'sqlite') {
const jsonValue = JSON.stringify(value === undefined ? null : value);
const sql = 'json_set(' + expr.sql() + ', ' + jsonPath.sql + ', json(?))';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters, [jsonValue]));
}
if (engine === 'mssql' || engine === 'mssqlNative') {
const mssqlValue = buildMssqlJsonValue(value);
const sql = 'JSON_MODIFY(' + expr.sql() + ', ' + jsonPath.sql + ', ' + mssqlValue.sql() + ')';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters, mssqlValue.parameters));
}
if (engine === 'oracle') {
const jsonValue = JSON.stringify(value === undefined ? null : value);
const sql = 'JSON_TRANSFORM(' + expr.sql() + ', SET ' + jsonPath.sql + ' = JSON(?))';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters, [jsonValue]));
}
return column.encode(context, row[column.alias]);
}
function buildJsonRemoveExpression(expr, jsonPath) {
if (engine === 'pg') {
const pathLiteral = buildPgPathLiteral(jsonPath.tokens);
const sql = expr.sql() + ' #- ' + pathLiteral;
return newParameterized(sql, expr.parameters);
}
if (engine === 'mysql') {
const sql = 'JSON_REMOVE(' + expr.sql() + ', ' + jsonPath.sql + ')';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters));
}
if (engine === 'sqlite') {
const sql = 'json_remove(' + expr.sql() + ', ' + jsonPath.sql + ')';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters));
}
if (engine === 'mssql' || engine === 'mssqlNative') {
const sql = 'JSON_MODIFY(' + expr.sql() + ', ' + jsonPath.sql + ', NULL)';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters));
}
if (engine === 'oracle') {
const sql = 'JSON_TRANSFORM(' + expr.sql() + ', REMOVE ' + jsonPath.sql + ')';
return newParameterized(sql, expr.parameters.concat(jsonPath.parameters));
}
return expr;
}
function buildJsonExtractExpression(columnSql, jsonPath, oldValue) {
if (engine === 'pg') {
const sql = columnSql + ' #> ' + buildPgPathLiteral(jsonPath.tokens);
return newParameterized(sql);
}
if (engine === 'mysql') {
const sql = 'JSON_EXTRACT(' + columnSql + ', ' + jsonPath.sql + ')';
return newParameterized(sql, jsonPath.parameters);
}
if (engine === 'sqlite') {
const sql = 'json_extract(' + columnSql + ', ' + jsonPath.sql + ')';
return newParameterized(sql, jsonPath.parameters);
}
if (engine === 'mssql' || engine === 'mssqlNative') {
const fn = isJsonObject(oldValue) ? 'JSON_QUERY' : 'JSON_VALUE';
const sql = fn + '(' + columnSql + ', ' + jsonPath.sql + ')';
return newParameterized(sql, jsonPath.parameters);
}
if (engine === 'oracle') {
const fn = isJsonObject(oldValue) ? 'JSON_QUERY' : 'JSON_VALUE';
const sql = fn + '(' + columnSql + ', ' + jsonPath.sql + ')';
return newParameterized(sql, jsonPath.parameters);
}
return newParameterized(columnSql);
}
function buildJsonPath(pathTokens) {
const tokens = Array.isArray(pathTokens) ? pathTokens : [];
if (engine === 'pg')
return { tokens, sql: buildPgPathLiteral(tokens), parameters: [] };
if (engine === 'oracle') {
let jsonPath = '$';
for (let i = 0; i < tokens.length; i++) {
const token = String(tokens[i]);
if (/^\d+$/.test(token))
jsonPath += '[' + token + ']';
else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(token))
jsonPath += '.' + token;
else
jsonPath += '["' + token.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]';
}
return { tokens, sql: '\'' + jsonPath.replace(/'/g, '\'\'') + '\'', parameters: [] };
}
let jsonPath = '$';
for (let i = 0; i < tokens.length; i++) {
const token = String(tokens[i]);
if (/^\d+$/.test(token))
jsonPath += '[' + token + ']';
else if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(token))
jsonPath += '.' + token;
else
jsonPath += '["' + token.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]';
}
return { tokens, sql: '?', parameters: [jsonPath] };
}
function buildPgPathLiteral(tokens) {
const parts = tokens.map(token => {
const text = String(token);
if (/^[A-Za-z0-9_]+$/.test(text))
return text;
return '"' + text.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
});
return '\'{'+ parts.join(',') + '}\'';
}
function encodeJsonValue(value, column) {
if (engine === 'oracle') {
if (value === null || value === undefined)
return newParameterized('null');
if (isJsonObject(value))
return column.encode(context, value);
if (typeof value === 'boolean' || typeof value === 'number')
return newParameterized('?', [String(value)]);
return newParameterized('?', [value]);
}
if (engine === 'pg') {
const jsonValue = JSON.stringify(value === undefined ? null : value);
return newParameterized('?::jsonb', [jsonValue]);
}
if (engine === 'mysql') {
const jsonValue = JSON.stringify(value === undefined ? null : value);
return newParameterized('CAST(? AS JSON)', [jsonValue]);
}
if (engine === 'sqlite') {
if (isJsonObject(value)) {
const jsonValue = JSON.stringify(value);
return newParameterized('?', [jsonValue]);
}
if (value === null || value === undefined)
return newParameterized('null');
return newParameterized('?', [value]);
}
if (engine === 'mssql' || engine === 'mssqlNative') {
if (isJsonObject(value))
return newParameterized('JSON_QUERY(?)', [JSON.stringify(value)]);
if (value === null || value === undefined)
return newParameterized('null');
return newParameterized('?', [String(value)]);
}
return column.encode(context, value);
}
function buildMssqlJsonValue(value) {
if (isJsonObject(value))
return newParameterized('JSON_QUERY(?)', [JSON.stringify(value)]);
if (value === null || value === undefined)
return newParameterized('null');
return newParameterized('?', [value]);
}
function isJsonObject(value) {
return value && typeof value === 'object';
}
return command;
}
module.exports = newUpdateCommandCore;