import {Constraint} from "../types"; import {AbstractBuilder} from "../builder/AbstractBuilder"; import {AppClass} from "../../lifecycle/AppClass"; /** * A value which can be escaped to be interpolated into an SQL query. */ export type EscapeValue = null | undefined | string | number | boolean | Date | QuerySafeValue | EscapeValue[] // FIXME | Select /** * Object mapping string field names to EscapeValue items. */ export type EscapeValueObject = { [field: string]: EscapeValue } /** * A wrapper class whose value is save to inject directly into a query. */ export class QuerySafeValue { constructor( /** The unescaped value. */ public readonly originalValue: any, /** The query-safe sanitized value. */ public readonly value: any, ) { } /** Cast the value to a query-safe string. */ toString() { return this.value } } /** * Treat the value as raw SQL that can be injected directly into a query. * This is dangerous and should NEVER be used to wrap user input. * @param value */ export function raw(value: any) { return new QuerySafeValue(value, value) } /** * Abstract class defining a particular dialect of SQL that is used to render * query builders to strings of SQL of that dialect for execution by Connection * instances. */ export abstract class SQLDialect extends AppClass { /** * Escape the given value and return the query-safe equivalent. * @param value */ public abstract escape(value: EscapeValue): QuerySafeValue /** * Render the given query builder as a "SELECT ..." query string. * * This function should escape the values before they are included in the query string. * @param builder */ public abstract renderSelect(builder: AbstractBuilder): string; /** * Render the given query builder as an "UPDATE ..." query string, setting the * column values from the given data object. * * This function should escape the values before they are included in the query string. * @param builder * @param data */ public abstract renderUpdate(builder: AbstractBuilder, data: {[key: string]: EscapeValue}): string; /** * Render the given query builder as a "DELETE ..." query string. * * This function should escape the values before they are included in the query string. * @param builder */ public abstract renderDelete(builder: AbstractBuilder): string; /** * Render the given query builder as a query that can be used to test if at * least 1 row exists for the given builder. * * The resultant query should return at least 1 row if that condition is met, * and should return NO rows otherwise. * * This function should escape the values before they are included in the query string. * * @example * The PostgreSQL dialect achieves this by removing the user-specified fields, * select-ing `TRUE`, and applying `LIMIT 1` to the query. This returns a single * row if the constraints have results, and nothing otherwise. * * @param builder */ public abstract renderExistential(builder: AbstractBuilder): string; /** * Render the given query as an "INSERT ..." query string, inserting rows for * the given data object(s). * * This function should escape the values before they are included in the query string. * * @param builder * @param data */ public abstract renderInsert(builder: AbstractBuilder, data: {[key: string]: EscapeValue}|{[key: string]: EscapeValue}[]): string; /** * Wrap the given query string as a "SELECT ..." query that returns the number of * rows matched by the original query string. * * The resultant query should return the `extollo_render_count` field with the * number of rows that the original `query` would return. * * @param query */ public abstract renderCount(query: string): string; /** * Given a rendered "SELECT ..." query string, wrap it such that the query will * only return the rows ranging from the `start` to `end` indices. * * @param query * @param start * @param end */ public abstract renderRangedSelect(query: string, start: number, end: number): string; /** * Given an array of Constraint objects, render them as WHERE-clause SQL in this dialect. * * This function should escape the values before they are included in the query string. * * @example * ```ts * dialect.renderConstraints([ * { * field: 'id', * operator: '<', * operand: 44, * preop: 'AND', * }, * { * field: 'id', * operator: '>', * operand: 30, * preop: 'AND', * }, * ]) // => 'id < 44 AND id > 30' * ``` * * @param constraints */ public abstract renderConstraints(constraints: Constraint[]): string; /** * Render the "SET ... [field = value ...]" portion of the update query. * * This function should escape the values before they are included in the query string. * * @example * dialect.renderUpdateSet({field1: 'value', field2: 45}) * // => "SET field1 = 'value', field2 = 45" * * @param data */ public abstract renderUpdateSet(data: {[key: string]: EscapeValue}): string; }