effect-qb 0.16.0 → 4.0.0-beta.66

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 (88) hide show
  1. package/README.md +7 -0
  2. package/dist/mysql.js +1858 -715
  3. package/dist/postgres/metadata.js +2036 -172
  4. package/dist/postgres.js +8011 -6849
  5. package/dist/sqlite.js +8433 -0
  6. package/package.json +7 -4
  7. package/src/internal/column-state.d.ts +3 -3
  8. package/src/internal/column-state.ts +3 -3
  9. package/src/internal/column.ts +8 -8
  10. package/src/internal/derived-table.ts +29 -3
  11. package/src/internal/dialect.ts +3 -1
  12. package/src/internal/dsl-mutation-runtime.ts +173 -4
  13. package/src/internal/dsl-plan-runtime.ts +165 -20
  14. package/src/internal/dsl-query-runtime.ts +60 -6
  15. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  16. package/src/internal/executor.ts +100 -43
  17. package/src/internal/expression-ast.ts +3 -2
  18. package/src/internal/grouping-key.ts +141 -1
  19. package/src/internal/implication-runtime.ts +2 -1
  20. package/src/internal/json/types.ts +155 -40
  21. package/src/internal/predicate/context.ts +14 -1
  22. package/src/internal/predicate/key.ts +19 -2
  23. package/src/internal/predicate/runtime.ts +27 -3
  24. package/src/internal/query.d.ts +1 -1
  25. package/src/internal/query.ts +253 -31
  26. package/src/internal/renderer.ts +35 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +60 -2
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +32 -40
  30. package/src/internal/runtime/value.ts +159 -39
  31. package/src/internal/scalar.d.ts +1 -1
  32. package/src/internal/scalar.ts +1 -1
  33. package/src/internal/schema-derivation.d.ts +12 -61
  34. package/src/internal/schema-derivation.ts +95 -43
  35. package/src/internal/table-options.ts +108 -1
  36. package/src/internal/table.d.ts +29 -22
  37. package/src/internal/table.ts +260 -53
  38. package/src/mysql/column.ts +24 -8
  39. package/src/mysql/datatypes/index.ts +21 -0
  40. package/src/mysql/errors/catalog.ts +5 -5
  41. package/src/mysql/errors/normalize.ts +2 -2
  42. package/src/mysql/executor.ts +4 -4
  43. package/src/mysql/function/temporal.ts +1 -1
  44. package/src/mysql/internal/dsl.ts +759 -235
  45. package/src/mysql/internal/renderer.ts +2 -1
  46. package/src/mysql/internal/sql-expression-renderer.ts +486 -130
  47. package/src/mysql/query.ts +9 -2
  48. package/src/mysql/table.ts +64 -35
  49. package/src/postgres/column.ts +14 -12
  50. package/src/postgres/errors/normalize.ts +2 -2
  51. package/src/postgres/executor.ts +52 -9
  52. package/src/postgres/function/core.ts +19 -1
  53. package/src/postgres/function/temporal.ts +1 -1
  54. package/src/postgres/internal/dsl.ts +705 -256
  55. package/src/postgres/internal/renderer.ts +2 -1
  56. package/src/postgres/internal/schema-ddl.ts +2 -1
  57. package/src/postgres/internal/schema-model.ts +6 -3
  58. package/src/postgres/internal/sql-expression-renderer.ts +420 -91
  59. package/src/postgres/json.ts +57 -17
  60. package/src/postgres/query.ts +9 -2
  61. package/src/postgres/schema-management.ts +92 -6
  62. package/src/postgres/schema.ts +1 -1
  63. package/src/postgres/table.ts +203 -75
  64. package/src/sqlite/column.ts +128 -0
  65. package/src/sqlite/datatypes/index.ts +79 -0
  66. package/src/sqlite/datatypes/spec.ts +98 -0
  67. package/src/sqlite/errors/catalog.ts +103 -0
  68. package/src/sqlite/errors/fields.ts +19 -0
  69. package/src/sqlite/errors/index.ts +19 -0
  70. package/src/sqlite/errors/normalize.ts +229 -0
  71. package/src/sqlite/errors/requirements.ts +71 -0
  72. package/src/sqlite/errors/types.ts +29 -0
  73. package/src/sqlite/executor.ts +227 -0
  74. package/src/sqlite/function/aggregate.ts +2 -0
  75. package/src/sqlite/function/core.ts +2 -0
  76. package/src/sqlite/function/index.ts +19 -0
  77. package/src/sqlite/function/string.ts +2 -0
  78. package/src/sqlite/function/temporal.ts +100 -0
  79. package/src/sqlite/function/window.ts +2 -0
  80. package/src/sqlite/internal/dialect.ts +37 -0
  81. package/src/sqlite/internal/dsl.ts +6927 -0
  82. package/src/sqlite/internal/renderer.ts +47 -0
  83. package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
  84. package/src/sqlite/json.ts +2 -0
  85. package/src/sqlite/query.ts +196 -0
  86. package/src/sqlite/renderer.ts +24 -0
  87. package/src/sqlite/table.ts +175 -0
  88. package/src/sqlite.ts +22 -0
