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
package/src/internal/dialect.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type * as Schema from "effect/Schema"
|
|
2
2
|
|
|
3
|
+
import type * as QueryAst from "./query-ast.js"
|
|
4
|
+
import type { Projection } from "./projections.js"
|
|
3
5
|
import type * as Expression from "./scalar.js"
|
|
6
|
+
import type * as Casing from "./casing.js"
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Mutable rendering state shared while serializing SQL for a concrete dialect.
|
|
@@ -11,6 +14,7 @@ import type * as Expression from "./scalar.js"
|
|
|
11
14
|
export interface RenderState {
|
|
12
15
|
readonly params: unknown[]
|
|
13
16
|
readonly valueMappings?: Expression.DriverValueMappings
|
|
17
|
+
readonly casing?: Casing.Options
|
|
14
18
|
readonly ctes: {
|
|
15
19
|
readonly name: string
|
|
16
20
|
readonly sql: string
|
|
@@ -18,7 +22,12 @@ export interface RenderState {
|
|
|
18
22
|
}[]
|
|
19
23
|
readonly cteNames: Set<string>
|
|
20
24
|
readonly cteSources: Map<string, unknown>
|
|
25
|
+
readonly sourceNames?: Map<string, {
|
|
26
|
+
readonly tableName: string
|
|
27
|
+
readonly columns: ReadonlyMap<string, string>
|
|
28
|
+
}>
|
|
21
29
|
readonly rowLocalColumns?: boolean
|
|
30
|
+
readonly allowExcluded?: boolean
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
export interface RenderValueContext {
|
|
@@ -27,6 +36,22 @@ export interface RenderValueContext {
|
|
|
27
36
|
readonly driverValueMapping?: Expression.DriverValueMapping
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
export const quoteDoubleQuotedIdentifier = (value: string): string => {
|
|
40
|
+
return `"${value.replaceAll("\"", "\"\"")}"`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const quoteBacktickIdentifier = (value: string): string => {
|
|
44
|
+
return `\`${value.replaceAll("`", "``")}\``
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const renderDbTypeName = (value: string): string =>
|
|
48
|
+
value
|
|
49
|
+
|
|
50
|
+
export interface RenderedAst {
|
|
51
|
+
readonly sql: string
|
|
52
|
+
readonly projections: readonly Projection[]
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
/**
|
|
31
56
|
* Minimal runtime contract for a SQL dialect.
|
|
32
57
|
*
|
|
@@ -40,4 +65,14 @@ export interface SqlDialect<Name extends string = string> {
|
|
|
40
65
|
renderLiteral(value: unknown, state: RenderState, context?: RenderValueContext): string
|
|
41
66
|
renderTableReference(tableName: string, baseTableName: string, schemaName?: string): string
|
|
42
67
|
renderConcat(values: readonly string[]): string
|
|
68
|
+
renderQueryAst(
|
|
69
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
70
|
+
state: RenderState,
|
|
71
|
+
dialect: SqlDialect<Name>
|
|
72
|
+
): RenderedAst
|
|
73
|
+
renderExpression(
|
|
74
|
+
expression: Expression.Any,
|
|
75
|
+
state: RenderState,
|
|
76
|
+
dialect: SqlDialect<Name>
|
|
77
|
+
): string
|
|
43
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Expression from "./scalar.js"
|
|
2
2
|
import * as Plan from "./row-set.js"
|
|
3
|
-
import
|
|
3
|
+
import { normalizeStatementFlag } from "./dsl-transaction-ddl-runtime.js"
|
|
4
4
|
|
|
5
5
|
type DslMutationRuntimeContext = {
|
|
6
6
|
readonly profile: {
|
|
@@ -23,150 +23,18 @@ type DslMutationRuntimeContext = {
|
|
|
23
23
|
readonly sourceDetails: (source: any) => { readonly sourceName: string; readonly sourceBaseName: string }
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export const expectInsertSourceKind = <
|
|
27
|
-
Source extends { readonly kind: string } | undefined
|
|
28
|
-
>(
|
|
29
|
-
source: Source
|
|
30
|
-
): Source => {
|
|
31
|
-
if (
|
|
32
|
-
source !== undefined &&
|
|
33
|
-
source.kind !== "values" &&
|
|
34
|
-
source.kind !== "query" &&
|
|
35
|
-
source.kind !== "unnest"
|
|
36
|
-
) {
|
|
37
|
-
throw new Error("Unsupported insert source kind")
|
|
38
|
-
}
|
|
39
|
-
return source
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
export const expectConflictClause = <
|
|
43
27
|
Conflict extends {
|
|
44
28
|
readonly kind: string
|
|
45
29
|
readonly action: string
|
|
46
|
-
readonly target?: { readonly kind: string }
|
|
30
|
+
readonly target?: { readonly kind: string; readonly name?: string }
|
|
47
31
|
} | undefined
|
|
48
32
|
>(
|
|
49
33
|
conflict: Conflict
|
|
50
|
-
): Conflict =>
|
|
51
|
-
if (conflict === undefined) {
|
|
52
|
-
return conflict
|
|
53
|
-
}
|
|
54
|
-
if (conflict.kind !== "conflict") {
|
|
55
|
-
throw new Error("Unsupported conflict clause kind")
|
|
56
|
-
}
|
|
57
|
-
if (conflict.action !== "doNothing" && conflict.action !== "doUpdate") {
|
|
58
|
-
throw new Error("Unsupported conflict action")
|
|
59
|
-
}
|
|
60
|
-
if (
|
|
61
|
-
conflict.target !== undefined &&
|
|
62
|
-
conflict.target.kind !== "columns" &&
|
|
63
|
-
conflict.target.kind !== "constraint"
|
|
64
|
-
) {
|
|
65
|
-
throw new Error("Unsupported conflict target kind")
|
|
66
|
-
}
|
|
67
|
-
return conflict
|
|
68
|
-
}
|
|
34
|
+
): Conflict => conflict
|
|
69
35
|
|
|
70
36
|
export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
71
|
-
const aliasedSourceKinds = new Set(["derived", "cte", "lateral", "values", "unnest", "tableFunction"])
|
|
72
|
-
const isRecord = (value: unknown): value is Record<PropertyKey, unknown> =>
|
|
73
|
-
typeof value === "object" && value !== null
|
|
74
|
-
|
|
75
|
-
const isTableTarget = (target: unknown): boolean =>
|
|
76
|
-
typeof target === "object" && target !== null && Table.TypeId in target && Plan.TypeId in target
|
|
77
|
-
|
|
78
|
-
const hasColumnRecord = (value: Record<PropertyKey, unknown>): boolean => isRecord(value.columns)
|
|
79
|
-
|
|
80
|
-
const isAliasedSource = (source: unknown): boolean => {
|
|
81
|
-
if (!isRecord(source)) {
|
|
82
|
-
return false
|
|
83
|
-
}
|
|
84
|
-
if (isTableTarget(source)) {
|
|
85
|
-
return true
|
|
86
|
-
}
|
|
87
|
-
if (!("kind" in source) || !("name" in source) || !("baseName" in source)) {
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
if (typeof source.kind !== "string" || !aliasedSourceKinds.has(source.kind)) {
|
|
91
|
-
return false
|
|
92
|
-
}
|
|
93
|
-
if (typeof source.name !== "string" || typeof source.baseName !== "string") {
|
|
94
|
-
return false
|
|
95
|
-
}
|
|
96
|
-
switch (source.kind) {
|
|
97
|
-
case "derived":
|
|
98
|
-
case "cte":
|
|
99
|
-
case "lateral":
|
|
100
|
-
return isRecord(source.plan) && Plan.TypeId in source.plan && hasColumnRecord(source)
|
|
101
|
-
case "values":
|
|
102
|
-
return Array.isArray(source.rows) && hasColumnRecord(source)
|
|
103
|
-
case "unnest":
|
|
104
|
-
return isRecord(source.arrays) && hasColumnRecord(source)
|
|
105
|
-
case "tableFunction":
|
|
106
|
-
return typeof source.functionName === "string" && Array.isArray(source.args) && hasColumnRecord(source)
|
|
107
|
-
}
|
|
108
|
-
return false
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const assertMutationTarget = (target: unknown, apiName: string): void => {
|
|
112
|
-
if (!isTableTarget(target)) {
|
|
113
|
-
throw new Error(`${apiName}(...) requires table targets`)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const assertAliasedSource = (source: unknown, apiName: string): void => {
|
|
118
|
-
if (!isAliasedSource(source)) {
|
|
119
|
-
throw new Error(`${apiName}(...) requires an aliased source`)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const assertMutationTargets = (
|
|
124
|
-
target: unknown,
|
|
125
|
-
apiName: string,
|
|
126
|
-
options: { readonly allowMultiple?: boolean } = {}
|
|
127
|
-
): void => {
|
|
128
|
-
const targets = Array.isArray(target) ? target : [target]
|
|
129
|
-
if (targets.length === 0) {
|
|
130
|
-
throw new Error(`${apiName}(...) requires at least one table target`)
|
|
131
|
-
}
|
|
132
|
-
if (Array.isArray(target) && targets.length === 1) {
|
|
133
|
-
throw new Error(`${apiName}(...) requires a table target, not a single-element target tuple`)
|
|
134
|
-
}
|
|
135
|
-
for (const entry of targets) {
|
|
136
|
-
assertMutationTarget(entry, apiName)
|
|
137
|
-
}
|
|
138
|
-
if (targets.length > 1 && options.allowMultiple !== true) {
|
|
139
|
-
throw new Error(`${apiName}(...) requires a single table target`)
|
|
140
|
-
}
|
|
141
|
-
if (targets.length > 1 && ctx.profile.dialect !== "mysql" && ctx.profile.dialect !== "sqlite") {
|
|
142
|
-
throw new Error(`${apiName}(...) only supports multiple mutation targets for mysql`)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const assertUniqueTargetNames = (targets: readonly { readonly tableName: string }[]): void => {
|
|
147
|
-
const seen = new Set<string>()
|
|
148
|
-
for (const target of targets) {
|
|
149
|
-
if (seen.has(target.tableName)) {
|
|
150
|
-
throw new Error(`mutation target source names must be unique: ${target.tableName}`)
|
|
151
|
-
}
|
|
152
|
-
seen.add(target.tableName)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const assertInsertSelectSource = (sourcePlan: any, selection: Record<string, unknown>): void => {
|
|
157
|
-
const statement = ctx.getQueryState(sourcePlan).statement
|
|
158
|
-
if (statement !== "select" && statement !== "set") {
|
|
159
|
-
throw new Error("insert sources only accept select-like query plans")
|
|
160
|
-
}
|
|
161
|
-
for (const value of Object.values(selection)) {
|
|
162
|
-
if (value === null || typeof value !== "object" || !(Expression.TypeId in value)) {
|
|
163
|
-
throw new Error("insert sources require a flat selection object")
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
37
|
const insert = (target: any, values?: Record<string, unknown>) => {
|
|
169
|
-
assertMutationTargets(target, "insert")
|
|
170
38
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
171
39
|
const assignments = values === undefined
|
|
172
40
|
? []
|
|
@@ -248,7 +116,6 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
248
116
|
|
|
249
117
|
const sourcePlan = source
|
|
250
118
|
const selection = sourcePlan[Plan.TypeId].selection as Record<string, Expression.Any>
|
|
251
|
-
assertInsertSelectSource(sourcePlan, selection)
|
|
252
119
|
const columns = ctx.normalizeInsertSelectColumns(selection)
|
|
253
120
|
return ctx.makePlan({
|
|
254
121
|
selection: current.selection,
|
|
@@ -271,17 +138,15 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
271
138
|
const current = plan[Plan.TypeId]
|
|
272
139
|
const currentAst = ctx.getAst(plan)
|
|
273
140
|
const currentQuery = ctx.getQueryState(plan)
|
|
274
|
-
if (currentQuery.statement !== "insert") {
|
|
275
|
-
throw new Error(`onConflict(...) is not supported for ${currentQuery.statement} statements`)
|
|
276
|
-
}
|
|
277
141
|
const insertTarget = currentAst.into!.source
|
|
278
|
-
const conflictTarget =
|
|
142
|
+
const conflictTarget = expectConflictClause({
|
|
143
|
+
kind: "conflict",
|
|
144
|
+
action: "doNothing",
|
|
145
|
+
target: ctx.buildConflictTarget(insertTarget, target)
|
|
146
|
+
}).target
|
|
279
147
|
const updateAssignments = options.update
|
|
280
148
|
? ctx.buildMutationAssignments(insertTarget, options.update)
|
|
281
149
|
: []
|
|
282
|
-
if (options.update !== undefined && updateAssignments.length === 0) {
|
|
283
|
-
throw new Error("conflict update assignments require at least one assignment")
|
|
284
|
-
}
|
|
285
150
|
const updateWhere = options.where === undefined
|
|
286
151
|
? undefined
|
|
287
152
|
: ctx.toDialectExpression(options.where)
|
|
@@ -311,9 +176,7 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
311
176
|
}
|
|
312
177
|
|
|
313
178
|
const update = (target: any, values: Record<string, unknown>) => {
|
|
314
|
-
assertMutationTargets(target, "update", { allowMultiple: true })
|
|
315
179
|
const targets = ctx.mutationTargetClauses(target)
|
|
316
|
-
assertUniqueTargetNames(targets)
|
|
317
180
|
const primaryTarget = targets[0]!
|
|
318
181
|
const assignments = ctx.buildMutationAssignments(target, values)
|
|
319
182
|
const targetNames = new Set(targets.map((entry: any) => entry.tableName))
|
|
@@ -340,13 +203,9 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
340
203
|
}
|
|
341
204
|
|
|
342
205
|
const upsert = (target: any, values: Record<string, unknown>, conflictColumns: string | readonly string[], updateValues?: Record<string, unknown>) => {
|
|
343
|
-
assertMutationTargets(target, "upsert")
|
|
344
206
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
345
207
|
const assignments = ctx.buildMutationAssignments(target, values)
|
|
346
208
|
const updateAssignments = updateValues ? ctx.buildMutationAssignments(target, updateValues) : []
|
|
347
|
-
if (updateValues !== undefined && updateAssignments.length === 0) {
|
|
348
|
-
throw new Error("upsert update assignments require at least one assignment")
|
|
349
|
-
}
|
|
350
209
|
const required = [
|
|
351
210
|
...assignments.flatMap((entry) => Object.keys(entry.value[Expression.TypeId].dependencies)),
|
|
352
211
|
...updateAssignments.flatMap((entry) => Object.keys(entry.value[Expression.TypeId].dependencies))
|
|
@@ -390,9 +249,7 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
390
249
|
}
|
|
391
250
|
|
|
392
251
|
const delete_ = (target: any) => {
|
|
393
|
-
assertMutationTargets(target, "delete", { allowMultiple: true })
|
|
394
252
|
const targets = ctx.mutationTargetClauses(target)
|
|
395
|
-
assertUniqueTargetNames(targets)
|
|
396
253
|
const primaryTarget = targets[0]!
|
|
397
254
|
return ctx.makePlan({
|
|
398
255
|
selection: {},
|
|
@@ -413,7 +270,8 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
413
270
|
}
|
|
414
271
|
|
|
415
272
|
const truncate = (target: any, options: { readonly restartIdentity?: boolean; readonly cascade?: boolean } = {}) => {
|
|
416
|
-
|
|
273
|
+
const restartIdentity = normalizeStatementFlag(options.restartIdentity)
|
|
274
|
+
const cascade = normalizeStatementFlag(options.cascade)
|
|
417
275
|
const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
|
|
418
276
|
return ctx.makePlan({
|
|
419
277
|
selection: {},
|
|
@@ -431,8 +289,8 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
431
289
|
},
|
|
432
290
|
truncate: {
|
|
433
291
|
kind: "truncate",
|
|
434
|
-
restartIdentity
|
|
435
|
-
cascade
|
|
292
|
+
restartIdentity,
|
|
293
|
+
cascade
|
|
436
294
|
},
|
|
437
295
|
where: [],
|
|
438
296
|
having: [],
|
|
@@ -443,19 +301,11 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
|
|
|
443
301
|
}
|
|
444
302
|
|
|
445
303
|
const merge = (target: any, source: any, on: any, options: any = {}) => {
|
|
446
|
-
assertMutationTargets(target, "merge")
|
|
447
|
-
assertAliasedSource(source, "merge")
|
|
448
304
|
const { sourceName: targetName, sourceBaseName: targetBaseName } = ctx.targetSourceDetails(target)
|
|
449
305
|
const { sourceName: usingName, sourceBaseName: usingBaseName } = ctx.sourceDetails(source)
|
|
450
|
-
if (targetName === usingName) {
|
|
451
|
-
throw new Error(`merge(...) source name must differ from target source name: ${targetName}`)
|
|
452
|
-
}
|
|
453
306
|
const onExpression = ctx.toDialectExpression(on)
|
|
454
307
|
const matched = options.whenMatched
|
|
455
308
|
const notMatched = options.whenNotMatched
|
|
456
|
-
if (matched && "delete" in matched && "update" in matched) {
|
|
457
|
-
throw new Error("merge whenMatched cannot specify both update and delete")
|
|
458
|
-
}
|
|
459
309
|
const matchedPredicate = matched?.predicate ? ctx.toDialectExpression(matched.predicate) : undefined
|
|
460
310
|
const matchedAssignments = matched && "update" in matched && matched.update
|
|
461
311
|
? ctx.buildMutationAssignments(target, matched.update)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as Expression from "./scalar.js"
|
|
2
2
|
import * as Plan from "./row-set.js"
|
|
3
|
-
import * as Table from "./table.js"
|
|
4
3
|
|
|
5
4
|
type DslPlanRuntimeContext = {
|
|
6
5
|
readonly profile: {
|
|
@@ -22,122 +21,28 @@ type DslPlanRuntimeContext = {
|
|
|
22
21
|
readonly attachInsertSource: (plan: any, source: any) => any
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
case "share":
|
|
30
|
-
return "for share"
|
|
31
|
-
}
|
|
32
|
-
throw new Error("lock(...) mode must be update or share for select statements")
|
|
33
|
-
}
|
|
24
|
+
type LockMode = "update" | "share" | "lowPriority" | "ignore" | "quick"
|
|
25
|
+
|
|
26
|
+
export const renderSelectLockMode = (mode: LockMode): string =>
|
|
27
|
+
mode === "update" ? "for update" : "for share"
|
|
34
28
|
|
|
35
29
|
export const renderMysqlMutationLockMode = (
|
|
36
|
-
mode:
|
|
37
|
-
|
|
30
|
+
mode: LockMode,
|
|
31
|
+
_statement: "update" | "delete"
|
|
38
32
|
): string => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return " low_priority"
|
|
42
|
-
case "ignore":
|
|
43
|
-
return " ignore"
|
|
44
|
-
case "quick":
|
|
45
|
-
if (statement === "delete") {
|
|
46
|
-
return " quick"
|
|
47
|
-
}
|
|
48
|
-
break
|
|
33
|
+
if (mode === "lowPriority") {
|
|
34
|
+
return " low_priority"
|
|
49
35
|
}
|
|
50
|
-
|
|
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
|
-
)
|
|
36
|
+
return mode === "ignore" ? " ignore" : " quick"
|
|
55
37
|
}
|
|
56
38
|
|
|
57
39
|
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
40
|
const sourceRequiredList = (source: any): readonly string[] =>
|
|
66
41
|
typeof source === "object" && source !== null && "required" in source
|
|
67
42
|
? ctx.currentRequiredList(source.required)
|
|
68
43
|
: []
|
|
69
44
|
|
|
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
|
-
|
|
136
45
|
const buildSetOperation = (kind: string, all: boolean, left: any, right: any) => {
|
|
137
|
-
assertSetOperandStatement(left)
|
|
138
|
-
assertSetOperandStatement(right)
|
|
139
|
-
assertPlanComplete(left)
|
|
140
|
-
assertPlanComplete(right)
|
|
141
46
|
const leftState = left[Plan.TypeId]
|
|
142
47
|
const leftAst = ctx.getAst(left)
|
|
143
48
|
const basePlan = leftAst.kind === "set"
|
|
@@ -206,17 +111,10 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
206
111
|
return ctx.attachInsertSource(plan, source)
|
|
207
112
|
}
|
|
208
113
|
|
|
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")
|
|
213
|
-
}
|
|
214
|
-
|
|
215
114
|
const sourceLike = source
|
|
216
115
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(sourceLike)
|
|
217
116
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(sourceLike)
|
|
218
117
|
const sourceRequired = sourceRequiredList(sourceLike)
|
|
219
|
-
assertSourceNameAvailable(current.available, sourceName)
|
|
220
118
|
|
|
221
119
|
if (currentQuery.statement === "select") {
|
|
222
120
|
const nextAvailable = {
|
|
@@ -276,7 +174,7 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
276
174
|
}, currentQuery.assumptions, currentQuery.capabilities, currentQuery.statement)
|
|
277
175
|
}
|
|
278
176
|
|
|
279
|
-
|
|
177
|
+
return plan
|
|
280
178
|
}
|
|
281
179
|
|
|
282
180
|
const having = (predicate: any) =>
|
|
@@ -309,16 +207,9 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
309
207
|
const current = plan[Plan.TypeId]
|
|
310
208
|
const currentAst = ctx.getAst(plan)
|
|
311
209
|
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
|
-
}
|
|
316
210
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(table)
|
|
317
211
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(table)
|
|
318
212
|
const sourceRequired = sourceRequiredList(table)
|
|
319
|
-
if (supportsJoinSources(currentQuery.statement)) {
|
|
320
|
-
assertSourceNameAvailable(current.available, sourceName)
|
|
321
|
-
}
|
|
322
213
|
const nextAvailable = {
|
|
323
214
|
...current.available,
|
|
324
215
|
[sourceName]: {
|
|
@@ -353,16 +244,9 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
353
244
|
const currentQuery = ctx.getQueryState(plan)
|
|
354
245
|
const onExpression = ctx.toDialectExpression(on)
|
|
355
246
|
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
|
-
}
|
|
360
247
|
const { sourceName, sourceBaseName } = ctx.sourceDetails(table)
|
|
361
248
|
const presenceWitnesses = ctx.presenceWitnessesOfSourceLike(table)
|
|
362
249
|
const sourceRequired = sourceRequiredList(table)
|
|
363
|
-
if (supportsJoinSources(currentQuery.statement)) {
|
|
364
|
-
assertSourceNameAvailable(current.available, sourceName)
|
|
365
|
-
}
|
|
366
250
|
const baseAvailable = (kind === "right" || kind === "full"
|
|
367
251
|
? Object.fromEntries(
|
|
368
252
|
Object.entries(current.available as Record<string, any>).map(([name, source]) => [name, {
|
|
@@ -408,9 +292,6 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
408
292
|
|
|
409
293
|
const orderBy = (value: any, direction: "asc" | "desc" = "asc") =>
|
|
410
294
|
(plan: any) => {
|
|
411
|
-
if (direction !== "asc" && direction !== "desc") {
|
|
412
|
-
throw new Error("orderBy(...) direction must be asc or desc")
|
|
413
|
-
}
|
|
414
295
|
const current = plan[Plan.TypeId]
|
|
415
296
|
const currentAst = ctx.getAst(plan)
|
|
416
297
|
const currentQuery = ctx.getQueryState(plan)
|
|
@@ -437,15 +318,6 @@ export const makeDslPlanRuntime = (ctx: DslPlanRuntimeContext) => {
|
|
|
437
318
|
const current = plan[Plan.TypeId]
|
|
438
319
|
const currentAst = ctx.getAst(plan)
|
|
439
320
|
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
|
-
}
|
|
449
321
|
return ctx.makePlan({
|
|
450
322
|
selection: current.selection,
|
|
451
323
|
required: current.required,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as Expression from "./scalar.js"
|
|
2
2
|
import * as Plan from "./row-set.js"
|
|
3
|
-
import { flattenSelection } from "./projections.js"
|
|
4
3
|
|
|
5
4
|
type DslQueryRuntimeContext = {
|
|
6
5
|
readonly profile: {
|
|
@@ -20,55 +19,13 @@ type DslQueryRuntimeContext = {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
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
|
-
|
|
53
22
|
const values = (rows: readonly [Record<string, any>, ...Record<string, any>[]]) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
...Record<string, Expression.Any>[]
|
|
60
|
-
]
|
|
23
|
+
const [first, ...rest] = rows
|
|
24
|
+
const normalizedRows = [
|
|
25
|
+
ctx.normalizeValuesRow(first),
|
|
26
|
+
...rest.map((row) => ctx.normalizeValuesRow(row))
|
|
27
|
+
] satisfies readonly [Record<string, Expression.Any>, ...Record<string, Expression.Any>[]]
|
|
61
28
|
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)
|
|
66
|
-
for (const row of normalizedRows) {
|
|
67
|
-
const rowKeys = Object.keys(row)
|
|
68
|
-
if (rowKeys.length !== columnNames.length || !rowKeys.every((key) => columnNameSet.has(key))) {
|
|
69
|
-
throw new Error("values(...) rows must project the same columns")
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
29
|
return Object.assign(Object.create(ctx.ValuesInputProto), {
|
|
73
30
|
kind: "values",
|
|
74
31
|
dialect: ctx.profile.dialect,
|
|
@@ -80,20 +37,6 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
80
37
|
const unnest = (columns: Record<string, readonly any[]>, alias: string) => {
|
|
81
38
|
const normalizedColumns = ctx.normalizeUnnestColumns(columns)
|
|
82
39
|
const columnNames = Object.keys(normalizedColumns)
|
|
83
|
-
if (columnNames.length === 0) {
|
|
84
|
-
throw new Error("unnest(...) requires at least one column array")
|
|
85
|
-
}
|
|
86
|
-
const firstColumn = normalizedColumns[columnNames[0] as keyof typeof normalizedColumns]
|
|
87
|
-
const rowCount = firstColumn?.length ?? 0
|
|
88
|
-
if (rowCount === 0) {
|
|
89
|
-
throw new Error("unnest(...) requires at least one row")
|
|
90
|
-
}
|
|
91
|
-
for (const columnName of columnNames) {
|
|
92
|
-
const values = normalizedColumns[columnName]!
|
|
93
|
-
if (values.length !== rowCount) {
|
|
94
|
-
throw new Error("unnest(...) column arrays must have the same length")
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
40
|
const firstRow = Object.fromEntries(
|
|
98
41
|
columnNames.map((columnName) => [columnName, normalizedColumns[columnName]![0]!])
|
|
99
42
|
) as Record<string, Expression.Any>
|
|
@@ -133,8 +76,6 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
133
76
|
}
|
|
134
77
|
|
|
135
78
|
const select = (selection: any = {}) => {
|
|
136
|
-
assertSelectionObject("select", selection)
|
|
137
|
-
assertSelectionTree("select", selection)
|
|
138
79
|
return ctx.makePlan({
|
|
139
80
|
selection,
|
|
140
81
|
required: ctx.extractRequiredRuntime(selection),
|
|
@@ -153,9 +94,6 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
153
94
|
|
|
154
95
|
const groupBy = (...values: readonly Expression.Any[]) =>
|
|
155
96
|
(plan: any) => {
|
|
156
|
-
if (values.length === 0) {
|
|
157
|
-
throw new Error("groupBy(...) requires at least one expression")
|
|
158
|
-
}
|
|
159
97
|
const current = plan[Plan.TypeId]
|
|
160
98
|
const currentAst = ctx.getAst(plan)
|
|
161
99
|
const currentQuery = ctx.getQueryState(plan)
|
|
@@ -174,22 +112,10 @@ export const makeDslQueryRuntime = (ctx: DslQueryRuntimeContext) => {
|
|
|
174
112
|
}
|
|
175
113
|
|
|
176
114
|
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
115
|
return (plan: any) => {
|
|
183
116
|
const current = plan[Plan.TypeId]
|
|
184
117
|
const currentAst = ctx.getAst(plan)
|
|
185
118
|
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
|
-
}
|
|
193
119
|
return ctx.makePlan({
|
|
194
120
|
selection,
|
|
195
121
|
required: [...ctx.currentRequiredList(current.required), ...ctx.extractRequiredRuntime(selection)].filter((name, index, list) =>
|