@riao/dbal
Version:
794 lines • 26.3 kB
JavaScript
"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