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
@@ -0,0 +1,204 @@
1
+ import * as Schema from "effect/Schema"
2
+
3
+ import { standardDatatypes } from "../standard/datatypes/index.js"
4
+ import * as ExpressionAst from "./expression-ast.js"
5
+ import * as Expression from "./scalar.js"
6
+ import {
7
+ makeExpression,
8
+ mergeAggregationManyRuntime,
9
+ mergeManyDependencies,
10
+ mergeNullabilityManyRuntime,
11
+ type MergeAggregation,
12
+ type MergeNullabilityTuple,
13
+ type NumericExpressionInput,
14
+ type TupleDependencies,
15
+ type TupleDialect
16
+ } from "./query.js"
17
+
18
+ type NumberLiteral<Value extends number> = Expression.Scalar<
19
+ Value,
20
+ ReturnType<typeof standardDatatypes.real>,
21
+ "never",
22
+ "standard",
23
+ "scalar",
24
+ never
25
+ > & {
26
+ readonly [ExpressionAst.TypeId]: ExpressionAst.LiteralNode<Value>
27
+ }
28
+
29
+ type AsExpression<Value extends NumericExpressionInput> =
30
+ Value extends Expression.Any ? Value : NumberLiteral<Extract<Value, number>>
31
+
32
+ type BinaryResult<
33
+ Kind extends Extract<ExpressionAst.BinaryKind, "add" | "subtract" | "multiply" | "divide">,
34
+ Left extends NumericExpressionInput,
35
+ Right extends NumericExpressionInput
36
+ > = Expression.Scalar<
37
+ number,
38
+ Expression.DbTypeOf<AsExpression<Left>>,
39
+ MergeNullabilityTuple<readonly [AsExpression<Left>, AsExpression<Right>]>,
40
+ TupleDialect<readonly [AsExpression<Left>, AsExpression<Right>]>,
41
+ MergeAggregation<Expression.KindOf<AsExpression<Left>>, Expression.KindOf<AsExpression<Right>>>,
42
+ TupleDependencies<readonly [AsExpression<Left>, AsExpression<Right>]>
43
+ > & {
44
+ readonly [ExpressionAst.TypeId]: ExpressionAst.BinaryNode<Kind, AsExpression<Left>, AsExpression<Right>>
45
+ }
46
+
47
+ const numberLiteral = <const Value extends number>(value: Value): NumberLiteral<Value> =>
48
+ makeExpression({
49
+ runtime: value,
50
+ dbType: standardDatatypes.real(),
51
+ runtimeSchema: Schema.Number,
52
+ nullability: "never",
53
+ dialect: "standard",
54
+ kind: "scalar",
55
+ dependencies: {}
56
+ }, {
57
+ kind: "literal",
58
+ value
59
+ })
60
+
61
+ const toExpression = <Value extends NumericExpressionInput>(
62
+ value: Value
63
+ ): AsExpression<Value> =>
64
+ (typeof value === "number" ? numberLiteral(value) : value) as AsExpression<Value>
65
+
66
+ const retargetLiteral = (
67
+ value: Expression.Any,
68
+ target: Expression.Any
69
+ ): Expression.Any => {
70
+ const ast = (value as Expression.Any & {
71
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
72
+ })[ExpressionAst.TypeId]
73
+ if (ast.kind !== "literal") {
74
+ return value
75
+ }
76
+ const state = target[Expression.TypeId]
77
+ return makeExpression({
78
+ runtime: value[Expression.TypeId].runtime,
79
+ dbType: state.dbType,
80
+ runtimeSchema: state.runtimeSchema,
81
+ driverValueMapping: state.driverValueMapping,
82
+ nullability: value[Expression.TypeId].nullability,
83
+ dialect: state.dialect,
84
+ kind: "scalar",
85
+ dependencies: {}
86
+ }, ast)
87
+ }
88
+
89
+ const binary = <
90
+ Kind extends Extract<ExpressionAst.BinaryKind, "add" | "subtract" | "multiply" | "divide">,
91
+ Left extends NumericExpressionInput,
92
+ Right extends NumericExpressionInput
93
+ >(
94
+ kind: Kind,
95
+ left: Left,
96
+ right: Right
97
+ ): BinaryResult<Kind, Left, Right> => {
98
+ let leftExpression = toExpression(left)
99
+ let rightExpression = toExpression(right)
100
+ const leftAst = (leftExpression as Expression.Any & {
101
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
102
+ })[ExpressionAst.TypeId]
103
+ const rightAst = (rightExpression as Expression.Any & {
104
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
105
+ })[ExpressionAst.TypeId]
106
+ if (leftAst.kind === "literal" && rightAst.kind !== "literal") {
107
+ leftExpression = retargetLiteral(leftExpression, rightExpression) as AsExpression<Left>
108
+ } else if (rightAst.kind === "literal" && leftAst.kind !== "literal") {
109
+ rightExpression = retargetLiteral(rightExpression, leftExpression) as AsExpression<Right>
110
+ }
111
+ const values = [leftExpression, rightExpression] as const
112
+ return makeExpression({
113
+ runtime: 0,
114
+ dbType: leftExpression[Expression.TypeId].dbType,
115
+ runtimeSchema: Schema.Number,
116
+ driverValueMapping: leftExpression[Expression.TypeId].driverValueMapping,
117
+ nullability: mergeNullabilityManyRuntime(values),
118
+ dialect: values.find((value) => value[Expression.TypeId].dialect !== "standard")?.[Expression.TypeId].dialect ??
119
+ leftExpression[Expression.TypeId].dialect,
120
+ kind: mergeAggregationManyRuntime(values),
121
+ dependencies: mergeManyDependencies(values)
122
+ }, {
123
+ kind,
124
+ left: leftExpression,
125
+ right: rightExpression
126
+ }) as BinaryResult<Kind, Left, Right>
127
+ }
128
+
129
+ export const add = <Left extends NumericExpressionInput, Right extends NumericExpressionInput>(
130
+ left: Left,
131
+ right: Right
132
+ ): BinaryResult<"add", Left, Right> => binary("add", left, right)
133
+
134
+ export const subtract = <Left extends NumericExpressionInput, Right extends NumericExpressionInput>(
135
+ left: Left,
136
+ right: Right
137
+ ): BinaryResult<"subtract", Left, Right> => binary("subtract", left, right)
138
+
139
+ export const multiply = <Left extends NumericExpressionInput, Right extends NumericExpressionInput>(
140
+ left: Left,
141
+ right: Right
142
+ ): BinaryResult<"multiply", Left, Right> => binary("multiply", left, right)
143
+
144
+ export const divide = <Left extends NumericExpressionInput, Right extends NumericExpressionInput>(
145
+ left: Left,
146
+ right: Right
147
+ ): BinaryResult<"divide", Left, Right> => binary("divide", left, right)
148
+
149
+ type UnaryResult<
150
+ Kind extends Extract<ExpressionAst.UnaryKind, "abs" | "negate">,
151
+ Value extends NumericExpressionInput,
152
+ Nullable extends Expression.Nullability,
153
+ ResultKind extends Expression.ScalarKind
154
+ > = Expression.Scalar<
155
+ number,
156
+ Expression.DbTypeOf<AsExpression<Value>>,
157
+ Nullable,
158
+ AsExpression<Value>[typeof Expression.TypeId]["dialect"],
159
+ ResultKind,
160
+ Expression.DependenciesOf<AsExpression<Value>>
161
+ > & {
162
+ readonly [ExpressionAst.TypeId]: ExpressionAst.UnaryNode<Kind, AsExpression<Value>>
163
+ }
164
+
165
+ const unary = <
166
+ Kind extends Extract<ExpressionAst.UnaryKind, "abs" | "negate">,
167
+ Value extends NumericExpressionInput,
168
+ Nullable extends Expression.Nullability,
169
+ ResultKind extends Expression.ScalarKind
170
+ >(
171
+ kind: Kind,
172
+ value: Value,
173
+ nullability: Nullable,
174
+ resultKind: ResultKind
175
+ ): UnaryResult<Kind, Value, Nullable, ResultKind> => {
176
+ const expression = toExpression(value)
177
+ return makeExpression({
178
+ runtime: 0,
179
+ dbType: expression[Expression.TypeId].dbType,
180
+ runtimeSchema: Schema.Number,
181
+ driverValueMapping: expression[Expression.TypeId].driverValueMapping,
182
+ nullability,
183
+ dialect: expression[Expression.TypeId].dialect,
184
+ kind: resultKind,
185
+ dependencies: expression[Expression.TypeId].dependencies
186
+ }, {
187
+ kind,
188
+ value: expression
189
+ }) as UnaryResult<Kind, Value, Nullable, ResultKind>
190
+ }
191
+
192
+ export const abs = <Value extends NumericExpressionInput>(
193
+ value: Value
194
+ ): UnaryResult<"abs", Value, Expression.NullabilityOf<AsExpression<Value>>, Expression.KindOf<AsExpression<Value>>> => {
195
+ const expression = toExpression(value)
196
+ return unary("abs", value, expression[Expression.TypeId].nullability, expression[Expression.TypeId].kind) as never
197
+ }
198
+
199
+ export const negate = <Value extends NumericExpressionInput>(
200
+ value: Value
201
+ ): UnaryResult<"negate", Value, Expression.NullabilityOf<AsExpression<Value>>, Expression.KindOf<AsExpression<Value>>> => {
202
+ const expression = toExpression(value)
203
+ return unary("negate", value, expression[Expression.TypeId].nullability, expression[Expression.TypeId].kind) as never
204
+ }
@@ -203,10 +203,20 @@ type LiteralExpression<Value extends LiteralValue> = Expression.Scalar<
203
203
  */
