mirror of
https://github.com/knex/knex.git
synced 2025-07-04 15:41:07 +00:00

* Avoid lodash typecheks Lodash is quite big project. Even with direct imports it loads [tons](https://github.com/knex/knex/pull/3804) of code and still bloats node_modules. Especially since lodash mostly used as a polyfill for modern features. In this diff I attempted to reduce lodash usage by replacing type checks with `typeof` operator which might be sufficient. Also replaced lodash/isObject with custom simplified utility which does not consider functions as objects and allows to simplify code in one place.
361 lines
11 KiB
JavaScript
361 lines
11 KiB
JavaScript
const clone = require('lodash/clone');
|
|
const each = require('lodash/each');
|
|
const isEmpty = require('lodash/isEmpty');
|
|
const isPlainObject = require('lodash/isPlainObject');
|
|
const Oracle_Compiler = require('../../oracle/query/compiler');
|
|
const ReturningHelper = require('../utils').ReturningHelper;
|
|
const BlobHelper = require('../utils').BlobHelper;
|
|
const { isString } = require('../../../util/is');
|
|
|
|
class Oracledb_Compiler extends Oracle_Compiler {
|
|
constructor(client, builder) {
|
|
super(client, builder);
|
|
}
|
|
|
|
// Compiles an "insert" query, allowing for multiple
|
|
// inserts using a single query statement.
|
|
insert() {
|
|
const self = this;
|
|
const outBindPrep = this._prepOutbindings(
|
|
this.single.insert,
|
|
this.single.returning
|
|
);
|
|
const outBinding = outBindPrep.outBinding;
|
|
const returning = outBindPrep.returning;
|
|
const insertValues = outBindPrep.values;
|
|
|
|
if (
|
|
Array.isArray(insertValues) &&
|
|
insertValues.length === 1 &&
|
|
isEmpty(insertValues[0])
|
|
) {
|
|
return this._addReturningToSqlAndConvert(
|
|
'insert into ' +
|
|
this.tableName +
|
|
' (' +
|
|
this.formatter.wrap(this.single.returning) +
|
|
') values (default)',
|
|
outBinding[0],
|
|
this.tableName,
|
|
returning
|
|
);
|
|
}
|
|
|
|
if (
|
|
isEmpty(this.single.insert) &&
|
|
typeof this.single.insert !== 'function'
|
|
) {
|
|
return '';
|
|
}
|
|
|
|
const insertData = this._prepInsert(insertValues);
|
|
|
|
const sql = {};
|
|
|
|
if (isString(insertData)) {
|
|
return this._addReturningToSqlAndConvert(
|
|
'insert into ' + this.tableName + ' ' + insertData,
|
|
outBinding[0],
|
|
this.tableName,
|
|
returning
|
|
);
|
|
}
|
|
|
|
if (insertData.values.length === 1) {
|
|
return this._addReturningToSqlAndConvert(
|
|
'insert into ' +
|
|
this.tableName +
|
|
' (' +
|
|
this.formatter.columnize(insertData.columns) +
|
|
') values (' +
|
|
this.formatter.parameterize(insertData.values[0]) +
|
|
')',
|
|
outBinding[0],
|
|
this.tableName,
|
|
returning
|
|
);
|
|
}
|
|
|
|
const insertDefaultsOnly = insertData.columns.length === 0;
|
|
sql.returning = returning;
|
|
sql.sql =
|
|
'begin ' +
|
|
insertData.values
|
|
.map(function (value, index) {
|
|
const parameterizedValues = !insertDefaultsOnly
|
|
? self.formatter.parameterize(value, self.client.valueForUndefined)
|
|
: '';
|
|
let subSql = 'insert into ' + self.tableName;
|
|
|
|
if (insertDefaultsOnly) {
|
|
// No columns given so only the default value
|
|
subSql +=
|
|
' (' +
|
|
self.formatter.wrap(self.single.returning) +
|
|
') values (default)';
|
|
} else {
|
|
subSql +=
|
|
' (' +
|
|
self.formatter.columnize(insertData.columns) +
|
|
') values (' +
|
|
parameterizedValues +
|
|
')';
|
|
}
|
|
|
|
let returningClause = '';
|
|
let intoClause = '';
|
|
// ToDo review if this code is still needed or could be dropped
|
|
// eslint-disable-next-line no-unused-vars
|
|
let usingClause = '';
|
|
let outClause = '';
|
|
|
|
each(value, function (val) {
|
|
if (!(val instanceof BlobHelper)) {
|
|
usingClause += ' ?,';
|
|
}
|
|
});
|
|
// eslint-disable-next-line no-unused-vars
|
|
usingClause = usingClause.slice(0, -1);
|
|
|
|
// Build returning and into clauses
|
|
outBinding[index].forEach(function (ret) {
|
|
const columnName = ret.columnName || ret;
|
|
returningClause += self.formatter.wrap(columnName) + ',';
|
|
intoClause += ' ?,';
|
|
outClause += ' out ?,';
|
|
|
|
// Add Helpers to bindings
|
|
if (ret instanceof BlobHelper) {
|
|
return self.formatter.bindings.push(ret);
|
|
}
|
|
self.formatter.bindings.push(new ReturningHelper(columnName));
|
|
});
|
|
|
|
// Strip last comma
|
|
returningClause = returningClause.slice(0, -1);
|
|
intoClause = intoClause.slice(0, -1);
|
|
outClause = outClause.slice(0, -1);
|
|
|
|
if (returningClause && intoClause) {
|
|
subSql += ' returning ' + returningClause + ' into' + intoClause;
|
|
}
|
|
|
|
// Pre bind position because subSql is an execute immediate parameter
|
|
// later position binding will only convert the ? params
|
|
subSql = self.formatter.client.positionBindings(subSql);
|
|
const parameterizedValuesWithoutDefaultAndBlob = parameterizedValues
|
|
.replace('DEFAULT, ', '')
|
|
.replace(', DEFAULT', '')
|
|
.replace('EMPTY_BLOB(), ', '')
|
|
.replace(', EMPTY_BLOB()', '');
|
|
return (
|
|
"execute immediate '" +
|
|
subSql.replace(/'/g, "''") +
|
|
(parameterizedValuesWithoutDefaultAndBlob || value
|
|
? "' using "
|
|
: '') +
|
|
parameterizedValuesWithoutDefaultAndBlob +
|
|
(parameterizedValuesWithoutDefaultAndBlob && outClause ? ',' : '') +
|
|
outClause +
|
|
';'
|
|
);
|
|
})
|
|
.join(' ') +
|
|
'end;';
|
|
|
|
sql.outBinding = outBinding;
|
|
if (returning[0] === '*') {
|
|
// Generate select statement with special order by
|
|
// to keep the order because 'in (..)' may change the order
|
|
sql.returningSql = function () {
|
|
return (
|
|
'select * from ' +
|
|
self.tableName +
|
|
' where ROWID in (' +
|
|
this.outBinding
|
|
.map(function (v, i) {
|
|
return ':' + (i + 1);
|
|
})
|
|
.join(', ') +
|
|
')' +
|
|
' order by case ROWID ' +
|
|
this.outBinding
|
|
.map(function (v, i) {
|
|
return 'when CHARTOROWID(:' + (i + 1) + ') then ' + i;
|
|
})
|
|
.join(' ') +
|
|
' end'
|
|
);
|
|
};
|
|
}
|
|
|
|
return sql;
|
|
}
|
|
|
|
_addReturningToSqlAndConvert(sql, outBinding, tableName, returning) {
|
|
const self = this;
|
|
const res = {
|
|
sql: sql,
|
|
};
|
|
|
|
if (!outBinding) {
|
|
return res;
|
|
}
|
|
const returningValues = Array.isArray(outBinding)
|
|
? outBinding
|
|
: [outBinding];
|
|
let returningClause = '';
|
|
let intoClause = '';
|
|
// Build returning and into clauses
|
|
returningValues.forEach(function (ret) {
|
|
const columnName = ret.columnName || ret;
|
|
returningClause += self.formatter.wrap(columnName) + ',';
|
|
intoClause += '?,';
|
|
|
|
// Add Helpers to bindings
|
|
if (ret instanceof BlobHelper) {
|
|
return self.formatter.bindings.push(ret);
|
|
}
|
|
self.formatter.bindings.push(new ReturningHelper(columnName));
|
|
});
|
|
res.sql = sql;
|
|
|
|
// Strip last comma
|
|
returningClause = returningClause.slice(0, -1);
|
|
intoClause = intoClause.slice(0, -1);
|
|
if (returningClause && intoClause) {
|
|
res.sql += ' returning ' + returningClause + ' into ' + intoClause;
|
|
}
|
|
res.outBinding = [outBinding];
|
|
if (returning[0] === '*') {
|
|
res.returningSql = function () {
|
|
return 'select * from ' + self.tableName + ' where ROWID = :1';
|
|
};
|
|
}
|
|
res.returning = returning;
|
|
|
|
return res;
|
|
}
|
|
|
|
_prepOutbindings(paramValues, paramReturning) {
|
|
const result = {};
|
|
let params = paramValues || [];
|
|
let returning = paramReturning || [];
|
|
if (!Array.isArray(params) && isPlainObject(paramValues)) {
|
|
params = [params];
|
|
}
|
|
// Always wrap returning argument in array
|
|
if (returning && !Array.isArray(returning)) {
|
|
returning = [returning];
|
|
}
|
|
|
|
const outBinding = [];
|
|
// Handle Buffer value as Blob
|
|
each(params, function (values, index) {
|
|
if (returning[0] === '*') {
|
|
outBinding[index] = ['ROWID'];
|
|
} else {
|
|
outBinding[index] = clone(returning);
|
|
}
|
|
each(values, function (value, key) {
|
|
if (value instanceof Buffer) {
|
|
values[key] = new BlobHelper(key, value);
|
|
|
|
// Delete blob duplicate in returning
|
|
const blobIndex = outBinding[index].indexOf(key);
|
|
if (blobIndex >= 0) {
|
|
outBinding[index].splice(blobIndex, 1);
|
|
values[key].returning = true;
|
|
}
|
|
outBinding[index].push(values[key]);
|
|
}
|
|
if (value === undefined) {
|
|
delete params[index][key];
|
|
}
|
|
});
|
|
});
|
|
result.returning = returning;
|
|
result.outBinding = outBinding;
|
|
result.values = params;
|
|
return result;
|
|
}
|
|
|
|
update() {
|
|
const self = this;
|
|
const sql = {};
|
|
const outBindPrep = this._prepOutbindings(
|
|
this.single.update || this.single.counter,
|
|
this.single.returning
|
|
);
|
|
const outBinding = outBindPrep.outBinding;
|
|
const returning = outBindPrep.returning;
|
|
|
|
const updates = this._prepUpdate(this.single.update);
|
|
const where = this.where();
|
|
|
|
let returningClause = '';
|
|
let intoClause = '';
|
|
|
|
if (isEmpty(updates) && typeof this.single.update !== 'function') {
|
|
return '';
|
|
}
|
|
|
|
// Build returning and into clauses
|
|
outBinding.forEach(function (out) {
|
|
out.forEach(function (ret) {
|
|
const columnName = ret.columnName || ret;
|
|
returningClause += self.formatter.wrap(columnName) + ',';
|
|
intoClause += ' ?,';
|
|
|
|
// Add Helpers to bindings
|
|
if (ret instanceof BlobHelper) {
|
|
return self.formatter.bindings.push(ret);
|
|
}
|
|
self.formatter.bindings.push(new ReturningHelper(columnName));
|
|
});
|
|
});
|
|
// Strip last comma
|
|
returningClause = returningClause.slice(0, -1);
|
|
intoClause = intoClause.slice(0, -1);
|
|
|
|
sql.outBinding = outBinding;
|
|
sql.returning = returning;
|
|
sql.sql =
|
|
'update ' +
|
|
this.tableName +
|
|
' set ' +
|
|
updates.join(', ') +
|
|
(where ? ' ' + where : '');
|
|
if (outBinding.length && !isEmpty(outBinding[0])) {
|
|
sql.sql += ' returning ' + returningClause + ' into' + intoClause;
|
|
}
|
|
if (returning[0] === '*') {
|
|
sql.returningSql = function () {
|
|
let sql = 'select * from ' + self.tableName;
|
|
const modifiedRowsCount = this.rowsAffected.length || this.rowsAffected;
|
|
let returningSqlIn = ' where ROWID in (';
|
|
let returningSqlOrderBy = ') order by case ROWID ';
|
|
|
|
// Needs special order by because in(...) change result order
|
|
for (let i = 0; i < modifiedRowsCount; i++) {
|
|
if (this.returning[0] === '*') {
|
|
returningSqlIn += ':' + (i + 1) + ', ';
|
|
returningSqlOrderBy +=
|
|
'when CHARTOROWID(:' + (i + 1) + ') then ' + i + ' ';
|
|
}
|
|
}
|
|
if (this.returning[0] === '*') {
|
|
this.returning = this.returning.slice(0, -1);
|
|
returningSqlIn = returningSqlIn.slice(0, -2);
|
|
returningSqlOrderBy = returningSqlOrderBy.slice(0, -1);
|
|
}
|
|
return (sql += returningSqlIn + returningSqlOrderBy + ' end');
|
|
};
|
|
}
|
|
|
|
return sql;
|
|
}
|
|
}
|
|
|
|
module.exports = Oracledb_Compiler;
|