UNPKG

@sqb/builder

Version:

Extensible multi-dialect SQL query builder written with TypeScript

294 lines (293 loc) 8.41 kB
import { coerceToInt } from 'putil-varhelpers'; import { SerializationType } from '../enums.js'; import { printArray } from '../helpers.js'; import { FieldExpression } from '../sql-objects/field-expression.js'; import { GroupColumn } from '../sql-objects/group-column.js'; import { OpAnd } from '../sql-objects/operators/op-and.js'; import { OrderColumn } from '../sql-objects/order-column.js'; import { TableName } from '../sql-objects/table-name.js'; import { isJoinStatement, isSerializable } from '../typeguards.js'; import { Query } from './query.js'; export class SelectQuery extends Query { _tables; _columns; _joins; _where; _groupBy; _orderBy; _limit; _offset; _alias; _distinct; constructor(...column) { super(); if (column.length) this.addColumn(...column); } get _type() { return SerializationType.SELECT_QUERY; } /** * Adds columns to query. */ addColumn(...column) { const self = this; this._columns = this._columns || []; for (const arg of column) { if (!arg) continue; if (Array.isArray(arg)) self.addColumn(...arg); else this._columns.push(isSerializable(arg) ? arg : new FieldExpression(arg)); } return this; } /** * Defines "from" part of query. */ from(...table) { this._tables = []; for (const arg of table) { if (!arg) continue; this._tables.push(typeof arg === 'string' ? new TableName(arg) : arg); } return this; } /** * Adds "join" statements to query */ join(...join) { this._joins = this._joins || []; for (const arg of join) { if (!arg) continue; if (!isJoinStatement(arg)) throw new TypeError('Join statement required'); this._joins.push(arg); } return this; } /** * Defines "where" part of query */ where(...condition) { this._where = this._where || new OpAnd(); this._where.add(...condition); return this; } /** * Defines "where" part of query */ groupBy(...field) { this._groupBy = this._groupBy || []; for (const arg of field) { if (!arg) continue; this._groupBy.push(typeof arg === 'string' ? new GroupColumn(arg) : arg); } return this; } /** * Defines "order by" part of query. */ orderBy(...field) { this._orderBy = this._orderBy || []; for (const arg of field) { if (!arg) continue; this._orderBy.push(typeof arg === 'string' ? new OrderColumn(arg) : arg); } return this; } /** * Sets alias for sub-select queries */ as(alias) { this._alias = alias; return this; } /** * Sets limit for query */ limit(limit) { this._limit = coerceToInt(limit); return this; } /** * Sets offset for query */ offset(offset) { this._offset = coerceToInt(offset); return this; } /** * Enables distinct mode */ distinct() { this._distinct = true; return this; } onFetch(listener) { this.on('fetch', listener); return this; } onceFetch(listener) { this.once('fetch', listener); return this; } /** * Performs serialization */ _serialize(ctx) { const o = { columns: this.__serializeSelectColumns(ctx), from: this.__serializeFrom(ctx), join: this.__serializeJoins(ctx), where: this.__serializeWhere(ctx), groupBy: this.__serializeGroupColumns(ctx), orderBy: this.__serializeOrderColumns(ctx), limit: this._limit, offset: this._offset, }; return ctx.serialize(this._type, o, () => { let out = 'select'; if (this._distinct) out += ' distinct'; // columns part /* istanbul ignore else */ if (o.columns) { out += o.columns.indexOf('\n') >= 0 ? '\n\t' + o.columns + '\b' : ' ' + o.columns; } // from part if (o.from) { out += (o.columns.length > 60 || o.columns.indexOf('\n') >= 0 ? '\n' : ' ') + o.from; } // join part if (o.join) out += '\n' + o.join; // where part if (o.where) out += '\n' + o.where; // group by part if (o.groupBy) out += '\n' + o.groupBy; // order by part if (o.orderBy) out += '\n' + o.orderBy; return out; }); } /** * */ __serializeSelectColumns(ctx) { const arr = []; if (this._columns) { for (const t of this._columns) { const s = ctx.anyToSQL(t); // t._serialize(ctx); if (s) { if (t instanceof SelectQuery) { if (!t._alias) throw new TypeError('Alias required for sub-select in columns'); arr.push(s + ' ' + t._alias); } else arr.push(s); } } } return ctx.serialize(SerializationType.SELECT_QUERY_COLUMNS, arr, () => printArray(arr) || '*'); } /** * */ __serializeFrom(ctx) { const arr = []; if (this._tables) { for (const t of this._tables) { const s = t._serialize(ctx); /* istanbul ignore else */ if (s) { if (t instanceof SelectQuery) { if (!t._alias) throw new TypeError('Alias required for sub-select in "from"'); arr.push('\n\t(' + s + ') ' + t._alias); } else arr.push(s); } } } return ctx.serialize(SerializationType.SELECT_QUERY_FROM, arr, () => { const s = arr.join(','); return s ? 'from' + (s.substring(0, 1) !== '\n' ? ' ' : '') + s : ''; }); } /** * */ __serializeJoins(ctx) { const arr = []; if (this._joins) { for (const t of this._joins) { const s = t._serialize(ctx); /* istanbul ignore else */ if (s) arr.push(s); } } return ctx.serialize(SerializationType.SELECT_QUERY_JOIN, arr, () => arr.join('\n')); } /** * */ __serializeWhere(ctx) { if (!this._where) return ''; const s = this._where._serialize(ctx); return ctx.serialize(SerializationType.CONDITIONS_BLOCK, s, () => /* istanbul ignore next */ s ? 'where ' + s : ''); } /** * */ __serializeGroupColumns(ctx) { const arr = []; if (this._groupBy) { for (const t of this._groupBy) { const s = t._serialize(ctx); /* istanbul ignore else */ if (s) arr.push(s); } } return ctx.serialize(SerializationType.SELECT_QUERY_GROUPBY, arr, () => { const s = printArray(arr); return s ? 'group by ' + s : ''; }); } __serializeOrderColumns(ctx) { const arr = []; if (this._orderBy) { for (const t of this._orderBy) { const s = t._serialize(ctx); /* istanbul ignore else */ if (s) arr.push(s); } } return ctx.serialize(SerializationType.SELECT_QUERY_ORDERBY, arr, () => { const s = printArray(arr); return s ? 'order by ' + s : ''; }); } }