effect-qb 0.16.0 → 4.0.0-beta.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/mysql.js +1858 -715
- package/dist/postgres/metadata.js +2036 -172
- package/dist/postgres.js +8011 -6849
- package/dist/sqlite.js +8433 -0
- package/package.json +7 -4
- package/src/internal/column-state.d.ts +3 -3
- package/src/internal/column-state.ts +3 -3
- package/src/internal/column.ts +8 -8
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +3 -1
- package/src/internal/dsl-mutation-runtime.ts +173 -4
- package/src/internal/dsl-plan-runtime.ts +165 -20
- package/src/internal/dsl-query-runtime.ts +60 -6
- package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
- package/src/internal/executor.ts +100 -43
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +141 -1
- package/src/internal/implication-runtime.ts +2 -1
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +27 -3
- package/src/internal/query.d.ts +1 -1
- package/src/internal/query.ts +253 -31
- package/src/internal/renderer.ts +35 -2
- package/src/internal/runtime/driver-value-mapping.ts +60 -2
- package/src/internal/runtime/normalize.ts +62 -38
- package/src/internal/runtime/schema.ts +32 -40
- package/src/internal/runtime/value.ts +159 -39
- package/src/internal/scalar.d.ts +1 -1
- package/src/internal/scalar.ts +1 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +95 -43
- package/src/internal/table-options.ts +108 -1
- package/src/internal/table.d.ts +29 -22
- package/src/internal/table.ts +260 -53
- package/src/mysql/column.ts +24 -8
- package/src/mysql/datatypes/index.ts +21 -0
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +4 -4
- package/src/mysql/function/temporal.ts +1 -1
- package/src/mysql/internal/dsl.ts +759 -235
- package/src/mysql/internal/renderer.ts +2 -1
- package/src/mysql/internal/sql-expression-renderer.ts +486 -130
- package/src/mysql/query.ts +9 -2
- package/src/mysql/table.ts +64 -35
- package/src/postgres/column.ts +14 -12
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +52 -9
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/function/temporal.ts +1 -1
- package/src/postgres/internal/dsl.ts +705 -256
- package/src/postgres/internal/renderer.ts +2 -1
- package/src/postgres/internal/schema-ddl.ts +2 -1
- package/src/postgres/internal/schema-model.ts +6 -3
- package/src/postgres/internal/sql-expression-renderer.ts +420 -91
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -2
- package/src/postgres/schema-management.ts +92 -6
- package/src/postgres/schema.ts +1 -1
- package/src/postgres/table.ts +203 -75
- package/src/sqlite/column.ts +128 -0
- package/src/sqlite/datatypes/index.ts +79 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +227 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +37 -0
- package/src/sqlite/internal/dsl.ts +6927 -0
- package/src/sqlite/internal/renderer.ts +47 -0
- package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
- package/src/sqlite/json.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +24 -0
- package/src/sqlite/table.ts +175 -0
- package/src/sqlite.ts +22 -0
|
@@ -22,8 +22,122 @@ type DslPlanRuntimeContext = {
|
|
|
22
22
|
readonly attachInsertSource: (plan: any, source: any) => any
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export const renderSelectLockMode = (mode: unknown): string => {
|
|
26
|
+
switch (mode) {
|
|
27
|
+
case "update":
|
|
28
|
+
return "for update"
|
|
29
|
+
case "share":
|
|
30
|
+
return "for share"
|
|
31
|
+
}
|
|
32
|
+
throw new Error("lock(...) mode must be update or share for select statements")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const renderMysqlMutationLockMode = (
|
|
36
|
+
mode: unknown,
|
|
37
|
+
statement: "update" | "delete"
|
|
38
|
+
): string => {
|
|
39
|
+
switch (mode) {
|
|
40
|
+
case "lowPriority":
|
|
41
|
+
return " low_priority"
|
|
42
|
+
case "ignore":
|
|
43
|
+
return " ignore"
|
|
44
|
+
case "quick":
|
|
45
|
+
if (statement === "delete") {
|
|
46
|
+
return " quick"
|
|
47
|
+
}
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
statement === "update"
|
|
52
|
+
? "lock(...) mode must be lowPriority or ignore for update statements"
|
|
53
|
+
: "lock(...) mode must be lowPriority, quick, or ignore for delete statements"
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
25
57
|
export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
58
|
+
const aliasedSourceKinds = new Set(["derived", "cte", "lateral", "values", "unnest", "tableFunction"])
|
|
59
|
+
const isRecord = (value: unknown): value is Record<PropertyKey, unknown> =>
|
|
60
|
+
typeof value === "object" && value !== null
|
|
61
|
+
|
|
62
|
+
const isPlan = (value: unknown): boolean => isRecord(value) && Plan.TypeId in value
|
|
63
|
+
const hasColumnRecord = (value: Record<PropertyKey, unknown>): boolean => isRecord(value.columns)
|
|
64
|
+
|
|
65
|
+
const sourceRequiredList = (source: any): readonly string[] =>
|
|
66
|
+
typeof source === "object" && source !== null && "required" in source
|
|
67
|
+
? ctx.currentRequiredList(source.required)
|
|
68
|
+
: []
|
|
69
|
+
|
|
70
|
+
const isAliasedSource = (source: unknown): boolean => {
|
|
71
|
+
if (!isRecord(source)) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
if (Table.TypeId in source) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
if (!("kind" in source) || !("name" in source) || !("baseName" in source)) {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
if (typeof source.kind !== "string" || !aliasedSourceKinds.has(source.kind)) {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
if (typeof source.name !== "string" || typeof source.baseName !== "string") {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
switch (source.kind) {
|
|
87
|
+
case "derived":
|
|
88
|
+
case "cte":
|
|
89
|
+
case "lateral":
|
|
90
|
+
return isPlan(source.plan) && hasColumnRecord(source)
|
|
91
|
+
case "values":
|
|
92
|
+
return Array.isArray(source.rows) && hasColumnRecord(source)
|
|
93
|
+
case "unnest":
|
|
94
|
+
return isRecord(source.arrays) && hasColumnRecord(source)
|
|
95
|
+
case "tableFunction":
|
|
96
|
+
return typeof source.functionName === "string" && Array.isArray(source.args) && hasColumnRecord(source)
|
|
97
|
+
}
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const assertAliasedSource = (source: unknown, message: string): void => {
|
|
102
|
+
if (!isAliasedSource(source)) {
|
|
103
|
+
throw new Error(message)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const assertPlanComplete = (plan: any): void => {
|
|
108
|
+
const required = ctx.currentRequiredList(plan[Plan.TypeId].required)
|
|
109
|
+
if (required.length > 0) {
|
|
110
|
+
throw new Error(`query references sources that are not yet in scope: ${required.join(", ")}`)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const assertSourceNameAvailable = (available: Record<string, unknown>, sourceName: string): void => {
|
|
115
|
+
if (sourceName in available) {
|
|
116
|
+
throw new Error(`query source name is already in scope: ${sourceName}`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const assertSelectHasBaseSourceForJoin = (statement: string, available: Record<string, unknown>): void => {
|
|
121
|
+
if (statement === "select" && Object.keys(available).length === 0) {
|
|
122
|
+
throw new Error("select joins require a from(...) source before joining")
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const supportsJoinSources = (statement: string): boolean =>
|
|
127
|
+
statement === "select" || statement === "update" || statement === "delete"
|
|
128
|
+
|
|
129
|
+
const assertSetOperandStatement = (plan: any): void => {
|
|
130
|
+
const statement = ctx.getQueryState(plan).statement
|
|
131
|
+
if (statement !== "select" && statement !== "set") {
|
|
132
|
+
throw new Error("set operator operands only accept select-like query plans")
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
26
136
|
const buildSetOperation = (kind: string, all: boolean, left: any, right: any) => {
|
|
137
|
+
assertSetOperandStatement(left)
|
|
138
|
+
assertSetOperandStatement(right)
|
|
139
|
+
assertPlanComplete(left)
|
|
140
|
+
assertPlanComplete(right)
|
|
27
141
|
const leftState = left[Plan.TypeId]
|
|
28
142
|
const leftAst = ctx.getAst(left)
|
|
29
143
|
const basePlan = leftAst.kind === "set"
|
|
@@ -92,32 +206,33 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
92
206
|
return ctx.attachInsertSource(plan, source)
|
|
93
207
|
}
|
|
94
208
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
("
|
|
99
|
-
(!(Table.TypeId in source) && !("name" in source && "baseName" in source))
|
|
100
|
-
) {
|
|
101
|
-
throw new Error("from(...) requires an aliased source in select/update statements")
|
|
209
|
+
assertAliasedSource(source, "from(...) requires an aliased source in select/update statements")
|
|
210
|
+
|
|
211
|
+
if (currentQuery.statement === "select" && currentAst.from !== undefined) {
|
|
212
|
+
throw new Error("select statements accept only one from(...) source; use joins for additional sources")
|
|
102
213
|
}
|
|
103
214
|
|
|
104
215
|
const sourceLike = source
|
|
105
216
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(sourceLike)
|
|
106
217
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(sourceLike)
|
|
218
|
+
const sourceRequired = sourceRequiredList(sourceLike)
|
|
219
|
+
assertSourceNameAvailable(current.available, sourceName)
|
|
107
220
|
|
|
108
221
|
if (currentQuery.statement === "select") {
|
|
222
|
+
const nextAvailable = {
|
|
223
|
+
[sourceName]: {
|
|
224
|
+
name: sourceName,
|
|
225
|
+
mode: "required",
|
|
226
|
+
baseName: sourceBaseName,
|
|
227
|
+
_presentFormula: ctx.trueFormula(),
|
|
228
|
+
_presenceWitnesses: presenceWitnesses
|
|
229
|
+
}
|
|
230
|
+
}
|
|
109
231
|
return ctx.makePlan({
|
|
110
232
|
selection: current.selection,
|
|
111
|
-
required: ctx.currentRequiredList(current.required).filter((name) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
name: sourceName,
|
|
115
|
-
mode: "required",
|
|
116
|
-
baseName: sourceBaseName,
|
|
117
|
-
_presentFormula: ctx.trueFormula(),
|
|
118
|
-
_presenceWitnesses: presenceWitnesses
|
|
119
|
-
}
|
|
120
|
-
},
|
|
233
|
+
required: [...ctx.currentRequiredList(current.required), ...sourceRequired].filter((name, index, values) =>
|
|
234
|
+
!(name in nextAvailable) && values.indexOf(name) === index),
|
|
235
|
+
available: nextAvailable,
|
|
121
236
|
dialect: current.dialect
|
|
122
237
|
}, {
|
|
123
238
|
...currentAst,
|
|
@@ -143,7 +258,8 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
143
258
|
}
|
|
144
259
|
return ctx.makePlan({
|
|
145
260
|
selection: current.selection,
|
|
146
|
-
required: ctx.currentRequiredList(current.required).filter((name) =>
|
|
261
|
+
required: [...ctx.currentRequiredList(current.required), ...sourceRequired].filter((name, index, values) =>
|
|
262
|
+
!(name in nextAvailable) && values.indexOf(name) === index),
|
|
147
263
|
available: nextAvailable,
|
|
148
264
|
dialect: current.dialect
|
|
149
265
|
}, {
|
|
@@ -193,8 +309,16 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
193
309
|
const current = plan[Plan.TypeId]
|
|
194
310
|
const currentAst = ctx.getAst(plan)
|
|
195
311
|
const currentQuery = ctx.getQueryState(plan)
|
|
312
|
+
if (supportsJoinSources(currentQuery.statement)) {
|
|
313
|
+
assertAliasedSource(table, "join(...) requires an aliased source in select/update/delete statements")
|
|
314
|
+
assertSelectHasBaseSourceForJoin(currentQuery.statement, current.available)
|
|
315
|
+
}
|
|
196
316
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(table)
|
|
197
317
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(table)
|
|
318
|
+
const sourceRequired = sourceRequiredList(table)
|
|
319
|
+
if (supportsJoinSources(currentQuery.statement)) {
|
|
320
|
+
assertSourceNameAvailable(current.available, sourceName)
|
|
321
|
+
}
|
|
198
322
|
const nextAvailable = {
|
|
199
323
|
...current.available,
|
|
200
324
|
[sourceName]: {
|
|
@@ -207,7 +331,8 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
207
331
|
}
|
|
208
332
|
return ctx.makePlan({
|
|
209
333
|
selection: current.selection,
|
|
210
|
-
required: ctx.currentRequiredList(current.required).filter((name) =>
|
|
334
|
+
required: [...ctx.currentRequiredList(current.required), ...sourceRequired].filter((name, index, values) =>
|
|
335
|
+
!(name in nextAvailable) && values.indexOf(name) === index),
|
|
211
336
|
available: nextAvailable,
|
|
212
337
|
dialect: current.dialect ?? table[Plan.TypeId]?.dialect ?? table.dialect
|
|
213
338
|
}, {
|
|
@@ -228,8 +353,16 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
228
353
|
const currentQuery = ctx.getQueryState(plan)
|
|
229
354
|
const onExpression = ctx.toDialectExpression(on)
|
|
230
355
|
const onFormula = ctx.formulaOfExpressionRuntime(onExpression)
|
|
356
|
+
if (supportsJoinSources(currentQuery.statement)) {
|
|
357
|
+
assertAliasedSource(table, "join(...) requires an aliased source in select/update/delete statements")
|
|
358
|
+
assertSelectHasBaseSourceForJoin(currentQuery.statement, current.available)
|
|
359
|
+
}
|
|
231
360
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(table)
|
|
232
361
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(table)
|
|
362
|
+
const sourceRequired = sourceRequiredList(table)
|
|
363
|
+
if (supportsJoinSources(currentQuery.statement)) {
|
|
364
|
+
assertSourceNameAvailable(current.available, sourceName)
|
|
365
|
+
}
|
|
233
366
|
const baseAvailable = (kind === "right" || kind === "full"
|
|
234
367
|
? Object.fromEntries(
|
|
235
368
|
Object.entries(current.available as Record<string, any>).map(([name, source]) => [name, {
|
|
@@ -253,7 +386,7 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
253
386
|
}
|
|
254
387
|
return ctx.makePlan({
|
|
255
388
|
selection: current.selection,
|
|
256
|
-
required: [...ctx.currentRequiredList(current.required), ...ctx.extractRequiredFromDialectInputRuntime(on)].filter((name, index, values) =>
|
|
389
|
+
required: [...ctx.currentRequiredList(current.required), ...sourceRequired, ...ctx.extractRequiredFromDialectInputRuntime(on)].filter((name, index, values) =>
|
|
257
390
|
!(name in nextAvailable) && values.indexOf(name) === index),
|
|
258
391
|
available: nextAvailable,
|
|
259
392
|
dialect: current.dialect ?? table.dialect ?? onExpression[Expression.TypeId].dialect
|
|
@@ -275,6 +408,9 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
275
408
|
|
|
276
409
|
const orderBy = (value: any, direction: "asc" | "desc" = "asc") =>
|
|
277
410
|
(plan: any) => {
|
|
411
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
412
|
+
throw new Error("orderBy(...) direction must be asc or desc")
|
|
413
|
+
}
|
|
278
414
|
const current = plan[Plan.TypeId]
|
|
279
415
|
const currentAst = ctx.getAst(plan)
|
|
280
416
|
const currentQuery = ctx.getQueryState(plan)
|
|
@@ -301,6 +437,15 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
301
437
|
const current = plan[Plan.TypeId]
|
|
302
438
|
const currentAst = ctx.getAst(plan)
|
|
303
439
|
const currentQuery = ctx.getQueryState(plan)
|
|
440
|
+
if (currentQuery.statement === "select") {
|
|
441
|
+
renderSelectLockMode(mode)
|
|
442
|
+
}
|
|
443
|
+
if (ctx.profile.dialect === "mysql" && currentQuery.statement === "update") {
|
|
444
|
+
renderMysqlMutationLockMode(mode, "update")
|
|
445
|
+
}
|
|
446
|
+
if (ctx.profile.dialect === "mysql" && currentQuery.statement === "delete") {
|
|
447
|
+
renderMysqlMutationLockMode(mode, "delete")
|
|
448
|
+
}
|
|
304
449
|
return ctx.makePlan({
|
|
305
450
|
selection: current.selection,
|
|
306
451
|
required: current.required,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Expression from "./scalar.js"
|
|
2
2
|
import * as Plan from "./row-set.js"
|
|
3
|
+
import { flattenSelection } from "./projections.js"
|
|
3
4
|
|
|
4
5
|
type DslQueryRuntimeContext = {
|
|
5
6
|
readonly profile: {
|
|
@@ -19,6 +20,36 @@ type DslQueryRuntimeContext = {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
23
|
+
const assertSelectionObject = (apiName: string, selection: any): void => {
|
|
24
|
+
if (
|
|
25
|
+
selection === null ||
|
|
26
|
+
typeof selection !== "object" ||
|
|
27
|
+
Array.isArray(selection) ||
|
|
28
|
+
Expression.TypeId in selection
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(`${apiName}(...) expects a projection object`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const assertSelectionTree = (apiName: string, selection: any): void => {
|
|
35
|
+
const visit = (value: any, isRoot: boolean): void => {
|
|
36
|
+
if (value !== null && typeof value === "object" && Expression.TypeId in value) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
40
|
+
throw new Error(`${apiName}(...) selection leaves must be expressions`)
|
|
41
|
+
}
|
|
42
|
+
const nested = Object.values(value)
|
|
43
|
+
if (!isRoot && nested.length === 0) {
|
|
44
|
+
throw new Error(`${apiName}(...) projection objects cannot contain empty nested selections`)
|
|
45
|
+
}
|
|
46
|
+
for (const item of nested) {
|
|
47
|
+
visit(item, false)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
visit(selection, true)
|
|
51
|
+
}
|
|
52
|
+
|
|
22
53
|
const values = (rows: readonly [Record<string, any>, ...Record<string, any>[]]) => {
|
|
23
54
|
if (rows.length === 0) {
|
|
24
55
|
throw new Error("values(...) requires at least one row")
|
|
@@ -28,10 +59,14 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
28
59
|
...Record<string, Expression.Any>[]
|
|
29
60
|
]
|
|
30
61
|
const columnNames = Object.keys(normalizedRows[0]!)
|
|
62
|
+
if (columnNames.length === 0) {
|
|
63
|
+
throw new Error("values(...) rows must specify at least one column")
|
|
64
|
+
}
|
|
65
|
+
const columnNameSet = new Set(columnNames)
|
|
31
66
|
for (const row of normalizedRows) {
|
|
32
67
|
const rowKeys = Object.keys(row)
|
|
33
|
-
if (rowKeys.length !== columnNames.length || !rowKeys.every((key
|
|
34
|
-
throw new Error("values(...) rows must project the same columns
|
|
68
|
+
if (rowKeys.length !== columnNames.length || !rowKeys.every((key) => columnNameSet.has(key))) {
|
|
69
|
+
throw new Error("values(...) rows must project the same columns")
|
|
35
70
|
}
|
|
36
71
|
}
|
|
37
72
|
return Object.assign(Object.create(ctx.ValuesInputProto), {
|
|
@@ -97,8 +132,10 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
97
132
|
return Object.assign(source, columns)
|
|
98
133
|
}
|
|
99
134
|
|
|
100
|
-
const select = (selection: any) =>
|
|
101
|
-
|
|
135
|
+
const select = (selection: any = {}) => {
|
|
136
|
+
assertSelectionObject("select", selection)
|
|
137
|
+
assertSelectionTree("select", selection)
|
|
138
|
+
return ctx.makePlan({
|
|
102
139
|
selection,
|
|
103
140
|
required: ctx.extractRequiredRuntime(selection),
|
|
104
141
|
available: {},
|
|
@@ -112,9 +149,13 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
112
149
|
groupBy: [],
|
|
113
150
|
orderBy: []
|
|
114
151
|
}, undefined, "read", "select")
|
|
152
|
+
}
|
|
115
153
|
|
|
116
154
|
const groupBy = (...values: readonly Expression.Any[]) =>
|
|
117
155
|
(plan: any) => {
|
|
156
|
+
if (values.length === 0) {
|
|
157
|
+
throw new Error("groupBy(...) requires at least one expression")
|
|
158
|
+
}
|
|
118
159
|
const current = plan[Plan.TypeId]
|
|
119
160
|
const currentAst = ctx.getAst(plan)
|
|
120
161
|
const currentQuery = ctx.getQueryState(plan)
|
|
@@ -132,11 +173,23 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
132
173
|
}, currentQuery.assumptions, currentQuery.capabilities, currentQuery.statement)
|
|
133
174
|
}
|
|
134
175
|
|
|
135
|
-
const returning = (selection: any) =>
|
|
136
|
-
(
|
|
176
|
+
const returning = (selection: any) => {
|
|
177
|
+
assertSelectionObject("returning", selection)
|
|
178
|
+
assertSelectionTree("returning", selection)
|
|
179
|
+
if (flattenSelection(selection as Record<string, unknown>).length === 0) {
|
|
180
|
+
throw new Error("returning(...) requires at least one selected expression")
|
|
181
|
+
}
|
|
182
|
+
return (plan: any) => {
|
|
137
183
|
const current = plan[Plan.TypeId]
|
|
138
184
|
const currentAst = ctx.getAst(plan)
|
|
139
185
|
const currentQuery = ctx.getQueryState(plan)
|
|
186
|
+
if (
|
|
187
|
+
currentQuery.statement !== "insert" &&
|
|
188
|
+
currentQuery.statement !== "update" &&
|
|
189
|
+
currentQuery.statement !== "delete"
|
|
190
|
+
) {
|
|
191
|
+
throw new Error(`returning(...) is not supported for ${currentQuery.statement} statements`)
|
|
192
|
+
}
|
|
140
193
|
return ctx.makePlan({
|
|
141
194
|
selection,
|
|
142
195
|
required: [...ctx.currentRequiredList(current.required), ...ctx.extractRequiredRuntime(selection)].filter((name, index, list) =>
|
|
@@ -148,6 +201,7 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
148
201
|
select: selection
|
|
149
202
|
}, currentQuery.assumptions, currentQuery.capabilities, currentQuery.statement, currentQuery.target, currentQuery.insertSource)
|
|
150
203
|
}
|
|
204
|
+
}
|
|
151
205
|
|
|
152
206
|
return {
|
|
153
207
|
values,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Plan from "./row-set.js"
|
|
2
|
+
import * as Table from "./table.js"
|
|
2
3
|
|
|
3
4
|
type DslTransactionDdlRuntimeContext = {
|
|
4
5
|
readonly profile: {
|
|
@@ -10,9 +11,71 @@ type DslTransactionDdlRuntimeContext = {
|
|
|
10
11
|
readonly defaultIndexName: (tableName: string, columns: readonly string[], unique: boolean) => string
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
const allowedIsolationLevels = new Set(["read committed", "repeatable read", "serializable"])
|
|
15
|
+
|
|
16
|
+
export const renderTransactionIsolationLevel = (isolationLevel: unknown): string => {
|
|
17
|
+
if (isolationLevel === undefined) {
|
|
18
|
+
return ""
|
|
19
|
+
}
|
|
20
|
+
if (typeof isolationLevel !== "string" || !allowedIsolationLevels.has(isolationLevel)) {
|
|
21
|
+
throw new Error("Unsupported transaction isolation level")
|
|
22
|
+
}
|
|
23
|
+
return `isolation level ${isolationLevel}`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const expectDdlClauseKind = <
|
|
27
|
+
Ddl extends { readonly kind: string },
|
|
28
|
+
Kind extends Ddl["kind"]
|
|
29
|
+
>(
|
|
30
|
+
ddl: Ddl | undefined,
|
|
31
|
+
kind: Kind
|
|
32
|
+
): Extract<Ddl, { readonly kind: Kind }> => {
|
|
33
|
+
if (ddl === undefined || ddl.kind !== kind) {
|
|
34
|
+
throw new Error("Unsupported DDL statement kind")
|
|
35
|
+
}
|
|
36
|
+
return ddl as Extract<Ddl, { readonly kind: Kind }>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const expectTruncateClause = <
|
|
40
|
+
Truncate extends { readonly kind: string }
|
|
41
|
+
>(
|
|
42
|
+
truncate: Truncate | undefined
|
|
43
|
+
): Extract<Truncate, { readonly kind: "truncate" }> => {
|
|
44
|
+
if (truncate === undefined || truncate.kind !== "truncate") {
|
|
45
|
+
throw new Error("Unsupported truncate statement kind")
|
|
46
|
+
}
|
|
47
|
+
return truncate as Extract<Truncate, { readonly kind: "truncate" }>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const validateIsolationLevel = (isolationLevel: unknown): void => {
|
|
51
|
+
renderTransactionIsolationLevel(isolationLevel)
|
|
52
|
+
}
|
|
53
|
+
|
|
13
54
|
export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContext) => {
|
|
14
|
-
const
|
|
15
|
-
|
|
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
|
+
}
|
|
63
|
+
|
|
64
|
+
const validateIndexColumns = (target: any, columns: readonly string[]): void => {
|
|
65
|
+
const fields = target[Table.TypeId]?.fields as Record<string, unknown> | undefined
|
|
66
|
+
if (fields === undefined) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
for (const columnName of columns) {
|
|
70
|
+
if (!(columnName in fields)) {
|
|
71
|
+
throw new Error(`effect-qb: unknown index column '${columnName}'`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const transaction = (options: { readonly isolationLevel?: any; readonly readOnly?: boolean } = {}) => {
|
|
77
|
+
validateIsolationLevel(options.isolationLevel)
|
|
78
|
+
return ctx.makePlan({
|
|
16
79
|
selection: {},
|
|
17
80
|
required: [],
|
|
18
81
|
available: {},
|
|
@@ -31,6 +94,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
31
94
|
groupBy: [],
|
|
32
95
|
orderBy: []
|
|
33
96
|
}, undefined, "transaction", "transaction")
|
|
97
|
+
}
|
|
34
98
|
|
|
35
99
|
const commit = () =>
|
|
36
100
|
ctx.makePlan({
|
|
@@ -131,6 +195,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
131
195
|
}, undefined, "transaction", "releaseSavepoint")
|
|
132
196
|
|
|
133
197
|
const createTable = (target: any, options: { readonly ifNotExists?: boolean } = {}) => {
|
|
198
|
+
assertTableTarget(target, "createTable")
|
|
134
199
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
135
200
|
return ctx.makePlan({
|
|
136
201
|
selection: {},
|
|
@@ -159,6 +224,7 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
159
224
|
}
|
|
160
225
|
|
|
161
226
|
const dropTable = (target: any, options: { readonly ifExists?: boolean } = {}) => {
|
|
227
|
+
assertTableTarget(target, "dropTable")
|
|
162
228
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
163
229
|
return ctx.makePlan({
|
|
164
230
|
selection: {},
|
|
@@ -187,7 +253,9 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
187
253
|
}
|
|
188
254
|
|
|
189
255
|
const createIndex = (target: any, columns: string | readonly string[], options: { readonly name?: string; readonly unique?: boolean; readonly ifNotExists?: boolean } = {}) => {
|
|
256
|
+
assertTableTarget(target, "createIndex")
|
|
190
257
|
const normalizedColumns = ctx.normalizeColumnList(columns)
|
|
258
|
+
validateIndexColumns(target, normalizedColumns)
|
|
191
259
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
192
260
|
return ctx.makePlan({
|
|
193
261
|
selection: {},
|
|
@@ -219,7 +287,9 @@ export const makeDslTransactionDdlRuntime = (ctx: DslTransactionDdlRuntimeContex
|
|
|
219
287
|
}
|
|
220
288
|
|
|
221
289
|
const dropIndex = (target: any, columns: string | readonly string[], options: { readonly name?: string; readonly ifExists?: boolean } = {}) => {
|
|
290
|
+
assertTableTarget(target, "dropIndex")
|
|
222
291
|
const normalizedColumns = ctx.normalizeColumnList(columns)
|
|
292
|
+
validateIndexColumns(target, normalizedColumns)
|
|
223
293
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
224
294
|
return ctx.makePlan({
|
|
225
295
|
selection: {},
|