@jakub.knejzlik/ts-query
Version:
TypeScript implementation of SQL builder
519 lines (518 loc) • 17.8 kB
JavaScript
"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;