firebird-query
Version:
node-firebird plugin for easy and safe query building.
483 lines (482 loc) • 18.7 kB
JavaScript
;
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;