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.
- package/README.md +7 -0
- package/dist/mysql.js +1858 -715
- package/dist/postgres/metadata.js +2036 -172
- package/dist/postgres.js +8011 -6849
- package/dist/sqlite.js +8433 -0
- package/package.json +7 -4
- package/src/internal/column-state.d.ts +3 -3
- package/src/internal/column-state.ts +3 -3
- package/src/internal/column.ts +8 -8
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +3 -1
- package/src/internal/dsl-mutation-runtime.ts +173 -4
- package/src/internal/dsl-plan-runtime.ts +165 -20
- package/src/internal/dsl-query-runtime.ts +60 -6
- package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
- package/src/internal/executor.ts +100 -43
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +141 -1
- package/src/internal/implication-runtime.ts +2 -1
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +27 -3
- package/src/internal/query.d.ts +1 -1
- package/src/internal/query.ts +253 -31
- package/src/internal/renderer.ts +35 -2
- package/src/internal/runtime/driver-value-mapping.ts +60 -2
- package/src/internal/runtime/normalize.ts +62 -38
- package/src/internal/runtime/schema.ts +32 -40
- package/src/internal/runtime/value.ts +159 -39
- package/src/internal/scalar.d.ts +1 -1
- package/src/internal/scalar.ts +1 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +95 -43
- package/src/internal/table-options.ts +108 -1
- package/src/internal/table.d.ts +29 -22
- package/src/internal/table.ts +260 -53
- package/src/mysql/column.ts +24 -8
- package/src/mysql/datatypes/index.ts +21 -0
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +4 -4
- package/src/mysql/function/temporal.ts +1 -1
- package/src/mysql/internal/dsl.ts +759 -235
- package/src/mysql/internal/renderer.ts +2 -1
- package/src/mysql/internal/sql-expression-renderer.ts +486 -130
- package/src/mysql/query.ts +9 -2
- package/src/mysql/table.ts +64 -35
- package/src/postgres/column.ts +14 -12
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +52 -9
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/function/temporal.ts +1 -1
- package/src/postgres/internal/dsl.ts +705 -256
- package/src/postgres/internal/renderer.ts +2 -1
- package/src/postgres/internal/schema-ddl.ts +2 -1
- package/src/postgres/internal/schema-model.ts +6 -3
- package/src/postgres/internal/sql-expression-renderer.ts +420 -91
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -2
- package/src/postgres/schema-management.ts +92 -6
- package/src/postgres/schema.ts +1 -1
- package/src/postgres/table.ts +203 -75
- package/src/sqlite/column.ts +128 -0
- package/src/sqlite/datatypes/index.ts +79 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +227 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +37 -0
- package/src/sqlite/internal/dsl.ts +6927 -0
- package/src/sqlite/internal/renderer.ts +47 -0
- package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
- package/src/sqlite/json.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +24 -0
- package/src/sqlite/table.ts +175 -0
- package/src/sqlite.ts +22 -0
package/src/internal/executor.ts
CHANGED
|
@@ -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 "
|
|
6
|
-
import * as SqlError from "
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
342
|
+
if ((Schema.is(schema as Schema.Top) as (value: unknown) => boolean)(normalized)) {
|
|
298
343
|
return normalized
|
|
299
344
|
}
|
|
300
345
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
320
|
-
projections.map((projection) => [projection.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
542
|
+
return Stream.mapArray(
|
|
486
543
|
sqlDriver.stream(rendered),
|
|
487
|
-
(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.
|
|
554
|
+
Stream.unwrap(
|
|
498
555
|
Effect.flatMap(SqlClient.SqlClient, (sql) =>
|
|
499
556
|
Effect.flatMap(
|
|
500
|
-
Effect.serviceOption(
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
47
|
+
output.add(columnPredicateKey(ast.tableName, ast.columnName))
|
|
47
48
|
}
|
|
48
49
|
return
|
|
49
50
|
}
|