UNPKG

@jakub.knejzlik/ts-query

Version:

TypeScript implementation of SQL builder

137 lines (136 loc) 5.77 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultFlavor = void 0; const dayjs_1 = __importDefault(require("dayjs")); const Expression_1 = require("../Expression"); const utc_1 = __importDefault(require("dayjs/plugin/utc")); const timezone_1 = __importDefault(require("dayjs/plugin/timezone")); dayjs_1.default.extend(utc_1.default); dayjs_1.default.extend(timezone_1.default); class DefaultFlavor { constructor(options) { this.options = options; this.columnQuotes = "`"; this.stringQuotes = `"`; this.options = options; } escapeColumn(name, legacyParsing) { if (name === "NULL") { return name; } if (legacyParsing) { // Handle unary minus prefix: -column_name → -`column_name` const unaryMinusMatch = name.match(/^-([a-zA-Z_][a-zA-Z0-9_.]*\*?)$/); if (unaryMinusMatch) { return `-${this.escapeColumn(unaryMinusMatch[1], legacyParsing)}`; } const columnMatch = name.match(/^[\.a-zA-Z0-9_*]+$/); if (columnMatch) { return name .split(".") .map(part => part === "*" ? "*" : `${this.columnQuotes}${part.replace(new RegExp(`/${this.columnQuotes}/`, "g"), `${this.columnQuotes}${this.columnQuotes}`)}${this.columnQuotes}`) .join("."); } // In legacy mode, allow through values that appear to be: // - Already escaped (contains column quotes) // - Complex expressions (parentheses, operators, spaces) // - String literals (already quoted with string quotes) // - Special values like *, +0, -1, TRUE, FALSE if (name.includes(this.columnQuotes) || name.includes(this.stringQuotes) || name.includes('(') || name.includes(')') || name.includes(' ') || name.includes(',') || /^[+\-]?\d+$/.test(name) || /^[*]$/.test(name) || name === 'TRUE' || name === 'FALSE') { return `${name}`; } // Escape the column name to prevent SQL injection return `${this.columnQuotes}${name .replace(new RegExp(this.columnQuotes, "g"), `${this.columnQuotes}${this.columnQuotes}`) .split(".") .join(`${this.columnQuotes}.${this.columnQuotes}`)}${this.columnQuotes}`; } return name .split(".") .map(part => part === "*" ? "*" : `${this.columnQuotes}${part}${this.columnQuotes}`) .join("."); } escapeTable(table) { if (table.indexOf("-") !== -1) { return `${this.columnQuotes}${table}${this.columnQuotes}`; } return this.escapeColumn(table); } escapeRawValue(value) { return `${value}`; } escapeValue(value) { var _a; if (value === null) { return "NULL"; } if (value instanceof Date) { return this.escapeValue((0, dayjs_1.default)(value).tz((_a = this.options) === null || _a === void 0 ? void 0 : _a.timezone).format("YYYY-MM-DD HH:mm:ss")); } if (typeof value === "string") { return `${this.stringQuotes}${value.replace(new RegExp(`${this.stringQuotes}`, "g"), `${this.stringQuotes}${this.stringQuotes}`)}${this.stringQuotes}`; } return `${value}`; } escapeLimitAndOffset(limit, offset) { let str = ""; if (limit !== undefined) { str += ` LIMIT ${limit}`; } if (offset !== undefined) { str += ` OFFSET ${offset}`; } return str; } escapeFunction(fn) { const args = fn.value .map((x) => Expression_1.Expression.deserialize(x)) .map((x) => x.toSQL(this)); if (fn.name === "DATEADD") { const argsValues = fn.value.map((x) => Expression_1.ExpressionBase.deserializeValue(x)); const interval = parseInt(argsValues[1].value.toString(), 10); if (isNaN(interval)) { throw new Error(`Invalid DATEADD interval: ${argsValues[1].value}`); } const intervalType = argsValues[2].value.toString().toLowerCase(); // MySQL supports: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, WEEK, QUARTER const validIntervalTypes = ['year', 'month', 'day', 'hour', 'minute', 'second', 'week', 'quarter']; if (!validIntervalTypes.includes(intervalType)) { throw new Error(`Invalid DATEADD interval type: ${intervalType}`); } return `DATE_ADD(${args[0]}, INTERVAL ${interval} ${intervalType.toUpperCase()})`; } return `${fn.name}(${args.join(",")})`; } escapeOperation(fn) { return ("(" + fn.value .map((x) => Expression_1.Expression.deserialize(x).toSQL(this)) .join(` ${fn.operation} `) + ")"); } escapeUnion(unionType, leftSQL, rightSQL) { return `(${leftSQL}) ${unionType} (${rightSQL})`; } escapeLikeCondition(column, pattern, isLike, caseInsensitive) { const notPrefix = isLike ? "" : "NOT "; if (caseInsensitive) { // MySQL/SQLite: use LOWER() wrapper return `LOWER(${column}) ${notPrefix}LIKE LOWER(${pattern})`; } return `${column} ${notPrefix}LIKE ${pattern}`; } } exports.DefaultFlavor = DefaultFlavor;