204
204
  export type ExpressionInput = Expression.Any | LiteralValue
205
205
 
206
+ type NumericBaseDbType = Expression.DbType.Base<string, string> & {
207
+ readonly family: "numeric" | "integer" | "real"
208
+ }
209
+
210
+ type NumericDbType =
211
+ | NumericBaseDbType
212
+ | (Expression.DbType.Domain<string, any, string> & {
213
+ readonly base: NumericDbType
214
+ })
215
+
206
216
  /** Input accepted by numeric clauses such as `limit(...)` and `offset(...)`. */
207
217
  export type NumericExpressionInput = Expression.Scalar<
208
218
  number,
209
- Expression.DbType.Any,
219
+ NumericDbType,
210
220
  Expression.Nullability,
211
221
  string,
212
222
  "scalar",
@@ -429,6 +439,8 @@ type GroupingKeyOfAst<Ast extends ExpressionAst.Any> =
429
439
  ? `function(${EscapeGroupingText<Name>},${JoinGroupingKeys<{
430
440
  readonly [K in keyof Args]: Args[K] extends Expression.Any ? GroupingKeyOfExpression<Args[K]> : never
431
441
  } & readonly string[]>})`
442
+ : Ast extends ExpressionAst.CustomSqlNode
443
+ ? string
432
444
  : Ast extends ExpressionAst.UnaryNode<infer Kind extends ExpressionAst.UnaryKind, infer Value extends Expression.Any>
