effect-qb 4.0.0-beta.66 → 4.0.0-beta.98
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 +5 -2
- package/dist/index.js +9376 -0
- package/dist/mysql.js +3921 -2573
- package/dist/postgres/metadata.js +2491 -1373
- package/dist/postgres.js +5453 -5155
- package/dist/sqlite.js +6895 -5529
- package/dist/standard.js +9330 -0
- package/package.json +9 -2
- package/src/casing.ts +71 -0
- package/src/index.ts +2 -0
- package/src/internal/casing.ts +89 -0
- package/src/internal/coercion/rules.ts +13 -1
- package/src/internal/column-state.ts +21 -15
- package/src/internal/column.ts +44 -7
- package/src/internal/datatypes/define.ts +7 -1
- package/src/internal/datatypes/enrich.ts +23 -0
- package/src/internal/datatypes/lookup.ts +81 -25
- package/src/internal/datatypes/matrix.ts +903 -0
- package/src/internal/datatypes/shape.ts +2 -0
- package/src/internal/derived-table.ts +4 -36
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/mysql.ts} +552 -361
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +657 -400
- package/src/{sqlite/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +505 -347
- package/src/internal/dialect.ts +35 -0
- package/src/internal/dsl-mutation-runtime.ts +12 -162
- package/src/internal/dsl-plan-runtime.ts +10 -138
- package/src/internal/dsl-query-runtime.ts +5 -79
- package/src/internal/dsl-transaction-ddl-runtime.ts +41 -65
- package/src/internal/executor.ts +14 -16
- package/src/internal/grouping-key.ts +87 -20
- package/src/internal/implication-runtime.ts +1 -1
- package/src/internal/json/path-access.ts +351 -0
- package/src/internal/predicate/runtime.ts +3 -0
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +64 -25
- package/src/internal/renderer.ts +26 -14
- package/src/internal/runtime/normalize.ts +12 -5
- package/src/internal/scalar.ts +7 -1
- package/src/internal/schema-derivation.d.ts +7 -7
- package/src/internal/schema-derivation.ts +9 -9
- package/src/internal/schema-expression.ts +2 -2
- package/src/internal/sql-expression-renderer.ts +19 -0
- package/src/internal/standard-dsl.ts +6978 -0
- package/src/internal/table-options.ts +126 -66
- package/src/internal/table.ts +652 -219
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +8 -9
- package/src/mysql/datatypes/index.ts +4 -2
- package/src/mysql/datatypes/spec.ts +6 -176
- package/src/mysql/errors/normalize.ts +0 -1
- package/src/mysql/executor.ts +7 -7
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +312 -154
- package/src/mysql/internal/renderer.ts +6 -2
- package/src/mysql/json.ts +7 -2
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/renderer.ts +37 -3
- package/src/mysql/type.ts +60 -0
- package/src/mysql.ts +7 -13
- package/src/postgres/check.ts +1 -0
- package/src/postgres/column-extension.ts +28 -0
- package/src/postgres/column.ts +2 -8
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +4 -2
- package/src/postgres/datatypes/spec.ts +6 -260
- package/src/postgres/errors/normalize.ts +0 -1
- package/src/postgres/executor.ts +7 -7
- package/src/postgres/foreign-key.ts +24 -0
- package/src/postgres/function/core.ts +1 -3
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/index.ts +1 -0
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +306 -163
- package/src/postgres/internal/renderer.ts +6 -2
- package/src/postgres/internal/schema-ddl.ts +22 -10
- package/src/postgres/internal/schema-model.ts +238 -7
- package/src/postgres/json-extension.ts +7 -0
- package/src/postgres/json.ts +762 -173
- package/src/postgres/jsonb.ts +37 -0
- package/src/postgres/primary-key.ts +24 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/renderer.ts +37 -3
- package/src/postgres/schema-management.ts +12 -11
- package/src/postgres/schema.ts +106 -15
- package/src/postgres/table.ts +205 -530
- package/src/postgres/type.ts +93 -10
- package/src/postgres/unique.ts +32 -0
- package/src/postgres.ts +20 -16
- package/src/sqlite/column-extension.ts +3 -0
- package/src/sqlite/column.ts +8 -9
- package/src/sqlite/datatypes/index.ts +4 -2
- package/src/sqlite/datatypes/spec.ts +6 -94
- package/src/sqlite/errors/normalize.ts +0 -1
- package/src/sqlite/executor.ts +7 -7
- package/src/sqlite/internal/dialect.ts +9 -4
- package/src/sqlite/internal/dsl.ts +301 -154
- package/src/sqlite/internal/renderer.ts +6 -2
- package/src/sqlite/json.ts +8 -2
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/renderer.ts +37 -3
- package/src/sqlite/type.ts +40 -0
- package/src/sqlite.ts +7 -13
- package/src/standard/cast.ts +113 -0
- package/src/standard/check.ts +17 -0
- package/src/standard/column.ts +163 -0
- package/src/standard/datatypes/index.ts +83 -0
- package/src/standard/datatypes/spec.ts +12 -0
- package/src/standard/dialect.ts +40 -0
- package/src/standard/foreign-key.ts +37 -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/index.ts +17 -0
- package/src/standard/internal/renderer.ts +45 -0
- package/src/standard/json.ts +883 -0
- package/src/standard/primary-key.ts +17 -0
- package/src/standard/query.ts +152 -0
- package/src/standard/renderer.ts +49 -0
- package/src/standard/table.ts +151 -0
- package/src/standard/unique.ts +17 -0
- package/src/standard.ts +32 -0
- package/src/internal/aggregation-validation.ts +0 -57
- package/src/internal/table.d.ts +0 -180
- package/src/mysql/table.ts +0 -186
- package/src/postgres/cast.ts +0 -45
- package/src/sqlite/table.ts +0 -175
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as Plan from "./row-set.js"
|
|
2
|
-
import * as Table from "./table.js"
|
|
3
2
|
|
|
4
3
|
type DslTransactionDdlRuntimeContext = {
|
|
5
4
|
readonly profile: {
|
|
@@ -11,16 +10,13 @@ type DslTransactionDdlRuntimeContext = {
|
|
|
11
10
|
readonly defaultIndexName: (tableName: string, columns: readonly string[], unique: boolean) => string
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export const renderTransactionIsolationLevel = (
|
|
14
|
+
isolationLevel: unknown
|
|
15
|
+
): string => {
|
|
17
16
|
if (isolationLevel === undefined) {
|
|
18
17
|
return ""
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
throw new Error("Unsupported transaction isolation level")
|
|
22
|
-
}
|
|
23
|
-
return `isolation level ${isolationLevel}`
|
|
19
|
+
return `isolation level ${isolationLevel as string}`
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
export const expectDdlClauseKind = <
|
|
@@ -28,53 +24,29 @@ export const expectDdlClauseKind = <
|
|
|
28
24
|
Kind extends Ddl["kind"]
|
|
29
25
|
>(
|
|
30
26
|
ddl: Ddl | undefined,
|
|
31
|
-
|
|
32
|
-
): Extract<Ddl, { readonly kind: Kind }> =>
|
|
33
|
-
|
|
34
|
-
throw new Error("Unsupported DDL statement kind")
|
|
35
|
-
}
|
|
36
|
-
return ddl as Extract<Ddl, { readonly kind: Kind }>
|
|
37
|
-
}
|
|
27
|
+
_kind: Kind
|
|
28
|
+
): Extract<Ddl, { readonly kind: Kind }> =>
|
|
29
|
+
ddl as Extract<Ddl, { readonly kind: Kind }>
|
|
38
30
|
|
|
39
31
|
export const expectTruncateClause = <
|
|
40
32
|
Truncate extends { readonly kind: string }
|
|
41
33
|
>(
|
|
42
34
|
truncate: Truncate | undefined
|
|
43
|
-
): Extract<Truncate, { readonly kind: "truncate" }> =>
|
|
44
|
-
|
|
45
|
-
throw new Error("Unsupported truncate statement kind")
|
|
46
|
-
}
|
|
47
|
-
return truncate as Extract<Truncate, { readonly kind: "truncate" }>
|
|
48
|
-
}
|
|
35
|
+
): Extract<Truncate, { readonly kind: "truncate" }> =>
|
|
36
|
+
truncate as Extract<Truncate, { readonly kind: "truncate" }>
|
|
49
37
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContext) => {
|
|
55
|
-
const isRecord = (value: unknown): value is Record<PropertyKey, unknown> =>
|
|
56
|
-
typeof value === "object" && value !== null
|
|
57
|
-
|
|
58
|
-
const assertTableTarget = (target: unknown, apiName: string): void => {
|
|
59
|
-
if (!isRecord(target) || !(Table.TypeId in target) || !(Plan.TypeId in target)) {
|
|
60
|
-
throw new Error(`${apiName}(...) requires a table target`)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
38
|
+
export const normalizeStatementFlag = (value: unknown): boolean =>
|
|
39
|
+
(value as boolean | undefined) ?? false
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!(columnName in fields)) {
|
|
71
|
-
throw new Error(`effect-qb: unknown index column '${columnName}'`)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
41
|
+
export const normalizeStatementIdentifier = (
|
|
42
|
+
_apiName: string,
|
|
43
|
+
_identifierName: string,
|
|
44
|
+
value: unknown
|
|
45
|
+
): string =>
|
|
46
|
+
value as string
|
|
75
47
|
|
|
48
|
+
export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContext) => {
|
|
76
49
|
const transaction = (options: { readonly isolationLevel?: any; readonly readOnly?: boolean } = {}) => {
|
|
77
|
-
validateIsolationLevel(options.isolationLevel)
|
|
78
50
|
return ctx.makePlan({
|
|
79
51
|
selection: {},
|
|
80
52
|
required: [],
|
|
@@ -134,8 +106,8 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
134
106
|
orderBy: []
|
|
135
107
|
}, undefined, "transaction", "rollback")
|
|
136
108
|
|
|
137
|
-
const savepoint = (name: string) =>
|
|
138
|
-
ctx.makePlan({
|
|
109
|
+
const savepoint = (name: string) => {
|
|
110
|
+
return ctx.makePlan({
|
|
139
111
|
selection: {},
|
|
140
112
|
required: [],
|
|
141
113
|
available: {},
|
|
@@ -153,9 +125,10 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
153
125
|
groupBy: [],
|
|
154
126
|
orderBy: []
|
|
155
127
|
}, undefined, "transaction", "savepoint")
|
|
128
|
+
}
|
|
156
129
|
|
|
157
|
-
const rollbackTo = (name: string) =>
|
|
158
|
-
ctx.makePlan({
|
|
130
|
+
const rollbackTo = (name: string) => {
|
|
131
|
+
return ctx.makePlan({
|
|
159
132
|
selection: {},
|
|
160
133
|
required: [],
|
|
161
134
|
available: {},
|
|
@@ -173,9 +146,10 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
173
146
|
groupBy: [],
|
|
174
147
|
orderBy: []
|
|
175
148
|
}, undefined, "transaction", "rollbackTo")
|
|
149
|
+
}
|
|
176
150
|
|
|
177
|
-
const releaseSavepoint = (name: string) =>
|
|
178
|
-
ctx.makePlan({
|
|
151
|
+
const releaseSavepoint = (name: string) => {
|
|
152
|
+
return ctx.makePlan({
|
|
179
153
|
selection: {},
|
|
180
154
|
required: [],
|
|
181
155
|
available: {},
|
|
@@ -193,9 +167,10 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
193
167
|
groupBy: [],
|
|
194
168
|
orderBy: []
|
|
195
169
|
}, undefined, "transaction", "releaseSavepoint")
|
|
170
|
+
}
|
|
196
171
|
|
|
197
172
|
const createTable = (target: any, options: { readonly ifNotExists?: boolean } = {}) => {
|
|
198
|
-
|
|
173
|
+
const ifNotExists = normalizeStatementFlag(options.ifNotExists)
|
|
199
174
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
200
175
|
return ctx.makePlan({
|
|
201
176
|
selection: {},
|
|
@@ -213,7 +188,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
213
188
|
},
|
|
214
189
|
ddl: {
|
|
215
190
|
kind: "createTable",
|
|
216
|
-
ifNotExists
|
|
191
|
+
ifNotExists
|
|
217
192
|
},
|
|
218
193
|
where: [],
|
|
219
194
|
having: [],
|
|
@@ -224,7 +199,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
224
199
|
}
|
|
225
200
|
|
|
226
201
|
const dropTable = (target: any, options: { readonly ifExists?: boolean } = {}) => {
|
|
227
|
-
|
|
202
|
+
const ifExists = normalizeStatementFlag(options.ifExists)
|
|
228
203
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
229
204
|
return ctx.makePlan({
|
|
230
205
|
selection: {},
|
|
@@ -242,7 +217,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
242
217
|
},
|
|
243
218
|
ddl: {
|
|
244
219
|
kind: "dropTable",
|
|
245
|
-
ifExists
|
|
220
|
+
ifExists
|
|
246
221
|
},
|
|
247
222
|
where: [],
|
|
248
223
|
having: [],
|
|
@@ -253,9 +228,10 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
253
228
|
}
|
|
254
229
|
|
|
255
230
|
const createIndex = (target: any, columns: string | readonly string[], options: { readonly name?: string; readonly unique?: boolean; readonly ifNotExists?: boolean } = {}) => {
|
|
256
|
-
assertTableTarget(target, "createIndex")
|
|
257
231
|
const normalizedColumns = ctx.normalizeColumnList(columns)
|
|
258
|
-
|
|
232
|
+
const unique = normalizeStatementFlag(options.unique)
|
|
233
|
+
const ifNotExists = normalizeStatementFlag(options.ifNotExists)
|
|
234
|
+
const name = options.name
|
|
259
235
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
260
236
|
return ctx.makePlan({
|
|
261
237
|
selection: {},
|
|
@@ -273,10 +249,10 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
273
249
|
},
|
|
274
250
|
ddl: {
|
|
275
251
|
kind: "createIndex",
|
|
276
|
-
name:
|
|
252
|
+
name: name ?? ctx.defaultIndexName(sourceBaseName, normalizedColumns, unique),
|
|
277
253
|
columns: normalizedColumns,
|
|
278
|
-
unique
|
|
279
|
-
ifNotExists
|
|
254
|
+
unique,
|
|
255
|
+
ifNotExists
|
|
280
256
|
},
|
|
281
257
|
where: [],
|
|
282
258
|
having: [],
|
|
@@ -287,9 +263,9 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
287
263
|
}
|
|
288
264
|
|
|
289
265
|
const dropIndex = (target: any, columns: string | readonly string[], options: { readonly name?: string; readonly ifExists?: boolean } = {}) => {
|
|
290
|
-
assertTableTarget(target, "dropIndex")
|
|
291
266
|
const normalizedColumns = ctx.normalizeColumnList(columns)
|
|
292
|
-
|
|
267
|
+
const ifExists = normalizeStatementFlag(options.ifExists)
|
|
268
|
+
const name = options.name
|
|
293
269
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
294
270
|
return ctx.makePlan({
|
|
295
271
|
selection: {},
|
|
@@ -307,8 +283,8 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
307
283
|
},
|
|
308
284
|
ddl: {
|
|
309
285
|
kind: "dropIndex",
|
|
310
|
-
name:
|
|
311
|
-
ifExists
|
|
286
|
+
name: name ?? ctx.defaultIndexName(sourceBaseName, normalizedColumns, false),
|
|
287
|
+
ifExists
|
|
312
288
|
},
|
|
313
289
|
where: [],
|
|
314
290
|
having: [],
|
package/src/internal/executor.ts
CHANGED
|
@@ -528,24 +528,28 @@ export const fromDriver = <
|
|
|
528
528
|
renderer: Renderer.Renderer<Dialect>,
|
|
529
529
|
sqlDriver: Driver<Dialect, Error, Context>
|
|
530
530
|
): Executor<Dialect, Error, Context> => {
|
|
531
|
-
const executor = {
|
|
531
|
+
const executor: Executor<Dialect, Error, Context> = {
|
|
532
532
|
dialect: renderer.dialect,
|
|
533
|
-
execute
|
|
533
|
+
execute<PlanValue extends Query.Plan.Any>(
|
|
534
|
+
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
535
|
+
) {
|
|
534
536
|
const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
|
|
535
537
|
return Effect.map(
|
|
536
538
|
sqlDriver.execute(rendered),
|
|
537
539
|
(rows) => remapRows<any>(rendered, rows)
|
|
538
|
-
)
|
|
540
|
+
) as Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
|
|
539
541
|
},
|
|
540
|
-
stream
|
|
542
|
+
stream<PlanValue extends Query.Plan.Any>(
|
|
543
|
+
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
544
|
+
) {
|
|
541
545
|
const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
|
|
542
546
|
return Stream.mapArray(
|
|
543
547
|
sqlDriver.stream(rendered),
|
|
544
548
|
(rows) => remapRows<any>(rendered, rows) as never
|
|
545
|
-
)
|
|
549
|
+
) as Stream.Stream<Query.ResultRow<PlanValue>, Error, Context>
|
|
546
550
|
}
|
|
547
551
|
}
|
|
548
|
-
return executor
|
|
552
|
+
return executor
|
|
549
553
|
}
|
|
550
554
|
|
|
551
555
|
export const streamFromSqlClient = <Dialect extends string>(
|
|
@@ -575,19 +579,13 @@ export const fromSqlClient = <Dialect extends string>(
|
|
|
575
579
|
stream: (query) => streamFromSqlClient(query)
|
|
576
580
|
}))
|
|
577
581
|
|
|
578
|
-
/** Runs an effect within the ambient `effect/unstable/sql` transaction service. */
|
|
579
|
-
export const withTransaction = <A, E, R>(
|
|
580
|
-
effect: Effect.Effect<A, E, R>
|
|
581
|
-
): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
|
|
582
|
-
Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))
|
|
583
|
-
|
|
584
582
|
/**
|
|
585
|
-
* Runs an effect
|
|
583
|
+
* Runs an effect within the ambient `effect/unstable/sql` transaction service.
|
|
586
584
|
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
585
|
+
* Nested calls rely on the underlying client transaction implementation for
|
|
586
|
+
* savepoint behavior.
|
|
589
587
|
*/
|
|
590
|
-
export const
|
|
588
|
+
export const withTransaction = <A, E, R>(
|
|
591
589
|
effect: Effect.Effect<A, E, R>
|
|
592
590
|
): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient> =>
|
|
593
591
|
Effect.flatMap(SqlClient.SqlClient, (sql) => sql.withTransaction(effect))
|
|
@@ -21,7 +21,9 @@ const subqueryPlanGroupingKey = (plan: unknown): string => {
|
|
|
21
21
|
|
|
22
22
|
const literalGroupingKey = (value: unknown): string => {
|
|
23
23
|
if (value instanceof Date) {
|
|
24
|
-
return
|
|
24
|
+
return Number.isNaN(value.getTime())
|
|
25
|
+
? "date:invalid"
|
|
26
|
+
: `date:${value.toISOString()}`
|
|
25
27
|
}
|
|
26
28
|
if (value === null) {
|
|
27
29
|
return "null"
|
|
@@ -44,6 +46,30 @@ const isExpression = (value: unknown): value is Expression.Any =>
|
|
|
44
46
|
const expressionGroupingKey = (value: unknown): string =>
|
|
45
47
|
isExpression(value) ? groupingKeyOfExpression(value) : "missing"
|
|
46
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
|
+
|
|
47
73
|
const escapeGroupingText = (value: string): string =>
|
|
48
74
|
value
|
|
49
75
|
.replace(/\\/g, "\\\\")
|
|
@@ -52,6 +78,30 @@ const escapeGroupingText = (value: string): string =>
|
|
|
52
78
|
.replace(/=/g, "\\=")
|
|
53
79
|
.replace(/>/g, "\\>")
|
|
54
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
|
+
|
|
55
105
|
const jsonSegmentGroupingKey = (segment: unknown): string => {
|
|
56
106
|
if (segment !== null && typeof segment === "object" && "kind" in segment) {
|
|
57
107
|
switch ((segment as { readonly kind: string }).kind) {
|
|
@@ -78,8 +128,15 @@ const jsonSegmentGroupingKey = (segment: unknown): string => {
|
|
|
78
128
|
return "unknown"
|
|
79
129
|
}
|
|
80
130
|
|
|
81
|
-
const jsonPathGroupingKey = (segments:
|
|
82
|
-
(segments
|
|
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
|
+
}
|
|
83
140
|
|
|
84
141
|
const isJsonPath = (value: unknown): value is JsonPath.Path =>
|
|
85
142
|
value !== null && typeof value === "object" && JsonPath.TypeId in value
|
|
@@ -97,9 +154,20 @@ const jsonOpaquePathGroupingKey = (value: unknown): string => {
|
|
|
97
154
|
return "jsonpath:unknown"
|
|
98
155
|
}
|
|
99
156
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
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(",")
|
|
103
171
|
|
|
104
172
|
export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
105
173
|
const ast = (expression as Expression.Any & {
|
|
@@ -111,11 +179,11 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
111
179
|
case "literal":
|
|
112
180
|
return `literal:${literalGroupingKey(ast.value)}`
|
|
113
181
|
case "cast":
|
|
114
|
-
return `cast(${
|
|
182
|
+
return `cast(${requiredExpressionGroupingKey("cast", ast.value)} as ${castTargetGroupingKey(ast.target)})`
|
|
115
183
|
case "collate":
|
|
116
|
-
return `collate(${
|
|
184
|
+
return `collate(${requiredExpressionGroupingKey("collate", ast.value)},${collationGroupingKey(ast.collation)})`
|
|
117
185
|
case "function":
|
|
118
|
-
return `function(${
|
|
186
|
+
return `function(${functionCallNameGroupingKey(ast.name)},${functionCallArgsGroupingKey(ast.args)})`
|
|
119
187
|
case "isNull":
|
|
120
188
|
case "isNotNull":
|
|
121
189
|
case "not":
|
|
@@ -124,7 +192,7 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
124
192
|
case "count":
|
|
125
193
|
case "max":
|
|
126
194
|
case "min":
|
|
127
|
-
return `${ast.kind}(${
|
|
195
|
+
return `${ast.kind}(${requiredExpressionGroupingKey(ast.kind, ast.value)})`
|
|
128
196
|
case "eq":
|
|
129
197
|
case "neq":
|
|
130
198
|
case "lt":
|
|
@@ -142,7 +210,7 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
142
210
|
case "contains":
|
|
143
211
|
case "containedBy":
|
|
144
212
|
case "overlaps":
|
|
145
|
-
return `${ast.kind}(${
|
|
213
|
+
return `${ast.kind}(${requiredBinaryExpressionGroupingKey(ast.kind, ast.left, ast.right)})`
|
|
146
214
|
case "and":
|
|
147
215
|
case "or":
|
|
148
216
|
case "coalesce":
|
|
@@ -150,19 +218,18 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
150
218
|
case "in":
|
|
151
219
|
case "notIn":
|
|
152
220
|
case "between":
|
|
153
|
-
return `${ast.kind}(${ast.values
|
|
221
|
+
return `${ast.kind}(${variadicGroupingKey(ast.kind, ast.values)})`
|
|
154
222
|
case "case":
|
|
155
|
-
return
|
|
156
|
-
`when:${groupingKeyOfExpression(branch.when)}=>${groupingKeyOfExpression(branch.then)}`).join("|")};else:${groupingKeyOfExpression(ast.else)})`
|
|
223
|
+
return caseGroupingKey(ast.branches, ast.else)
|
|
157
224
|
case "exists":
|
|
158
225
|
return `exists(${subqueryPlanGroupingKey(ast.plan)})`
|
|
159
226
|
case "scalarSubquery":
|
|
160
227
|
return `scalarSubquery(${subqueryPlanGroupingKey(ast.plan)})`
|
|
161
228
|
case "inSubquery":
|
|
162
|
-
return `inSubquery(${
|
|
229
|
+
return `inSubquery(${requiredExpressionGroupingKey("inSubquery", ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
|
|
163
230
|
case "comparisonAny":
|
|
164
231
|
case "comparisonAll":
|
|
165
|
-
return `${ast.kind}(${ast.operator},${
|
|
232
|
+
return `${ast.kind}(${ast.operator},${requiredExpressionGroupingKey(quantifiedComparisonGroupingName(ast.kind), ast.left)},${subqueryPlanGroupingKey(ast.plan)})`
|
|
166
233
|
case "jsonGet":
|
|
167
234
|
case "jsonPath":
|
|
168
235
|
case "jsonAccess":
|
|
@@ -176,7 +243,7 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
176
243
|
case "jsonKeyExists":
|
|
177
244
|
case "jsonHasAnyKeys":
|
|
178
245
|
case "jsonHasAllKeys":
|
|
179
|
-
return `json(${ast.kind},${expressionGroupingKey(ast.base)},${(ast.keys
|
|
246
|
+
return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonKeysGroupingKey(ast.keys)})`
|
|
180
247
|
case "jsonConcat":
|
|
181
248
|
case "jsonMerge":
|
|
182
249
|
return `json(${ast.kind},${expressionGroupingKey(ast.left)},${expressionGroupingKey(ast.right)},)`
|
|
@@ -192,9 +259,9 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
192
259
|
case "jsonPathMatch":
|
|
193
260
|
return `json(${ast.kind},${expressionGroupingKey(ast.base)},${jsonOpaquePathGroupingKey(ast.query)})`
|
|
194
261
|
case "jsonBuildObject":
|
|
195
|
-
return `json(${ast.kind},${(ast.entries
|
|
262
|
+
return `json(${ast.kind},${jsonBuildObjectGroupingKey(ast.entries)})`
|
|
196
263
|
case "jsonBuildArray":
|
|
197
|
-
return `json(${ast.kind},${(ast.values
|
|
264
|
+
return `json(${ast.kind},${jsonBuildArrayGroupingKey(ast.values)})`
|
|
198
265
|
case "jsonToJson":
|
|
199
266
|
case "jsonToJsonb":
|
|
200
267
|
case "jsonTypeOf":
|
|
@@ -203,7 +270,7 @@ export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
|
203
270
|
case "jsonStripNulls":
|
|
204
271
|
return `json(${ast.kind},${expressionGroupingKey(ast.value)})`
|
|
205
272
|
default:
|
|
206
|
-
|
|
273
|
+
return `unknown:${typeof ast === "object" && ast !== null && "kind" in ast ? String((ast as { readonly kind: unknown }).kind) : "ast"}`
|
|
207
274
|
}
|
|
208
275
|
}
|
|
209
276
|
|
|
@@ -41,7 +41,7 @@ const collectPresenceWitnesses = (
|
|
|
41
41
|
return
|
|
42
42
|
}
|
|
43
43
|
if (Expression.TypeId in selection && ExpressionAst.TypeId in selection) {
|
|
44
|
-
const expression = selection as
|
|
44
|
+
const expression = selection as AstBackedExpression
|
|
45
45
|
const ast = expression[ExpressionAst.TypeId]
|
|
46
46
|
if (ast.kind === "column" && expression[Expression.TypeId].nullability === "never") {
|
|
47
47
|
output.add(columnPredicateKey(ast.tableName, ast.columnName))
|