effect-qb 0.21.0 → 0.22.0
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/dist/index.js +663 -261
- package/dist/mysql.js +2840 -492
- package/dist/postgres/metadata.js +113 -14
- package/dist/postgres.js +2596 -285
- package/dist/sqlite.js +2783 -450
- package/dist/standard.js +663 -261
- package/package.json +1 -1
- package/src/internal/analytics.ts +264 -0
- package/src/internal/coercion/errors.d.ts +1 -1
- package/src/internal/coercion/errors.ts +1 -1
- package/src/internal/custom-sql-renderer.ts +20 -0
- package/src/internal/dialect-numeric.ts +332 -0
- package/src/internal/dialect-renderers/mysql.ts +68 -13
- package/src/internal/dialect-renderers/postgres.ts +83 -13
- package/src/internal/dialect-renderers/sqlite.ts +57 -13
- package/src/internal/dynamic.ts +131 -0
- package/src/internal/executor.ts +198 -7
- package/src/internal/expression-ast.ts +62 -1
- package/src/internal/fragment.ts +120 -0
- package/src/internal/function-constraints.ts +105 -0
- package/src/internal/grouping-key.ts +26 -0
- package/src/internal/numeric.ts +204 -0
- package/src/internal/query.ts +13 -1
- package/src/internal/runtime/normalize.ts +5 -2
- package/src/internal/runtime/schema.ts +2 -1
- package/src/internal/standard-dsl.ts +43 -20
- package/src/internal/window-frame.ts +30 -0
- package/src/internal/window-renderer.ts +25 -0
- package/src/mysql/executor.ts +126 -45
- package/src/mysql/function/aggregate.ts +87 -1
- package/src/mysql/function/index.ts +5 -2
- package/src/mysql/function/numeric.ts +185 -0
- package/src/mysql/function/temporal.ts +6 -6
- package/src/mysql/function/window.ts +12 -0
- package/src/mysql/internal/dsl.ts +38 -20
- package/src/mysql.ts +2 -0
- package/src/postgres/executor.ts +111 -45
- package/src/postgres/function/aggregate.ts +124 -1
- package/src/postgres/function/core.ts +2 -1
- package/src/postgres/function/index.ts +19 -1
- package/src/postgres/function/numeric.ts +246 -0
- package/src/postgres/function/window.ts +12 -0
- package/src/postgres/internal/dsl.ts +38 -20
- package/src/sqlite/executor.ts +96 -45
- package/src/sqlite/function/aggregate.ts +90 -1
- package/src/sqlite/function/index.ts +5 -2
- package/src/sqlite/function/numeric.ts +139 -0
- package/src/sqlite/function/window.ts +12 -0
- package/src/sqlite/internal/dsl.ts +38 -20
- package/src/sqlite.ts +2 -0
- package/src/standard/function/core.ts +8 -1
- package/src/standard/function/index.ts +18 -11
- package/src/standard/function/string.ts +1 -1
- package/src/standard/function/window.ts +10 -1
- package/src/standard/query.ts +19 -7
- package/src/standard/type.ts +16 -0
- package/src/standard.ts +4 -0
- package/src/standard/function/temporal.ts +0 -78
package/package.json
CHANGED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
2
|
+
import * as Expression from "./scalar.js"
|
|
3
|
+
import {
|
|
4
|
+
makeExpression,
|
|
5
|
+
mergeManyDependencies,
|
|
6
|
+
type MergeDialect,
|
|
7
|
+
type MergeNullabilityTuple,
|
|
8
|
+
type TupleDependencies,
|
|
9
|
+
type TupleDialect
|
|
10
|
+
} from "./query.js"
|
|
11
|
+
import { literal } from "./standard-dsl.js"
|
|
12
|
+
import { validateWindowFrame } from "./window-frame.js"
|
|
13
|
+
|
|
14
|
+
export interface WindowOrderTerm<
|
|
15
|
+
Value extends Expression.Any = Expression.Any
|
|
16
|
+
> {
|
|
17
|
+
readonly value: Value
|
|
18
|
+
readonly direction?: "asc" | "desc"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WindowOrderSpec<
|
|
22
|
+
PartitionBy extends readonly Expression.Any[] = readonly Expression.Any[],
|
|
23
|
+
OrderBy extends readonly [WindowOrderTerm, ...WindowOrderTerm[]] =
|
|
24
|
+
readonly [WindowOrderTerm, ...WindowOrderTerm[]]
|
|
25
|
+
> {
|
|
26
|
+
readonly partitionBy?: PartitionBy
|
|
27
|
+
readonly orderBy: OrderBy
|
|
28
|
+
readonly frame?: never
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WindowSpec<
|
|
32
|
+
PartitionBy extends readonly Expression.Any[] = readonly Expression.Any[],
|
|
33
|
+
OrderBy extends readonly [WindowOrderTerm, ...WindowOrderTerm[]] =
|
|
34
|
+
readonly [WindowOrderTerm, ...WindowOrderTerm[]]
|
|
35
|
+
> {
|
|
36
|
+
readonly partitionBy?: PartitionBy
|
|
37
|
+
readonly orderBy: OrderBy
|
|
38
|
+
readonly frame?: ExpressionAst.WindowFrameNode<"rows" | "range">
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type PartitionExpressions<Spec extends WindowSpec> =
|
|
42
|
+
Spec["partitionBy"] extends readonly Expression.Any[] ? Spec["partitionBy"] : readonly []
|
|
43
|
+
|
|
44
|
+
type OrderExpression<Spec extends WindowSpec> =
|
|
45
|
+
Spec["orderBy"][number] extends WindowOrderTerm<infer Value> ? Value : never
|
|
46
|
+
|
|
47
|
+
type SpecExpressions<Spec extends WindowSpec> = readonly [
|
|
48
|
+
...PartitionExpressions<Spec>,
|
|
49
|
+
...ReadonlyArray<OrderExpression<Spec>>
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
type WindowInputDialect<
|
|
53
|
+
Value extends Expression.Any,
|
|
54
|
+
Spec extends WindowSpec,
|
|
55
|
+
Extra extends readonly Expression.Any[] = readonly []
|
|
56
|
+
> = TupleDialect<readonly [Value, ...SpecExpressions<Spec>, ...Extra]>
|
|
57
|
+
|
|
58
|
+
type WindowDialectConstraint<
|
|
59
|
+
Dialect extends string,
|
|
60
|
+
Value extends Expression.Any,
|
|
61
|
+
Spec extends WindowSpec
|
|
62
|
+
> = Exclude<WindowInputDialect<Value, Spec>, Dialect | "standard"> extends never
|
|
63
|
+
? unknown
|
|
64
|
+
: {
|
|
65
|
+
readonly __effect_qb_error__: "effect-qb: window expression is incompatible with this dialect"
|
|
66
|
+
readonly __effect_qb_expression_dialect__: WindowInputDialect<Value, Spec>
|
|
67
|
+
readonly __effect_qb_target_dialect__: Dialect
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type WindowResult<
|
|
71
|
+
Kind extends Extract<ExpressionAst.WindowKind, "lag" | "lead" | "firstValue" | "lastValue">,
|
|
72
|
+
Value extends Expression.Any,
|
|
73
|
+
Spec extends WindowSpec,
|
|
74
|
+
Nullable extends Expression.Nullability,
|
|
75
|
+
Extra extends readonly Expression.Any[] = readonly [],
|
|
76
|
+
Dialect extends string = WindowInputDialect<Value, Spec, Extra>
|
|
77
|
+
> = Expression.Scalar<
|
|
78
|
+
Expression.RuntimeOf<Value>,
|
|
79
|
+
Expression.DbTypeOf<Value>,
|
|
80
|
+
Nullable,
|
|
81
|
+
Dialect,
|
|
82
|
+
"window",
|
|
83
|
+
TupleDependencies<readonly [Value, ...SpecExpressions<Spec>, ...Extra]>
|
|
84
|
+
> & {
|
|
85
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.WindowNode<
|
|
86
|
+
Kind,
|
|
87
|
+
Value,
|
|
88
|
+
PartitionExpressions<Spec>,
|
|
89
|
+
readonly ExpressionAst.WindowOrderByNode[]
|
|
90
|
+
>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const normalizeSpec = <Spec extends WindowSpec>(spec: Spec) => {
|
|
94
|
+
validateWindowFrame(spec.frame)
|
|
95
|
+
return {
|
|
96
|
+
partitionBy: spec.partitionBy ?? [],
|
|
97
|
+
orderBy: spec.orderBy.map((term) => ({
|
|
98
|
+
value: term.value,
|
|
99
|
+
direction: term.direction ?? "asc"
|
|
100
|
+
})),
|
|
101
|
+
frame: spec.frame
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const rejectExplicitPortableFrame = (
|
|
106
|
+
functionName: string,
|
|
107
|
+
spec: WindowOrderSpec
|
|
108
|
+
): void => {
|
|
109
|
+
if ((spec as WindowSpec).frame !== undefined) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${functionName} does not accept an explicit frame on the portable Function API; use a dialect Function helper`
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const windowExpression = <
|
|
117
|
+
Kind extends Extract<ExpressionAst.WindowKind, "lag" | "lead" | "firstValue" | "lastValue">,
|
|
118
|
+
Value extends Expression.Any,
|
|
119
|
+
Spec extends WindowSpec,
|
|
120
|
+
Nullable extends Expression.Nullability
|
|
121
|
+
>(
|
|
122
|
+
kind: Kind,
|
|
123
|
+
value: Value,
|
|
124
|
+
spec: Spec,
|
|
125
|
+
nullability: Nullable,
|
|
126
|
+
options: {
|
|
127
|
+
readonly offset?: Expression.Any
|
|
128
|
+
readonly defaultValue?: Expression.Any
|
|
129
|
+
readonly fallbackDialect?: string
|
|
130
|
+
} = {}
|
|
131
|
+
): WindowResult<Kind, Value, Spec, Nullable> => {
|
|
132
|
+
const normalized = normalizeSpec(spec)
|
|
133
|
+
const { fallbackDialect, ...astOptions } = options
|
|
134
|
+
const expressions = [
|
|
135
|
+
value,
|
|
136
|
+
...normalized.partitionBy,
|
|
137
|
+
...normalized.orderBy.map((term) => term.value),
|
|
138
|
+
...(options.offset === undefined ? [] : [options.offset]),
|
|
139
|
+
...(options.defaultValue === undefined ? [] : [options.defaultValue])
|
|
140
|
+
]
|
|
141
|
+
return makeExpression({
|
|
142
|
+
runtime: undefined as unknown as Expression.RuntimeOf<Value>,
|
|
143
|
+
dbType: value[Expression.TypeId].dbType,
|
|
144
|
+
runtimeSchema: value[Expression.TypeId].runtimeSchema,
|
|
145
|
+
driverValueMapping: value[Expression.TypeId].driverValueMapping,
|
|
146
|
+
nullability,
|
|
147
|
+
dialect: expressions.find((entry) => entry[Expression.TypeId].dialect !== "standard")?.[Expression.TypeId].dialect ??
|
|
148
|
+
fallbackDialect ??
|
|
149
|
+
value[Expression.TypeId].dialect,
|
|
150
|
+
kind: "window",
|
|
151
|
+
dependencies: mergeManyDependencies(expressions)
|
|
152
|
+
}, {
|
|
153
|
+
kind: "window",
|
|
154
|
+
function: kind,
|
|
155
|
+
value,
|
|
156
|
+
...astOptions,
|
|
157
|
+
...normalized
|
|
158
|
+
}) as never
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface OffsetOptions<
|
|
162
|
+
Value extends Expression.Any,
|
|
163
|
+
Spec extends WindowOrderSpec
|
|
164
|
+
> {
|
|
165
|
+
readonly spec: Spec
|
|
166
|
+
readonly offset?: number
|
|
167
|
+
readonly default?: Expression.RuntimeOf<Value>
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Value from a preceding row in the ordered window. */
|
|
171
|
+
export const lag = <
|
|
172
|
+
Value extends Expression.Any,
|
|
173
|
+
Spec extends WindowOrderSpec
|
|
174
|
+
>(
|
|
175
|
+
value: Value,
|
|
176
|
+
options: OffsetOptions<Value, Spec>
|
|
177
|
+
): WindowResult<"lag", Value, Spec, "maybe"> => {
|
|
178
|
+
rejectExplicitPortableFrame("lag", options.spec)
|
|
179
|
+
if (options.offset !== undefined && (!Number.isSafeInteger(options.offset) || options.offset < 0)) {
|
|
180
|
+
throw new Error("lag offset must be a non-negative safe integer")
|
|
181
|
+
}
|
|
182
|
+
return windowExpression("lag", value, options.spec, "maybe", {
|
|
183
|
+
...(options.offset === undefined && options.default === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { offset: literal(options.offset ?? 1) }),
|
|
186
|
+
...(options.default === undefined ? {} : { defaultValue: literal(options.default as any) })
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Value from a following row in the ordered window. */
|
|
191
|
+
export const lead = <
|
|
192
|
+
Value extends Expression.Any,
|
|
193
|
+
Spec extends WindowOrderSpec
|
|
194
|
+
>(
|
|
195
|
+
value: Value,
|
|
196
|
+
options: OffsetOptions<Value, Spec>
|
|
197
|
+
): WindowResult<"lead", Value, Spec, "maybe"> => {
|
|
198
|
+
rejectExplicitPortableFrame("lead", options.spec)
|
|
199
|
+
if (options.offset !== undefined && (!Number.isSafeInteger(options.offset) || options.offset < 0)) {
|
|
200
|
+
throw new Error("lead offset must be a non-negative safe integer")
|
|
201
|
+
}
|
|
202
|
+
return windowExpression("lead", value, options.spec, "maybe", {
|
|
203
|
+
...(options.offset === undefined && options.default === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: { offset: literal(options.offset ?? 1) }),
|
|
206
|
+
...(options.default === undefined ? {} : { defaultValue: literal(options.default as any) })
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** First value in the portable default window frame. */
|
|
211
|
+
export const firstValue = <
|
|
212
|
+
Value extends Expression.Any,
|
|
213
|
+
Spec extends WindowOrderSpec
|
|
214
|
+
>(
|
|
215
|
+
value: Value,
|
|
216
|
+
spec: Spec
|
|
217
|
+
): WindowResult<"firstValue", Value, Spec, Expression.NullabilityOf<Value>> => {
|
|
218
|
+
rejectExplicitPortableFrame("firstValue", spec)
|
|
219
|
+
return windowExpression("firstValue", value, spec, value[Expression.TypeId].nullability)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Last value in the portable default window frame. */
|
|
223
|
+
export const lastValue = <
|
|
224
|
+
Value extends Expression.Any,
|
|
225
|
+
Spec extends WindowOrderSpec
|
|
226
|
+
>(
|
|
227
|
+
value: Value,
|
|
228
|
+
spec: Spec
|
|
229
|
+
): WindowResult<"lastValue", Value, Spec, Expression.NullabilityOf<Value>> => {
|
|
230
|
+
rejectExplicitPortableFrame("lastValue", spec)
|
|
231
|
+
return windowExpression("lastValue", value, spec, value[Expression.TypeId].nullability)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const makeDialectFirstValue = <Dialect extends string>(dialect: Dialect) =>
|
|
235
|
+
<
|
|
236
|
+
Value extends Expression.Any,
|
|
237
|
+
Spec extends WindowSpec
|
|
238
|
+
>(
|
|
239
|
+
value: Value,
|
|
240
|
+
spec: Spec & WindowDialectConstraint<Dialect, Value, Spec>
|
|
241
|
+
): WindowResult<
|
|
242
|
+
"firstValue",
|
|
243
|
+
Value,
|
|
244
|
+
Spec,
|
|
245
|
+
"maybe",
|
|
246
|
+
readonly [],
|
|
247
|
+
MergeDialect<WindowInputDialect<Value, Spec>, Dialect>
|
|
248
|
+
> => windowExpression("firstValue", value, spec, "maybe", { fallbackDialect: dialect }) as never
|
|
249
|
+
|
|
250
|
+
export const makeDialectLastValue = <Dialect extends string>(dialect: Dialect) =>
|
|
251
|
+
<
|
|
252
|
+
Value extends Expression.Any,
|
|
253
|
+
Spec extends WindowSpec
|
|
254
|
+
>(
|
|
255
|
+
value: Value,
|
|
256
|
+
spec: Spec & WindowDialectConstraint<Dialect, Value, Spec>
|
|
257
|
+
): WindowResult<
|
|
258
|
+
"lastValue",
|
|
259
|
+
Value,
|
|
260
|
+
Spec,
|
|
261
|
+
"maybe",
|
|
262
|
+
readonly [],
|
|
263
|
+
MergeDialect<WindowInputDialect<Value, Spec>, Dialect>
|
|
264
|
+
> => windowExpression("lastValue", value, spec, "maybe", { fallbackDialect: dialect }) as never
|
|
@@ -13,5 +13,5 @@ export type CastTargetError<Source extends Expression.DbType.Any, Target extends
|
|
|
13
13
|
readonly __effect_qb_source_db_type__: Source;
|
|
14
14
|
readonly __effect_qb_target_db_type__: Target;
|
|
15
15
|
readonly __effect_qb_dialect__: Dialect;
|
|
16
|
-
readonly __effect_qb_hint__: "Use one of the supported
|
|
16
|
+
readonly __effect_qb_hint__: "Use one of the supported Type.<kind>() witnesses";
|
|
17
17
|
};
|
|
@@ -25,5 +25,5 @@ export type CastTargetError<
|
|
|
25
25
|
readonly __effect_qb_source_db_type__: Source
|
|
26
26
|
readonly __effect_qb_target_db_type__: Target
|
|
27
27
|
readonly __effect_qb_dialect__: Dialect
|
|
28
|
-
readonly __effect_qb_hint__: "Use one of the supported
|
|
28
|
+
readonly __effect_qb_hint__: "Use one of the supported Type.<kind>() witnesses"
|
|
29
29
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RenderState, SqlDialect } from "./dialect.js"
|
|
2
|
+
import type * as ExpressionAst from "./expression-ast.js"
|
|
3
|
+
import * as Expression from "./scalar.js"
|
|
4
|
+
|
|
5
|
+
export const renderCustomSql = (
|
|
6
|
+
node: ExpressionAst.CustomSqlNode,
|
|
7
|
+
state: RenderState,
|
|
8
|
+
dialect: SqlDialect,
|
|
9
|
+
renderExpression: (expression: Expression.Any, state: RenderState, dialect: SqlDialect) => string
|
|
10
|
+
): string => node.strings.map((part, index) => {
|
|
11
|
+
const value = node.values[index]
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return part
|
|
14
|
+
}
|
|
15
|
+
return part + (
|
|
16
|
+
Expression.TypeId in value
|
|
17
|
+
? renderExpression(value, state, dialect)
|
|
18
|
+
: value.parts.map((segment) => dialect.quoteIdentifier(segment)).join(".")
|
|
19
|
+
)
|
|
20
|
+
}).join("")
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
2
|
+
import {
|
|
3
|
+
makeExpression,
|
|
4
|
+
mergeAggregationManyRuntime,
|
|
5
|
+
mergeManyDependencies,
|
|
6
|
+
type MergeAggregation,
|
|
7
|
+
type MergeNullabilityTuple,
|
|
8
|
+
type TupleDependencies
|
|
9
|
+
} from "./query.js"
|
|
10
|
+
import * as Expression from "./scalar.js"
|
|
11
|
+
|
|
12
|
+
export type Input = Expression.Any | number
|
|
13
|
+
|
|
14
|
+
export type Literal<
|
|
15
|
+
Value extends number,
|
|
16
|
+
Db extends Expression.DbType.Any,
|
|
17
|
+
Dialect extends string
|
|
18
|
+
> = Expression.Scalar<Value, Db, "never", Dialect, "scalar", never> & {
|
|
19
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.LiteralNode<Value>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type AsExpression<
|
|
23
|
+
Value extends Input,
|
|
24
|
+
LiteralDb extends Expression.DbType.Any,
|
|
25
|
+
Dialect extends string
|
|
26
|
+
> = Value extends Expression.Any
|
|
27
|
+
? Value
|
|
28
|
+
: Literal<Extract<Value, number>, LiteralDb, Dialect>
|
|
29
|
+
|
|
30
|
+
export type DbTypeOfInput<
|
|
31
|
+
Value extends Input,
|
|
32
|
+
LiteralDb extends Expression.DbType.Any,
|
|
33
|
+
Dialect extends string
|
|
34
|
+
> = Expression.DbTypeOf<AsExpression<Value, LiteralDb, Dialect>>
|
|
35
|
+
|
|
36
|
+
export type InputDialect<
|
|
37
|
+
Value extends Input,
|
|
38
|
+
LiteralDb extends Expression.DbType.Any,
|
|
39
|
+
Dialect extends string
|
|
40
|
+
> = AsExpression<Value, LiteralDb, Dialect>[typeof Expression.TypeId]["dialect"]
|
|
41
|
+
|
|
42
|
+
export type DialectConstraint<
|
|
43
|
+
Value extends Input,
|
|
44
|
+
LiteralDb extends Expression.DbType.Any,
|
|
45
|
+
Dialect extends string
|
|
46
|
+
> = Exclude<InputDialect<Value, LiteralDb, Dialect>, Dialect | "standard"> extends never
|
|
47
|
+
? unknown
|
|
48
|
+
: {
|
|
49
|
+
readonly __effect_qb_error__: "effect-qb: numeric expression is incompatible with this dialect"
|
|
50
|
+
readonly __effect_qb_expression_dialect__: InputDialect<Value, LiteralDb, Dialect>
|
|
51
|
+
readonly __effect_qb_target_dialect__: Dialect
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type BinaryValues<
|
|
55
|
+
Left extends Input,
|
|
56
|
+
Right extends Input,
|
|
57
|
+
LiteralDb extends Expression.DbType.Any,
|
|
58
|
+
Dialect extends string
|
|
59
|
+
> = readonly [
|
|
60
|
+
AsExpression<Left, LiteralDb, Dialect>,
|
|
61
|
+
AsExpression<Right, LiteralDb, Dialect>
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
export type BinaryNullability<
|
|
65
|
+
Left extends Input,
|
|
66
|
+
Right extends Input,
|
|
67
|
+
LiteralDb extends Expression.DbType.Any,
|
|
68
|
+
Dialect extends string
|
|
69
|
+
> = MergeNullabilityTuple<BinaryValues<Left, Right, LiteralDb, Dialect>>
|
|
70
|
+
|
|
71
|
+
type ZeroPossibility<Value extends Input> = Value extends number
|
|
72
|
+
? number extends Value ? "possible" : Value extends 0 ? "always" : "never"
|
|
73
|
+
: "possible"
|
|
74
|
+
|
|
75
|
+
export type ZeroDivisorNullability<
|
|
76
|
+
Left extends Input,
|
|
77
|
+
Right extends Input,
|
|
78
|
+
LiteralDb extends Expression.DbType.Any,
|
|
79
|
+
Dialect extends string
|
|
80
|
+
> = BinaryNullability<Left, Right, LiteralDb, Dialect> extends infer Nullable extends Expression.Nullability
|
|
81
|
+
? Nullable extends "always"
|
|
82
|
+
? "always"
|
|
83
|
+
: ZeroPossibility<Right> extends "always" ? "always"
|
|
84
|
+
: ZeroPossibility<Right> extends "possible" ? "maybe" : Nullable
|
|
85
|
+
: never
|
|
86
|
+
|
|
87
|
+
export type BinaryResult<
|
|
88
|
+
Left extends Input,
|
|
89
|
+
Right extends Input,
|
|
90
|
+
LiteralDb extends Expression.DbType.Any,
|
|
91
|
+
ResultDb extends Expression.DbType.Any,
|
|
92
|
+
Nullable extends Expression.Nullability,
|
|
93
|
+
Dialect extends string
|
|
94
|
+
> = Expression.Scalar<
|
|
95
|
+
Expression.RuntimeOfDbType<ResultDb>,
|
|
96
|
+
ResultDb,
|
|
97
|
+
Nullable,
|
|
98
|
+
Dialect,
|
|
99
|
+
MergeAggregation<
|
|
100
|
+
Expression.KindOf<AsExpression<Left, LiteralDb, Dialect>>,
|
|
101
|
+
Expression.KindOf<AsExpression<Right, LiteralDb, Dialect>>
|
|
102
|
+
>,
|
|
103
|
+
TupleDependencies<BinaryValues<Left, Right, LiteralDb, Dialect>>
|
|
104
|
+
> & {
|
|
105
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.BinaryNode<"modulo">
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type RoundResult<
|
|
109
|
+
Value extends Input,
|
|
110
|
+
LiteralDb extends Expression.DbType.Any,
|
|
111
|
+
ResultDb extends Expression.DbType.Any,
|
|
112
|
+
Nullable extends Expression.Nullability,
|
|
113
|
+
Dialect extends string,
|
|
114
|
+
WithScale extends boolean
|
|
115
|
+
> = Expression.Scalar<
|
|
116
|
+
Expression.RuntimeOfDbType<ResultDb>,
|
|
117
|
+
ResultDb,
|
|
118
|
+
Nullable,
|
|
119
|
+
Dialect,
|
|
120
|
+
Expression.KindOf<AsExpression<Value, LiteralDb, Dialect>>,
|
|
121
|
+
Expression.DependenciesOf<AsExpression<Value, LiteralDb, Dialect>>
|
|
122
|
+
> & {
|
|
123
|
+
readonly [ExpressionAst.TypeId]: WithScale extends true
|
|
124
|
+
? ExpressionAst.FunctionCallNode<"round">
|
|
125
|
+
: ExpressionAst.UnaryNode<"round">
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type AggregateResult<
|
|
129
|
+
Kind extends "sum" | "avg",
|
|
130
|
+
Value extends Input,
|
|
131
|
+
LiteralDb extends Expression.DbType.Any,
|
|
132
|
+
ResultDb extends Expression.DbType.Any,
|
|
133
|
+
Dialect extends string
|
|
134
|
+
> = Expression.Scalar<
|
|
135
|
+
Expression.RuntimeOfDbType<ResultDb>,
|
|
136
|
+
ResultDb,
|
|
137
|
+
"maybe",
|
|
138
|
+
Dialect,
|
|
139
|
+
"aggregate",
|
|
140
|
+
Expression.DependenciesOf<AsExpression<Value, LiteralDb, Dialect>>
|
|
141
|
+
> & {
|
|
142
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.UnaryNode<
|
|
143
|
+
Kind,
|
|
144
|
+
AsExpression<Value, LiteralDb, Dialect>
|
|
145
|
+
>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const literal = <
|
|
149
|
+
Value extends number,
|
|
150
|
+
Db extends Expression.DbType.Any,
|
|
151
|
+
Dialect extends string
|
|
152
|
+
>(
|
|
153
|
+
value: Value,
|
|
154
|
+
dbType: Db,
|
|
155
|
+
dialect: Dialect
|
|
156
|
+
): Literal<Value, Db, Dialect> =>
|
|
157
|
+
makeExpression({
|
|
158
|
+
runtime: value,
|
|
159
|
+
dbType,
|
|
160
|
+
driverValueMapping: dbType.driverValueMapping,
|
|
161
|
+
nullability: "never",
|
|
162
|
+
dialect,
|
|
163
|
+
kind: "scalar",
|
|
164
|
+
dependencies: {}
|
|
165
|
+
}, {
|
|
166
|
+
kind: "literal",
|
|
167
|
+
value
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const asExpression = <
|
|
171
|
+
Value extends Input,
|
|
172
|
+
Db extends Expression.DbType.Any,
|
|
173
|
+
Dialect extends string
|
|
174
|
+
>(
|
|
175
|
+
value: Value,
|
|
176
|
+
literalDb: Db,
|
|
177
|
+
dialect: Dialect
|
|
178
|
+
): AsExpression<Value, Db, Dialect> =>
|
|
179
|
+
(typeof value === "number"
|
|
180
|
+
? literal(value, literalDb, dialect)
|
|
181
|
+
: value) as AsExpression<Value, Db, Dialect>
|
|
182
|
+
|
|
183
|
+
export const modulo = <
|
|
184
|
+
Left extends Input,
|
|
185
|
+
Right extends Input,
|
|
186
|
+
LiteralDb extends Expression.DbType.Any,
|
|
187
|
+
ResultDb extends Expression.DbType.Any,
|
|
188
|
+
Nullable extends Expression.Nullability,
|
|
189
|
+
Dialect extends string
|
|
190
|
+
>(
|
|
191
|
+
left: Left,
|
|
192
|
+
right: Right,
|
|
193
|
+
options: {
|
|
194
|
+
readonly dialect: Dialect
|
|
195
|
+
readonly literalDb: LiteralDb
|
|
196
|
+
readonly resultDb: ResultDb
|
|
197
|
+
readonly nullability: Nullable
|
|
198
|
+
}
|
|
199
|
+
): BinaryResult<Left, Right, LiteralDb, ResultDb, Nullable, Dialect> => {
|
|
200
|
+
const leftExpression = asExpression(left, options.literalDb, options.dialect)
|
|
201
|
+
const rightExpression = asExpression(right, options.literalDb, options.dialect)
|
|
202
|
+
const values = [leftExpression, rightExpression] as const
|
|
203
|
+
return (makeExpression as any)({
|
|
204
|
+
runtime: undefined,
|
|
205
|
+
dbType: options.resultDb,
|
|
206
|
+
driverValueMapping: options.resultDb.driverValueMapping,
|
|
207
|
+
nullability: options.nullability,
|
|
208
|
+
dialect: options.dialect,
|
|
209
|
+
kind: mergeAggregationManyRuntime(values),
|
|
210
|
+
dependencies: mergeManyDependencies(values)
|
|
211
|
+
}, {
|
|
212
|
+
kind: "modulo",
|
|
213
|
+
left: leftExpression,
|
|
214
|
+
right: rightExpression
|
|
215
|
+
}) as BinaryResult<Left, Right, LiteralDb, ResultDb, Nullable, Dialect>
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const round = <
|
|
219
|
+
Value extends Input,
|
|
220
|
+
LiteralDb extends Expression.DbType.Any,
|
|
221
|
+
ScaleDb extends Expression.DbType.Any,
|
|
222
|
+
ResultDb extends Expression.DbType.Any,
|
|
223
|
+
Nullable extends Expression.Nullability,
|
|
224
|
+
Dialect extends string,
|
|
225
|
+
WithScale extends boolean
|
|
226
|
+
>(
|
|
227
|
+
value: Value,
|
|
228
|
+
scale: number | undefined,
|
|
229
|
+
options: {
|
|
230
|
+
readonly dialect: Dialect
|
|
231
|
+
readonly literalDb: LiteralDb
|
|
232
|
+
readonly scaleDb: ScaleDb
|
|
233
|
+
readonly resultDb: ResultDb
|
|
234
|
+
readonly nullability: Nullable
|
|
235
|
+
}
|
|
236
|
+
): RoundResult<Value, LiteralDb, ResultDb, Nullable, Dialect, WithScale> => {
|
|
237
|
+
const expression = asExpression(value, options.literalDb, options.dialect)
|
|
238
|
+
if (scale === undefined) {
|
|
239
|
+
return (makeExpression as any)({
|
|
240
|
+
runtime: undefined,
|
|
241
|
+
dbType: options.resultDb,
|
|
242
|
+
driverValueMapping: options.resultDb.driverValueMapping,
|
|
243
|
+
nullability: options.nullability,
|
|
244
|
+
dialect: options.dialect,
|
|
245
|
+
kind: expression[Expression.TypeId].kind,
|
|
246
|
+
dependencies: expression[Expression.TypeId].dependencies
|
|
247
|
+
}, {
|
|
248
|
+
kind: "round",
|
|
249
|
+
value: expression
|
|
250
|
+
}) as RoundResult<Value, LiteralDb, ResultDb, Nullable, Dialect, WithScale>
|
|
251
|
+
}
|
|
252
|
+
if (!Number.isSafeInteger(scale)) {
|
|
253
|
+
throw new Error("round scale must be a safe integer")
|
|
254
|
+
}
|
|
255
|
+
const scaleExpression = literal(scale, options.scaleDb, options.dialect)
|
|
256
|
+
return (makeExpression as any)({
|
|
257
|
+
runtime: undefined,
|
|
258
|
+
dbType: options.resultDb,
|
|
259
|
+
driverValueMapping: options.resultDb.driverValueMapping,
|
|
260
|
+
nullability: options.nullability,
|
|
261
|
+
dialect: options.dialect,
|
|
262
|
+
kind: expression[Expression.TypeId].kind,
|
|
263
|
+
dependencies: expression[Expression.TypeId].dependencies
|
|
264
|
+
}, {
|
|
265
|
+
kind: "function",
|
|
266
|
+
name: "round",
|
|
267
|
+
args: [expression, scaleExpression]
|
|
268
|
+
}) as RoundResult<Value, LiteralDb, ResultDb, Nullable, Dialect, WithScale>
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const aggregate = <
|
|
272
|
+
Kind extends "sum" | "avg",
|
|
273
|
+
Value extends Input,
|
|
274
|
+
LiteralDb extends Expression.DbType.Any,
|
|
275
|
+
ResultDb extends Expression.DbType.Any,
|
|
276
|
+
Dialect extends string
|
|
277
|
+
>(
|
|
278
|
+
kind: Kind,
|
|
279
|
+
value: Value,
|
|
280
|
+
options: {
|
|
281
|
+
readonly dialect: Dialect
|
|
282
|
+
readonly literalDb: LiteralDb
|
|
283
|
+
readonly resultDb: ResultDb
|
|
284
|
+
}
|
|
285
|
+
): AggregateResult<Kind, Value, LiteralDb, ResultDb, Dialect> => {
|
|
286
|
+
const expression = asExpression(value, options.literalDb, options.dialect)
|
|
287
|
+
return (makeExpression as any)({
|
|
288
|
+
runtime: undefined,
|
|
289
|
+
dbType: options.resultDb,
|
|
290
|
+
driverValueMapping: options.resultDb.driverValueMapping,
|
|
291
|
+
nullability: "maybe",
|
|
292
|
+
dialect: options.dialect,
|
|
293
|
+
kind: "aggregate",
|
|
294
|
+
dependencies: expression[Expression.TypeId].dependencies
|
|
295
|
+
}, {
|
|
296
|
+
kind,
|
|
297
|
+
value: expression
|
|
298
|
+
}) as AggregateResult<Kind, Value, LiteralDb, ResultDb, Dialect>
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const inputNullability = (value: Input): Expression.Nullability =>
|
|
302
|
+
typeof value === "number"
|
|
303
|
+
? "never"
|
|
304
|
+
: value[Expression.TypeId].nullability
|
|
305
|
+
|
|
306
|
+
export const binaryInputNullability = (
|
|
307
|
+
left: Input,
|
|
308
|
+
right: Input
|
|
309
|
+
): Expression.Nullability => {
|
|
310
|
+
const leftNullability = inputNullability(left)
|
|
311
|
+
const rightNullability = inputNullability(right)
|
|
312
|
+
if (leftNullability === "always" || rightNullability === "always") {
|
|
313
|
+
return "always"
|
|
314
|
+
}
|
|
315
|
+
return leftNullability === "maybe" || rightNullability === "maybe"
|
|
316
|
+
? "maybe"
|
|
317
|
+
: "never"
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export const nullableForZeroDivisor = (
|
|
321
|
+
left: Input,
|
|
322
|
+
right: Input
|
|
323
|
+
): Expression.Nullability => {
|
|
324
|
+
const merged = binaryInputNullability(left, right)
|
|
325
|
+
if (merged === "always") {
|
|
326
|
+
return "always"
|
|
327
|
+
}
|
|
328
|
+
if (typeof right === "number") {
|
|
329
|
+
return right === 0 ? "always" : merged
|
|
330
|
+
}
|
|
331
|
+
return "maybe"
|
|
332
|
+
}
|