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.
Files changed (58) hide show
  1. package/dist/index.js +663 -261
  2. package/dist/mysql.js +2840 -492
  3. package/dist/postgres/metadata.js +113 -14
  4. package/dist/postgres.js +2596 -285
  5. package/dist/sqlite.js +2783 -450
  6. package/dist/standard.js +663 -261
  7. package/package.json +1 -1
  8. package/src/internal/analytics.ts +264 -0
  9. package/src/internal/coercion/errors.d.ts +1 -1
  10. package/src/internal/coercion/errors.ts +1 -1
  11. package/src/internal/custom-sql-renderer.ts +20 -0
  12. package/src/internal/dialect-numeric.ts +332 -0
  13. package/src/internal/dialect-renderers/mysql.ts +68 -13
  14. package/src/internal/dialect-renderers/postgres.ts +83 -13
  15. package/src/internal/dialect-renderers/sqlite.ts +57 -13
  16. package/src/internal/dynamic.ts +131 -0
  17. package/src/internal/executor.ts +198 -7
  18. package/src/internal/expression-ast.ts +62 -1
  19. package/src/internal/fragment.ts +120 -0
  20. package/src/internal/function-constraints.ts +105 -0
  21. package/src/internal/grouping-key.ts +26 -0
  22. package/src/internal/numeric.ts +204 -0
  23. package/src/internal/query.ts +13 -1
  24. package/src/internal/runtime/normalize.ts +5 -2
  25. package/src/internal/runtime/schema.ts +2 -1
  26. package/src/internal/standard-dsl.ts +43 -20
  27. package/src/internal/window-frame.ts +30 -0
  28. package/src/internal/window-renderer.ts +25 -0
  29. package/src/mysql/executor.ts +126 -45
  30. package/src/mysql/function/aggregate.ts +87 -1
  31. package/src/mysql/function/index.ts +5 -2
  32. package/src/mysql/function/numeric.ts +185 -0
  33. package/src/mysql/function/temporal.ts +6 -6
  34. package/src/mysql/function/window.ts +12 -0
  35. package/src/mysql/internal/dsl.ts +38 -20
  36. package/src/mysql.ts +2 -0
  37. package/src/postgres/executor.ts +111 -45
  38. package/src/postgres/function/aggregate.ts +124 -1
  39. package/src/postgres/function/core.ts +2 -1
  40. package/src/postgres/function/index.ts +19 -1
  41. package/src/postgres/function/numeric.ts +246 -0
  42. package/src/postgres/function/window.ts +12 -0
  43. package/src/postgres/internal/dsl.ts +38 -20
  44. package/src/sqlite/executor.ts +96 -45
  45. package/src/sqlite/function/aggregate.ts +90 -1
  46. package/src/sqlite/function/index.ts +5 -2
  47. package/src/sqlite/function/numeric.ts +139 -0
  48. package/src/sqlite/function/window.ts +12 -0
  49. package/src/sqlite/internal/dsl.ts +38 -20
  50. package/src/sqlite.ts +2 -0
  51. package/src/standard/function/core.ts +8 -1
  52. package/src/standard/function/index.ts +18 -11
  53. package/src/standard/function/string.ts +1 -1
  54. package/src/standard/function/window.ts +10 -1
  55. package/src/standard/query.ts +19 -7
  56. package/src/standard/type.ts +16 -0
  57. package/src/standard.ts +4 -0
  58. package/src/standard/function/temporal.ts +0 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-qb",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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 Q.type.<kind>() witnesses";
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 Q.type.<kind>() witnesses"
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
+ }