@prisma-next/sql-builder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/package.json +66 -0
- package/src/exports/types.ts +8 -0
- package/src/expression.ts +158 -0
- package/src/index.ts +8 -0
- package/src/resolve.ts +14 -0
- package/src/runtime/builder-base.ts +369 -0
- package/src/runtime/expression-impl.ts +23 -0
- package/src/runtime/field-proxy.ts +37 -0
- package/src/runtime/functions.ts +190 -0
- package/src/runtime/index.ts +4 -0
- package/src/runtime/joined-tables-impl.ts +238 -0
- package/src/runtime/mutation-impl.ts +317 -0
- package/src/runtime/query-impl.ts +254 -0
- package/src/runtime/sql.ts +31 -0
- package/src/runtime/table-proxy-impl.ts +173 -0
- package/src/scope.ts +83 -0
- package/src/types/db.ts +13 -0
- package/src/types/grouped-query.ts +68 -0
- package/src/types/joined-tables.ts +6 -0
- package/src/types/mutation-query.ts +73 -0
- package/src/types/select-query.ts +64 -0
- package/src/types/shared.ts +121 -0
- package/src/types/table-proxy.ts +52 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AnyExpression as AstExpression } from '@prisma-next/sql-relational-core/ast';
|
|
2
|
+
import type { Expression } from '../expression';
|
|
3
|
+
// biome-ignore lint/correctness/noUnusedImports: used in `declare` property
|
|
4
|
+
import type { ExpressionType, ScopeField } from '../scope';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runtime wrapper around a relational-core AST expression node.
|
|
8
|
+
* Carries ScopeField metadata (codecId, nullable) for plan generation.
|
|
9
|
+
*/
|
|
10
|
+
export class ExpressionImpl<T extends ScopeField = ScopeField> implements Expression<T> {
|
|
11
|
+
declare readonly [ExpressionType]: T;
|
|
12
|
+
private readonly ast: AstExpression;
|
|
13
|
+
readonly field: T;
|
|
14
|
+
|
|
15
|
+
constructor(ast: AstExpression, field: T) {
|
|
16
|
+
this.ast = ast;
|
|
17
|
+
this.field = field;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
buildAst(): AstExpression {
|
|
21
|
+
return this.ast;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ColumnRef, IdentifierRef } from '@prisma-next/sql-relational-core/ast';
|
|
2
|
+
import type { FieldProxy } from '../expression';
|
|
3
|
+
import type { Scope, ScopeTable } from '../scope';
|
|
4
|
+
import { ExpressionImpl } from './expression-impl';
|
|
5
|
+
|
|
6
|
+
export function createFieldProxy<S extends Scope>(scope: S): FieldProxy<S> {
|
|
7
|
+
return new Proxy({} as FieldProxy<S>, {
|
|
8
|
+
get(_target, prop: string) {
|
|
9
|
+
if (Object.hasOwn(scope.topLevel, prop)) {
|
|
10
|
+
const topField = scope.topLevel[prop];
|
|
11
|
+
if (topField) return new ExpressionImpl(IdentifierRef.of(prop), topField);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (Object.hasOwn(scope.namespaces, prop)) {
|
|
15
|
+
const nsFields = scope.namespaces[prop];
|
|
16
|
+
if (nsFields) return createNamespaceProxy(prop, nsFields);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return undefined;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createNamespaceProxy(
|
|
25
|
+
namespaceName: string,
|
|
26
|
+
fields: ScopeTable,
|
|
27
|
+
): Record<string, ExpressionImpl> {
|
|
28
|
+
return new Proxy({} as Record<string, ExpressionImpl>, {
|
|
29
|
+
get(_target, prop: string) {
|
|
30
|
+
if (Object.hasOwn(fields, prop)) {
|
|
31
|
+
const field = fields[prop];
|
|
32
|
+
if (field) return new ExpressionImpl(ColumnRef.of(namespaceName, prop), field);
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AggregateExpr,
|
|
3
|
+
AndExpr,
|
|
4
|
+
type AnyExpression as AstExpression,
|
|
5
|
+
BinaryExpr,
|
|
6
|
+
type BinaryOp,
|
|
7
|
+
ExistsExpr,
|
|
8
|
+
ListExpression,
|
|
9
|
+
LiteralExpr,
|
|
10
|
+
NullCheckExpr,
|
|
11
|
+
OperationExpr,
|
|
12
|
+
OrExpr,
|
|
13
|
+
ParamRef,
|
|
14
|
+
SubqueryExpr,
|
|
15
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
16
|
+
import type { QueryOperationEntry } from '@prisma-next/sql-relational-core/query-operations';
|
|
17
|
+
import type {
|
|
18
|
+
AggregateFunctions,
|
|
19
|
+
AggregateOnlyFunctions,
|
|
20
|
+
BooleanCodecType,
|
|
21
|
+
BuiltinFunctions,
|
|
22
|
+
Expression,
|
|
23
|
+
ExpressionOrValue,
|
|
24
|
+
Functions,
|
|
25
|
+
} from '../expression';
|
|
26
|
+
import type { QueryContext, ScopeField, Subquery } from '../scope';
|
|
27
|
+
import { ExpressionImpl } from './expression-impl';
|
|
28
|
+
|
|
29
|
+
type CodecTypes = Record<string, { readonly input: unknown }>;
|
|
30
|
+
type ExprOrVal<T extends ScopeField = ScopeField> = ExpressionOrValue<T, CodecTypes>;
|
|
31
|
+
|
|
32
|
+
const BOOL_FIELD: BooleanCodecType = { codecId: 'pg/bool@1', nullable: false };
|
|
33
|
+
|
|
34
|
+
function resolve(value: ExprOrVal): AstExpression {
|
|
35
|
+
if (value instanceof ExpressionImpl) return value.buildAst();
|
|
36
|
+
return ParamRef.of(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveToAst(value: ExprOrVal): AstExpression {
|
|
40
|
+
if (value instanceof ExpressionImpl) return value.buildAst();
|
|
41
|
+
return new LiteralExpr(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function boolExpr(astNode: AstExpression): ExpressionImpl<BooleanCodecType> {
|
|
45
|
+
return new ExpressionImpl(astNode, BOOL_FIELD);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function eq(a: ExprOrVal, b: ExprOrVal): ExpressionImpl<BooleanCodecType> {
|
|
49
|
+
if (b === null) return boolExpr(NullCheckExpr.isNull(resolve(a)));
|
|
50
|
+
if (a === null) return boolExpr(NullCheckExpr.isNull(resolve(b)));
|
|
51
|
+
return boolExpr(new BinaryExpr('eq', resolve(a), resolve(b)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ne(a: ExprOrVal, b: ExprOrVal): ExpressionImpl<BooleanCodecType> {
|
|
55
|
+
if (b === null) return boolExpr(NullCheckExpr.isNotNull(resolve(a)));
|
|
56
|
+
if (a === null) return boolExpr(NullCheckExpr.isNotNull(resolve(b)));
|
|
57
|
+
return boolExpr(new BinaryExpr('neq', resolve(a), resolve(b)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function comparison(a: ExprOrVal, b: ExprOrVal, op: BinaryOp): ExpressionImpl<BooleanCodecType> {
|
|
61
|
+
return boolExpr(new BinaryExpr(op, resolve(a), resolve(b)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function inOrNotIn(
|
|
65
|
+
expr: Expression<ScopeField>,
|
|
66
|
+
valuesOrSubquery: Subquery<Record<string, ScopeField>> | ExprOrVal[],
|
|
67
|
+
op: 'in' | 'notIn',
|
|
68
|
+
): ExpressionImpl<BooleanCodecType> {
|
|
69
|
+
const left = expr.buildAst();
|
|
70
|
+
const binaryFn = op === 'in' ? BinaryExpr.in : BinaryExpr.notIn;
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(valuesOrSubquery)) {
|
|
73
|
+
const refs = valuesOrSubquery.map((v) => resolve(v));
|
|
74
|
+
return boolExpr(binaryFn(left, ListExpression.of(refs)));
|
|
75
|
+
}
|
|
76
|
+
return boolExpr(binaryFn(left, SubqueryExpr.of(valuesOrSubquery.buildAst())));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function numericAgg(
|
|
80
|
+
fn: 'sum' | 'avg' | 'min' | 'max',
|
|
81
|
+
expr: Expression<ScopeField>,
|
|
82
|
+
): ExpressionImpl<{ codecId: string; nullable: true }> {
|
|
83
|
+
return new ExpressionImpl(AggregateExpr[fn](expr.buildAst()), {
|
|
84
|
+
codecId: (expr as ExpressionImpl).field.codecId,
|
|
85
|
+
nullable: true as const,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createBuiltinFunctions() {
|
|
90
|
+
return {
|
|
91
|
+
eq: (a: ExprOrVal, b: ExprOrVal) => eq(a, b),
|
|
92
|
+
ne: (a: ExprOrVal, b: ExprOrVal) => ne(a, b),
|
|
93
|
+
gt: (a: ExprOrVal, b: ExprOrVal) => comparison(a, b, 'gt'),
|
|
94
|
+
gte: (a: ExprOrVal, b: ExprOrVal) => comparison(a, b, 'gte'),
|
|
95
|
+
lt: (a: ExprOrVal, b: ExprOrVal) => comparison(a, b, 'lt'),
|
|
96
|
+
lte: (a: ExprOrVal, b: ExprOrVal) => comparison(a, b, 'lte'),
|
|
97
|
+
and: (...exprs: ExprOrVal<BooleanCodecType>[]) => boolExpr(AndExpr.of(exprs.map(resolveToAst))),
|
|
98
|
+
or: (...exprs: ExprOrVal<BooleanCodecType>[]) => boolExpr(OrExpr.of(exprs.map(resolveToAst))),
|
|
99
|
+
exists: (subquery: Subquery<Record<string, ScopeField>>) =>
|
|
100
|
+
boolExpr(ExistsExpr.exists(subquery.buildAst())),
|
|
101
|
+
notExists: (subquery: Subquery<Record<string, ScopeField>>) =>
|
|
102
|
+
boolExpr(ExistsExpr.notExists(subquery.buildAst())),
|
|
103
|
+
in: (
|
|
104
|
+
expr: Expression<ScopeField>,
|
|
105
|
+
valuesOrSubquery: Subquery<Record<string, ScopeField>> | ExprOrVal[],
|
|
106
|
+
) => inOrNotIn(expr, valuesOrSubquery, 'in'),
|
|
107
|
+
notIn: (
|
|
108
|
+
expr: Expression<ScopeField>,
|
|
109
|
+
valuesOrSubquery: Subquery<Record<string, ScopeField>> | ExprOrVal[],
|
|
110
|
+
) => inOrNotIn(expr, valuesOrSubquery, 'notIn'),
|
|
111
|
+
} satisfies BuiltinFunctions<CodecTypes>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createAggregateOnlyFunctions() {
|
|
115
|
+
return {
|
|
116
|
+
count: (expr?: Expression<ScopeField>) => {
|
|
117
|
+
const astExpr = expr ? expr.buildAst() : undefined;
|
|
118
|
+
return new ExpressionImpl(AggregateExpr.count(astExpr), {
|
|
119
|
+
codecId: 'pg/int8@1',
|
|
120
|
+
nullable: false,
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
sum: (expr: Expression<ScopeField>) => numericAgg('sum', expr),
|
|
124
|
+
avg: (expr: Expression<ScopeField>) => numericAgg('avg', expr),
|
|
125
|
+
min: (expr: Expression<ScopeField>) => numericAgg('min', expr),
|
|
126
|
+
max: (expr: Expression<ScopeField>) => numericAgg('max', expr),
|
|
127
|
+
} satisfies AggregateOnlyFunctions;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createExtensionFunction(
|
|
131
|
+
name: string,
|
|
132
|
+
entry: QueryOperationEntry,
|
|
133
|
+
): (...args: ExprOrVal[]) => ExpressionImpl {
|
|
134
|
+
return (...args: ExprOrVal[]) => {
|
|
135
|
+
const resolvedArgs = args.map((arg, i) => {
|
|
136
|
+
if (arg instanceof ExpressionImpl) return arg.buildAst();
|
|
137
|
+
const codecId = entry.args[i]?.codecId;
|
|
138
|
+
return ParamRef.of(arg, codecId ? { codecId } : undefined);
|
|
139
|
+
});
|
|
140
|
+
const self = resolvedArgs[0] as AstExpression;
|
|
141
|
+
const restArgs = resolvedArgs.slice(1);
|
|
142
|
+
|
|
143
|
+
return new ExpressionImpl(
|
|
144
|
+
new OperationExpr({
|
|
145
|
+
method: name,
|
|
146
|
+
forTypeId: entry.args[0]?.codecId ?? 'unknown',
|
|
147
|
+
self,
|
|
148
|
+
args: restArgs.length > 0 ? restArgs : undefined,
|
|
149
|
+
returns: { kind: 'typeId', type: entry.returns.codecId },
|
|
150
|
+
lowering: entry.lowering,
|
|
151
|
+
}),
|
|
152
|
+
entry.returns,
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createFunctions<QC extends QueryContext>(
|
|
158
|
+
queryOperationTypes: Readonly<Record<string, QueryOperationEntry>>,
|
|
159
|
+
): Functions<QC> {
|
|
160
|
+
const builtins = createBuiltinFunctions();
|
|
161
|
+
|
|
162
|
+
return new Proxy({} as Functions<QC>, {
|
|
163
|
+
get(_target, prop: string) {
|
|
164
|
+
const builtin = (builtins as Record<string, unknown>)[prop];
|
|
165
|
+
if (builtin) return builtin;
|
|
166
|
+
|
|
167
|
+
const extOp = queryOperationTypes[prop];
|
|
168
|
+
if (extOp) {
|
|
169
|
+
return createExtensionFunction(prop, extOp);
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createAggregateFunctions<QC extends QueryContext>(
|
|
177
|
+
queryOperationTypes: Readonly<Record<string, QueryOperationEntry>>,
|
|
178
|
+
): AggregateFunctions<QC> {
|
|
179
|
+
const baseFns = createFunctions<QC>(queryOperationTypes);
|
|
180
|
+
const aggregates = createAggregateOnlyFunctions();
|
|
181
|
+
|
|
182
|
+
return new Proxy({} as AggregateFunctions<QC>, {
|
|
183
|
+
get(_target, prop: string) {
|
|
184
|
+
const agg = (aggregates as Record<string, unknown>)[prop];
|
|
185
|
+
if (agg) return agg;
|
|
186
|
+
|
|
187
|
+
return (baseFns as Record<string, unknown>)[prop];
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AndExpr,
|
|
3
|
+
DerivedTableSource,
|
|
4
|
+
JoinAst,
|
|
5
|
+
type TableSource,
|
|
6
|
+
} from '@prisma-next/sql-relational-core/ast';
|
|
7
|
+
import type {
|
|
8
|
+
AggregateFunctions,
|
|
9
|
+
Expression,
|
|
10
|
+
ExpressionBuilder,
|
|
11
|
+
ExtractScopeFields,
|
|
12
|
+
FieldProxy,
|
|
13
|
+
WithField,
|
|
14
|
+
WithFields,
|
|
15
|
+
} from '../expression';
|
|
16
|
+
import type {
|
|
17
|
+
EmptyRow,
|
|
18
|
+
Expand,
|
|
19
|
+
JoinOuterScope,
|
|
20
|
+
JoinSource,
|
|
21
|
+
MergeScopes,
|
|
22
|
+
NullableScope,
|
|
23
|
+
QueryContext,
|
|
24
|
+
Scope,
|
|
25
|
+
ScopeField,
|
|
26
|
+
ScopeTable,
|
|
27
|
+
Subquery,
|
|
28
|
+
} from '../scope';
|
|
29
|
+
import type { JoinedTables } from '../types/joined-tables';
|
|
30
|
+
import type { SelectQuery } from '../types/select-query';
|
|
31
|
+
import type { LateralBuilder } from '../types/shared';
|
|
32
|
+
import {
|
|
33
|
+
BuilderBase,
|
|
34
|
+
type BuilderContext,
|
|
35
|
+
type BuilderState,
|
|
36
|
+
cloneState,
|
|
37
|
+
emptyState,
|
|
38
|
+
mergeScopes,
|
|
39
|
+
nullableScope,
|
|
40
|
+
resolveSelectArgs,
|
|
41
|
+
} from './builder-base';
|
|
42
|
+
import { createFieldProxy } from './field-proxy';
|
|
43
|
+
import { createFunctions } from './functions';
|
|
44
|
+
import { SelectQueryImpl } from './query-impl';
|
|
45
|
+
|
|
46
|
+
export class JoinedTablesImpl<QC extends QueryContext, AvailableScope extends Scope>
|
|
47
|
+
extends BuilderBase<QC['capabilities']>
|
|
48
|
+
implements JoinedTables<QC, AvailableScope>
|
|
49
|
+
{
|
|
50
|
+
readonly #state: BuilderState;
|
|
51
|
+
|
|
52
|
+
constructor(state: BuilderState, ctx: BuilderContext) {
|
|
53
|
+
super(ctx);
|
|
54
|
+
this.#state = state;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lateralJoin = this._gate(
|
|
58
|
+
{ sql: { lateral: true } },
|
|
59
|
+
'lateralJoin',
|
|
60
|
+
<Alias extends string, LateralRow extends Record<string, ScopeField>>(
|
|
61
|
+
alias: Alias,
|
|
62
|
+
builder: (lateral: LateralBuilder<QC, AvailableScope>) => Subquery<LateralRow>,
|
|
63
|
+
): JoinedTables<
|
|
64
|
+
QC,
|
|
65
|
+
MergeScopes<AvailableScope, { topLevel: LateralRow; namespaces: Record<Alias, LateralRow> }>
|
|
66
|
+
> => {
|
|
67
|
+
const { derivedSource, lateralScope } = this.#buildLateral(alias, builder);
|
|
68
|
+
const resultScope = mergeScopes(
|
|
69
|
+
this.#state.scope as AvailableScope,
|
|
70
|
+
lateralScope as { topLevel: LateralRow; namespaces: Record<Alias, LateralRow> },
|
|
71
|
+
);
|
|
72
|
+
return this.#addLateralJoin('inner', resultScope, derivedSource);
|
|
73
|
+
},
|
|
74
|
+
) as JoinedTables<QC, AvailableScope>['lateralJoin'];
|
|
75
|
+
|
|
76
|
+
outerLateralJoin = this._gate(
|
|
77
|
+
{ sql: { lateral: true } },
|
|
78
|
+
'outerLateralJoin',
|
|
79
|
+
<Alias extends string, LateralRow extends Record<string, ScopeField>>(
|
|
80
|
+
alias: Alias,
|
|
81
|
+
builder: (lateral: LateralBuilder<QC, AvailableScope>) => Subquery<LateralRow>,
|
|
82
|
+
): JoinedTables<
|
|
83
|
+
QC,
|
|
84
|
+
MergeScopes<
|
|
85
|
+
AvailableScope,
|
|
86
|
+
NullableScope<{ topLevel: LateralRow; namespaces: Record<Alias, LateralRow> }>
|
|
87
|
+
>
|
|
88
|
+
> => {
|
|
89
|
+
const { derivedSource, lateralScope } = this.#buildLateral(alias, builder);
|
|
90
|
+
const resultScope = mergeScopes(
|
|
91
|
+
this.#state.scope as AvailableScope,
|
|
92
|
+
nullableScope(
|
|
93
|
+
lateralScope as { topLevel: LateralRow; namespaces: Record<Alias, LateralRow> },
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
return this.#addLateralJoin('left', resultScope, derivedSource);
|
|
97
|
+
},
|
|
98
|
+
) as JoinedTables<QC, AvailableScope>['outerLateralJoin'];
|
|
99
|
+
|
|
100
|
+
select<Columns extends (keyof AvailableScope['topLevel'] & string)[]>(
|
|
101
|
+
...columns: Columns
|
|
102
|
+
): SelectQuery<QC, AvailableScope, WithFields<EmptyRow, AvailableScope['topLevel'], Columns>>;
|
|
103
|
+
select<Alias extends string, Field extends ScopeField>(
|
|
104
|
+
alias: Alias,
|
|
105
|
+
expr: (fields: FieldProxy<AvailableScope>, fns: AggregateFunctions<QC>) => Expression<Field>,
|
|
106
|
+
): SelectQuery<QC, AvailableScope, WithField<EmptyRow, Field, Alias>>;
|
|
107
|
+
select<Result extends Record<string, Expression<ScopeField>>>(
|
|
108
|
+
callback: (fields: FieldProxy<AvailableScope>, fns: AggregateFunctions<QC>) => Result,
|
|
109
|
+
): SelectQuery<QC, AvailableScope, Expand<ExtractScopeFields<Result>>>;
|
|
110
|
+
select(...args: unknown[]): unknown {
|
|
111
|
+
const { projections, newRowFields } = resolveSelectArgs(args, this.#state.scope, this.ctx);
|
|
112
|
+
return new SelectQueryImpl<QC, AvailableScope>(
|
|
113
|
+
cloneState(this.#state, {
|
|
114
|
+
projections: [...this.#state.projections, ...projections],
|
|
115
|
+
rowFields: { ...this.#state.rowFields, ...newRowFields },
|
|
116
|
+
}),
|
|
117
|
+
this.ctx,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
innerJoin<Other extends JoinSource<ScopeTable, string | never>>(
|
|
122
|
+
other: Other,
|
|
123
|
+
on: ExpressionBuilder<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>, QC>,
|
|
124
|
+
): JoinedTables<QC, MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>> {
|
|
125
|
+
const targetScope = mergeScopes(
|
|
126
|
+
this.#state.scope as AvailableScope,
|
|
127
|
+
other.getJoinOuterScope() as Other[typeof JoinOuterScope],
|
|
128
|
+
);
|
|
129
|
+
return this.#addJoin(other, 'inner', targetScope, on);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
outerLeftJoin<Other extends JoinSource<ScopeTable, string | never>>(
|
|
133
|
+
other: Other,
|
|
134
|
+
on: ExpressionBuilder<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>, QC>,
|
|
135
|
+
): JoinedTables<QC, MergeScopes<AvailableScope, NullableScope<Other[typeof JoinOuterScope]>>> {
|
|
136
|
+
const targetScope = mergeScopes(
|
|
137
|
+
this.#state.scope as AvailableScope,
|
|
138
|
+
nullableScope(other.getJoinOuterScope() as Other[typeof JoinOuterScope]),
|
|
139
|
+
);
|
|
140
|
+
return this.#addJoin(other, 'left', targetScope, on);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
outerRightJoin<Other extends JoinSource<ScopeTable, string | never>>(
|
|
144
|
+
other: Other,
|
|
145
|
+
on: ExpressionBuilder<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>, QC>,
|
|
146
|
+
): JoinedTables<QC, MergeScopes<NullableScope<AvailableScope>, Other[typeof JoinOuterScope]>> {
|
|
147
|
+
const targetScope = mergeScopes(
|
|
148
|
+
nullableScope(this.#state.scope as AvailableScope),
|
|
149
|
+
other.getJoinOuterScope() as Other[typeof JoinOuterScope],
|
|
150
|
+
);
|
|
151
|
+
return this.#addJoin(other, 'right', targetScope, on);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
outerFullJoin<Other extends JoinSource<ScopeTable, string | never>>(
|
|
155
|
+
other: Other,
|
|
156
|
+
on: ExpressionBuilder<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>, QC>,
|
|
157
|
+
): JoinedTables<
|
|
158
|
+
QC,
|
|
159
|
+
MergeScopes<NullableScope<AvailableScope>, NullableScope<Other[typeof JoinOuterScope]>>
|
|
160
|
+
> {
|
|
161
|
+
const targetScope = mergeScopes(
|
|
162
|
+
nullableScope(this.#state.scope as AvailableScope),
|
|
163
|
+
nullableScope(other.getJoinOuterScope() as Other[typeof JoinOuterScope]),
|
|
164
|
+
);
|
|
165
|
+
return this.#addJoin(other, 'full', targetScope, on);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#addJoin<Other extends JoinSource<ScopeTable, string | never>, ResultScope extends Scope>(
|
|
169
|
+
other: Other,
|
|
170
|
+
joinType: 'inner' | 'left' | 'right' | 'full',
|
|
171
|
+
resultScope: ResultScope,
|
|
172
|
+
onExpr: ExpressionBuilder<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>, QC>,
|
|
173
|
+
): JoinedTables<QC, ResultScope> {
|
|
174
|
+
const fieldProxy = createFieldProxy(
|
|
175
|
+
mergeScopes(
|
|
176
|
+
this.#state.scope as AvailableScope,
|
|
177
|
+
other.getJoinOuterScope() as Other[typeof JoinOuterScope],
|
|
178
|
+
),
|
|
179
|
+
) as FieldProxy<MergeScopes<AvailableScope, Other[typeof JoinOuterScope]>>;
|
|
180
|
+
const fns = createFunctions<QC>(this.ctx.queryOperationTypes);
|
|
181
|
+
const onResult = onExpr(fieldProxy, fns);
|
|
182
|
+
const joinAst = new JoinAst(joinType, other.buildAst(), onResult.buildAst());
|
|
183
|
+
|
|
184
|
+
return new JoinedTablesImpl(
|
|
185
|
+
cloneState(this.#state, {
|
|
186
|
+
joins: [...this.#state.joins, joinAst],
|
|
187
|
+
scope: resultScope,
|
|
188
|
+
}),
|
|
189
|
+
this.ctx,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#buildLateral(
|
|
194
|
+
alias: string,
|
|
195
|
+
builderFn: (
|
|
196
|
+
lateral: LateralBuilder<QC, AvailableScope>,
|
|
197
|
+
) => Subquery<Record<string, ScopeField>>,
|
|
198
|
+
) {
|
|
199
|
+
const lateralBuilder: LateralBuilder<QC, AvailableScope> = {
|
|
200
|
+
from: (other) => {
|
|
201
|
+
const otherScope = other.getJoinOuterScope();
|
|
202
|
+
const parentMerged = mergeScopes(this.#state.scope, otherScope);
|
|
203
|
+
return new SelectQueryImpl(
|
|
204
|
+
emptyState(other.buildAst() as TableSource, parentMerged),
|
|
205
|
+
this.ctx,
|
|
206
|
+
) as unknown as SelectQuery<QC, AvailableScope, EmptyRow>;
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const subquery = builderFn(lateralBuilder);
|
|
211
|
+
const subqueryAst = subquery.buildAst();
|
|
212
|
+
const derivedSource = DerivedTableSource.as(alias, subqueryAst);
|
|
213
|
+
const subqueryRowFields: ScopeTable = subquery.getRowFields();
|
|
214
|
+
const lateralScope: Scope = {
|
|
215
|
+
topLevel: subqueryRowFields,
|
|
216
|
+
namespaces: { [alias]: subqueryRowFields },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return { derivedSource, lateralScope };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#addLateralJoin<ResultScope extends Scope>(
|
|
223
|
+
joinType: 'inner' | 'left',
|
|
224
|
+
resultScope: ResultScope,
|
|
225
|
+
derivedSource: DerivedTableSource,
|
|
226
|
+
): JoinedTables<QC, ResultScope> {
|
|
227
|
+
const onExpr = AndExpr.of([]);
|
|
228
|
+
const joinAst = new JoinAst(joinType, derivedSource, onExpr, true);
|
|
229
|
+
|
|
230
|
+
return new JoinedTablesImpl(
|
|
231
|
+
cloneState(this.#state, {
|
|
232
|
+
joins: [...this.#state.joins, joinAst],
|
|
233
|
+
scope: resultScope,
|
|
234
|
+
}),
|
|
235
|
+
this.ctx,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|