@@ -1,9 +1,10 @@
1
1
  import * as Chunk from "effect/Chunk"
2
2
  import * as Effect from "effect/Effect"
3
+ import * as Exit from "effect/Exit"
3
4
  import * as Option from "effect/Option"
4
5
  import * as Schema from "effect/Schema"
5
- import * as SqlClient from "@effect/sql/SqlClient"
6
- import * as SqlError from "@effect/sql/SqlError"
6
+ import * as SqlClient from "effect/unstable/sql/SqlClient"
7
+ import * as SqlError from "effect/unstable/sql/SqlError"
7
8
  import * as Stream from "effect/Stream"
8
9
 
9
10
  import * as Expression from "./scalar.js"
@@ -16,6 +17,8 @@ import * as Query from "./query.js"
16
17
  import * as QueryAst from "./query-ast.js"
17
18
  import * as Renderer from "./renderer.js"
18
19
  import * as Plan from "./row-set.js"
20
+ import { columnPredicateKey } from "./predicate/runtime.js"
21
+ import { isJsonValue } from "./runtime/normalize.js"
19
22
 
20
23
  /** Flat database row keyed by rendered projection aliases. */
21
24
  export type FlatRow = Readonly<Record<string, unknown>>
@@ -42,6 +45,10 @@ export interface RowDecodeError {
42
45
  readonly normalized?: unknown
43
46
  readonly stage: "normalize" | "schema"
44
47
  readonly cause: unknown
48
+ readonly schemaError?: {
49
+ readonly message: string
50
+ readonly issue: unknown
51
+ }
45
52
  }
46
53
 
