diff --git a/src/queryBuilder/InternalOptions.ts b/src/queryBuilder/InternalOptions.ts new file mode 100644 index 0000000..f126e1a --- /dev/null +++ b/src/queryBuilder/InternalOptions.ts @@ -0,0 +1,32 @@ +import { nany } from "../ninja.ts"; + +export class InternalOptions { + skipUndefined: boolean; + keepImplicitJoinProps: boolean; + returnImmediatelyValue?: nany; + isInternalQuery: boolean; + debug: boolean; + schema?: nany; + + constructor() { + this.skipUndefined = false; + this.keepImplicitJoinProps = false; + this.returnImmediatelyValue = undefined; + this.isInternalQuery = false; + this.debug = false; + this.schema = undefined; + } + + clone() { + const copy = new InternalOptions(); + + copy.skipUndefined = this.skipUndefined; + copy.keepImplicitJoinProps = this.keepImplicitJoinProps; + copy.returnImmediatelyValue = this.returnImmediatelyValue; + copy.isInternalQuery = this.isInternalQuery; + copy.debug = this.debug; + copy.schema = this.schema; + + return copy; + } +} diff --git a/src/queryBuilder/QueryBuilderContext.ts b/src/queryBuilder/QueryBuilderContext.ts new file mode 100644 index 0000000..e96f4fa --- /dev/null +++ b/src/queryBuilder/QueryBuilderContext.ts @@ -0,0 +1,27 @@ +import { nany } from "../ninja.ts"; +import { QueryBuilderContextBase } from "./QueryBuilderContextBase.ts"; + +export class QueryBuilderContext extends QueryBuilderContextBase { + runBefore: nany[]; + runAfter: nany[]; + onBuild: nany[]; + + constructor(builder?: nany) { + super(builder); + + this.runBefore = []; + this.runAfter = []; + this.onBuild = []; + } + + clone() { + const ctx = new QueryBuilderContext(); + super.cloneInto(ctx); + + ctx.runBefore = this.runBefore.slice(); + ctx.runAfter = this.runAfter.slice(); + ctx.onBuild = this.onBuild.slice(); + + return ctx; + } +} diff --git a/src/queryBuilder/QueryBuilderContextBase.ts b/src/queryBuilder/QueryBuilderContextBase.ts new file mode 100644 index 0000000..6e9c5f6 --- /dev/null +++ b/src/queryBuilder/QueryBuilderContextBase.ts @@ -0,0 +1,33 @@ +import { Knex } from "knex"; +import { nany } from "../ninja.ts"; +import { InternalOptions } from "./InternalOptions.ts"; +import { QueryBuilderUserContext } from "./QueryBuilderUserContext.ts"; + +export class QueryBuilderContextBase { + userContext?: QueryBuilderUserContext; + options?: InternalOptions; + knex?: Knex; + aliasMap?: Map; + tableMap?: Map; + + constructor(builder?: nany) { + this.userContext = builder + ? new QueryBuilderUserContext(builder) + : undefined; + this.options = builder ? new InternalOptions() : undefined; + } + + static get InternalOptions() { + return InternalOptions; + } + + cloneInto( + newContext: QueryBuilderContextBase, + ): void { + newContext.userContext = this.userContext; + newContext.options = this.options?.clone(); + newContext.knex = this.knex; + newContext.aliasMap = this.aliasMap; + newContext.tableMap = this.tableMap; + } +} diff --git a/src/queryBuilder/QueryBuilderUserContext.ts b/src/queryBuilder/QueryBuilderUserContext.ts new file mode 100644 index 0000000..65baaf5 --- /dev/null +++ b/src/queryBuilder/QueryBuilderUserContext.ts @@ -0,0 +1,26 @@ +import { nany } from "../ninja.ts"; +import { Knex } from "knex"; + +export class QueryBuilderUserContext { + #builder: nany; + + constructor(builder: nany) { + this.#builder = builder; + } + + get transaction(): Knex { + return this.#builder.knex(); + } + + newFromObject(builder: nany, obj: unknown): QueryBuilderUserContext { + const ctx = new QueryBuilderUserContext(builder); + Object.assign(ctx, obj); + return ctx; + } + + newMerge(builder: nany, obj: unknown): QueryBuilderUserContext { + const ctx = new QueryBuilderUserContext(builder); + Object.assign(ctx, this, obj); + return ctx; + } +} diff --git a/src/queryBuilder/RawBuilder.ts b/src/queryBuilder/RawBuilder.ts new file mode 100644 index 0000000..e8a7f12 --- /dev/null +++ b/src/queryBuilder/RawBuilder.ts @@ -0,0 +1,83 @@ +import { isPlainObject } from "../utils/object.ts"; +import { buildArg } from "../utils/build.ts"; +import { nany } from "../ninja.ts"; +import { Knex } from "knex"; + +export interface BuilderWithKnex { + knex(): Knex; +} + +export class RawBuilder { + #sql: string; + #args: nany[]; + #as?: string; + + constructor(sql: string, args: nany[]) { + this.#sql = `${sql}`; + this.#args = args; + } + + get alias(): string | undefined { + return this.#as; + } + + as(as: string) { + this.#as = as; + return this; + } + + toKnexRaw(builder: BuilderWithKnex): Knex.Raw { + let args = null; + let sql = this.#sql; + + if (this.#args.length === 1 && isPlainObject(this.#args[0])) { + args = buildObject(this.#args[0], builder); + + if (this.#as) { + args.__alias__ = this.#as; + sql += " as :__alias__:"; + } + } else { + args = buildArray(this.#args, builder); + + if (this.#as) { + args.push(this.#as); + sql += " as ??"; + } + } + + return builder.knex().raw(sql, args); + } +} + +export function buildArray(arr: nany[], builder: nany) { + return arr.map((it) => buildArg(it, builder)); +} + +export function buildObject(obj: nany, builder: nany) { + return Object.keys(obj).reduce((args, key) => { + args[key] = buildArg(obj[key], builder); + return args; + }, {} as nany); +} + +export function normalizeRawArgs(argsIn: [string, ...nany[]]) { + const [sql, ...restArgs] = argsIn; + + if (restArgs.length === 1 && Array.isArray(restArgs[0])) { + return { + sql, + args: restArgs[0], + }; + } else { + return { + sql, + args: restArgs, + }; + } +} + +export function raw(...argsIn: [string, ...nany[]]) { + const { sql, args } = normalizeRawArgs(argsIn); + return new RawBuilder(sql, args); +} diff --git a/src/utils/build.ts b/src/utils/build.ts new file mode 100644 index 0000000..ead7474 --- /dev/null +++ b/src/utils/build.ts @@ -0,0 +1,27 @@ +import { Knex } from "knex"; +import { nany } from "../ninja.ts"; +import { isFunction, isObject } from "./object.ts"; + +export interface ToKnexRaw { + toKnexRaw(builder: nany): Knex.Raw; +} +export type Arg = number | string | boolean | ToKnexRaw | nany; // TODO: add QueryBuilderBase type + +// deno-lint-ignore no-explicit-any +function isToKnexRaw(arg: any): arg is ToKnexRaw { + return isFunction(arg.toKnexRaw); +} + +export function buildArg(arg: Arg, builder: nany) { + if (!isObject(arg)) { + return arg; + } + + if (isToKnexRaw(arg)) { + return arg.toKnexRaw(builder); + } else if (arg.isObjectionQueryBuilderBase === true) { // TODO: replace with instance check + return arg.subqueryOf(builder).toKnexQuery(); + } else { + return arg; + } +}