@mas-soft/mas-core-server
Version:
main application
774 lines • 27.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const oracle = require("oracledb");
const parametrized_sql_1 = require("./parametrized-sql");
const util_1 = require("util");
const assert = require("assert");
const debug = require('debug')('mas:sql:connector');
class SqlConnector {
constructor(_config) {
this.settings = {};
this._models = {};
this.name = "oracle";
this.settings = _config;
}
getPlaceholderForValue(key) {
return ':' + key;
}
;
getCountForAffectedRows(model, info) {
return info && info.rowsAffected;
}
;
getInsertedId(model, info) {
return info && info.outBinds && info.outBinds[0][0];
}
;
buildInsertDefaultValues(model, data, options) {
let idCol = this.idColumnEscaped(model);
return '(' + idCol + ') VALUES(DEFAULT)';
}
;
idColumnEscaped(model) {
return this.escapeName(this.idColumn(model));
}
;
idColumn(model) {
let name = this.idNames(model)[0];
let dbName = this.dbName;
if (typeof dbName === 'function') {
name = dbName(name);
}
return name;
}
;
buildInsertReturning(model, data, options) {
let modelDef = this.getModelDefinition(model);
let type = modelDef.properties[this.idNames(model)[0]].type;
let outParam = null;
if (type === Number) {
outParam = { type: oracle.NUMBER, dir: oracle.BIND_OUT };
}
else if (type === Date) {
outParam = { type: oracle.DATE, dir: oracle.BIND_OUT };
}
else {
outParam = { type: oracle.STRING, dir: oracle.BIND_OUT };
}
let params = [outParam];
let retStm = 'RETURNING ' + this.idColumnEscaped(model) + ' into ?';
let returningStmt = new parametrized_sql_1.ParameterizedSQL(retStm, params);
return returningStmt;
}
;
dateToOracle(val, dateOnly) {
if (util_1.isDate(val)) {
return val;
}
else {
return new Date(val);
}
function fz(v) {
return v < 10 ? '0' + v : v;
}
function ms(v) {
if (v < 10) {
return '00' + v;
}
else if (v < 100) {
return '0' + v;
}
else {
return '' + v;
}
}
let dateStr = [
val.getUTCFullYear(),
fz(val.getUTCMonth() + 1),
fz(val.getUTCDate()),
].join('-') + ' ' + [
fz(val.getUTCHours()),
fz(val.getUTCMinutes()),
fz(val.getUTCSeconds()),
].join(':');
if (!dateOnly) {
dateStr += '.' + ms(val.getMilliseconds());
}
if (dateOnly) {
return new parametrized_sql_1.ParameterizedSQL("to_date(?,'yyyy-mm-dd hh24:mi:ss')", [dateStr]);
}
else {
return new parametrized_sql_1.ParameterizedSQL("to_timestamp(?,'yyyy-mm-dd hh24:mi:ss.ff3')", [dateStr]);
}
}
toColumnValue(prop, val) {
if (val == null) {
if (prop.autoIncrement || prop.id) {
return new parametrized_sql_1.ParameterizedSQL('DEFAULT');
}
else {
return null;
}
}
if (prop.type === String) {
return String(val);
}
if (prop.type === Number) {
if (isNaN(val)) {
return val;
}
return val;
}
if (prop.type === Date || prop.type.name === 'Timestamp') {
return this.dateToOracle(val, prop.type === Date);
}
if (prop.type === Boolean) {
if (val) {
return 'Y';
}
else {
return 'N';
}
}
return this.serializeObject(val);
}
;
fromColumnValue(prop, val) {
if (val == null) {
return val;
}
let type = prop && prop.type;
if (type === Boolean) {
if (typeof val === 'boolean') {
return val;
}
else {
return (val === 'Y' || val === 'y' || val === 'T' ||
val === 't' || val === '1');
}
}
return val;
}
;
dbName(name) {
if (!name) {
return name;
}
return name.toUpperCase();
}
;
escapeName(name) {
if (!name) {
return name;
}
return '"' + name.replace(/\./g, '"."') + '"';
}
;
tableEscaped(model) {
let schemaName = this.schema(model);
if (schemaName && schemaName !== this.settings.user) {
return this.escapeName(schemaName) + '.' +
this.escapeName(this.table(model));
}
else {
return this.escapeName(this.table(model));
}
}
;
buildLimit(limit, offset) {
if (isNaN(offset)) {
offset = 0;
}
let sql = 'OFFSET ' + offset + ' ROWS';
if (limit >= 0) {
sql += ' FETCH NEXT ' + limit + ' ROWS ONLY';
}
return sql;
}
applyPagination(model, stmt, filter) {
let offset = filter.offset || filter.skip || 0;
if (this.settings.supportsOffsetFetch) {
let limitClause = this.buildLimit(filter.limit, filter.offset || filter.skip);
return stmt.merge(limitClause);
}
else {
let paginatedSQL = 'SELECT * FROM (' + stmt.sql + ' ' +
')' + ' ' + ' WHERE R > ' + offset;
if (filter.limit !== -1) {
paginatedSQL += ' AND R <= ' + (offset + filter.limit);
}
stmt.sql = paginatedSQL + ' ';
return stmt;
}
}
;
__buildColumnNames(model, filter) {
let fieldsFilter = filter && filter.fields;
let cols = this.getModelDefinition(model).properties;
if (!cols) {
return '*';
}
let self = this;
let keys = Object.keys(cols);
if (Array.isArray(fieldsFilter) && fieldsFilter.length > 0) {
keys = fieldsFilter.filter(function (f) {
return cols[f];
});
}
else if ('object' === typeof fieldsFilter &&
Object.keys(fieldsFilter).length > 0) {
let included = [];
let excluded = [];
keys.forEach(function (k) {
if (fieldsFilter[k]) {
included.push(k);
}
else if ((k in fieldsFilter) && !fieldsFilter[k]) {
excluded.push(k);
}
});
if (included.length > 0) {
keys = included;
}
else if (excluded.length > 0) {
excluded.forEach(function (e) {
let index = keys.indexOf(e);
keys.splice(index, 1);
});
}
}
let names = keys.map(function (c) {
return self.columnEscaped(model, c);
});
return names.join(',');
}
;
columnEscaped(model, property) {
return this.escapeName(this.column(model, property));
}
;
buildColumnNames(model, filter) {
let columnNames = this.__buildColumnNames(model, filter);
if (filter.limit || filter.offset || filter.skip) {
let orderBy = this.buildOrderBy(model, filter.order);
columnNames += ',ROW_NUMBER() OVER' + ' (' + orderBy + ') R';
}
return columnNames;
}
;
buildSelect(model, filter) {
if (!filter.order) {
let idNames = this.idNames(model);
if (idNames && idNames.length) {
filter.order = idNames;
}
}
let selectStmt = new parametrized_sql_1.ParameterizedSQL('SELECT ' +
this.buildColumnNames(model, filter) +
' FROM ' + this.tableEscaped(model));
if (filter) {
if (filter.where) {
let whereStmt = this.buildWhere(model, filter.where);
selectStmt.merge(whereStmt);
}
if (filter.limit || filter.skip || filter.offset) {
selectStmt = this.applyPagination(model, selectStmt, filter);
}
else {
if (filter.order) {
selectStmt.merge(this.buildOrderBy(model, filter.order));
}
}
}
return this.parameterize(selectStmt);
}
;
serializeObject(obj) {
let val;
if (obj && typeof obj.toJSON === 'function') {
obj = obj.toJSON();
}
if (typeof obj !== 'string') {
val = JSON.stringify(obj);
}
else {
val = obj;
}
return val;
}
;
buildOrderBy(model, order) {
if (!order) {
return '';
}
let self = this;
if (typeof order === 'string') {
order = [order];
}
let clauses = [];
for (var i = 0, n = order.length; i < n; i++) {
let t = order[i].split(/[\s,]+/);
if (t.length === 1) {
clauses.push(self.columnEscaped(model, order[i]));
}
else {
clauses.push(self.columnEscaped(model, t[0]) + ' ' + t[1]);
}
}
return 'ORDER BY ' + clauses.join(',');
}
;
buildWhere(model, where) {
let whereClause = this._buildWhere(model, where);
if (whereClause.sql) {
whereClause.sql = 'WHERE ' + whereClause.sql;
}
return whereClause;
}
;
_buildWhere(model, where) {
let columnValue, sqlExp;
if (!where) {
return new parametrized_sql_1.ParameterizedSQL('');
}
if (typeof where !== 'object' || Array.isArray(where)) {
debug('Invalid value for where: %j', where);
return new parametrized_sql_1.ParameterizedSQL('');
}
let self = this;
let props = self.getModelDefinition(model).properties;
debug({ props, model, where });
let whereStmts = [];
for (var key in where) {
let stmt = new parametrized_sql_1.ParameterizedSQL('', []);
if (key === 'and' || key === 'or') {
let branches = [];
let branchParams = [];
let clauses = where[key];
if (Array.isArray(clauses)) {
for (var i = 0, n = clauses.length; i < n; i++) {
let stmtForClause = self._buildWhere(model, clauses[i]);
if (stmtForClause.sql) {
stmtForClause.sql = '(' + stmtForClause.sql + ')';
branchParams = branchParams.concat(stmtForClause.params);
branches.push(stmtForClause.sql);
}
}
stmt.merge({
sql: branches.join(' ' + key.toUpperCase() + ' '),
params: branchParams,
});
whereStmts.push(stmt);
continue;
}
}
let p = props[key];
if (p == null) {
debug('Unknown property %s is skipped for model %s', key, model);
throw new Error(`Unknown property ${key} in model ${model}`);
}
let expression = where[key];
let columnName = self.columnEscaped(model, key);
if (expression === null || expression === undefined) {
stmt.merge(columnName + ' IS NULL');
}
else if (expression && expression.constructor === Object) {
let operator = Object.keys(expression)[0];
expression = expression[operator];
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
columnValue = [];
if (Array.isArray(expression)) {
for (var j = 0, m = expression.length; j < m; j++) {
columnValue.push(this.toColumnValue(p, expression[j]));
}
}
else {
columnValue.push(this.toColumnValue(p, expression));
}
if (operator === 'between') {
let v1 = columnValue[0] === undefined ? null : columnValue[0];
let v2 = columnValue[1] === undefined ? null : columnValue[1];
columnValue = [v1, v2];
}
else {
if (columnValue.length === 0) {
if (operator === 'inq') {
columnValue = [null];
}
else {
continue;
}
}
}
}
else if (operator === 'regexp' && expression instanceof RegExp) {
columnValue = expression;
}
else {
columnValue = this.toColumnValue(p, expression);
}
sqlExp = self.buildExpression(columnName, operator, columnValue, p);
stmt.merge(sqlExp);
}
else {
columnValue = self.toColumnValue(p, expression);
if (columnValue === null) {
stmt.merge(columnName + ' IS NULL');
}
else {
if (columnValue instanceof parametrized_sql_1.ParameterizedSQL) {
stmt.merge(columnName + '=').merge(columnValue);
}
else {
stmt.merge({
sql: columnName + '=?',
params: [columnValue],
});
}
}
}
whereStmts.push(stmt);
}
let params = [];
let sqls = [];
for (var k = 0, s = whereStmts.length; k < s; k++) {
sqls.push(whereStmts[k].sql);
params = params.concat(whereStmts[k].params);
}
let whereStmt = new parametrized_sql_1.ParameterizedSQL({
sql: sqls.join(' AND '),
params: params,
});
return whereStmt;
}
;
parameterize(ps) {
ps = new parametrized_sql_1.ParameterizedSQL(ps);
let parts = ps.sql.split(parametrized_sql_1.ParameterizedSQL.PLACEHOLDER);
let clause = [];
for (var j = 0, m = parts.length; j < m; j++) {
clause.push(parts[j]);
if (j !== parts.length - 1) {
clause.push(this.getPlaceholderForValue(j + 1));
}
}
ps.sql = clause.join('');
return ps;
}
;
idNames(model) {
let props = this.getModelDefinition(model).properties;
let keys = Object.keys(props);
let ids = keys.filter(a => {
return props[a].id;
}).map(a => a);
return ids;
}
;
idName(model) {
let ids = this.idNames(model);
return ids;
}
;
isIdValuePresent(idValue) {
try {
assert(idValue !== null && idValue !== undefined, 'id value is required');
return true;
}
catch (err) {
return false;
}
}
id(model, prop) {
let p = this.getModelDefinition(model).properties[prop];
return p && p.id;
}
;
getModelDefinition(modelName) {
if (util_1.isString(modelName))
return this._models[modelName];
else
return modelName.definition;
}
;
column(model, property) {
let prop = this.getPropertyDefinition(model, property);
let columnName;
if (prop && prop[this.name]) {
columnName = prop[this.name].column || prop[this.name].columnName;
if (columnName) {
return columnName;
}
}
columnName = property;
if (typeof this.dbName === 'function') {
columnName = this.dbName(columnName);
}
return columnName;
}
;
schema(model) {
let dbMeta = this.getConnectorSpecificSettings(model);
let schemaName = (dbMeta && (dbMeta.schema || dbMeta.schemaName)) ||
(this.settings.schema || this.settings.schemaName) ||
this.getDefaultSchemaName();
return schemaName;
}
;
getDefaultSchemaName() {
return '';
}
;
getConnectorSpecificSettings(modelName) {
let settings = this.getModelDefinition(modelName).settings || {};
return settings[this.name];
}
;
table(model) {
let dbMeta = this.getConnectorSpecificSettings(model);
let tableName;
if (dbMeta) {
tableName = dbMeta.table || dbMeta.tableName;
if (tableName) {
return tableName;
}
}
tableName = model.definition.name || model.name;
if (typeof this.dbName === 'function') {
tableName = this.dbName(tableName);
}
return tableName;
}
;
define(modelDefinition) {
modelDefinition.settings = modelDefinition.settings || {};
this._models[modelDefinition.model.modelName] = modelDefinition;
}
;
defineProperty(model, propertyName, propertyDefinition) {
let modelDef = this.getModelDefinition(model);
modelDef.properties[propertyName] = propertyDefinition;
}
;
buildFields(model, data, excludeIds) {
let keys = Object.keys(data);
return this._buildFieldsForKeys(model, data, keys, excludeIds);
}
;
_buildFieldsForKeys(model, data, keys, excludeIds) {
let props = this.getModelDefinition(model).properties;
let fields = {
names: [],
columnValues: [],
properties: [],
};
for (var i = 0, n = keys.length; i < n; i++) {
let key = keys[i];
let p = props[key];
if (p == null) {
debug('Unknown property %s is skipped for model %s', key, model);
continue;
}
if (excludeIds && p.id) {
continue;
}
let k = this.columnEscaped(model, key);
let v = this.toColumnValue(p, data[key]);
if (v !== undefined) {
fields.names.push(k);
if (v instanceof parametrized_sql_1.ParameterizedSQL) {
fields.columnValues.push(v);
}
else {
fields.columnValues.push(new parametrized_sql_1.ParameterizedSQL(parametrized_sql_1.ParameterizedSQL.PLACEHOLDER, [v]));
}
fields.properties.push(p);
}
}
return fields;
}
;
buildInsertInto(model, fields, options) {
let stmt = new parametrized_sql_1.ParameterizedSQL('INSERT INTO ' + this.tableEscaped(model));
let columnNames = fields.names.join(',');
if (columnNames) {
stmt.merge('(' + columnNames + ')', '');
}
return stmt;
}
;
buildInsert(model, data, options = {}) {
let fields = this.buildFields(model, data);
let insertStmt = this.buildInsertInto(model, fields, options);
let columnValues = fields.columnValues;
let fieldNames = fields.names;
if (fieldNames.length) {
let values = parametrized_sql_1.ParameterizedSQL.join(columnValues, ',');
values.sql = 'VALUES(' + values.sql + ')';
insertStmt.merge(values);
}
else {
insertStmt.merge(this.buildInsertDefaultValues(model, data, options));
}
let returning = this.buildInsertReturning(model, data, options);
if (returning) {
insertStmt.merge(returning);
}
return this.parameterize(insertStmt);
}
;
buildExpression(columnName, operator, columnValue, propertyDescriptor) {
let val = columnValue;
if (columnValue instanceof RegExp) {
val = columnValue.source;
operator = 'regexp';
}
switch (operator) {
case 'like':
return new parametrized_sql_1.ParameterizedSQL({
sql: columnName + " LIKE ? ESCAPE '\\'",
params: [val],
});
case 'nlike':
return new parametrized_sql_1.ParameterizedSQL({
sql: columnName + " NOT LIKE ? ESCAPE '\\'",
params: [val],
});
case 'regexp':
let flag = '';
if (columnValue.ignoreCase) {
flag += 'i';
}
if (columnValue.multiline) {
flag += 'm';
}
if (columnValue.global) {
debug('{{Oracle}} regex syntax does not respect the {{`g`}} flag');
}
if (flag) {
return new parametrized_sql_1.ParameterizedSQL({
sql: 'REGEXP_LIKE(' + columnName + ', ?, ?)',
params: [val, flag],
});
}
else {
return new parametrized_sql_1.ParameterizedSQL({
sql: 'REGEXP_LIKE(' + columnName + ', ?)',
params: [val],
});
}
default:
let exp = this.__buildExpression(columnName, operator, columnValue, propertyDescriptor);
return exp;
}
}
;
__buildExpression(columnName, operator, columnValue, propertyValue) {
function buildClause(columnValue, separator, grouping) {
let values = [];
for (var i = 0, n = columnValue.length; i < n; i++) {
if (columnValue[i] instanceof parametrized_sql_1.ParameterizedSQL) {
values.push(columnValue[i]);
}
else {
values.push(new parametrized_sql_1.ParameterizedSQL(parametrized_sql_1.ParameterizedSQL.PLACEHOLDER, [columnValue[i]]));
}
}
separator = separator || ',';
let clause = parametrized_sql_1.ParameterizedSQL.join(values, separator);
if (grouping) {
clause.sql = '(' + clause.sql + ')';
}
return clause;
}
let sqlExp = columnName;
let clause;
if (columnValue instanceof parametrized_sql_1.ParameterizedSQL) {
clause = columnValue;
}
else {
clause = new parametrized_sql_1.ParameterizedSQL(parametrized_sql_1.ParameterizedSQL.PLACEHOLDER, [columnValue]);
}
switch (operator) {
case 'gt':
sqlExp += '>';
break;
case 'gte':
sqlExp += '>=';
break;
case 'lt':
sqlExp += '<';
break;
case 'lte':
sqlExp += '<=';
break;
case 'between':
sqlExp += ' BETWEEN ';
clause = buildClause(columnValue, ' AND ', false);
break;
case 'inq':
sqlExp += ' IN ';
clause = buildClause(columnValue, ',', true);
break;
case 'nin':
sqlExp += ' NOT IN ';
clause = buildClause(columnValue, ',', true);
break;
case 'neq':
if (columnValue == null) {
return new parametrized_sql_1.ParameterizedSQL(sqlExp + ' IS NOT NULL');
}
sqlExp += '!=';
break;
case 'like':
sqlExp += ' LIKE ';
break;
case 'nlike':
sqlExp += ' NOT LIKE ';
break;
case 'regexp':
sqlExp += ' REGEXP ';
break;
}
let stmt = parametrized_sql_1.ParameterizedSQL.join([sqlExp, clause]);
return stmt;
}
;
getPropertyDefinition(modelName, propName) {
let model = this.getModelDefinition(modelName);
return model && model.properties[propName];
}
;
buildUpdate(model, where, data) {
var fields = this.buildFieldsForUpdate(model, data);
return this._constructUpdateQuery(model, where, fields);
}
;
_constructUpdateQuery(model, where, fields) {
var updateClause = new parametrized_sql_1.ParameterizedSQL('UPDATE ' + this.tableEscaped(model));
var whereClause = this.buildWhere(model, where);
updateClause.merge([fields, whereClause]);
return this.parameterize(updateClause);
}
;
buildFieldsForUpdate(model, data, excludeIds) {
if (excludeIds === undefined) {
excludeIds = true;
}
var fields = this.buildFields(model, data, excludeIds);
return this._constructUpdateParameterizedSQL(fields);
}
;
_constructUpdateParameterizedSQL(fields) {
var columns = new parametrized_sql_1.ParameterizedSQL('');
for (var i = 0, n = fields.names.length; i < n; i++) {
var clause = parametrized_sql_1.ParameterizedSQL.append(fields.names[i], fields.columnValues[i], '=');
columns.merge(clause, ',');
}
columns.sql = 'SET ' + columns.sql;
return columns;
}
;
}
exports.SqlConnector = SqlConnector;
//# sourceMappingURL=connector.js.map