Skip to content

Commit

Permalink
Add expr in subquery
Browse files Browse the repository at this point in the history
  • Loading branch information
etienne-dldc committed Jan 29, 2024
1 parent 4356f8b commit 50f0dd0
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 51 deletions.
61 changes: 54 additions & 7 deletions src/Expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { Ast } from '@dldc/sqlite';
import { builder, Utils } from '@dldc/sqlite';
import { Datatype } from './Datatype';
import { Random } from './Random';
import type { ITableQuery, ITableQueryDependency } from './TableQuery.types';
import { PRIV, TYPES } from './utils/constants';
import { appendDependencies } from './utils/dependencies';
import { expectNever, mapObject } from './utils/functions';
import type { ExprResultFrom, ExprsNullables } from './utils/types';

Expand Down Expand Up @@ -32,6 +34,8 @@ export interface IExprInternal {
// JsonExpr is transformed to JsonRef when converted to a ref
// JsonRef is wrapped in a json() function when unsed in other json functions
readonly jsonMode?: JsonMode;
// Used for X in (select X from ...) where the target is a CTE that needs to be defined
readonly dependencies?: Array<ITableQueryDependency>;
}

export const Expr = (() => {
Expand Down Expand Up @@ -64,6 +68,8 @@ export const Expr = (() => {
isNull,
inList,
notInList,
inSubquery,
notInSubquery,

compare,

Expand All @@ -80,31 +86,36 @@ export const Expr = (() => {
return create(builder.Expr.AggregateFunctions.count({ params: expr.ast }), {
parse: Datatype.number.parse,
nullable: false,
dependencies: expr[PRIV].dependencies,
});
},
// Note: for the following functions, the result is always nullable because the result is null when the input is empty
avg: <Expr extends IExpr<number, boolean>>(expr: Expr): IExpr<number, true> => {
return create(builder.Expr.AggregateFunctions.avg({ params: expr.ast }), {
parse: Datatype.number.parse,
nullable: true,
dependencies: expr[PRIV].dependencies,
});
},
sum: <Expr extends IExpr<number, boolean>>(expr: Expr): IExpr<number, true> => {
return create(builder.Expr.AggregateFunctions.sum({ params: expr.ast }), {
parse: Datatype.number.parse,
nullable: true,
dependencies: expr[PRIV].dependencies,
});
},
min: <Expr extends IExprUnknow>(expr: Expr): IExpr<Expr[TYPES]['val'], true> => {
return create(builder.Expr.AggregateFunctions.min({ params: expr.ast }), {
parse: expr[PRIV].parse,
nullable: true,
dependencies: expr[PRIV].dependencies,
});
},
max: <Expr extends IExprUnknow>(expr: Expr): IExpr<Expr[TYPES]['val'], true> => {
return create(builder.Expr.AggregateFunctions.max({ params: expr.ast }), {
parse: expr[PRIV].parse,
nullable: true,
dependencies: expr[PRIV].dependencies,
});
},
},
Expand All @@ -130,6 +141,7 @@ export const Expr = (() => {
return create(builder.Expr.add(left.ast, right.ast), {
parse: Datatype.number.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -140,6 +152,7 @@ export const Expr = (() => {
return create(builder.Expr.equal(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand Down Expand Up @@ -174,6 +187,7 @@ export const Expr = (() => {
return create(builder.Expr.different(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -184,13 +198,15 @@ export const Expr = (() => {
return create(builder.Expr.like(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

function or<L extends IExprUnknow, R extends IExprUnknow>(left: L, right: R): IExpr<boolean, ExprsNullables<[L, R]>> {
return create(builder.Expr.or(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -201,6 +217,7 @@ export const Expr = (() => {
return create(builder.Expr.and(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -215,6 +232,7 @@ export const Expr = (() => {
return create(builder.Expr.lowerThan(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -225,6 +243,7 @@ export const Expr = (() => {
return create(builder.Expr.lowerThanOrEqual(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -235,6 +254,7 @@ export const Expr = (() => {
return create(builder.Expr.greaterThan(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -245,6 +265,7 @@ export const Expr = (() => {
return create(builder.Expr.greaterThanOrEqual(left.ast, right.ast), {
parse: Datatype.boolean.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

Expand All @@ -255,33 +276,55 @@ export const Expr = (() => {
return create(builder.Expr.concatenate(left.ast, right.ast), {
parse: Datatype.text.parse,
nullable: someNullable(left, right),
dependencies: mergeExprDependencies(left, right),
});
}

function isNull(expr: IExprUnknow): IExpr<boolean, false> {
return create(builder.Expr.isNull(expr.ast), { parse: Datatype.boolean.parse, nullable: false });
return create(builder.Expr.isNull(expr.ast), {
parse: Datatype.boolean.parse,
nullable: false,
dependencies: expr[PRIV].dependencies,
});
}

function inList(left: IExprUnknow, items: IExprUnknow[]): IExpr<boolean, false> {
return create(builder.Expr.In.list(left.ast, Utils.arrayToNonEmptyArray(items.map((item) => item.ast))), {
nullable: false,
parse: Datatype.boolean.parse,
dependencies: mergeExprDependencies(left, ...items),
});
}

function notInList(left: IExprUnknow, items: IExprUnknow[]): IExpr<boolean, false> {
return create(builder.Expr.NotIn.list(left.ast, Utils.arrayToNonEmptyArray(items.map((item) => item.ast))), {
nullable: false,
parse: Datatype.boolean.parse,
dependencies: mergeExprDependencies(left, ...items),
});
}

function inSubquery<RTable extends ITableQuery<any, any>>(
expr: IExprUnknow,
subquery: RTable,
): IExpr<boolean, false> {
return create(builder.Expr.In.tableName(expr.ast, subquery[PRIV].name), {
nullable: false,
parse: Datatype.boolean.parse,
dependencies: appendDependencies(expr[PRIV].dependencies ?? [], subquery[PRIV]),
});
}

// function inSubquery<RTable extends ITableQuery<any, any>>(expr: IExprUnknow, subquery: RTable): IExpr<boolean, false> {
// return create(builder.Expr.In.select(left.ast, {}), {
// nullable: false,
// parse: Datatype.boolean.parse,
// });
// }
function notInSubquery<RTable extends ITableQuery<any, any>>(
expr: IExprUnknow,
subquery: RTable,
): IExpr<boolean, false> {
return create(builder.Expr.NotIn.tableName(expr.ast, subquery[PRIV].name), {
nullable: false,
parse: Datatype.boolean.parse,
dependencies: [subquery[PRIV]],
});
}

function external<Val extends string | number | boolean | null>(
val: Val,
Expand Down Expand Up @@ -373,4 +416,8 @@ export const Expr = (() => {
function someNullable(...exprs: IExprUnknow[]): boolean {
return exprs.some((expr) => expr[PRIV].nullable);
}

function mergeExprDependencies(...exprs: IExprUnknow[]): ITableQueryDependency[] {
return exprs.flatMap((expr) => expr[PRIV].dependencies ?? []);
}
})();
51 changes: 21 additions & 30 deletions src/TableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import type {
ITableQuery,
ITableQueryDependency,
ITableQueryInternal,
ITableQueryState,
OrderingTerms,
SelectFn,
} from './TableQuery.types';
import { ZendbErreur } from './ZendbErreur';
import { PRIV, TYPES } from './utils/constants';
import { appendDependencies, mergeDependencies } from './utils/dependencies';
import { mapObject } from './utils/functions';
import { isStateEmpty } from './utils/isStateEmpty';
import { extractParams } from './utils/params';
import type { AnyRecord, ExprRecord, ExprRecordNested, ExprRecordOutput, FilterEqualCols } from './utils/types';
import { whereEqual } from './utils/whereEqual';
Expand Down Expand Up @@ -87,11 +88,12 @@ export const TableQuery = (() => {

function where(whereFn: ColsFn<InCols, IExprUnknow>): ITableQuery<InCols, OutCols> {
const result = resolveColFn(whereFn)(internal.inputColsRefs);
const nextDependencies = mergeDependencies(internal.dependencies, result[PRIV].dependencies);
if (internal.state.where) {
const whereAnd = Expr.and(internal.state.where, result);
return create({ ...internal, state: { ...internal.state, where: whereAnd } });
return create({ ...internal, dependencies: nextDependencies, state: { ...internal.state, where: whereAnd } });
}
return create({ ...internal, state: { ...internal.state, where: result } });
return create({ ...internal, dependencies: nextDependencies, state: { ...internal.state, where: result } });
}

function groupBy(groupFn: ColsFn<InCols, Array<IExprUnknow>>): ITableQuery<InCols, OutCols> {
Expand All @@ -104,7 +106,11 @@ export const TableQuery = (() => {
if (having === internal.state.having) {
return self;
}
return create({ ...internal, state: { ...internal.state, having } });
return create({
...internal,
dependencies: mergeDependencies(internal.dependencies, having[PRIV].dependencies),
state: { ...internal.state, having },
});
}

function select<NewOutCols extends ExprRecord>(
Expand All @@ -115,11 +121,12 @@ export const TableQuery = (() => {
if ((nextOutputColsExprs as any) === internal.outputColsExprs) {
return self as any;
}
const { select, columnsRef } = resolvedColumns(internal.from, nextOutputColsExprs);
const { select, columnsRef, dependencies } = resolvedColumns(internal.from, nextOutputColsExprs);
return create<InCols, NewOutCols>({
...internal,
outputColsRefs: nextOutputColsExprs,
outputColsExprs: columnsRef as any,
dependencies: mergeDependencies(internal.dependencies, dependencies),
state: { ...internal.state, select },
});
}
Expand Down Expand Up @@ -190,7 +197,7 @@ export const TableQuery = (() => {
...internal.state,
joins: [...(internal.state.joins ?? []), joinItem],
},
dependencies: mergeDependencies(internal.dependencies, table[PRIV]),
dependencies: appendDependencies(internal.dependencies, table[PRIV]),
});
}

Expand Down Expand Up @@ -222,7 +229,7 @@ export const TableQuery = (() => {
...internal.state,
joins: [...(internal.state.joins ?? []), joinItem],
},
dependencies: mergeDependencies(internal.dependencies, table[PRIV]),
dependencies: appendDependencies(internal.dependencies, table[PRIV]),
});
}

Expand Down Expand Up @@ -385,7 +392,9 @@ export const TableQuery = (() => {
: builder.From.Table(internal.from.name),
resultColumns: state.select ? Utils.arrayToNonEmptyArray(state.select) : [builder.ResultColumn.Star()],
where: state.where?.ast,
groupBy: state.groupBy ? { exprs: Utils.arrayToNonEmptyArray(state.groupBy.map((e) => e.ast)) } : undefined,
groupBy: state.groupBy
? { exprs: Utils.arrayToNonEmptyArray(state.groupBy.map((e) => e.ast)), having: state.having?.ast }
: undefined,
},
orderBy: state.orderBy ? Utils.arrayToNonEmptyArray(state.orderBy) : undefined,
limit: state.limit
Expand All @@ -401,12 +410,14 @@ export const TableQuery = (() => {
function resolvedColumns(
table: Ast.Identifier,
selected: ExprRecord,
): { select: Array<Ast.Node<'ResultColumn'>>; columnsRef: ExprRecord } {
): { select: Array<Ast.Node<'ResultColumn'>>; columnsRef: ExprRecord; dependencies: ITableQueryDependency[] } {
let dependencies: ITableQueryDependency[] = [];
const select = Object.entries(selected).map(([key, expr]): Ast.Node<'ResultColumn'> => {
dependencies = mergeDependencies(dependencies, expr[PRIV].dependencies);
return builder.ResultColumn.Expr(expr.ast, key);
});
const columnsRef = exprsToRefs(table, selected);
return { select, columnsRef };
return { select, columnsRef, dependencies };
}

function exprsToRefs(table: Ast.Identifier, exprs: ExprRecord): ExprRecord {
Expand Down Expand Up @@ -444,24 +455,4 @@ export const TableQuery = (() => {
state: {},
});
}

function mergeDependencies(
prevDeps: Array<ITableQueryDependency>,
table: ITableQueryInternal<any, any>,
): Array<ITableQueryDependency> {
if (isStateEmpty(table.state)) {
if (table.dependencies.length === 0) {
// No state and no dependencies, this is a base table, we can skip it
return prevDeps;
}
// No state but has dependencies, we can just keep the dependencies
return [...prevDeps, ...table.dependencies];
}
// Has state, we need to add it to the dependencies as well as all its dependencies
return [...prevDeps, ...table.dependencies, table];
}

function isStateEmpty(state: ITableQueryState): boolean {
return Object.values(state).every((v) => v === undefined);
}
})();
46 changes: 46 additions & 0 deletions src/utils/dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { ITableQueryDependency, ITableQueryInternal } from '../TableQuery.types';
import { isStateEmpty } from './isStateEmpty';

export function appendDependencies(
prevDeps: Array<ITableQueryDependency>,
table: ITableQueryInternal<any, any>,
): Array<ITableQueryDependency> {
if (isStateEmpty(table.state)) {
if (table.dependencies.length === 0) {
// No state and no dependencies, this is a base table, we can skip it
return prevDeps;
}
// No state but has dependencies, we can just keep the dependencies
return [...prevDeps, ...table.dependencies];
}
// Has state, we need to add it to the dependencies as well as all its dependencies
return [...prevDeps, ...table.dependencies, table];
}

export function asTableDependency(table: ITableQueryInternal<any, any>): Array<ITableQueryDependency> {
if (isStateEmpty(table.state)) {
if (table.dependencies.length === 0) {
// No state and no dependencies, this is a base table, we can skip it
return [];
}
// No state but has dependencies, we can just keep the dependencies
return [...table.dependencies];
}
return [...table.dependencies, table];
}

export function mergeDependencies(
left: Array<ITableQueryDependency> | undefined,
right: Array<ITableQueryDependency> | undefined,
): Array<ITableQueryDependency> {
if (!left && !right) {
return [];
}
if (!left) {
return right!;
}
if (!right) {
return left;
}
return [...left, ...right];
}
5 changes: 5 additions & 0 deletions src/utils/isStateEmpty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ITableQueryState } from '../TableQuery.types';

export function isStateEmpty(state: ITableQueryState): boolean {
return Object.values(state).every((v) => v === undefined);
}
Loading

0 comments on commit 50f0dd0

Please sign in to comment.