UNPKG

firebird-query

Version:

node-firebird plugin for easy and safe query building.

483 lines (482 loc) 18.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FirebirdQuery = void 0; const node_firebird_1 = __importDefault(require("node-firebird")); const isolationMap = { READ_COMMITTED: node_firebird_1.default.ISOLATION_READ_COMMITTED, READ_COMMITTED_READ_ONLY: node_firebird_1.default.ISOLATION_READ_COMMITTED_READ_ONLY, READ_UNCOMMITTED: node_firebird_1.default.ISOLATION_READ_UNCOMMITTED, REPEATABLE_READ: node_firebird_1.default.ISOLATION_REPEATABLE_READ, SERIALIZABLE: node_firebird_1.default.ISOLATION_SERIALIZABLE, }; const escape = (...val) => node_firebird_1.default.escape(val.join("")); const buildWhereClause = (obj, prefix = "", clause = "AND") => { const clauses = []; if (isSingleUndefinedValue(obj)) { return defaultCondition(clause); } for (const key in obj) { const val = obj[key]; if (isWhereObjectArr(val)) { clauses.push(handleOrCondition(val, prefix, key)); } else if (isWhereObject(val)) { clauses.push(...handleObjectCondition(val, prefix, key)); } else if (isPrimitiveValue(val)) { clauses.push(handlePrimitiveValue(val, prefix, key)); } else if (isManuallyEscapedStatement(val)) { return val(node_firebird_1.default.escape); } else { clauses.push(defaultCondition(clause)); } } return clauses.join(" AND "); }; const isSingleUndefinedValue = (obj) => Object.values(obj).length === 1 && Object.values(obj)[0] === undefined; const defaultCondition = (clause) => clause === "AND" ? "1=1" : "1=0"; const isWhereObject = (val) => typeof val === "object" && !Array.isArray(val) && !(val instanceof Date); const isWhereObjectArr = (val) => Array.isArray(val) && isWhereObject(val[0]); const isManuallyEscapedStatement = (val) => typeof val === "function"; const isPrimitiveValue = (val) => typeof val === "string" || typeof val === "number" || typeof val === "boolean" || val instanceof Date || val === null || val === undefined; const handleOrCondition = (val, prefix, type = "OR") => { const orClauses = val.map((orObj) => buildWhereClause(orObj, prefix, type)); return `(${orClauses.join(` ${type} `)})`; }; const handleObjectCondition = (val, prefix, key) => { const clauses = []; for (const subKey in val) { const value = val[subKey]; let condition = ""; switch (subKey) { case "ne": condition = `${prefix}${key} != ${node_firebird_1.default.escape(value)}`; break; case "gt": condition = `${prefix}${key} > ${node_firebird_1.default.escape(value)}`; break; case "gte": condition = `${prefix}${key} >= ${node_firebird_1.default.escape(value)}`; break; case "lt": condition = `${prefix}${key} < ${node_firebird_1.default.escape(value)}`; break; case "lte": condition = `${prefix}${key} <= ${node_firebird_1.default.escape(value)}`; break; case "between": condition = `${prefix}${key} BETWEEN ${node_firebird_1.default.escape(value["from"])} AND ${node_firebird_1.default.escape(value["to"])}`; break; case "IN": condition = `${prefix}${key} IN (${value .map((i) => node_firebird_1.default.escape(i)) .join(", ")})`; break; case "notIN": condition = `${prefix}${key} NOT IN (${value .map((i) => node_firebird_1.default.escape(i)) .join(", ")})`; break; case "startsWith": condition = `${prefix}${key} LIKE ${escape(value, "%")}`; break; case "endsWith": condition = `${prefix}${key} LIKE ${escape("%", value)}`; break; case "contains": condition = `${prefix}${key} CONTAINING ${node_firebird_1.default.escape(value)}`; break; } if (value === undefined) { condition = "1=1"; } if (Array.isArray(value) && value.length === 0) { condition = "1=1"; } if (condition) { clauses.push(condition); } } return clauses; }; const handlePrimitiveValue = (val, prefix, key) => val === undefined ? "1=1" : `${prefix}${key} = ${node_firebird_1.default.escape(val)}`; const sqlBuilder = (strings, params) => { return strings .map((cur, i) => { const param = params[i]; if (isWhereObject(param)) { const conditions = param; const conditionResult = buildWhereClause(conditions); return cur + conditionResult; } else if (isPrimitiveValue(param)) { const isLastStr = i === strings.length - 1; const valueResult = !isLastStr ? node_firebird_1.default.escape(param) : ""; return cur + valueResult; } else if (isManuallyEscapedStatement(param)) { return cur + param((s) => node_firebird_1.default.escape(s)); } else { return ""; } }) .join(""); }; const paginatedQuery = (query, take, page) => { const skip = take * (page - 1); return `SELECT FIRST ${take} SKIP ${skip} * FROM (${query.replace(/;*$/g, "")});`; }; const insertOneQuery = (params) => { const { tableName, rowValues, returning = [] } = params; const columns = Object.keys(rowValues); const columnsStr = columns.join(", "); const escapedValues = columns.map((key) => node_firebird_1.default.escape(rowValues[key])); const valuesStr = escapedValues.join(", "); let query = `INSERT INTO ${tableName} (${columnsStr}) VALUES (${valuesStr})`; if (returning.length > 0) { query += ` RETURNING ${returning.join(", ")}`; } query += ";"; return query; }; const insertManyQuery = ({ tableName, columnNames, rowValues, }) => { const sortedColumnsStr = columnNames .slice() .sort((a, b) => String(a).localeCompare(String(b))) .join(", "); const selectStatements = rowValues.map((row) => { const sortedRow = Object.entries(row).sort(([a], [b]) => String(a).localeCompare(String(b))); const valuesList = sortedRow .map(([, value]) => node_firebird_1.default.escape(value)) .join(", "); return `SELECT ${valuesList} FROM RDB$DATABASE`; }); const toInsertStatement = selectStatements.join(" UNION ALL "); const query = `INSERT INTO ${tableName} (${sortedColumnsStr}) ${toInsertStatement};`; return query; }; const updateOneQuery = ({ tableName, rowValues, returning = [], where, }) => { const toSet = Object.entries(rowValues).map(([columnName, value]) => value !== undefined ? `${columnName} = ${escape(value)}` : undefined); const filteredToSet = toSet.filter((item) => item !== undefined); const valuesStr = filteredToSet.join(", "); const whereStr = buildWhereClause(where); let query = `UPDATE ${tableName} SET ${valuesStr} WHERE ${whereStr}`; if (returning.length > 0) { query += ` RETURNING ${returning.join(", ")}`; } query += ";"; return query; }; const updateOrInsertQuery = ({ tableName, rowValues, returning = [], }) => { const columns = Object.keys(rowValues); const columnsStr = columns.join(", "); const escapedValues = columns.map((key) => escape(rowValues[key])); const valuesStr = escapedValues.join(", "); let query = `UPDATE OR INSERT INTO ${tableName} (${columnsStr}) VALUES (${valuesStr})`; if (returning.length > 0) { query += ` RETURNING ${returning.join(", ")}`; } query += ";"; return query; }; const deleteOneQuery = (params) => { const { where, returning = [], tableName } = params; const whereClauses = buildWhereClause(where); let query = `DELETE FROM ${tableName} WHERE ${whereClauses}`; if (returning.length > 0) { query += ` RETURNING ${returning.join(", ")}`; } query += ";"; return query; }; const defaultOptions = { host: process.env.DB_HOST, port: Number.parseInt(process.env.DB_PORT || "") || 3050, database: process.env.DB_DATABASE, user: process.env.DB_USER || "SYSDBA", password: process.env.DB_PASSWORD, }; class FirebirdQuery { constructor(poolOptions = Object.assign({}, defaultOptions), fqOptions) { const { maxConnections = 15 } = poolOptions, options = __rest(poolOptions, ["maxConnections"]); this.options = options; this.queryLogger = fqOptions === null || fqOptions === void 0 ? void 0 : fqOptions.queryLogger; this.conn = node_firebird_1.default.pool(maxConnections, options); this.defaultIsolationLevel = (fqOptions === null || fqOptions === void 0 ? void 0 : fqOptions.defaultIsolationLevel) || "READ_COMMITTED"; } get db() { return new Promise((res, rej) => { this.conn.get((err, db) => { if (err) { return rej(err); } return res(db); }); }); } manageQuery(query) { return new Promise((res, rej) => { if (this.queryLogger) { this.queryLogger(query); } this.db .then((db) => { db.query(query, [], (err, data) => { if (err) { return rej(err); } db.detach((err) => { if (err) { return rej(err); } return res(data); }); }); }) .catch((err) => rej(err)); }); } get queryRaw() { return this.handleRawQuery((query) => __awaiter(this, void 0, void 0, function* () { return this.manageQuery(query); })); } get insertOne() { return this.handleInsertOne((query) => __awaiter(this, void 0, void 0, function* () { return this.manageQuery(query); })); } get insertMany() { return this.handleInsertMany((query, length) => __awaiter(this, void 0, void 0, function* () { yield this.manageQuery(query); return `${length} rows inserted`; })); } get updateOne() { return this.handleUpdateOne((query) => __awaiter(this, void 0, void 0, function* () { return this.manageQuery(query); })); } get updateOrInsert() { return this.handleUpdateOrInsert((query) => __awaiter(this, void 0, void 0, function* () { return this.manageQuery(query); })); } get deleteOne() { return this.handleDeleteOne((query) => __awaiter(this, void 0, void 0, function* () { return this.manageQuery(query); })); } destroy() { return new Promise((res, rej) => { this.conn.destroy((err) => { if (err) { if (err instanceof Error) { return rej(err); } return rej(new Error("Error destroying connection")); } return res(); }); }); } initTransaction(cb, isolation) { const isolationLevel = isolation || this.defaultIsolationLevel; return new Promise((res, rej) => { node_firebird_1.default.attach(this.options, (err, db) => { if (err) { return rej(new Error("Error Establishing a Database Connection")); } db.transaction(isolationMap[isolationLevel], (err, tx) => { if (err) { return rej(new Error("Error initializing transaction")); } return res(cb(this.txHandler(tx, db))); }); }); }); } handleRawQuery(cb) { return (strings, ...params) => { const sanitizedQuery = sqlBuilder(strings, params); return { getQuery: () => sanitizedQuery, execute: () => { return cb(sanitizedQuery); }, paginated: (take, page = 1) => __awaiter(this, void 0, void 0, function* () { const pagQuery = paginatedQuery(sanitizedQuery, take, page); return cb(pagQuery); }), }; }; } handleInsertOne(cb) { return (params) => { const query = insertOneQuery(params); return { getQuery: () => query, execute: () => { return cb(query); }, }; }; } handleInsertMany(cb) { return (params) => { const query = insertManyQuery(params); return { getQuery: () => query, execute: () => { return cb(query, params.rowValues.length); }, }; }; } handleUpdateOne(cb) { return (params) => { const query = updateOneQuery(params); return { getQuery: () => query, execute: () => { return cb(query); }, }; }; } handleUpdateOrInsert(cb) { return (params) => { const query = updateOrInsertQuery(params); return { getQuery: () => query, execute: () => { return cb(query); }, }; }; } handleDeleteOne(cb) { return (params) => { const query = deleteOneQuery(params); return { getQuery: () => query, execute: () => { return cb(query); }, }; }; } txHandler(tx, db) { const executeTransactionQuery = (query, processResult) => { return new Promise((res, rej) => { if (this.queryLogger) { this.queryLogger(query); } tx.query(query, [], (err, data) => { if (err) { tx.rollback((txErr) => { if (txErr) { return rej(txErr); } }); return rej(err); } return res(processResult(data)); }); }); }; return { queryRaw: this.handleRawQuery((query) => { return executeTransactionQuery(query, (data) => data); }), insertOne: this.handleInsertOne((query) => { return executeTransactionQuery(query, (data) => data); }), insertMany: this.handleInsertMany((query, length) => { return executeTransactionQuery(query, () => `${length} row${length > 1 ? "s" : ""} inserted`); }), updateOne: this.handleUpdateOne((query) => { return executeTransactionQuery(query, (data) => data); }), updateOrInsert: this.handleUpdateOrInsert((query) => { return executeTransactionQuery(query, (data) => data); }), deleteOne: this.handleDeleteOne((query) => { return executeTransactionQuery(query, (data) => data); }), commit: () => { return new Promise((res, rej) => { tx.commit((err) => { if (err) { return rej(err); } db.detach((err) => { if (err) { return rej(err); } return res(); }); }); }); }, rollback: () => new Promise((res, rej) => { tx.rollback((err) => { if (err) { return rej(err); } return res(); }); }), commitRetaining: () => { return new Promise((res, rej) => { tx.commitRetaining((err) => { if (err) { return rej(err); } return res(); }); }); }, rollbackRetaining: () => new Promise((res, rej) => { tx.rollbackRetaining((err) => { if (err) { return rej(err); } return res(); }); }), }; } } exports.FirebirdQuery = FirebirdQuery;