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.
- package/README.md +4 -0
- package/dist/index.js +8065 -0
- package/dist/mysql.js +4036 -2418
- package/dist/postgres/metadata.js +2536 -625
- package/dist/postgres.js +8248 -7857
- package/dist/sqlite.js +8854 -0
- package/dist/standard.js +8019 -0
- package/package.json +15 -3
- package/src/casing.ts +71 -0
- package/src/index.ts +2 -0
- package/src/internal/casing.ts +89 -0
- package/src/internal/column-state.ts +11 -6
- package/src/internal/column.ts +44 -7
- package/src/internal/datatypes/define.ts +2 -1
- package/src/internal/datatypes/enrich.ts +23 -0
- package/src/internal/datatypes/lookup.ts +14 -7
- package/src/internal/derived-table.ts +7 -13
- package/src/internal/dialect-renderers/mysql.ts +2046 -0
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
- package/src/internal/dialect.ts +37 -0
- package/src/internal/dsl-mutation-runtime.ts +29 -10
- package/src/internal/dsl-plan-runtime.ts +41 -24
- package/src/internal/dsl-query-runtime.ts +11 -31
- package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
- package/src/internal/executor.ts +57 -15
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +216 -9
- package/src/internal/implication-runtime.ts +3 -2
- 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 +30 -3
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +315 -54
- package/src/internal/renderer.ts +51 -6
- package/src/internal/runtime/driver-value-mapping.ts +58 -0
- package/src/internal/runtime/normalize.ts +74 -43
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- package/src/internal/scalar.ts +6 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +90 -38
- package/src/internal/schema-expression.ts +2 -2
- package/src/internal/sql-expression-renderer.ts +19 -0
- package/src/internal/standard-dsl.ts +6885 -0
- package/src/internal/table-options.ts +229 -62
- package/src/internal/table.d.ts +33 -32
- package/src/internal/table.ts +469 -160
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +27 -12
- package/src/mysql/datatypes/index.ts +24 -2
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +7 -5
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +906 -324
- package/src/mysql/internal/renderer.ts +7 -2
- package/src/mysql/json.ts +37 -0
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/query.ts +9 -2
- package/src/mysql/renderer.ts +31 -4
- package/src/mysql.ts +4 -12
- package/src/postgres/column-extension.ts +28 -0
- package/src/postgres/column.ts +9 -13
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +3 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +55 -10
- package/src/postgres/function/core.ts +20 -4
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +850 -359
- package/src/postgres/internal/renderer.ts +7 -2
- package/src/postgres/internal/schema-ddl.ts +22 -9
- package/src/postgres/internal/schema-model.ts +244 -10
- package/src/postgres/json.ts +100 -24
- package/src/postgres/jsonb.ts +38 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +31 -4
- package/src/postgres/schema-management.ts +108 -16
- package/src/postgres/schema.ts +98 -15
- package/src/postgres/table.ts +203 -398
- package/src/postgres/type.ts +8 -7
- package/src/postgres.ts +9 -11
- package/src/sqlite/column-extension.ts +3 -0
- package/src/sqlite/column.ts +127 -0
- package/src/sqlite/datatypes/index.ts +80 -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 +229 -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 +42 -0
- package/src/sqlite/internal/dsl.ts +6979 -0
- package/src/sqlite/internal/renderer.ts +51 -0
- package/src/sqlite/json.ts +39 -0
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +51 -0
- package/src/sqlite.ts +14 -0
- package/src/standard/column.ts +163 -0
- package/src/standard/datatypes/index.ts +83 -0
- package/src/standard/datatypes/spec.ts +98 -0
- package/src/standard/dialect.ts +40 -0
- package/src/standard/function/aggregate.ts +2 -0
- package/src/standard/function/core.ts +2 -0
- package/src/standard/function/index.ts +18 -0
- package/src/standard/function/string.ts +2 -0
- package/src/standard/function/temporal.ts +78 -0
- package/src/standard/function/window.ts +2 -0
- package/src/standard/internal/renderer.ts +45 -0
- package/src/standard/query.ts +152 -0
- package/src/standard/renderer.ts +21 -0
- package/src/standard/table.ts +147 -0
- package/src/standard.ts +18 -0
- package/src/internal/aggregation-validation.ts +0 -57
- package/src/mysql/table.ts +0 -157
package/src/internal/executor.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
320
|
-
projections.map((projection) => [projection.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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(${
|
|
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}(${
|
|
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
|
-
|
|
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
|
|
221
|
+
return `${ast.kind}(${variadicGroupingKey(ast.kind, ast.values)})`
|
|
62
222
|
case "case":
|
|
63
|
-
return
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
47
|
+
output.add(columnPredicateKey(ast.tableName, ast.columnName))
|
|
47
48
|
}
|
|
48
49
|
return
|
|
49
50
|
}
|