47
54
  /**
@@ -181,26 +188,35 @@ const makeRowDecodeError = (
181
188
  stage: RowDecodeError["stage"],
182
189
  cause: unknown,
183
190
  normalized?: unknown
184
- ): RowDecodeError => ({
185
- _tag: "RowDecodeError",
186
- message: stage === "normalize"
187
- ? `Failed to normalize projection '${projection.alias}'`
188
- : `Failed to decode projection '${projection.alias}' against its runtime schema`,
189
- dialect: rendered.dialect,
190
- query: {
191
- sql: rendered.sql,
192
- params: rendered.params
193
- },
194
- projection: {
195
- alias: projection.alias,
196
- path: projection.path
197
- },
198
- dbType: expression[Expression.TypeId].dbType,
199
- raw,
200
- normalized,
201
- stage,
202
- cause
203
- })
191
+ ): RowDecodeError => {
192
+ const schemaError = Schema.isSchemaError(cause)
193
+ ? {
194
+ message: cause.message,
195
+ issue: cause.issue
196
+ }
197
+ : undefined
198
+ return {
199
+ _tag: "RowDecodeError",
200
+ message: stage === "normalize"
201
+ ? `Failed to normalize projection '${projection.alias}'`
202
+ : `Failed to decode projection '${projection.alias}' against its runtime schema`,
203
+ dialect: rendered.dialect,
204
+ query: {
205
+ sql: rendered.sql,
206
+ params: rendered.params
207
+ },
208
+ projection: {
209
+ alias: projection.alias,
210
+ path: projection.path
211
+ },
212
+ dbType: expression[Expression.TypeId].dbType,
213
+ raw,
214
+ normalized,
215
+ stage,
216
+ cause,
217
+ ...(schemaError === undefined ? {} : { schemaError })
218
+ }
219
+ }
204
220
 
205
221
  const hasOptionalSourceDependency = (
206
222
  expression: Expression.Any,
@@ -221,7 +237,7 @@ const effectiveRuntimeNullability = (
221
237
  return "always"
222
238
  }
223
239
  if (ast.kind === "column") {
224
- const key = `${ast.tableName}.${ast.columnName}`
240
+ const key = columnPredicateKey(ast.tableName, ast.columnName)
225
241
  if (scope.absentSourceNames.has(ast.tableName) || scope.nullKeys.has(key)) {
226
242
  return "always"
227
243
  }
@@ -237,6 +253,20 @@ const effectiveRuntimeNullability = (
237
253
  : nullability
238
254
  }
239
255
 
256
+ const dbTypeAllowsTopLevelJsonNull = (
257
+ dbType: Expression.DbType.Any
258
+ ): boolean => {
259
+ if ("base" in dbType) {
260
+ return dbTypeAllowsTopLevelJsonNull(dbType.base)
261
+ }
262
+ return ("variant" in dbType && dbType.variant === "json") || dbType.runtime === "json"
263
+ }
264
+
265
+ const schemaAcceptsNull = (
266
+ schema: Schema.Top | undefined
267
+ ): boolean =>
268
+ schema !== undefined && (Schema.is(schema) as (value: unknown) => boolean)(null)
269
+
240
270
  const decodeProjectionValue = (
241
271
  rendered: Renderer.RenderedQuery<any, any>,
242
272
  projection: Renderer.RenderedQuery<any, any>["projections"][number],
@@ -262,8 +292,12 @@ const decodeProjectionValue = (
262
292
  }
263
293
 
264
294
  const nullability = effectiveRuntimeNullability(expression, scope)
295
+ const schema = expressionRuntimeSchema(expression, { assumptions: scope.assumptions })
265
296
  if (normalized === null) {
266
297
  if (nullability === "never") {
298
+ if (dbTypeAllowsTopLevelJsonNull(expression[Expression.TypeId].dbType) && schemaAcceptsNull(schema)) {
299
+ return null
300
+ }
267
301
  throw makeRowDecodeError(
268
302
  rendered,
269
303
  projection,
@@ -289,20 +323,36 @@ const decodeProjectionValue = (
289
323
  )
290
324
  }
291
325
 
292
- const schema = expressionRuntimeSchema(expression, { assumptions: scope.assumptions })
326
+ if (dbTypeAllowsTopLevelJsonNull(expression[Expression.TypeId].dbType) && !isJsonValue(normalized)) {
327
+ throw makeRowDecodeError(
328
+ rendered,
329
+ projection,
330
+ expression,
331
+ raw,
332
+ "schema",
333
+ new Error("Expected a JSON value"),
334
+ normalized
335
+ )
336
+ }
337
+
293
338
  if (schema === undefined) {
294
339
  return normalized
295
340
  }
296
341
 
297
- if ((Schema.is(schema as Schema.Schema.Any) as (value: unknown) => boolean)(normalized)) {
342
+ if ((Schema.is(schema as Schema.Top) as (value: unknown) => boolean)(normalized)) {
298
343
  return normalized
299
344
  }
300
345
 
301
- try {
302
- return (Schema.decodeUnknownSync as any)(schema)(normalized)
303
- } catch (cause) {
304
- throw makeRowDecodeError(rendered, projection, expression, raw, "schema", cause, normalized)
346
+ const decoded = (Schema.decodeUnknownExit as any)(schema)(normalized)
347
+ if (Exit.isSuccess(decoded)) {
348
+ return decoded.value
305
349
  }
350
+
351
+ const cause = Option.match(Exit.findErrorOption(decoded), {
352
+ onNone: () => decoded.cause,
353
+ onSome: (schemaError) => schemaError
354
+ })
355
+ throw makeRowDecodeError(rendered, projection, expression, raw, "schema", cause, normalized)
306
356
  }
307
357
 
308
358
  export const makeRowDecoder = (
@@ -316,8 +366,8 @@ export const makeRowDecoder = (
316
366
  const projections = flattenSelection(
317
367
  Query.getAst(plan).select as Record<string, unknown>
318
368
  )
319
- const byAlias = new Map(
320
- projections.map((projection) => [projection.alias, projection.expression] as const)
369
+ const byPath = new Map(
370
+ projections.map((projection) => [JSON.stringify(projection.path), projection.expression] as const)
321
371
  )
322
372
  const driverMode = options.driverMode ?? "raw"
323
373
  const valueMappings = options.valueMappings ?? rendered.valueMappings
@@ -325,12 +375,19 @@ export const makeRowDecoder = (
325
375
  return (row) => {
326
376
  const decoded: Record<string, unknown> = {}
327
377
  for (const projection of rendered.projections) {
328
- if (!(projection.alias in row)) {
329
- continue
330
- }
331
- const expression = byAlias.get(projection.alias)
378
+ const expression = byPath.get(JSON.stringify(projection.path))
332
379
  if (expression === undefined) {
333
- continue
380
+ throw new Error(`Rendered projection path '${projection.path.join(".")}' does not exist in the query selection`)
381
+ }
382
+ if (!(projection.alias in row)) {
383
+ throw makeRowDecodeError(
384
+ rendered,
385
+ projection,
386
+ expression,
387
+ undefined,
388
+ "schema",
389
+ new Error(`Missing required projection alias '${projection.alias}'`)
390
+ )
334
391
  }
335
392
  setPath(
336
393
  decoded,
@@ -352,7 +409,7 @@ export const decodeChunk = (
352
409
  } = {}
353
410
  ): Chunk.Chunk<any> => {
354
411
  const decodeRow = makeRowDecoder(rendered, plan, options)
355
- return Chunk.unsafeFromArray(Chunk.toReadonlyArray(rows).map((row) => decodeRow(row)))
412
+ return Chunk.fromIterable(Chunk.toReadonlyArray(rows).map((row) => decodeRow(row)))
356
413
  }
357
414
 
358
415
  export const decodeRows = (
@@ -482,9 +539,9 @@ export const fromDriver = <
482
539
  },
483
540
  stream(plan: any) {
484
541
  const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
485
- return Stream.mapChunks(
542
+ return Stream.mapArray(
486
543
  sqlDriver.stream(rendered),
487
- (rows) => Chunk.unsafeFromArray(remapRows<any>(rendered, Chunk.toReadonlyArray(rows)))
544
+ (rows) => remapRows<any>(rendered, rows) as never
488
545
  )
489
546
  }
490
547
  }
@@ -494,10 +551,10 @@ export const fromDriver = <
494
551
  export const streamFromSqlClient = <Dialect extends string>(
495
552
  query: Renderer.RenderedQuery<any, Dialect>
496
553
  ): Stream.Stream<FlatRow, SqlError.SqlError, SqlClient.SqlClient> =>
497
- Stream.unwrapScoped(
554
+ Stream.unwrap(
498
555
  Effect.flatMap(SqlClient.SqlClient, (sql) =>
499
556
  Effect.flatMap(
500
- Effect.serviceOption(SqlClient.TransactionConnection),
557
+ Effect.serviceOption(sql.transactionService),
501
558
  Option.match({
502
559
  onNone: () => sql.reserve,
503
560
  onSome: ([connection]) => Effect.succeed(connection)
@@ -518,7 +575,7 @@ export const fromSqlClient = <Dialect extends string>(
518
575
  stream: (query) => streamFromSqlClient(query)
519
576
  }))
520
577
 
521
- /** Runs an effect within the ambient `@effect/sql` transaction service. */
578
+ /** Runs an effect within the ambient `effect/unstable/sql` transaction service. */
522
579
  export const withTransaction = <A, E, R>(
523
580
  effect: Effect.Effect<A, E, R>
524
581
  ): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