433
445
  ? `${Kind}(${GroupingKeyOfExpression<Value>})`
434
446
  : Ast extends ExpressionAst.BinaryNode<infer Kind extends ExpressionAst.BinaryKind, infer Left extends Expression.Any, infer Right extends Expression.Any>
@@ -164,8 +164,11 @@ const normalizeOffsetTime = (value: unknown): string => {
164
164
  return `${formatLocalTime(validDate(value))}Z`
165
165
  }
166
166
  const raw = expectString(value, "offset time").trim()
167
- if (isValidOffsetTimeString(raw)) {
168
- return raw
167
+ const canonical = raw
168
+ .replace(/([+-]\d{2})$/, "$1:00")
169
+ .replace(/([+-]\d{2})(\d{2})$/, "$1:$2")
170
+ if (isValidOffsetTimeString(canonical)) {
171
+ return canonical
169
172
  }
170
173
  throw new Error("Expected an offset-time value")
171
174
  }
@@ -379,6 +379,7 @@ const deriveRuntimeSchema = (
379
379
  case "jsonTypeOf":
380
380
  return Schema.String
381
381
  case "count":
382
+ return runtimeSchemaForDbType(state.dbType)
382
383
  case "jsonLength":
383
384
  return FiniteNumberSchema
384
385
  case "max":
@@ -396,7 +397,7 @@ const deriveRuntimeSchema = (
396
397
  case "window":
397
398
  return ast.function === "over" && ast.value !== undefined
398
399
  ? expressionRuntimeSchema(ast.value, context)
399
- : FiniteNumberSchema
400
+ : runtimeSchemaForDbType(state.dbType)
400
401
  case "jsonGet":
401
402
  case "jsonPath":
402
403
  case "jsonAccess":
@@ -4,6 +4,7 @@ import * as Schema from "effect/Schema"
4
4
  import { standardDatatypes } from "../standard/datatypes/index.js"
5
5
 
6
6
  import * as Expression from "./scalar.js"
7
+ import type * as FunctionConstraint from "./function-constraints.js"
7
8
  import * as Plan from "./row-set.js"
8
9
  import * as Table from "./table.js"
9
10
  import type {
@@ -135,6 +136,7 @@ import type { FormulaOfPredicate } from "./predicate/normalize.js"
135
136
  import type { TrueFormula } from "./predicate/formula.js"
136
137
  import { assumeFormulaTrue, formulaOfExpression as formulaOfExpressionRuntime, trueFormula } from "./predicate/runtime.js"
137
138
  import { dedupeGroupedExpressions } from "./grouping-key.js"
139
+ import { validateWindowFrame } from "./window-frame.js"
138
140
  import { makeDslMutationRuntime } from "./dsl-mutation-runtime.js"
139
141
  import { makeDslPlanRuntime } from "./dsl-plan-runtime.js"
140
142
  import { makeDslQueryRuntime } from "./dsl-query-runtime.js"
@@ -1426,6 +1428,7 @@ type WindowSpecInput<
1426
1428
  > = {
1427
1429
  readonly partitionBy?: PartitionBy
1428
1430
  readonly orderBy?: OrderBy
1431
+ readonly frame?: never
1429
1432
  }
1430
1433
 
1431
1434
  type OrderedWindowSpecInput<
@@ -1434,6 +1437,7 @@ type OrderedWindowSpecInput<
1434
1437
  > = {
1435
1438
  readonly partitionBy?: PartitionBy
1436
1439
  readonly orderBy: OrderBy
1440
+ readonly frame?: never
1437
1441
  }
1438
1442
 
1439
1443
  type WindowOrderExpressionTuple<
@@ -1506,8 +1510,8 @@ type NumberWindowExpression<
1506
1510
  PartitionBy extends readonly WindowPartitionInput[],
1507
1511
  OrderBy extends NonEmptyWindowOrderTerms
1508
1512
  > = AstBackedExpression<
1509
- number,
1510
- Expression.DbType.Any,
1513
+ RuntimeOfDbType<ReturnType<typeof standardDatatypes.bigint>>,
1514
+ ReturnType<typeof standardDatatypes.bigint>,
1511
1515
  "never",
1512
1516
  NumberWindowDialectOf<PartitionBy, OrderBy>,
1513
1517
  "window",
@@ -1798,6 +1802,12 @@ const profile: QueryDialectProfile<Dialect, TextDb, NumericDb, BoolDb, Timestamp
1798
1802
  >(
1799
1803
  spec: WindowSpecInput<PartitionBy, OrderBy> | OrderedWindowSpecInput<PartitionBy, Extract<OrderBy, NonEmptyWindowOrderTerms>> | undefined
1800
1804
  ) => {
1805
+ if ((spec as { readonly frame?: ExpressionAst.WindowFrameNode } | undefined)?.frame !== undefined) {
1806
+ throw new Error(
1807
+ "over does not accept an explicit frame on the portable Function API; use a dialect Function.over helper"
1808
+ )
1809
+ }
1810
+ validateWindowFrame(spec?.frame)
1801
1811
  const partitionBy = [...(spec?.partitionBy ?? [])] as unknown as PartitionBy
1802
1812
  const orderBy = (spec?.orderBy ?? []).map((term) => {
1803
1813
  const direction = term.direction ?? "asc"
@@ -1812,7 +1822,8 @@ const profile: QueryDialectProfile<Dialect, TextDb, NumericDb, BoolDb, Timestamp
1812
1822
  } & readonly { readonly value: WindowOrderInput; readonly direction: OrderDirection }[]
1813
1823
  return {
1814
1824
  partitionBy,
1815
- orderBy
1825
+ orderBy,
1826
+ frame: spec?.frame
1816
1827
  }
1817
1828
  }
1818
1829
 
@@ -2142,12 +2153,17 @@ type BinaryPredicateExpression<
2142
2153
  }
2143
2154
 
2144
2155
  const upper = <Value extends ExpressionInput>(
2145
- value: Value
2156
+ value: Value & FunctionConstraint.CaseConversionInput<
2157
+ NoInfer<Value>,
2158
+ DialectDbTypeOfInput<NoInfer<Value>, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2159
+ Dialect,
2160
+ "upper"
2161
+ >
2146
2162
  ): AstBackedExpression<
2147
2163
  string,
2148
2164
  TextDb,
2149
2165
  NullabilityOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2150
- DialectOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2166
+ Dialect,
2151
2167
  KindOf<DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>,
2152
2168
  DependenciesOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2153
2169
  ExpressionAst.UnaryNode<"upper", DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>
@@ -2157,7 +2173,7 @@ type BinaryPredicateExpression<
2157
2173
  runtime: "" as string,
2158
2174
  dbType: profile.textDb as TextDb,
2159
2175
  nullability: expression[Expression.TypeId].nullability as NullabilityOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2160
- dialect: expression[Expression.TypeId].dialect,
2176
+ dialect: profile.dialect as Dialect,
2161
2177
  kind: expression[Expression.TypeId].kind as KindOf<DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>,
2162
2178
 
2163
2179
  dependencies: expression[Expression.TypeId].dependencies
@@ -2168,12 +2184,17 @@ type BinaryPredicateExpression<
2168
2184
  }
2169
2185
 
2170
2186
  const lower = <Value extends ExpressionInput>(
2171
- value: Value
2187
+ value: Value & FunctionConstraint.CaseConversionInput<
2188
+ NoInfer<Value>,
2189
+ DialectDbTypeOfInput<NoInfer<Value>, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2190
+ Dialect,
2191
+ "lower"
2192
+ >
2172
2193
  ): AstBackedExpression<
2173
2194
  string,
2174
2195
  TextDb,
2175
2196
  NullabilityOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2176
- DialectOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2197
+ Dialect,
2177
2198
  KindOf<DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>,
2178
2199
  DependenciesOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2179
2200
  ExpressionAst.UnaryNode<"lower", DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>
@@ -2183,7 +2204,7 @@ type BinaryPredicateExpression<
2183
2204
  runtime: "" as string,
2184
2205
  dbType: profile.textDb as TextDb,
2185
2206
  nullability: expression[Expression.TypeId].nullability as NullabilityOfDialectStringInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
2186
- dialect: expression[Expression.TypeId].dialect,
2207
+ dialect: profile.dialect as Dialect,
2187
2208
  kind: expression[Expression.TypeId].kind as KindOf<DialectAsStringExpression<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>,
2188
2209
 
2189
2210
  dependencies: expression[Expression.TypeId].dependencies
@@ -3494,8 +3515,8 @@ type BinaryPredicateExpression<
3494
3515
  const count = <Value extends ExpressionInput>(
3495
3516
  value: Value
3496
3517
  ): AstBackedExpression<
3497
- number,
3498
- NumericDb,
3518
+ RuntimeOfDbType<ReturnType<typeof standardDatatypes.bigint>>,
3519
+ ReturnType<typeof standardDatatypes.bigint>,
3499
3520
  "never",
3500
3521
  DialectOfDialectInput<Value, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>,
3501
3522
  "aggregate",
@@ -3504,8 +3525,8 @@ type BinaryPredicateExpression<
3504
3525
  > => {
3505
3526
  const expression = toDialectExpression(value)
3506
3527
  return makeExpression({
3507
- runtime: 0 as number,
3508
- dbType: profile.numericDb as NumericDb,
3528
+ runtime: undefined as unknown as RuntimeOfDbType<ReturnType<typeof standardDatatypes.bigint>>,
3529
+ dbType: standardDatatypes.bigint(),
3509
3530
  nullability: "never",
3510
3531
  dialect: expression[Expression.TypeId].dialect,
3511
3532
  kind: "aggregate",
@@ -3717,7 +3738,8 @@ type BinaryPredicateExpression<
3717
3738
  function: "over",
3718
3739
  value,
3719
3740
  partitionBy: normalized.partitionBy,
3720
- orderBy: normalized.orderBy
3741
+ orderBy: normalized.orderBy,
3742
+ frame: normalized.frame
3721
3743
  })
3722
3744
  }
3723
3745
 
@@ -3732,8 +3754,8 @@ type BinaryPredicateExpression<
3732
3754
  const normalized = normalizeWindowSpec(spec)
3733
3755
  const expressions = mergeWindowExpressions(undefined, normalized.partitionBy, normalized.orderBy)
3734
3756
  return makeExpression({
3735
- runtime: 0 as number,
3736
- dbType: profile.numericDb as Expression.DbType.Any,
3757
+ runtime: undefined as unknown as RuntimeOfDbType<ReturnType<typeof standardDatatypes.bigint>>,
3758
+ dbType: standardDatatypes.bigint(),
3737
3759
  nullability: "never",
3738
3760
  dialect: (expressions.find((expression) => expression[Expression.TypeId].dialect !== undefined)?.[Expression.TypeId].dialect ?? profile.dialect) as NumberWindowDialectOf<PartitionBy, OrderBy>,
3739
3761
  kind: "window",
@@ -3743,7 +3765,8 @@ type BinaryPredicateExpression<
3743
3765
  kind: "window",
3744
3766
  function: kind,
3745
3767
  partitionBy: normalized.partitionBy,
3746
- orderBy: normalized.orderBy
3768
+ orderBy: normalized.orderBy,
3769
+ frame: normalized.frame
3747
3770
  })
3748
3771
  }
3749
3772
 
@@ -3772,7 +3795,7 @@ type BinaryPredicateExpression<
3772
3795
  buildNumberWindow("denseRank", spec)
3773
3796
 
3774
3797
  const max = <Value extends Expression.Any>(
3775
- value: Value
3798
+ value: Value & FunctionConstraint.OrderedInput<NoInfer<Value>, Dialect, "max">
3776
3799
  ): AstBackedExpression<
3777
3800
  Expression.RuntimeOf<Value>,
3778
3801
  Expression.DbTypeOf<Value>,
@@ -3796,7 +3819,7 @@ type BinaryPredicateExpression<
3796
3819
  })
