UNPKG

@riao/dbal

Version:
794 lines 26.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DatabaseQueryBuilder = void 0; const statement_builder_1 = require("../builder/statement-builder"); const comparison_1 = require("../comparison"); const tokens_1 = require("../tokens"); const functions_1 = require("../functions"); const expression_1 = require("../expression"); const function_token_1 = require("../functions/function-token"); const subquery_1 = require("./subquery"); const case_expression_1 = require("./case-expression"); class DatabaseQueryBuilder extends statement_builder_1.StatementBuilder { // ------------------------------------------------------------------------ // Expression // ------------------------------------------------------------------------ expression(expr) { if (Array.isArray(expr)) { this.sql.openParens(); for (const e of expr) { this.expression(e); } this.sql.closeParens(); } else if ((0, expression_1.isExpressionToken)(expr)) { const token = expr; if ((0, comparison_1.isComparisonToken)(token)) { this.buildComparisonToken(token); } else if ((0, expression_1.isLogicalToken)(token)) { this.logical(token); } else if ((0, expression_1.isIdentifierToken)(token)) { this.sql.columnName(token.name); } else if ((0, functions_1.isDatabaseFunction)(token)) { this.databaseFunction(token); } else if ((0, expression_1.isMathToken)(token)) { this.math(token); } else if ((0, expression_1.isRawExprToken)(token)) { this.rawExpression(token); } } else if (expr && typeof expr === 'object') { if (expr instanceof Buffer || expr instanceof Date) { this.sql.placeholder(expr); } else if (expr instanceof subquery_1.Subquery) { this.subquery(expr); } else if (expr instanceof case_expression_1.CaseExpression) { this.caseExpression(expr); } else { this.keyValueExpression(expr); } } else { this.sql.placeholder(expr); } } subquery(subquery) { this.sql.openParens(); this.select(subquery.query); this.sql.closeParens(); this.sql.trimEnd(' '); } caseExpression(expr) { this.sql.openParens(); this.sql.append('CASE '); if (expr.value) { this.expression(expr.value); this.sql.space(); } for (const condition of expr.case) { this.sql.append('WHEN '); this.expression(condition.when); this.sql.append('THEN '); this.expression(condition.then); } if (expr.else) { this.sql.append('ELSE '); this.expression(expr.else); } this.sql.append('END'); this.sql.closeParens(); this.sql.trimEnd(' '); } rawExpression(expr) { this.sql.append(expr.sql); if (expr.params) { this.sql.appendParams(expr.params); } return this; } keyValueExpression(kv) { if (!Object.keys(kv).length) { return this; } this.sql.openParens(); for (const key in kv) { const value = kv[key]; this.sql.columnName(key); this.sql.space(); if (value === null) { this.isNull(); } else if ((0, expression_1.isExpressionToken)(value)) { const token = value; if ((0, comparison_1.isComparisonToken)(token)) { this.expression(value); } else if ((0, expression_1.isLogicalToken)(token)) { this.expression(value); } else if ((0, expression_1.isIdentifierToken)(token)) { this.equal(value); } else if ((0, functions_1.isDatabaseFunction)(token)) { this.equal(value); } else if ((0, expression_1.isRawExprToken)(token)) { this.equal(value); } } else { this.equal(value); } this.and(); } this.sql.trimEnd(this.sql.operators.and); this.sql.closeParens(); return this; } // ------------------------------------------------------------------------ // Set // ------------------------------------------------------------------------ set(options) { this.setStatement(); this.setColumn(options); return this; } setStatement() { this.sql.append('SET '); return this; } setColumn(options) { this.sql.columnName(options.column); this.sql.append(' = '); this.expression(options.value); return this; } // ------------------------------------------------------------------------ // Select // ------------------------------------------------------------------------ selectColumn(column) { if (typeof column === 'string') { this.sql.columnName(column); } else if (typeof column === 'object') { if ('column' in column) { this.sql.columnName(column.column); } else if ('query' in column) { this.expression(column.query); } if (column.as) { this.sql.append(' AS '); this.sql.columnName(column.as); } } return this; } selectColumnList(columns) { if (columns) { for (const column of columns) { this.selectColumn(column); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.space(); } else { this.sql.append('* '); } return this; } selectFrom(from) { this.sql.append('FROM '); if (typeof from === 'string') { this.sql.tableName(from); } else if (typeof from === 'object') { for (const alias in from) { const expr = from[alias]; if (typeof expr === 'string') { this.sql.tableName(expr); } else { this.expression(expr); } this.sql.append(' '); this.sql.columnName(alias); this.sql.append(', '); } this.sql.trimEnd(', '); } this.sql.space(); return this; } tableAlias(alias) { this.sql.append('AS '); this.sql.tableName(alias); this.sql.space(); return this; } selectStatement() { this.sql.append('SELECT '); return this; } distinctStatement() { this.sql.append('DISTINCT '); return this; } selectTop(limit) { // This will be overridden in mssql for LIMIT func return this; } // ------------------------------------------------------------------------ // Comparisons // ------------------------------------------------------------------------ equal(value) { this.sql.append(this.sql.operators.equals + ' '); this.expression(value); } like(value) { this.sql.append(this.sql.operators.like + ' '); this.expression(value); } lt(value) { this.sql.append(this.sql.operators.lt + ' '); this.expression(value); } lte(value) { this.sql.append(this.sql.operators.lte + ' '); this.expression(value); } gt(value) { this.sql.append(this.sql.operators.gt + ' '); this.expression(value); } gte(value) { this.sql.append(this.sql.operators.gte + ' '); this.expression(value); } notEqual(value) { this.sql.append(this.sql.operators.notEqual + ' '); this.expression(value); } isNull() { this.sql.append(this.sql.operators.is + ' ' + this.sql.operators.null + ' '); } inArray(values) { this.sql.append(this.sql.operators.in + ' '); this.sql.openParens(); for (const value of values) { this.expression(value); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.closeParens(); } between(a, b) { this.sql.append(this.sql.operators.between + ' '); this.expression(a); this.sql.append('AND '); this.expression(b); } notNull() { this.sql.append('NOT NULL '); } buildComparisonToken(condition) { if (condition.op === comparison_1.ComparisonOperator.EQUALS) { this.equal(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.NOT_EQUAL) { this.notEqual(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.LIKE) { this.like(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.LT) { this.lt(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.LTE) { this.lte(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.GT) { this.gt(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.GTE) { this.gte(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.IN_ARRAY) { this.inArray(condition.value); } else if (condition.op === comparison_1.ComparisonOperator.BETWEEN) { this.between(condition.value.a, condition.value.b); } return this; } // ------------------------------------------------------------------------ // Logical // ------------------------------------------------------------------------ and() { this.sql.append(this.sql.operators.and + ' '); } or() { this.sql.append(this.sql.operators.or + ' '); } not(value) { if (value === null) { this.sql.append(this.sql.operators.is + ' '); this.notNull(); return; } const isComparison = (0, expression_1.isExpressionToken)(value) && (0, comparison_1.isComparisonToken)(value); const conditionType = isComparison ? value === null || value === void 0 ? void 0 : value.op : null; if (isComparison && (conditionType === comparison_1.ComparisonOperator.LIKE || conditionType === comparison_1.ComparisonOperator.IN_ARRAY)) { this.sql.append('NOT '); this.buildComparisonToken(value); } else if (isComparison) { throw new Error('Cannot use not() with ' + conditionType); } else if (typeof value === 'object' || Array.isArray(value)) { this.sql.append('!'); this.expression(value); } else { this.notEqual(value); } } logical(token) { if (token.op === expression_1.LogicalOperator.AND) { this.and(); } else if (token.op === expression_1.LogicalOperator.OR) { this.or(); } else if (token.op === expression_1.LogicalOperator.NOT) { this.not(token.expr); } } // ------------------------------------------------------------------------ // Math // ------------------------------------------------------------------------ addition() { this.sql.append(this.sql.operators.addition + ' '); } subtraction() { this.sql.append(this.sql.operators.subtraction + ' '); } multiplication() { this.sql.append(this.sql.operators.multiplication + ' '); } division() { this.sql.append(this.sql.operators.division + ' '); } modulo() { this.sql.append(this.sql.operators.modulo + ' '); } math(token) { if (token.op === expression_1.MathOperation.ADD) { this.addition(); } if (token.op === expression_1.MathOperation.SUB) { this.subtraction(); } if (token.op === expression_1.MathOperation.MUL) { this.multiplication(); } if (token.op === expression_1.MathOperation.DIV) { this.division(); } if (token.op === expression_1.MathOperation.MOD) { this.modulo(); } } // ------------------------------------------------------------------------ // Where // ------------------------------------------------------------------------ where(where) { this.sql.append('WHERE '); this.expression(where); return this; } limit(nRecords) { this.sql.append('LIMIT ' + nRecords + ' '); return this; } offset(nRecords) { this.sql.append('OFFSET ' + nRecords + ' '); return this; } groupBy(by) { this.sql.append('GROUP BY '); for (const key of by) { this.sql.columnName(key); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.space(); return this; } having(query) { this.sql.append('HAVING '); this.expression(query); return this; } orderBy(by) { this.sql.append('ORDER BY '); for (const key in by) { this.sql.columnName(key); this.sql.append(' ' + by[key] + ', '); } this.sql.trimEnd(', '); this.sql.space(); } select(query) { var _a, _b; this.selectStatement(); if (query.distinct) { this.distinctStatement(); } if ('top' in query) { this.selectTop(query.top); } this.selectColumnList(query.columns); if (query.table) { this.selectFrom(query.table); } if (query.tableAlias) { this.tableAlias(query.tableAlias); } for (const join of (_a = query.join) !== null && _a !== void 0 ? _a : []) { this.join(join); } if (query.where) { this.where(query.where); } if ((_b = query.groupBy) === null || _b === void 0 ? void 0 : _b.length) { this.groupBy(query.groupBy); } if (query.having) { this.having(query.having); } if (query.orderBy) { this.orderBy(query.orderBy); } this.pagination(query); return this; } pagination(query) { if (query.limit) { this.limit(query.limit); } if (query.offset !== undefined) { this.offset(query.offset); } return this; } // ------------------------------------------------------------------------ // Join // ------------------------------------------------------------------------ join(join) { var _a; this.sql.append(((_a = join.type) !== null && _a !== void 0 ? _a : '') + ' JOIN '); this.sql.tableName(join.table); this.sql.space(); if (join.alias) { this.tableAlias(join.alias); } if (join.on) { this.sql.append('ON '); this.expression(join.on); } } // ------------------------------------------------------------------------ // Insert // ------------------------------------------------------------------------ insertIntoStatement(table) { this.sql.append('INSERT INTO '); this.sql.tableName(table); this.sql.space(); return this; } insertColumnNames(record) { this.sql.openParens(); this.sql.commaSeparate(Object.keys(record).map((name) => this.sql.getEnclosedName(name))); this.sql.closeParens(); return this; } insertOnDuplicateKeyUpdate(assignment) { this.sql.append('ON DUPLICATE KEY UPDATE '); this.updateKeyValues(assignment); return this; } insertIfNotExists() { this.insertOnDuplicateKeyUpdate({ id: (0, tokens_1.columnName)('id') }); return this; } insertOutput(primaryKey) { // This will be overridden in mssql to return the insert id(s) return this; } insertReturning(primaryKey) { // This will be overridden in postgres to return the insert id(s) return this; } insert(options) { var _a; this.insertIntoStatement(options.table); if (!Array.isArray(options.records)) { options.records = [options.records]; } const columns = {}; const insertions = []; if (options.records.length) { for (const rec of options.records) { for (const key in rec) { if (!(key in columns)) { columns[key] = true; } } } for (const rec of options.records) { const insertion = {}; for (const key in columns) { insertion[key] = (_a = rec[key]) !== null && _a !== void 0 ? _a : null; } insertions.push(insertion); } this.insertColumnNames(columns); } if (options.primaryKey) { this.insertOutput(options.primaryKey); } this.sql.append('VALUES '); for (let i = 0; i < insertions.length; i++) { const record = insertions[i]; this.sql.openParens(); for (const key in record) { this.expression(record[key]); this.sql = this.sql.trimEnd(); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.closeParens(); this.sql = this.sql.trimEnd(); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.space(); if (options.ifNotExists) { this.insertIfNotExists(); } else if (options.onDuplicateKeyUpdate) { this.insertOnDuplicateKeyUpdate(options.onDuplicateKeyUpdate); } this.sql.trimEnd(' '); if (options.primaryKey) { this.insertReturning(options.primaryKey); } return this; } // ------------------------------------------------------------------------ // Update // ------------------------------------------------------------------------ updateStatement(table) { this.sql.append('UPDATE '); this.sql.tableName(table); this.sql.space(); return this; } updateKeyValues(values) { const keys = Object.keys(values); for (const key of keys) { this.sql.columnName(key); this.sql.append(' = '); this.expression(values[key]); this.sql = this.sql.trimEnd(); this.sql.append(', '); } this.sql.trimEnd(', '); this.sql.space(); return this; } updateSetStatement(values) { this.sql.append('SET '); this.updateKeyValues(values); return this; } update(options) { var _a; this.updateStatement(options.table); for (const join of (_a = options.join) !== null && _a !== void 0 ? _a : []) { this.join(join); } this.updateSetStatement(options.set); if (options.from) { this.selectFrom(options.from); } if (options.where) { this.where(options.where); } return this; } // ------------------------------------------------------------------------ // Delete // ------------------------------------------------------------------------ deleteStatement(table) { this.sql.append('DELETE FROM '); this.sql.tableName(table); this.sql.space(); return this; } delete(options) { var _a; this.deleteStatement(options.table); for (const join of (_a = options.join) !== null && _a !== void 0 ? _a : []) { this.join(join); } if (options.where) { this.where(options.where); } return this; } // ------------------------------------------------------------------------ // Database Functions // ------------------------------------------------------------------------ /** * Append a database function the query * * @param fn Database function token */ databaseFunction(fn) { switch (fn.fn) { case function_token_1.DatabaseFunctionKeys.AVERAGE: this.average(fn); break; case function_token_1.DatabaseFunctionKeys.COUNT: this.count(fn); break; case function_token_1.DatabaseFunctionKeys.MIN: this.min(fn); break; case function_token_1.DatabaseFunctionKeys.MAX: this.max(fn); break; case function_token_1.DatabaseFunctionKeys.SUM: this.sum(fn); break; case function_token_1.DatabaseFunctionKeys.CURRENT_TIMESTAMP: this.currentTimestamp(fn); break; case function_token_1.DatabaseFunctionKeys.DATE: this.date(fn); break; case function_token_1.DatabaseFunctionKeys.YEAR: this.year(fn); break; case function_token_1.DatabaseFunctionKeys.UUID: this.uuid(); break; } return this; } average(fn) { this.sql.append('AVG'); this.sql.openParens(); if (fn.params.options.distinct) { this.distinctStatement(); } this.expression(fn.params.expr); this.sql.closeParens(); return this; } count(fn) { var _a, _b, _c; this.sql.append('COUNT('); if ((_a = fn.params) === null || _a === void 0 ? void 0 : _a.distinct) { this.distinctStatement(); } if ((_b = fn.params) === null || _b === void 0 ? void 0 : _b.expr) { this.expression(fn.params.expr); } else if ((_c = fn.params) === null || _c === void 0 ? void 0 : _c.column) { this.sql.columnName(fn.params.column); } else { this.sql.append('*'); } this.sql.append(')'); return this; } min(fn) { this.sql.append('MIN'); this.sql.openParens(); this.expression(fn.params); this.sql.closeParens(); return this; } max(fn) { this.sql.append('MAX'); this.sql.openParens(); this.expression(fn.params); this.sql.closeParens(); return this; } sum(fn) { this.sql.append('SUM'); this.sql.openParens(); if (fn.params.options.distinct) { this.distinctStatement(); } this.expression(fn.params.expr); this.sql.closeParens(); return this; } currentTimestamp(fn) { this.sql.append('CURRENT_TIMESTAMP'); return this; } date(fn) { var _a; this.sql.append('date'); this.sql.openParens(); if ((_a = fn.params) === null || _a === void 0 ? void 0 : _a.expr) { this.expression(fn.params.expr); } else { this.expression(functions_1.DatabaseFunctions.currentTimestamp()); } this.sql.closeParens(); return this; } year(fn) { var _a; this.sql.append('year'); this.sql.openParens(); if ((_a = fn.params) === null || _a === void 0 ? void 0 : _a.expr) { this.expression(fn.params.expr); } else { this.expression(functions_1.DatabaseFunctions.currentTimestamp()); } this.sql.closeParens(); return this; } uuid() { this.sql.append('uuid()'); return this; } // ------------------------------------------------------------------------ // Triggers // ------------------------------------------------------------------------ getTriggerOld() { return 'OLD'; } getTriggerOldColumn(column) { return `${this.getTriggerOld()}.${column}`; } getTriggerNew() { return 'NEW'; } getTriggerNewColumn(column) { return `${this.getTriggerNew()}.${column}`; } triggerSetValue(options) { return this.set({ column: `NEW.${options.column}`, value: options.value, }); } } exports.DatabaseQueryBuilder = DatabaseQueryBuilder; //# sourceMappingURL=query-builder.js.map