@@ -527,7 +584,7 @@ export const withTransaction = <A, E, R>(
527
584
  /**
528
585
  * Runs an effect in a nested transaction scope.
529
586
  *
530
- * When the ambient `@effect/sql` client is already inside a transaction, the
587
+ * When the ambient `effect/unstable/sql` client is already inside a transaction, the
531
588
  * underlying client implementation uses a savepoint.
532
589
  */
533
590
  export const withSavepoint = <A, E, R>(
@@ -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,5 +1,23 @@
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) {
@@ -20,17 +38,84 @@ const literalGroupingKey = (value: unknown): string => {
20
38
  }
21
39
  }
22
40
 
41
+ const isExpression = (value: unknown): value is Expression.Any =>
42
+ value !== null && typeof value === "object" && Expression.TypeId in value
43
+
44
+ const expressionGroupingKey = (value: unknown): string =>
45
+ isExpression(value) ? groupingKeyOfExpression(value) : "missing"
46
+
47
+ const escapeGroupingText = (value: string): string =>
48
+ value
49
+ .replace(/\\/g, "\\\\")
50
+ .replace(/,/g, "\\,")
51
+ .replace(/\|/g, "\\|")
52
+ .replace(/=/g, "\\=")
53
+ .replace(/>/g, "\\>")
54
+
55
+ const jsonSegmentGroupingKey = (segment: unknown): string => {
56
+ if (segment !== null && typeof segment === "object" && "kind" in segment) {
57
+ switch ((segment as { readonly kind: string }).kind) {
58
+ case "key":
59
+ return `key:${escapeGroupingText((segment as JsonPath.KeySegment).key)}`
60
+ case "index":
61
+ return `index:${(segment as JsonPath.IndexSegment).index}`
62
+ case "wildcard":
63
+ return "wildcard"
64
+ case "slice": {
65
+ const slice = segment as JsonPath.SliceSegment
66
+ return `slice:${slice.start ?? ""}:${slice.end ?? ""}`
67
+ }
68
+ case "descend":
69
+ return "descend"
70
+ }
71
+ }
72
+ if (typeof segment === "string") {
73
+ return `key:${escapeGroupingText(segment)}`
74
+ }
75
+ if (typeof segment === "number") {
76
+ return `index:${segment}`
77
+ }
78
+ return "unknown"
79
+ }
80
+
81
+ const jsonPathGroupingKey = (segments: readonly unknown[] | undefined): string =>
82
+ (segments ?? []).map(jsonSegmentGroupingKey).join(",")
83
+
84
+ const isJsonPath = (value: unknown): value is JsonPath.Path =>
85
+ value !== null && typeof value === "object" && JsonPath.TypeId in value
86
+
87
+ const jsonOpaquePathGroupingKey = (value: unknown): string => {
88
+ if (isJsonPath(value)) {
89
+ return `jsonpath:${jsonPathGroupingKey(value.segments)}`
90
+ }
91
+ if (typeof value === "string") {
92
+ return `jsonpath:${escapeGroupingText(value)}`
93
+ }
94
+ if (isExpression(value)) {
95
+ return `jsonpath:${groupingKeyOfExpression(value)}`
96
+ }
97
+ return "jsonpath:unknown"
98
+ }
99
+
100
+ const jsonEntryGroupingKey = (
101
+ entry: { readonly key: string; readonly value: Expression.Any }
102
+ ): string => `${escapeGroupingText(entry.key)}=>${groupingKeyOfExpression(entry.value)}`
103
+
23
104
  export const groupingKeyOfExpression = (expression: Expression.Any): string => {
24
105
  const ast = (expression as Expression.Any & {
25
106
  readonly [ExpressionAst.TypeId]: ExpressionAst.Any
26
107
  })[ExpressionAst.TypeId]
27
108
  switch (ast.kind) {
28
109
  case "column":
29
- return `column:${ast.tableName}.${ast.columnName}`
110
+ return `column:${columnPredicateKey(ast.tableName, ast.columnName)}`
30
111
  case "literal":
31
112
  return `literal:${literalGroupingKey(ast.value)}`
32
113
  case "cast":
33
114
  return `cast(${groupingKeyOfExpression(ast.value)} as ${ast.target.dialect}:${ast.target.kind})`
115
+ case "collate":
116
+ return `collate(${groupingKeyOfExpression(ast.value)},${ast.collation.map(escapeGroupingText).join(",")})`
117
+ case "function":
118
+ return `function(${escapeGroupingText(ast.name)},${ast.args.map(groupingKeyOfExpression).join(",")})`
34
119
  case "isNull":
35
120
  case "isNotNull":
36
121
  case "not":
@@ -48,8 +133,15 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
48
133
  case "gte":
49
134
  case "like":
50
135
  case "ilike":
136
+ case "regexMatch":
137
+ case "regexIMatch":
138
+ case "regexNotMatch":
139
+ case "regexNotIMatch":
51
140
  case "isDistinctFrom":
52
141
  case "isNotDistinctFrom":
142
+ case "contains":
143
+ case "containedBy":
144
+ case "overlaps":
53
145
  return `${ast.kind}(${groupingKeyOfExpression(ast.left)},${groupingKeyOfExpression(ast.right)})`
54
146
  case "and":
55
147
  case "or":
@@ -62,6 +154,54 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
62
154
  case "case":
63
155
  return `case(${ast.branches.map((branch: ExpressionAst.CaseBranchNode) =>
64
156
  `when:${groupingKeyOfExpression(branch.when)}=>${groupingKeyOfExpression(branch.then)}`).join("|")};else:${groupingKeyOfExpression(ast.else)})`
157
+ case "exists":
158
+ return `exists(${subqueryPlanGroupingKey(ast.plan)})`
159
+ case "scalarSubquery":
160
+ return `scalarSubquery(${subqueryPlanGroupingKey(ast.plan)})`
161
+ case "inSubquery":
162
+ return `inSubquery(${groupingKeyOfExpression(ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
163
+ case "comparisonAny":
164
+ case "comparisonAll":
165
+ return `${ast.kind}(${ast.operator},${groupingKeyOfExpression(ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
166
+ case "jsonGet":
167
+ case "jsonPath":
168
+ case "jsonAccess":
169
+ case "jsonTraverse":
170
+ case "jsonGetText":
171
+ case "jsonPathText":
172
+ case "jsonAccessText":
173
+ case "jsonTraverseText":
174
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonPathGroupingKey(ast.segments)})`
175
+ case "jsonHasKey":
176
+ case "jsonKeyExists":
177
+ case "jsonHasAnyKeys":
178
+ case "jsonHasAllKeys":
179
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${(ast.keys ?? []).map(escapeGroupingText).join(",")})`
180
+ case "jsonConcat":
181
+ case "jsonMerge":
182
+ return `json(${ast.kind},${expressionGroupingKey(ast.left)},${expressionGroupingKey(ast.right)},)`
183
+ case "jsonDelete":
184
+ case "jsonDeletePath":
185
+ case "jsonRemove":
186
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(undefined)},${jsonPathGroupingKey(ast.segments)})`
187
+ case "jsonSet":
188
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(ast.newValue)},${jsonPathGroupingKey(ast.segments)})`
189
+ case "jsonInsert":
190
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${expressionGroupingKey(ast.insert)},${jsonPathGroupingKey(ast.segments)})`
191
+ case "jsonPathExists":
192
+ case "jsonPathMatch":
193
+ return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonOpaquePathGroupingKey(ast.query)})`
194
+ case "jsonBuildObject":
195
+ return `json(${ast.kind},${(ast.entries ?? []).map(jsonEntryGroupingKey).join("|")})`
196
+ case "jsonBuildArray":
197
+ return `json(${ast.kind},${(ast.values ?? []).map(groupingKeyOfExpression).join(",")})`
198
+ case "jsonToJson":
199
+ case "jsonToJsonb":
200
+ case "jsonTypeOf":
201
+ case "jsonLength":
202
+ case "jsonKeys":
203
+ case "jsonStripNulls":
204
+ return `json(${ast.kind},${expressionGroupingKey(ast.value)})`
65
205
  default:
66
206
  throw new Error("Unsupported expression for grouping key generation")
67
207
  }
@@ -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,
@@ -43,7 +44,7 @@ const collectPresenceWitnesses = (
43
44
  const expression = selection as unknown 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
  }