3797
3820
 
3798
3821
  const min = <Value extends Expression.Any>(
3799
- value: Value
3822
+ value: Value & FunctionConstraint.OrderedInput<NoInfer<Value>, Dialect, "min">
3800
3823
  ): AstBackedExpression<
3801
3824
  Expression.RuntimeOf<Value>,
3802
3825
  Expression.DbTypeOf<Value>,
@@ -3831,7 +3854,7 @@ type BinaryPredicateExpression<
3831
3854
  const coalesce = <
3832
3855
  Values extends readonly [ExpressionInput, ExpressionInput, ...ExpressionInput[]]
3833
3856
  >(
3834
- ...values: Values
3857
+ ...values: Values & FunctionConstraint.CoalesceConstraint<DialectExpressionTuple<Values, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>, Dialect>
3835
3858
  ): AstBackedExpression<
3836
3859
  CoalesceRuntimeTuple<DialectExpressionTuple<Values, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>>,
3837
3860
  Expression.DbTypeOf<DialectExpressionTuple<Values, Dialect, TextDb, NumericDb, BoolDb, TimestampDb, NullDb>[number]>,
@@ -0,0 +1,30 @@
1
+ import type * as ExpressionAst from "./expression-ast.js"
2
+
3
+ const validateBoundary = (
4
+ boundary: ExpressionAst.WindowFrameBoundary
5
+ ): void => {
6
+ if (typeof boundary === "object") {
7
+ const value = "preceding" in boundary ? boundary.preceding : boundary.following
8
+ if (!Number.isSafeInteger(value) || value < 0) {
9
+ throw new Error("window frame offsets must be non-negative safe integers")
10
+ }
11
+ }
12
+ }
13
+
14
+ export const validateWindowFrame = (
15
+ frame: ExpressionAst.WindowFrameNode | undefined
16
+ ): void => {
17
+ if (frame === undefined) {
18
+ return
19
+ }
20
+ validateBoundary(frame.start)
21
+ if (frame.end !== undefined) {
22
+ validateBoundary(frame.end)
23
+ }
24
+ if (frame.start === "unboundedFollowing") {
25
+ throw new Error("window frame cannot start with unbounded following")
26
+ }
27
+ if (frame.end === "unboundedPreceding") {
28
+ throw new Error("window frame cannot end with unbounded preceding")
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ import type * as ExpressionAst from "./expression-ast.js"
2
+
3
+ const renderBoundary = (
4
+ boundary: ExpressionAst.WindowFrameBoundary
5
+ ): string => {
6
+ if (boundary === "unboundedPreceding") {
7
+ return "unbounded preceding"
8
+ }
9
+ if (boundary === "currentRow") {
10
+ return "current row"
11
+ }
12
+ if (boundary === "unboundedFollowing") {
13
+ return "unbounded following"
14
+ }
15
+ if ("preceding" in boundary) {
16
+ return `${boundary.preceding} preceding`
17
+ }
18
+ return `${boundary.following} following`
19
+ }
20
+
21
+ export const renderWindowFrame = (
22
+ frame: ExpressionAst.WindowFrameNode
23
+ ): string => frame.end === undefined
24
+ ? `${frame.unit} ${renderBoundary(frame.start)}`
25
+ : `${frame.unit} between ${renderBoundary(frame.start)} and ${renderBoundary(frame.end)}`