UNPKG

@mas-soft/mas-core-server

Version:

main application

774 lines 27.1 kB
"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