@useorbis/db-sdk
Version:
Orbis' Typescript SDK for building open-data experiences.
215 lines (214 loc) • 6.22 kB
JavaScript
import { $in } from "./operators.js";
import { StatementHistory } from "./historyProvider.js";
import { SqlSelectBuilder } from "./sqlbuild/index.js";
import { catchError } from "../../util/tryit.js";
import { OrbisError } from "../../util/results.js";
// TODO: Improve typing for operators (and enable nested queries)
export class SelectStatement extends StatementHistory {
#orbis;
#table;
#columns = new Set();
#limit;
#offset;
#contexts = [];
#where;
#orderBy;
#raw;
constructor(orbis) {
super();
this.#orbis = orbis;
}
static buildQueryFromJson(jsonQuery) {
const builder = new SqlSelectBuilder(jsonQuery);
return builder.build();
}
#warnUnique(method) {
console.warn(`[QueryBuilder:select] Overwriting existing ${method} data. Only the last .${method}() call will be used.`);
}
#checkRawQuery() {
if (this.#raw) {
throw `[QueryBuilder:select] Raw SQL query has been declared using .raw(), all query building functions have been disabled.`;
}
}
raw(query, params = []) {
if (this.#raw) {
this.#warnUnique("raw");
}
this.#raw = {
query,
params,
};
return this;
}
from(tableName) {
this.#checkRawQuery();
if (this.#table) {
this.#warnUnique("from");
}
this.#table = tableName;
return this;
}
column(column) {
this.#checkRawQuery();
this.#columns.add(column);
return this;
}
columns(...columns) {
this.#checkRawQuery();
const _columns = new Set(columns);
this.#columns = new Set([...this.#columns, ..._columns]);
return this;
}
deselectColumn(column) {
this.#columns.delete(column);
return this;
}
clearColumns() {
this.#columns = new Set();
return this;
}
limit(limit) {
this.#checkRawQuery();
if (this.#limit) {
this.#warnUnique("limit");
}
this.#limit = limit;
return this;
}
offset(offset) {
this.#checkRawQuery();
if (this.#offset) {
this.#warnUnique("offset");
}
this.#offset = offset;
return this;
}
context(context) {
this.#checkRawQuery();
if (this.#contexts.length) {
this.#warnUnique("context");
}
this.#contexts = [context];
return this;
}
contexts(...contexts) {
this.#checkRawQuery();
if (this.#contexts.length) {
this.#warnUnique("contexts");
}
this.#contexts = contexts;
return this;
}
where(whereClause) {
this.#checkRawQuery();
if (this.#where) {
this.#warnUnique("where");
}
this.#where = whereClause;
return this;
}
orderBy(...params) {
this.#checkRawQuery();
if (this.#orderBy) {
this.#warnUnique("orderBy");
}
if (!params.length) {
this.#orderBy = undefined;
return this;
}
this.#orderBy = params;
return this;
}
#checkForImplicitIn(whereClause) {
for (const [key, value] of Object.entries(whereClause)) {
if (!Array.isArray(value)) {
if (typeof value === "object") {
whereClause[key] = this.#checkForImplicitIn(value);
}
continue;
}
if (!["$in", "$and", "$or"].includes(key)) {
whereClause[key] = $in(...value);
continue;
}
whereClause[key] = value.map((condition) => {
if (typeof condition === "object") {
return this.#checkForImplicitIn(condition);
}
return condition;
});
}
return whereClause;
}
get jsonQuery() {
if (this.#raw) {
return {
$raw: {
query: this.#raw.query,
params: this.#raw.params,
},
};
}
if (!this.#table) {
throw "[QueryBuilder:select] Cannot build a select statement without a specified table.";
}
const whereClause = {
...(this.#where || {}),
...((this.#contexts.length && { _metadata_context: this.#contexts }) ||
{}),
};
return {
$table: this.#table,
$columns: Array.from(this.#columns),
$where: this.#checkForImplicitIn(Object.keys(whereClause).length > 1
? {
$and: Object.entries(whereClause).map((v) => ({
[v[0]]: v[1],
})),
}
: whereClause),
$orderBy: this.#orderBy,
$limit: this.#limit,
$offset: this.#offset,
};
}
build() {
const builder = new SqlSelectBuilder(this.jsonQuery);
return builder.build();
}
get runs() {
return super.runs;
}
async run(env) {
const timestamp = Date.now();
const query = this.jsonQuery;
const parsedQuery = this.build();
const [result, error] = await catchError(() => this.#orbis.node.query(query, env));
if (error) {
super.storeResult({
query: {
json: query,
parsed: parsedQuery,
runOverwriteEnv: env,
activeNodeEnv: this.#orbis.node.env,
},
error,
success: false,
timestamp,
});
throw new OrbisError(error.message, { error, query, env });
}
super.storeResult({
query: {
json: query,
parsed: parsedQuery,
runOverwriteEnv: env,
activeNodeEnv: this.#orbis.node.env,
},
result,
success: true,
timestamp,
});
return result;
}
}