effect-qb 0.17.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 +3053 -2505
- package/dist/postgres/metadata.js +1366 -1250
- package/dist/postgres.js +2020 -2719
- package/dist/sqlite.js +3226 -2732
- package/dist/standard.js +8019 -0
- package/package.json +10 -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 +4 -36
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/mysql.ts} +548 -359
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +654 -399
- package/src/{sqlite/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +501 -345
- 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 +10 -6
- package/src/internal/grouping-key.ts +87 -20
- package/src/internal/implication-runtime.ts +1 -1
- 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 +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 +126 -66
- package/src/internal/table.d.ts +33 -32
- package/src/internal/table.ts +406 -155
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +10 -11
- package/src/mysql/datatypes/index.ts +3 -2
- package/src/mysql/executor.ts +7 -5
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +219 -155
- package/src/mysql/internal/renderer.ts +6 -2
- package/src/mysql/json.ts +37 -0
- package/src/mysql/query-extension.ts +16 -0
- 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 +5 -11
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +3 -2
- package/src/postgres/executor.ts +7 -5
- package/src/postgres/function/core.ts +1 -3
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +208 -160
- 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.ts +43 -7
- package/src/postgres/jsonb.ts +38 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/renderer.ts +31 -4
- package/src/postgres/schema-management.ts +17 -12
- package/src/postgres/schema.ts +98 -15
- package/src/postgres/table.ts +193 -524
- 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 +10 -11
- package/src/sqlite/datatypes/index.ts +3 -2
- package/src/sqlite/executor.ts +7 -5
- package/src/sqlite/internal/dialect.ts +9 -4
- package/src/sqlite/internal/dsl.ts +208 -155
- package/src/sqlite/internal/renderer.ts +6 -2
- package/src/sqlite/json.ts +37 -0
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/renderer.ts +31 -4
- package/src/sqlite.ts +4 -12
- 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 -183
- package/src/sqlite/table.ts +0 -183
|
@@ -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
|
@@ -509,24 +509,28 @@ export const fromDriver = <
|
|
|
509
509
|
renderer: Renderer.Renderer<Dialect>,
|
|
510
510
|
sqlDriver: Driver<Dialect, Error, Context>
|
|
511
511
|
): Executor<Dialect, Error, Context> => {
|
|
512
|
-
const executor = {
|
|
512
|
+
const executor: Executor<Dialect, Error, Context> = {
|
|
513
513
|
dialect: renderer.dialect,
|
|
514
|
-
execute
|
|
514
|
+
execute<PlanValue extends Query.Plan.Any>(
|
|
515
|
+
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
516
|
+
) {
|
|
515
517
|
const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
|
|
516
518
|
return Effect.map(
|
|
517
519
|
sqlDriver.execute(rendered),
|
|
518
520
|
(rows) => remapRows<any>(rendered, rows)
|
|
519
|
-
)
|
|
521
|
+
) as Effect.Effect<Query.ResultRows<PlanValue>, Error, Context>
|
|
520
522
|
},
|
|
521
|
-
stream
|
|
523
|
+
stream<PlanValue extends Query.Plan.Any>(
|
|
524
|
+
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
525
|
+
) {
|
|
522
526
|
const rendered = renderer.render(plan) as Renderer.RenderedQuery<any, Dialect>
|
|
523
527
|
return Stream.mapChunks(
|
|
524
528
|
sqlDriver.stream(rendered),
|
|
525
529
|
(rows) => Chunk.unsafeFromArray(remapRows<any>(rendered, Chunk.toReadonlyArray(rows)))
|
|
526
|
-
)
|
|
530
|
+
) as Stream.Stream<Query.ResultRow<PlanValue>, Error, Context>
|
|
527
531
|
}
|
|
528
532
|
}
|
|
529
|
-
return executor
|
|
533
|
+
return executor
|
|
530
534
|
}
|
|
531
535
|
|
|
532
536
|
export const streamFromSqlClient = <Dialect extends string>(
|
|
@@ -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))
|
|
@@ -474,6 +474,9 @@ const valueKeyOfLiteral = (value: unknown): string => {
|
|
|
474
474
|
return "null"
|
|
475
475
|
}
|
|
476
476
|
if (value instanceof Date) {
|
|
477
|
+
if (Number.isNaN(value.getTime())) {
|
|
478
|
+
throw new Error("Expected a valid Date value")
|
|
479
|
+
}
|
|
477
480
|
return `date:${value.toISOString()}`
|
|
478
481
|
}
|
|
479
482
|
return "unknown"
|
package/src/internal/query.d.ts
CHANGED
|
@@ -37,6 +37,31 @@ export interface QueryState<Outstanding extends string, AvailableNames extends s
|
|
|
37
37
|
}
|
|
38
38
|
/** Effective SQL dialect carried by an expression. */
|
|
39
39
|
export type DialectOf<Value extends Expression.Any> = Value[typeof Expression.TypeId]["dialect"];
|
|
40
|
+
type ConcreteDialect<D> = D extends "standard" ? never : D;
|
|
41
|
+
type IsDialectUnion<Union, All = Union> = Union extends any ? [
|
|
42
|
+
All
|
|
43
|
+
] extends [Union] ? false : true : false;
|
|
44
|
+
export type DialectConflictError<A extends string, B extends string> = string & {
|
|
45
|
+
readonly _tag: "DialectConflict";
|
|
46
|
+
readonly left: A;
|
|
47
|
+
readonly right: B;
|
|
48
|
+
};
|
|
49
|
+
export type MergeDialect<A extends string, B extends string> = [
|
|
50
|
+
ConcreteDialect<A>,
|
|
51
|
+
ConcreteDialect<B>
|
|
52
|
+
] extends [never, never] ? "standard" : [
|
|
53
|
+
ConcreteDialect<A>,
|
|
54
|
+
ConcreteDialect<B>
|
|
55
|
+
] extends [never, infer C extends string] ? C : [
|
|
56
|
+
ConcreteDialect<A>,
|
|
57
|
+
ConcreteDialect<B>
|
|
58
|
+
] extends [infer C extends string, never] ? C : ConcreteDialect<A> extends ConcreteDialect<B> ? ConcreteDialect<B> extends ConcreteDialect<A> ? A : DialectConflictError<A, B> : DialectConflictError<A, B>;
|
|
59
|
+
/** Collapses the portable standard tag out of a dialect union. */
|
|
60
|
+
export type NormalizeDialect<Dialect extends string> = [
|
|
61
|
+
Dialect
|
|
62
|
+
] extends [never] ? never : [
|
|
63
|
+
Exclude<Dialect, "standard">
|
|
64
|
+
] extends [never] ? "standard" : IsDialectUnion<Exclude<Dialect, "standard">> extends true ? DialectConflictError<Exclude<Dialect, "standard">, Exclude<Dialect, "standard">> : Exclude<Dialect, "standard">;
|
|
40
65
|
/** Source dependency union carried by an expression. */
|
|
41
66
|
export type DependenciesOf<Value extends Expression.Any> = Expression.DependenciesOf<Value>;
|
|
42
67
|
/** Aggregation kind carried by an expression. */
|
|
@@ -135,10 +160,11 @@ export type SelectionShape = Expression.Any | {
|
|
|
135
160
|
export type ExtractRequired<Selection> = Selection extends Expression.Any ? RequiredFromDependencies<DependenciesOf<Selection>> : Selection extends Record<string, any> ? {
|
|
136
161
|
[K in keyof Selection]: ExtractRequired<Selection[K]>;
|
|
137
162
|
}[keyof Selection] : never;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[K in keyof Selection]: ExtractDialect<Selection[K]>;
|
|
163
|
+
type ExtractDialectRaw<Selection> = Selection extends Expression.Any ? DialectOf<Selection> : Selection extends Record<string, any> ? {
|
|
164
|
+
[K in keyof Selection]: ExtractDialectRaw<Selection[K]>;
|
|
141
165
|
}[keyof Selection] : never;
|
|
166
|
+
/** Walks a selection tree and extracts the effective dialects referenced by its leaves. */
|
|
167
|
+
export type ExtractDialect<Selection> = NormalizeDialect<Extract<ExtractDialectRaw<Selection>, string>>;
|
|
142
168
|
/**
|
|
143
169
|
* Minimal table-like shape required by `from(...)` and joins.
|
|
144
170
|
*
|
|
@@ -419,8 +445,7 @@ export type MutationTargetNamesOf<Target extends MutationTargetInput> = Target e
|
|
|
419
445
|
export type UpdateInputOfTarget<Target extends MutationTargetInput> = Target extends MutationTargetLike ? MutationInputOf<Table.UpdateOf<Target>> : Target extends MutationTargetTuple ? Simplify<{
|
|
420
446
|
readonly [K in MutationTargetNamesOf<Target>]?: MutationInputOf<Table.UpdateOf<MutationTargetByName<Target, K>>>;
|
|
421
447
|
}> : never;
|
|
422
|
-
|
|
423
|
-
export type SourceDialectOf<Source extends SourceLike> = Source extends TableLike<any, infer Dialect> ? Dialect : Source extends {
|
|
448
|
+
type SourceDialectOfRaw<Source extends SourceLike> = Source extends TableLike<any, infer Dialect> ? Dialect : Source extends {
|
|
424
449
|
readonly kind: "derived";
|
|
425
450
|
readonly plan: infer PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>;
|
|
426
451
|
} ? PlanDialectOf<PlanValue> : Source extends {
|
|
@@ -432,6 +457,8 @@ export type SourceDialectOf<Source extends SourceLike> = Source extends TableLik
|
|
|
432
457
|
} ? PlanDialectOf<PlanValue> : Source extends {
|
|
433
458
|
readonly dialect: infer Dialect extends string;
|
|
434
459
|
} ? Dialect : never;
|
|
460
|
+
/** Extracts the effective dialect from a source. */
|
|
461
|
+
export type SourceDialectOf<Source extends SourceLike> = NormalizeDialect<SourceDialectOfRaw<Source>>;
|
|
435
462
|
/** Extracts the base table name from a source. */
|
|
436
463
|
export type SourceBaseNameOf<Source extends SourceLike> = Source extends TableLike<any, any> ? Source[typeof Table.TypeId]["baseName"] : Source extends {
|
|
437
464
|
readonly kind: "derived";
|
|
@@ -493,7 +520,7 @@ export type RequiredOfPlan<PlanValue extends QueryPlan<any, any, any, any, any,
|
|
|
493
520
|
/** Extracts the available-source scope carried by a query plan. */
|
|
494
521
|
export type AvailableOfPlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>> = QueryPlanParts<PlanValue>["available"];
|
|
495
522
|
/** Extracts the effective dialect carried by a query plan. */
|
|
496
|
-
export type PlanDialectOf<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>> = QueryPlanParts<PlanValue>["dialect"]
|
|
523
|
+
export type PlanDialectOf<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>> = NormalizeDialect<QueryPlanParts<PlanValue>["dialect"]>;
|
|
497
524
|
/** Extracts the grouped-source phantom carried by a query plan. */
|
|
498
525
|
export type GroupedOfPlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any, any, any>> = QueryPlanState<PlanValue>["grouped"];
|
|
499
526
|
/** Extracts the available-name phantom carried by a query plan. */
|
|
@@ -562,7 +589,7 @@ type MergeNullability<Left extends Expression.Nullability, Right extends Express
|
|
|
562
589
|
/** Folds nullability across a tuple for null-propagating scalar operators. */
|
|
563
590
|
export type MergeNullabilityTuple<Values extends readonly Expression.Any[], Current extends Expression.Nullability = "never"> = Values extends readonly [infer Head extends Expression.Any, ...infer Tail extends readonly Expression.Any[]] ? MergeNullabilityTuple<Tail, MergeNullability<Current, Expression.NullabilityOf<Head>>> : Current;
|
|
564
591
|
/** Dialect union across a tuple of expressions. */
|
|
565
|
-
export type TupleDialect<Values extends readonly Expression.Any[]> = Values[number] extends never ? never : DialectOf<Values[number]
|
|
592
|
+
export type TupleDialect<Values extends readonly Expression.Any[]> = Values[number] extends never ? never : NormalizeDialect<DialectOf<Values[number]>>;
|
|
566
593
|
/** Dependency union across a tuple of scalar expressions. */
|
|
567
594
|
export type TupleDependencies<Values extends readonly Expression.Any[]> = Values[number] extends never ? never : DependenciesOf<Values[number]>;
|
|
568
595
|
/** Builds the canonical dependency union from a string union of table names. */
|
|
@@ -661,7 +688,7 @@ type AggregationCompatibilityError<PlanValue extends QueryPlan<any, any, any, an
|
|
|
661
688
|
};
|
|
662
689
|
type DialectCompatibilityError<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>, EngineDialect extends string> = PlanValue & {
|
|
663
690
|
readonly __effect_qb_error__: "effect-qb: plan dialect is not compatible with the target renderer or executor";
|
|
664
|
-
readonly __effect_qb_plan_dialect__: PlanValue
|
|
691
|
+
readonly __effect_qb_plan_dialect__: PlanDialectOf<PlanValue>;
|
|
665
692
|
readonly __effect_qb_target_dialect__: EngineDialect;
|
|
666
693
|
readonly __effect_qb_hint__: "Use the matching dialect module or renderer/executor";
|
|
667
694
|
};
|
|
@@ -678,11 +705,11 @@ export type AggregationCompatiblePlan<PlanValue extends QueryPlan<any, any, any,
|
|
|
678
705
|
/** Narrows a query plan to aggregate-compatible, source-complete plans. */
|
|
679
706
|
export type CompletePlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>> = StatementOfPlan<PlanValue> extends "insert" ? InsertSourceStateOfPlan<PlanValue> extends "missing" ? InsertHasOptionalDefaults<PlanValue> extends true ? PlanValue : InsertSourceCompletenessError<PlanValue> : HasKnownOutstanding<RequiredOfPlan<PlanValue>> extends true ? SourceCompletenessError<PlanValue, Extract<RequiredOfPlan<PlanValue>, string>> : IsAggregationCompatibleSelection<SelectionOfPlan<PlanValue>, GroupedOfPlan<PlanValue>> extends true ? PlanValue : AggregationCompatibilityError<PlanValue> : HasKnownOutstanding<RequiredOfPlan<PlanValue>> extends true ? SourceCompletenessError<PlanValue, Extract<RequiredOfPlan<PlanValue>, string>> : IsAggregationCompatibleSelection<SelectionOfPlan<PlanValue>, GroupedOfPlan<PlanValue>> extends true ? PlanValue : AggregationCompatibilityError<PlanValue>;
|
|
680
707
|
/** Whether a plan dialect is compatible with a target engine dialect. */
|
|
681
|
-
type IsDialectCompatible<PlanDialect extends string, EngineDialect extends string> = [PlanDialect] extends [never] ? true :
|
|
708
|
+
type IsDialectCompatible<PlanDialect extends string, EngineDialect extends string> = [PlanDialect] extends [never] ? true : Exclude<PlanDialect, EngineDialect | "standard"> extends never ? true : false;
|
|
682
709
|
/** Narrows a complete plan to those compatible with a target engine dialect. */
|
|
683
|
-
export type DialectCompatiblePlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>, EngineDialect extends string> = IsDialectCompatible<PlanValue
|
|
710
|
+
export type DialectCompatiblePlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>, EngineDialect extends string> = IsDialectCompatible<PlanDialectOf<PlanValue>, EngineDialect> extends true ? CompletePlan<PlanValue> : DialectCompatibilityError<PlanValue, EngineDialect>;
|
|
684
711
|
/** Nested-plan compatibility used by subquery expressions such as `exists(...)`. */
|
|
685
|
-
export type DialectCompatibleNestedPlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>, EngineDialect extends string> = IsDialectCompatible<PlanValue
|
|
712
|
+
export type DialectCompatibleNestedPlan<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>, EngineDialect extends string> = IsDialectCompatible<PlanDialectOf<PlanValue>, EngineDialect> extends true ? AggregationCompatiblePlan<PlanValue> : DialectCompatibilityError<PlanValue, EngineDialect>;
|
|
686
713
|
type SetOperandStatement = "select" | "set";
|
|
687
714
|
type IsUnion<Value, All = Value> = Value extends any ? ([All] extends [Value] ? false : true) : never;
|
|
688
715
|
type SingleSelectedExpressionError<PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>> = PlanValue & {
|