effect-qb 0.16.0 → 0.19.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 (128) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +8065 -0
  3. package/dist/mysql.js +4036 -2418
  4. package/dist/postgres/metadata.js +2536 -625
  5. package/dist/postgres.js +8248 -7857
  6. package/dist/sqlite.js +8854 -0
  7. package/dist/standard.js +8019 -0
  8. package/package.json +15 -3
  9. package/src/casing.ts +71 -0
  10. package/src/index.ts +2 -0
  11. package/src/internal/casing.ts +89 -0
  12. package/src/internal/column-state.ts +11 -6
  13. package/src/internal/column.ts +44 -7
  14. package/src/internal/datatypes/define.ts +2 -1
  15. package/src/internal/datatypes/enrich.ts +23 -0
  16. package/src/internal/datatypes/lookup.ts +14 -7
  17. package/src/internal/derived-table.ts +7 -13
  18. package/src/internal/dialect-renderers/mysql.ts +2046 -0
  19. package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
  20. package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
  21. package/src/internal/dialect.ts +37 -0
  22. package/src/internal/dsl-mutation-runtime.ts +29 -10
  23. package/src/internal/dsl-plan-runtime.ts +41 -24
  24. package/src/internal/dsl-query-runtime.ts +11 -31
  25. package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
  26. package/src/internal/executor.ts +57 -15
  27. package/src/internal/expression-ast.ts +3 -2
  28. package/src/internal/grouping-key.ts +216 -9
  29. package/src/internal/implication-runtime.ts +3 -2
  30. package/src/internal/json/types.ts +155 -40
  31. package/src/internal/predicate/context.ts +14 -1
  32. package/src/internal/predicate/key.ts +19 -2
  33. package/src/internal/predicate/runtime.ts +30 -3
  34. package/src/internal/query.d.ts +38 -11
  35. package/src/internal/query.ts +315 -54
  36. package/src/internal/renderer.ts +51 -6
  37. package/src/internal/runtime/driver-value-mapping.ts +58 -0
  38. package/src/internal/runtime/normalize.ts +74 -43
  39. package/src/internal/runtime/schema.ts +5 -3
  40. package/src/internal/runtime/value.ts +153 -30
  41. package/src/internal/scalar.ts +6 -1
  42. package/src/internal/schema-derivation.d.ts +12 -61
  43. package/src/internal/schema-derivation.ts +90 -38
  44. package/src/internal/schema-expression.ts +2 -2
  45. package/src/internal/sql-expression-renderer.ts +19 -0
  46. package/src/internal/standard-dsl.ts +6885 -0
  47. package/src/internal/table-options.ts +229 -62
  48. package/src/internal/table.d.ts +33 -32
  49. package/src/internal/table.ts +469 -160
  50. package/src/mysql/column-extension.ts +3 -0
  51. package/src/mysql/column.ts +27 -12
  52. package/src/mysql/datatypes/index.ts +24 -2
  53. package/src/mysql/errors/catalog.ts +5 -5
  54. package/src/mysql/errors/normalize.ts +2 -2
  55. package/src/mysql/executor.ts +7 -5
  56. package/src/mysql/internal/dialect.ts +9 -4
  57. package/src/mysql/internal/dsl.ts +906 -324
  58. package/src/mysql/internal/renderer.ts +7 -2
  59. package/src/mysql/json.ts +37 -0
  60. package/src/mysql/query-extension.ts +16 -0
  61. package/src/mysql/query.ts +9 -2
  62. package/src/mysql/renderer.ts +31 -4
  63. package/src/mysql.ts +4 -12
  64. package/src/postgres/column-extension.ts +28 -0
  65. package/src/postgres/column.ts +9 -13
  66. package/src/postgres/datatypes/index.d.ts +2 -1
  67. package/src/postgres/datatypes/index.ts +3 -2
  68. package/src/postgres/errors/normalize.ts +2 -2
  69. package/src/postgres/executor.ts +55 -10
  70. package/src/postgres/function/core.ts +20 -4
  71. package/src/postgres/function/index.ts +1 -17
  72. package/src/postgres/internal/dialect.ts +9 -4
  73. package/src/postgres/internal/dsl.ts +850 -359
  74. package/src/postgres/internal/renderer.ts +7 -2
  75. package/src/postgres/internal/schema-ddl.ts +22 -9
  76. package/src/postgres/internal/schema-model.ts +244 -10
  77. package/src/postgres/json.ts +100 -24
  78. package/src/postgres/jsonb.ts +38 -0
  79. package/src/postgres/query-extension.ts +2 -0
  80. package/src/postgres/query.ts +9 -2
  81. package/src/postgres/renderer.ts +31 -4
  82. package/src/postgres/schema-management.ts +108 -16
  83. package/src/postgres/schema.ts +98 -15
  84. package/src/postgres/table.ts +203 -398
  85. package/src/postgres/type.ts +8 -7
  86. package/src/postgres.ts +9 -11
  87. package/src/sqlite/column-extension.ts +3 -0
  88. package/src/sqlite/column.ts +127 -0
  89. package/src/sqlite/datatypes/index.ts +80 -0
  90. package/src/sqlite/datatypes/spec.ts +98 -0
  91. package/src/sqlite/errors/catalog.ts +103 -0
  92. package/src/sqlite/errors/fields.ts +19 -0
  93. package/src/sqlite/errors/index.ts +19 -0
  94. package/src/sqlite/errors/normalize.ts +229 -0
  95. package/src/sqlite/errors/requirements.ts +71 -0
  96. package/src/sqlite/errors/types.ts +29 -0
  97. package/src/sqlite/executor.ts +229 -0
  98. package/src/sqlite/function/aggregate.ts +2 -0
  99. package/src/sqlite/function/core.ts +2 -0
  100. package/src/sqlite/function/index.ts +19 -0
  101. package/src/sqlite/function/string.ts +2 -0
  102. package/src/sqlite/function/temporal.ts +100 -0
  103. package/src/sqlite/function/window.ts +2 -0
  104. package/src/sqlite/internal/dialect.ts +42 -0
  105. package/src/sqlite/internal/dsl.ts +6979 -0
  106. package/src/sqlite/internal/renderer.ts +51 -0
  107. package/src/sqlite/json.ts +39 -0
  108. package/src/sqlite/query-extension.ts +2 -0
  109. package/src/sqlite/query.ts +196 -0
  110. package/src/sqlite/renderer.ts +51 -0
  111. package/src/sqlite.ts +14 -0
  112. package/src/standard/column.ts +163 -0
  113. package/src/standard/datatypes/index.ts +83 -0
  114. package/src/standard/datatypes/spec.ts +98 -0
  115. package/src/standard/dialect.ts +40 -0
  116. package/src/standard/function/aggregate.ts +2 -0
  117. package/src/standard/function/core.ts +2 -0
  118. package/src/standard/function/index.ts +18 -0
  119. package/src/standard/function/string.ts +2 -0
  120. package/src/standard/function/temporal.ts +78 -0
  121. package/src/standard/function/window.ts +2 -0
  122. package/src/standard/internal/renderer.ts +45 -0
  123. package/src/standard/query.ts +152 -0
  124. package/src/standard/renderer.ts +21 -0
  125. package/src/standard/table.ts +147 -0
  126. package/src/standard.ts +18 -0
  127. package/src/internal/aggregation-validation.ts +0 -57
  128. package/src/mysql/table.ts +0 -157
