UNPKG

@jakub.knejzlik/ts-query

Version:

TypeScript implementation of SQL builder

519 lines (518 loc) 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Q = exports.Query = exports.SelectQuery = exports.UnionType = exports.QueryBase = exports.escapeTable = exports.Table = void 0; const dayjs_1 = require("dayjs"); const Condition_1 = require("./Condition"); const CreateTableAsSelect_1 = require("./CreateTableAsSelect"); const CreateViewAsSelect_1 = require("./CreateViewAsSelect"); const Expression_1 = require("./Expression"); const aws_timestream_1 = require("./flavors/aws-timestream"); const mysql_1 = require("./flavors/mysql"); const sqlite_1 = require("./flavors/sqlite"); const Function_1 = require("./Function"); const interfaces_1 = require("./interfaces"); const Mutation_1 = require("./Mutation"); const flavors = { mysql: new mysql_1.MySQLFlavor(), awsTimestream: new aws_timestream_1.AWSTimestreamFlavor(), sqlite: new sqlite_1.SQLiteFlavor(), }; class Table { constructor(source, alias) { this.source = source; this.alias = alias; } clone() { return new this.constructor(this.source, this.alias); } getTableName() { var _a; if (typeof this.source === "string") { return this.source; } return (_a = this.source.table) === null || _a === void 0 ? void 0 : _a.getTableName(); } toSQL(flavor, options) { let table = this.source; if (!isSelectQuery(table) && (options === null || options === void 0 ? void 0 : options.transformTable)) { table = options.transformTable(table); options = Object.assign(Object.assign({}, options), { transformTable: undefined }); } const isSelect = isSelectQuery(table); let alias = this.alias; const tableName = (0, exports.escapeTable)(table, flavor, options); if (isSelect && !alias) alias = "t"; return `${tableName}${alias ? ` AS ${flavor.escapeColumn(alias)}` : ""}`; } toJSON() { return { type: "Table", source: this.source, alias: this.alias, }; } static fromJSON(json) { if (typeof json.source === "object" && json.source["type"] === interfaces_1.OperationType.SELECT) { return new Table(SelectQuery.fromJSON(json.source), json.alias); } return new Table(json.source, json.alias); } serialize() { return JSON.stringify(this.toJSON()); } static deserialize(json) { return Table.fromJSON(JSON.parse(json)); } } exports.Table = Table; function isSelectQuery(table) { return table instanceof SelectQuery; } const escapeTable = (table, flavor, options) => { if (isSelectQuery(table)) return `(${table.toSQL(flavor, options)})`; return flavor.escapeTable(table); }; exports.escapeTable = escapeTable; class QueryBase { constructor() { this._tables = []; this._joins = []; } getOperationType() { return interfaces_1.MetadataOperationType.SELECT; } // @ts-ignore get table() { if (this._tables.length === 0) return undefined; return this._tables[0]; } get tables() { return this._tables; } from(table, alias) { const clone = this.clone(); if (isSelectQuery(table)) { clone._tables = [new Table(table.clone(), alias)]; } else { clone._tables = [new Table(table, alias)]; } return clone; } getTableNames() { return [ ...this._tables.map((t) => t.getTableName()).filter((x) => x), ...this._joins.map((j) => j.getTableName()).filter((x) => x), ]; } /** * join function to join tables with all join types */ join(table, condition, type = "INNER") { const clone = this.clone(); clone._joins.push(new Join(table, condition, type)); return clone; } innerJoin(table, condition) { return this.join(table, condition, "INNER"); } leftJoin(table, condition) { return this.join(table, condition, "LEFT"); } rightJoin(table, condition) { return this.join(table, condition, "RIGHT"); } fullJoin(table, condition) { return this.join(table, condition, "FULL"); } crossJoin(table, condition) { return this.join(table, condition, "CROSS"); } clone() { const clone = new this.constructor(); clone._tables = [...this._tables.map((t) => t.clone())]; clone._joins = [...this._joins.map((j) => j.clone())]; return clone; } toSQL(flavor, options) { return this.tables.length > 0 ? `FROM ${this.tables .map((table) => table.toSQL(flavor, options)) .join(",")}` : ""; } } exports.QueryBase = QueryBase; class Join { constructor(table, condition, type = "INNER") { this._table = table; this._condition = condition; this._type = type; } clone() { const clone = new this.constructor(this._table, this._condition, this._type); return clone; } getTableName() { return this._table.getTableName(); } toSQL(flavor, options) { return `${this._type} JOIN ${this._table.toSQL(flavor, options)}${this._condition ? ` ON ${this._condition.toSQL(flavor)}` : ""}`; } toJSON() { var _a; return { type: "Join", table: this._table.toJSON(), condition: (_a = this._condition) === null || _a === void 0 ? void 0 : _a.toJSON(), joinType: this._type, }; } static fromJSON(json) { return new Join(Table.fromJSON(json.table), json.condition && Condition_1.Condition.fromJSON(json.condition), json.joinType); } serialize() { return JSON.stringify(this.toJSON()); } static deserialize(json) { return Join.fromJSON(JSON.parse(json)); } } class SelectBaseQuery extends QueryBase { constructor() { super(...arguments); this._fields = []; } clone() { const clone = super.clone(); clone._fields = [...this._fields]; return clone; } // @deprecated please use addField field(name, alias) { return this.addFields([{ name, alias }]); } // add single field addField(name, alias) { return this.addFields([{ name, alias }]); } // add multiple fields addFields(fields) { const clone = this.clone(); clone._fields.push(...fields.map((f) => ({ name: Expression_1.Expression.deserialize(f.name), alias: f.alias, }))); return clone; } removeFields() { return this.fields([]); } // reset fields fields(fields) { const clone = this.clone(); clone._fields = []; return clone.addFields(fields); } toSQL(flavor, options) { const columns = this._fields.length > 0 ? this._fields .map((f) => `${Expression_1.Expression.deserialize(f.name).toSQL(flavor, options)}${f.alias ? ` AS ${flavor.escapeColumn(f.alias)}` : ""}`) .join(", ") : "*"; return `SELECT ${columns} ${super.toSQL(flavor, options)}`; } } var UnionType; (function (UnionType) { UnionType["UNION"] = "UNION"; UnionType["UNION_ALL"] = "UNION ALL"; })(UnionType || (exports.UnionType = UnionType = {})); class SelectQuery extends SelectBaseQuery { constructor() { super(...arguments); this._where = []; this._having = []; this._orderBy = []; this._groupBy = []; this._unionQueries = []; } clone() { const clone = super.clone(); clone._where = [...this._where]; clone._having = [...this._having]; clone._limit = this._limit; clone._offset = this._offset; clone._orderBy = [...this._orderBy]; clone._groupBy = [...this._groupBy]; clone._unionQueries = this._unionQueries.map((u) => ({ query: u.query.clone(), type: u.type, })); return clone; } where(condition) { if (condition === null) return this; const clone = this.clone(); clone._where.push(condition); return clone; } removeWhere() { const clone = this.clone(); clone._where = []; return clone; } having(condition) { const clone = this.clone(); clone._having.push(condition); return clone; } removeHaving() { const clone = this.clone(); clone._having = []; return clone; } getLimit() { return this._limit; } clearLimit() { const clone = this.clone(); clone._limit = undefined; return clone; } limit(limit) { const clone = this.clone(); clone._limit = limit; return clone; } getOffset() { return this._offset; } clearOffset() { const clone = this.clone(); clone._offset = undefined; return clone; } offset(offset) { const clone = this.clone(); clone._offset = offset; return clone; } getOrderBy() { return this._orderBy; } orderBy(field, direction = "ASC") { const clone = this.clone(); clone._orderBy.push({ field: Expression_1.Expression.deserialize(field), direction, }); return clone; } removeOrderBy() { const clone = this.clone(); clone._orderBy = []; return clone; } getGroupBy() { return this._groupBy; } groupBy(...field) { const clone = this.clone(); clone._groupBy.push(...field.map((f) => Expression_1.Expression.deserialize(f))); return clone; } removeGroupBy() { const clone = this.clone(); clone._groupBy = []; return clone; } union(query, type = UnionType.UNION) { const clone = this.clone(); clone._unionQueries.push({ query, type }); return clone; } getTableNames() { return Array.from(new Set([ ...super.getTableNames(), ...this._unionQueries.reduce((acc, u) => [...acc, ...u.query.getTableNames()], []), ])); } toSQL(flavor = flavors.mysql, options, transformProcessed = false) { var _a; let sql = ""; if ((options === null || options === void 0 ? void 0 : options.transformSelectQuery) && !transformProcessed) { const q = options.transformSelectQuery(this); return q.toSQL(flavor, options, true); } else { sql = super.toSQL(flavor, options); } if (((_a = this._joins) === null || _a === void 0 ? void 0 : _a.length) > 0) { sql += ` ${this._joins.map((j) => j.toSQL(flavor, options)).join(" ")}`; } if (this._where.length > 0) { sql += ` WHERE ${this._where.map((w) => w.toSQL(flavor)).join(" AND ")}`; } if (this._groupBy.length > 0) { sql += ` GROUP BY ${this._groupBy .map((c) => c.toSQL(flavor)) .join(", ")}`; } if (this._having.length > 0) { sql += ` HAVING ${this._having .map((w) => w.toSQL(flavor)) .join(" AND ")}`; } if (this._orderBy.length > 0) { sql += ` ORDER BY ${this._orderBy .map((o) => `${o.field.toSQL(flavor)} ${o.direction}`) .join(", ")}`; } sql += flavor.escapeLimitAndOffset(this._limit, this._offset); this._unionQueries.forEach((unionQuery) => { sql = flavor.escapeUnion(unionQuery.type, sql, unionQuery.query.toSQL(flavor, options)); }); return sql; } // serialization serialize() { return JSON.stringify(this.toJSON()); } toJSON() { return { type: interfaces_1.OperationType.SELECT, tables: this._tables.map((table) => typeof table === "string" ? table : table.toJSON()), unionQueries: this._unionQueries.length > 0 ? this._unionQueries.map((u) => ({ type: u.type, query: u.query.toJSON(), })) : undefined, joins: this._joins.length > 0 ? this._joins.map((join) => join.toJSON()) : undefined, fields: this._fields.length > 0 ? this._fields.map((f) => ({ name: Expression_1.Expression.deserialize(f.name).serialize(), alias: f.alias, })) : undefined, where: this._where.length > 0 ? this._where.map((condition) => condition.toJSON()) : undefined, having: this._having.length > 0 ? this._having.map((condition) => condition.toJSON()) : undefined, orderBy: this._orderBy.length > 0 ? this._orderBy.map((o) => ({ field: o.field.serialize(), direction: o.direction, })) : undefined, groupBy: this._groupBy.length > 0 ? this._groupBy.map((c) => c.serialize()) : undefined, limit: this._limit, offset: this._offset, }; } static fromJSON(json) { var _a, _b, _c; const query = new SelectQuery(); query._tables = json.tables.map((table) => { return Table.fromJSON(table); }); query._unionQueries = (json.unionQueries || []).map((u) => ({ type: u.type, query: SelectQuery.fromJSON(u.query), })); query._joins = (json.joins || []).map((joinJson) => Join.fromJSON(joinJson)); query._fields = ((_a = json.fields) !== null && _a !== void 0 ? _a : []).map((field) => ({ name: Expression_1.Expression.deserialize(field.name), alias: field.alias, })); query._where = (json.where || []).map((conditionJson) => Condition_1.Condition.fromJSON(conditionJson)); query._having = (json.having || []).map((conditionJson) => Condition_1.Condition.fromJSON(conditionJson)); query._limit = json.limit; query._offset = json.offset; query._orderBy = ((_b = json.orderBy) !== null && _b !== void 0 ? _b : []).map((o) => ({ field: Expression_1.Expression.deserialize(o.field), direction: o.direction, })); query._groupBy = ((_c = json.groupBy) !== null && _c !== void 0 ? _c : []).map((v) => Expression_1.Expression.deserialize(v)); return query; } } exports.SelectQuery = SelectQuery; const deserialize = (json) => { try { const parsed = JSON.parse(json); switch (parsed.type) { case interfaces_1.OperationType.SELECT: return SelectQuery.fromJSON(parsed); case interfaces_1.OperationType.DELETE: return Mutation_1.DeleteMutation.fromJSON(parsed); case interfaces_1.OperationType.INSERT: return Mutation_1.InsertMutation.fromJSON(parsed); case interfaces_1.OperationType.UPDATE: return Mutation_1.UpdateMutation.fromJSON(parsed); case interfaces_1.OperationType.CREATE_TABLE_AS: return CreateTableAsSelect_1.CreateTableAsSelect.fromJSON(parsed); case interfaces_1.OperationType.CREATE_VIEW_AS: return CreateViewAsSelect_1.CreateViewAsSelect.fromJSON(parsed); default: throw new Error("Unknown mutation type"); } } catch (e) { throw new Error(`Error parsing query: ${e.message}`); } }; const deserializeRaw = (json) => { try { return deserialize(json); } catch (e) { return Expression_1.Expression.deserialize(json); } }; const inputValueToExpressionValue = (val) => { if ((0, dayjs_1.isDayjs)(val)) return val.toDate(); return val; }; exports.Query = { table: (name, alias) => new Table(name, alias), select: () => { return new SelectQuery(); }, stats: () => new SelectQuery().from("(?)", "t"), delete: (from, alias) => new Mutation_1.DeleteMutation(from, alias), update: (table, alias) => new Mutation_1.UpdateMutation(table, alias), insert: (into) => new Mutation_1.InsertMutation(into), createTableAs: (table, select) => new CreateTableAsSelect_1.CreateTableAsSelect(table, select), createViewAs: (table, select) => new CreateViewAsSelect_1.CreateViewAsSelect(table, select), createOrReplaceViewAs: (table, select) => new CreateViewAsSelect_1.CreateViewAsSelect(table, select, true), deserialize, deserializeRaw, flavors, null: () => new Expression_1.RawExpression("NULL"), raw: (val) => new Expression_1.RawExpression(val), expr: (val) => Expression_1.ExpressionBase.deserialize(inputValueToExpressionValue(val)), exprValue: (val) => Expression_1.ExpressionBase.deserializeValue(inputValueToExpressionValue(val)), value: (val) => Expression_1.ExpressionBase.deserializeValue(inputValueToExpressionValue(val)), column: (col) => Expression_1.Expression.escapeColumn(col), S: (literals) => { return Function_1.Fn.string(`${literals}`); }, string: (literals) => { return Function_1.Fn.string(`${literals}`); }, }; exports.Q = exports.Query;