import { Exp } from "./Exp"; import { isNull } from "./Operators"; import { SQL } from "./SQL"; import { SqlType } from "./SqlType"; import { Unsafe } from "./Unsafe"; /** * A database column. A column is often a literal column table, but can also * be an expression over such a column or a constant expression. * * @param s Phantom type parameter. This will never have a concrete type. It * is used only to enforce type safety * * @param a The type of the value that the Column contains */ export class Col { protected dummy: [Col, s, a]; } export function colWrap(val: Exp): Col { return val; } export function colUnwrap(val: Col): Exp { return val; } export function liftC2(f: (x: Exp, y: Exp) => Exp): ((x: Col, y: Col) => Col) { return (x: Col, y: Col): Col => { return colWrap(f(colUnwrap(x), colUnwrap(y))); }; } function nullParser(val: string): never { throw new Error(`Tried to parse a nullCol. This is likely a bug in zol, please report. Column value: "${val}"`); } export function nullCol(): Col { return colWrap({ type: "ELit", lit: { type: "LNull" }, parser: nullParser }); } export function booleanCol(val: boolean): Col { return Unsafe.unsafeCast(colWrap({ type: "ELit", lit: { type: "LText", value: val ? "t" : "f" }, parser: SqlType.booleanParser }), "BOOLEAN", SqlType.booleanParser); } export function textCol(str: string): Col { return Unsafe.unsafeCast(colWrap({ type: "ELit", lit: { type: "LText", value: str }, parser: SqlType.stringParser }), "TEXT", SqlType.stringParser); } export function numberCol(val: number): Col { return colWrap({ type: "ELit", lit: { type: "LInt", value: val }, parser: SqlType.numberParser }); } /** * Perform a conditional on a column * * SQL equivalent: `CASE` */ export function ifThenElse(if_: Col, then: Col, else_: Col): Col { // the "then" and "else_" columns are of the same type, so they // (supposedly) have the same parser, so we can arbitrarily pick // either one. // // However: we must watch out if "then" or "else_" was set to "nullCol()", // and in that case choose the other one's parser. // // (If both "then" and "else_" were set to "nullCol()" then the // the column will always have a NULL value and the parser we set // here wlil never be called) const parser = (then).parser !== nullParser ? (then).parser : (else_).parser; return colWrap({ type: "EIfThenElse", expIf: colUnwrap(if_), expThen: colUnwrap(then), expElse: colUnwrap(else_), parser: parser }); } /** * Applies the given function to the given nullable column where it isn't null, * and returns the given default value where it is. * * @param nullable A nullable column to match against * @param replacement This is the value that will be returned if the nullable column is NULL * @param f This function will be called if the nullable column is not null, and its result will be returned */ export function matchNull(nullable: Col, replacement: Col, f: (col: Col) => Col): Col { return ifThenElse(isNull(nullable), replacement, f(nullable)); } /** * If the second value is null, return the first value. Otherwise return the * second value. */ export function ifNull(replacement: Col, nullable: Col): Col { return matchNull(nullable, replacement, x => x); }