@@ -16,6 +16,8 @@ import * as Query from "./query.js"
16
16
  import * as QueryAst from "./query-ast.js"
17
17
  import * as Renderer from "./renderer.js"
18
18
  import * as Plan from "./row-set.js"
19
+ import { columnPredicateKey } from "./predicate/runtime.js"
20
+ import { isJsonValue } from "./runtime/normalize.js"
19
21
 
20
22
  /** Flat database row keyed by rendered projection aliases. */
21
23
  export type FlatRow = Readonly<Record<string, unknown>>
@@ -221,7 +223,7 @@ const effectiveRuntimeNullability = (
221
223
  return "always"
222
224
  }
223
225
  if (ast.kind === "column") {
224
- const key = `${ast.tableName}.${ast.columnName}`
226
+ const key = columnPredicateKey(ast.tableName, ast.columnName)
225
227
  if (scope.absentSourceNames.has(ast.tableName) || scope.nullKeys.has(key)) {
226
228
  return "always"
227
229
  }
@@ -237,6 +239,20 @@ const effectiveRuntimeNullability = (
237
239
  : nullability
238
240
  }
239
241
 
242
+ const dbTypeAllowsTopLevelJsonNull = (
243
+ dbType: Expression.DbType.Any
244
+ ): boolean => {
245
+ if ("base" in dbType) {
246
+ return dbTypeAllowsTopLevelJsonNull(dbType.base)
247
+ }
248
+ return ("variant" in dbType && dbType.variant === "json") || dbType.runtime === "json"
249
+ }
250
+
251
+ const schemaAcceptsNull = (
252
+ schema: Schema.Schema.Any | undefined
253
+ ): boolean =>
254
+ schema !== undefined && (Schema.is(schema) as (value: unknown) => boolean)(null)
255
+
240
256
  const decodeProjectionValue = (
241
257
  rendered: Renderer.RenderedQuery<any, any>,
242
258
  projection: Renderer.RenderedQuery<any, any>["projections"][number],
@@ -262,8 +278,12 @@ const decodeProjectionValue = (
262
278
  }
263
279
 
264
280
  const nullability = effectiveRuntimeNullability(expression, scope)
281
+ const schema = expressionRuntimeSchema(expression, { assumptions: scope.assumptions })
265
282
  if (normalized === null) {
266
283
  if (nullability === "never") {
284
+ if (dbTypeAllowsTopLevelJsonNull(expression[Expression.TypeId].dbType) && schemaAcceptsNull(schema)) {
285
+ return null
286
+ }
267
287
  throw makeRowDecodeError(
268
288
  rendered,
269
289
  projection,
@@ -289,7 +309,18 @@ const decodeProjectionValue = (
289
309
  )
290
310
  }
291
311
 
292
- const schema = expressionRuntimeSchema(expression, { assumptions: scope.assumptions })
312
+ if (dbTypeAllowsTopLevelJsonNull(expression[Expression.TypeId].dbType) && !isJsonValue(normalized)) {
313
+ throw makeRowDecodeError(
314
+ rendered,
315
+ projection,
316
+ expression,
317
+ raw,
318
+ "schema",
319
+ new Error("Expected a JSON value"),
320
+ normalized
321
+ )
322
+ }
323
+
293
324
  if (schema === undefined) {
294
325
  return normalized
295
326
  }
@@ -316,8 +347,8 @@ export const makeRowDecoder = (
316
347
  const projections = flattenSelection(
317
348
  Query.getAst(plan).select as Record<string, unknown>
318
349
  )
319
- const byAlias = new Map(
320
- projections.map((projection) => [projection.alias, projection.expression] as const)
350
+ const byPath = new Map(
351
+ projections.map((projection) => [JSON.stringify(projection.path), projection.expression] as const)
321
352
  )
322
353
  const driverMode = options.driverMode ?? "raw"
323
354
  const valueMappings = options.valueMappings ?? rendered.valueMappings
@@ -325,12 +356,19 @@ export const makeRowDecoder = (
325
356
  return (row) => {
326
357
  const decoded: Record<string, unknown> = {}
327
358
  for (const projection of rendered.projections) {
328
- if (!(projection.alias in row)) {
329
- continue
330
- }
331
- const expression = byAlias.get(projection.alias)
359
+ const expression = byPath.get(JSON.stringify(projection.path))
332
360
  if (expression === undefined) {
333
- continue
361
+ throw new Error(`Rendered projection path '${projection.path.join(".")}' does not exist in the query selection`)
362
+ }
363
+ if (!(projection.alias in row)) {
364
+ throw makeRowDecodeError(
365
+ rendered,
366
+ projection,
367
+ expression,
368
+ undefined,
369
+ "schema",
370
+ new Error(`Missing required projection alias '${projection.alias}'`)
371
+ )
334
372
  }
335
373
  setPath(
336
374
  decoded,
@@ -471,24 +509,28 @@ export const fromDriver = <
471
509
  renderer: Renderer.Renderer<Dialect>,
472
510
  sqlDriver: Driver<Dialect, Error, Context>
473
511
  ): Executor<Dialect, Error, Context> => {
474
- const executor = {
512
+ const executor: Executor<Dialect, Error, Context> = {
475
513
  dialect: renderer.dialect,
476
- execute(plan: any) {
514
+ execute<PlanValue extends Query.Plan.Any>(
515
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
516
+ ) {
477
517
  const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
478
518
  return Effect.map(
479
519
  sqlDriver.execute(rendered),
480
520
  (rows) => remapRows<any>(rendered, rows)
481
- )
521
+ ) as Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
482
522
  },
483
- stream(plan: any) {
523
+ stream<PlanValue extends Query.Plan.Any>(
524
+ plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
525
+ ) {
484
526
  const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
485
527
  return Stream.mapChunks(
486
528
  sqlDriver.stream(rendered),
487
529
  (rows) => Chunk.unsafeFromArray(remapRows<any>(rendered, Chunk.toReadonlyArray(rows)))
488
- )
530
+ ) as Stream.Stream<Query.ResultRow<PlanValue>, Error, Context>
489
531
  }
490
532
  }
491
- return executor as unknown as Executor<Dialect, Error, Context>
533
+ return executor
492
534
  }
493
535
 
494
536
  export const streamFromSqlClient = <Dialect extends string>(
@@ -36,11 +36,12 @@ export interface CastNode<
36
36
 
37
37
  /** Explicit collation captured by the internal expression AST. */
38
38
  export interface CollateNode<
39
- Value extends Expression.Any = Expression.Any
39
+ Value extends Expression.Any = Expression.Any,
40
+ Collation extends readonly [string, ...string[]] = readonly [string, ...string[]]
40
41
  > {
41
42
  readonly kind: "collate"
42
43
  readonly value: Value
43
- readonly collation: readonly [string, ...string[]]
44
+ readonly collation: Collation
44
45
  }
45
46
 
46
47
  /** General SQL function call captured by the internal expression AST. */
@@ -1,9 +1,29 @@
1
1
  import * as Expression from "./scalar.js"
2
2
  import * as ExpressionAst from "./expression-ast.js"
3
+ import * as JsonPath from "./json/path.js"
4
+ import { columnPredicateKey } from "./predicate/runtime.js"
5
+
6
+ const subqueryPlanIds = new WeakMap<object, string>()
7
+ let nextSubqueryPlanId = 0
8
+
9
+ const subqueryPlanGroupingKey = (plan: unknown): string => {
10
+ if (plan === null || typeof plan !== "object") {
11
+ return "unknown"
12
+ }
13
+ const existing = subqueryPlanIds.get(plan)
14
+ if (existing !== undefined) {
15
+ return existing
16
+ }
17
+ const next = `${nextSubqueryPlanId++}`
18
+ subqueryPlanIds.set(plan, next)
19
+ return next
20
+ }
3
21
 
4
22
  const literalGroupingKey = (value: unknown): string => {
5
23
  if (value instanceof Date) {
6
- return `date:${value.toISOString()}`
24
+ return Number.isNaN(value.getTime())
25
+ ? "date:invalid"
26
+ : `date:${value.toISOString()}`
7
27
  }
8
28
  if (value === null) {
9
29
  return "null"
@@ -20,17 +40,150 @@ const literalGroupingKey = (value: unknown): string => {
20
40
  }
21
41
  }
22
42
 
43
+ const isExpression = (value: unknown): value is Expression.Any =>
44
+ value !== null && typeof value === "object" && Expression.TypeId in value
45
+
46
+ const expressionGroupingKey = (value: unknown): string =>
47
+ isExpression(value) ? groupingKeyOfExpression(value) : "missing"
48
+
49
+ const requiredExpressionGroupingKey = (
50
+ _functionName: string,
51
+ value: unknown
52
+ ): string => groupingKeyOfExpression(value as Expression.Any)
53
+
54
+ const requiredBinaryExpressionGroupingKey = (
55
+ _functionName: string,
56
+ left: unknown,
57
+ right: unknown
58
+ ): string => `${groupingKeyOfExpression(left as Expression.Any)},${groupingKeyOfExpression(right as Expression.Any)}`
59
+
60
+ const functionCallArgsGroupingKey = (args: unknown): string =>
61
+ (args as readonly Expression.Any[]).map(groupingKeyOfExpression).join(",")
62
+
63
+ const variadicGroupingKey = (
64
+ _functionName: string,
65
+ values: unknown
66
+ ): string => (values as readonly Expression.Any[]).map(groupingKeyOfExpression).join(",")
67
+
68
+ const castTargetGroupingKey = (target: unknown): string => {
69
+ const typed = target as { readonly dialect?: string; readonly kind?: string } | null | undefined
70
+ return `${typed?.dialect}:${typed?.kind}`
71
+ }
72
+
73
+ const escapeGroupingText = (value: string): string =>
74
+ value
75
+ .replace(/\\/g, "\\\\")
76
+ .replace(/,/g, "\\,")
77
+ .replace(/\|/g, "\\|")
78
+ .replace(/=/g, "\\=")
79
+ .replace(/>/g, "\\>")
80
+
81
+ const functionCallNameGroupingKey = (name: unknown): string =>
82
+ escapeGroupingText(name as string)
83
+
84
+ const quantifiedComparisonGroupingName = (
85
+ kind: "comparisonAny" | "comparisonAll"
86
+ ): "compareAny" | "compareAll" =>
87
+ kind === "comparisonAny" ? "compareAny" : "compareAll"
88
+
89
+ const caseGroupingKey = (
90
+ branches: unknown,
91
+ fallback: unknown
92
+ ): string => {
93
+ const typedBranches = branches as readonly ExpressionAst.CaseBranchNode[]
94
+ return `case(${typedBranches.map((branch) =>
95
+ `when:${groupingKeyOfExpression(branch.when)}=>${groupingKeyOfExpression(branch.then)}`).join("|")};else:${groupingKeyOfExpression(fallback as Expression.Any)})`
96
+ }
97
+
98
+ const collationGroupingKey = (collation: unknown): string => {
99
+ if (Array.isArray(collation)) {
100
+ return collation.map((part) => escapeGroupingText(String(part))).join(",")
101
+ }
102
+ return escapeGroupingText(String(collation))
103
+ }
104
+
105
+ const jsonSegmentGroupingKey = (segment: unknown): string => {
106
+ if (segment !== null && typeof segment === "object" && "kind" in segment) {
107
+ switch ((segment as { readonly kind: string }).kind) {
108
+ case "key":
109
+ return `key:${escapeGroupingText((segment as JsonPath.KeySegment).key)}`
110
+ case "index":
111
+ return `index:${(segment as JsonPath.IndexSegment).index}`
112
+ case "wildcard":
113
+ return "wildcard"
114
+ case "slice": {
115
+ const slice = segment as JsonPath.SliceSegment
116
+ return `slice:${slice.start ?? ""}:${slice.end ?? ""}`
117
+ }
118
+ case "descend":
119
+ return "descend"
120
+ }
121
+ }
122
+ if (typeof segment === "string") {
123
+ return `key:${escapeGroupingText(segment)}`
124
+ }
125
+ if (typeof segment === "number") {
126
+ return `index:${segment}`
127
+ }
128
+ return "unknown"
129
+ }
130
+
131
+ const jsonPathGroupingKey = (segments: unknown): string => {
132
+ if (segments === undefined) {
133
+ return ""
134
+ }
135
+ if (!Array.isArray(segments)) {
136
+ return "unknown"
137
+ }
138
+ return segments.map(jsonSegmentGroupingKey).join(",")
139
+ }
140
+
141
+ const isJsonPath = (value: unknown): value is JsonPath.Path =>
142
+ value !== null && typeof value === "object" && JsonPath.TypeId in value
143
+
144
+ const jsonOpaquePathGroupingKey = (value: unknown): string => {
145
+ if (isJsonPath(value)) {
146
+ return `jsonpath:${jsonPathGroupingKey(value.segments)}`
147
+ }
148
+ if (typeof value === "string") {
149
+ return `jsonpath:${escapeGroupingText(value)}`
150
+ }
151
+ if (isExpression(value)) {
152
+ return `jsonpath:${groupingKeyOfExpression(value)}`
153
+ }
154
+ return "jsonpath:unknown"
155
+ }
156
+
157
+ const jsonKeysGroupingKey = (keys: unknown): string => {
158
+ if (!Array.isArray(keys) || keys.length === 0) {
159
+ return ""
160
+ }
161
+ return keys.map((key) => escapeGroupingText(String(key))).join(",")
162
+ }
163
+
164
+ const jsonBuildObjectGroupingKey = (entries: unknown): string => {
165
+ return (entries as readonly { readonly key: string; readonly value: Expression.Any }[]).map((entry) =>
166
+ `${escapeGroupingText(entry.key)}=>${groupingKeyOfExpression(entry.value)}`).join("|")
167
+ }
168
+
169
+ const jsonBuildArrayGroupingKey = (values: unknown): string =>
170
+ (values as readonly Expression.Any[]).map(groupingKeyOfExpression).join(",")
171
+
23
172
  export const groupingKeyOfExpression = (expression: Expression.Any): string => {
24
173
  const ast = (expression as Expression.Any & {
25
174
  readonly [ExpressionAst.TypeId]: ExpressionAst.Any
26
175
  })[ExpressionAst.TypeId]
27
176
  switch (ast.kind) {
28
177
  case "column":
29
- return `column:${ast.tableName}.${ast.columnName}`
178
+ return `column:${columnPredicateKey(ast.tableName, ast.columnName)}`
30
179
  case "literal":
31
180
  return `literal:${literalGroupingKey(ast.value)}`
32
181
  case "cast":
33
- return `cast(${groupingKeyOfExpression(ast.value)} as ${ast.target.dialect}:${ast.target.kind})`
182
+ return `cast(${requiredExpressionGroupingKey("cast", ast.value)} as ${castTargetGroupingKey(ast.target)})`
183
+ case "collate":
184
+ return `collate(${requiredExpressionGroupingKey("collate", ast.value)},${collationGroupingKey(ast.collation)})`
185
+ case "function":
186
+ return `function(${functionCallNameGroupingKey(ast.name)},${functionCallArgsGroupingKey(ast.args)})`
34
187
  case "isNull":
35
188
  case "isNotNull":
36
189
  case "not":
@@ -39,7 +192,7 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
39
192
  case "count":
40
193
  case "max":
41
194
  case "min":
42
- return `${ast.kind}(${groupingKeyOfExpression(ast.value)})`
195
+ return `${ast.kind}(${requiredExpressionGroupingKey(ast.kind, ast.value)})`
43
196
  case "eq":
44
197
  case "neq":
45
198
  case "lt":
@@ -48,9 +201,16 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
48
201
  case "gte":
49
202
  case "like":
50
203
  case "ilike":
204
+ case "regexMatch":
205
+ case "regexIMatch":
206
+ case "regexNotMatch":
207
+ case "regexNotIMatch":
51
208
  case "isDistinctFrom":
52
209
  case "isNotDistinctFrom":
53
- return `${ast.kind}(${groupingKeyOfExpression(ast.left)},${groupingKeyOfExpression(ast.right)})`
210
+ case "contains":
211
+ case "containedBy":
212
+ case "overlaps":
213
+ return `${ast.kind}(${requiredBinaryExpressionGroupingKey(ast.kind, ast.left, ast.right)})`
54
214
  case "and":
55
215
  case "or":
56
216
  case "coalesce":
@@ -58,12 +218,59 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
58
218
  case "in":
59
219
  case "notIn":
60
220
  case "between":
61
- return `${ast.kind}(${ast.values.map(groupingKeyOfExpression).join(",")})`
221
+ return `${ast.kind}(${variadicGroupingKey(ast.kind, ast.values)})`
62
222
  case "case":
63
- return `case(${ast.branches.map((branch: ExpressionAst.CaseBranchNode) =>
64
- `when:${groupingKeyOfExpression(branch.when)}=>${groupingKeyOfExpression(branch.then)}`).join("|")};else:${groupingKeyOfExpression(ast.else)})`
223
+ return caseGroupingKey(ast.branches, ast.else)
224
+ case "exists":
225
+ return `exists(${subqueryPlanGroupingKey(ast.plan)})`
226
+ case "scalarSubquery":
227
+ return `scalarSubquery(${subqueryPlanGroupingKey(ast.plan)})`
228
+ case "inSubquery":
229
+ return `inSubquery(${requiredExpressionGroupingKey("inSubquery", ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
230
+ case "comparisonAny":
231
+ case "comparisonAll":
232
+ return `${ast.kind}(${ast.operator},${requiredExpressionGroupingKey(quantifiedComparisonGroupingName(ast.kind), ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
233
+ case "jsonGet":
234
+ case "jsonPath":
235
+ case "jsonAccess":
236
+ case "jsonTraverse":
237
+ case "jsonGetText":
238
+ case "jsonPathText":
239
+ case "jsonAccessText":
240
+ case "jsonTraverseText":
241
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonPathGroupingKey(ast.segments)})`
242
+ case "jsonHasKey":
243
+ case "jsonKeyExists":
244
+ case "jsonHasAnyKeys":
245
+ case "jsonHasAllKeys":
246
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonKeysGroupingKey(ast.keys)})`
247
+ case "jsonConcat":
248
+ case "jsonMerge":
249
+ return `json(${ast.kind},${expressionGroupingKey(ast.left)},${expressionGroupingKey(ast.right)},)`
250
+ case "jsonDelete":
251
+ case "jsonDeletePath":
252
+ case "jsonRemove":
253
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(undefined)},${jsonPathGroupingKey(ast.segments)})`
254
+ case "jsonSet":
255
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(ast.newValue)},${jsonPathGroupingKey(ast.segments)})`
256
+ case "jsonInsert":
257
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(ast.insert)},${jsonPathGroupingKey(ast.segments)})`
258
+ case "jsonPathExists":
259
+ case "jsonPathMatch":
260
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonOpaquePathGroupingKey(ast.query)})`
261
+ case "jsonBuildObject":
262
+ return `json(${ast.kind},${jsonBuildObjectGroupingKey(ast.entries)})`
263
+ case "jsonBuildArray":
264
+ return `json(${ast.kind},${jsonBuildArrayGroupingKey(ast.values)})`
265
+ case "jsonToJson":
266
+ case "jsonToJsonb":
267
+ case "jsonTypeOf":
268
+ case "jsonLength":
269
+ case "jsonKeys":
270
+ case "jsonStripNulls":
271
+ return `json(${ast.kind},${expressionGroupingKey(ast.value)})`
65
272
  default:
66
- throw new Error("Unsupported expression for grouping key generation")
273
+ return `unknown:${typeof ast === "object" && ast !== null && "kind" in ast ? String((ast as { readonly kind: unknown }).kind) : "ast"}`
67
274
  }
68
275
  }
69
276
 
@@ -5,6 +5,7 @@ import * as Table from "./table.js"
5
5
  import type { PredicateFormula } from "./predicate/formula.js"
6
6
  import {
7
7
  assumeFormulaTrue,
8
+ columnPredicateKey,
8
9
  contradictsFormula,
9
10
  guaranteedNonNullKeys,
10
11
  guaranteedNullKeys,
@@ -40,10 +41,10 @@ const collectPresenceWitnesses = (
40
41
  return
41
42
  }
42
43
  if (Expression.TypeId in selection && ExpressionAst.TypeId in selection) {
43
- const expression = selection as unknown as AstBackedExpression
44
+ const expression = selection as AstBackedExpression
44
45
  const ast = expression[ExpressionAst.TypeId]
45
46
  if (ast.kind === "column" && expression[Expression.TypeId].nullability === "never") {
46
- output.add(`${ast.tableName}.${ast.columnName}`)
47
+ output.add(columnPredicateKey(ast.tableName, ast.columnName))
47
48
  }
48
49
  return
49
50
  }