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/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts}
RENAMED
|
@@ -1,44 +1,49 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema"
|
|
2
2
|
|
|
3
|
-
import * as Query from "
|
|
4
|
-
import * as Expression from "
|
|
5
|
-
import * as Table from "
|
|
6
|
-
import * as QueryAst from "
|
|
7
|
-
import type
|
|
8
|
-
import * as ExpressionAst from "
|
|
9
|
-
import * as JsonPath from "
|
|
10
|
-
import { renderSelectLockMode } from "
|
|
11
|
-
import { expectConflictClause
|
|
12
|
-
import { expectDdlClauseKind, expectTruncateClause, renderTransactionIsolationLevel } from "
|
|
3
|
+
import * as Query from "../query.js"
|
|
4
|
+
import * as Expression from "../scalar.js"
|
|
5
|
+
import * as Table from "../table.js"
|
|
6
|
+
import * as QueryAst from "../query-ast.js"
|
|
7
|
+
import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
|
|
8
|
+
import * as ExpressionAst from "../expression-ast.js"
|
|
9
|
+
import * as JsonPath from "../json/path.js"
|
|
10
|
+
import { renderSelectLockMode } from "../dsl-plan-runtime.js"
|
|
11
|
+
import { expectConflictClause } from "../dsl-mutation-runtime.js"
|
|
12
|
+
import { expectDdlClauseKind, expectTruncateClause, normalizeStatementFlag, normalizeStatementIdentifier, renderTransactionIsolationLevel } from "../dsl-transaction-ddl-runtime.js"
|
|
13
13
|
import {
|
|
14
14
|
renderJsonSelectSql,
|
|
15
15
|
renderSelectSql,
|
|
16
16
|
toDriverValue
|
|
17
|
-
} from "
|
|
18
|
-
import { normalizeDbValue } from "
|
|
19
|
-
import { flattenSelection, type Projection } from "
|
|
20
|
-
import {
|
|
21
|
-
import * as SchemaExpression from "
|
|
22
|
-
import { renderReferentialAction, type DdlExpressionLike } from "
|
|
17
|
+
} from "../runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../runtime/normalize.js"
|
|
19
|
+
import { flattenSelection, type Projection } from "../projections.js"
|
|
20
|
+
import { groupingKeyOfExpression } from "../grouping-key.js"
|
|
21
|
+
import * as SchemaExpression from "../schema-expression.js"
|
|
22
|
+
import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
|
|
23
|
+
import * as Casing from "../casing.js"
|
|
23
24
|
|
|
24
25
|
const renderDbType = (
|
|
25
26
|
dialect: SqlDialect,
|
|
26
27
|
dbType: Expression.DbType.Any
|
|
27
28
|
): string => {
|
|
28
|
-
if (dialect.name === "
|
|
29
|
-
return "
|
|
29
|
+
if (dialect.name === "postgres" && dbType.kind === "blob") {
|
|
30
|
+
return "bytea"
|
|
30
31
|
}
|
|
31
|
-
return dbType.kind
|
|
32
|
+
return renderDbTypeName(dbType.kind)
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
36
|
+
"element" in dbType
|
|
37
|
+
|
|
34
38
|
const renderCastType = (
|
|
35
39
|
dialect: SqlDialect,
|
|
36
|
-
dbType:
|
|
40
|
+
dbType: unknown
|
|
37
41
|
): string => {
|
|
42
|
+
const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
|
|
38
43
|
if (dialect.name !== "mysql") {
|
|
39
|
-
return
|
|
44
|
+
return renderDbTypeName(kind)
|
|
40
45
|
}
|
|
41
|
-
switch (
|
|
46
|
+
switch (kind) {
|
|
42
47
|
case "text":
|
|
43
48
|
return "char"
|
|
44
49
|
case "uuid":
|
|
@@ -53,7 +58,159 @@ const renderCastType = (
|
|
|
53
58
|
case "json":
|
|
54
59
|
return "json"
|
|
55
60
|
default:
|
|
56
|
-
return
|
|
61
|
+
return renderDbTypeName(kind)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const casingForTable = (
|
|
66
|
+
table: Table.AnyTable,
|
|
67
|
+
state: RenderState
|
|
68
|
+
): Casing.Options | undefined =>
|
|
69
|
+
Casing.merge(state.casing, table[Table.TypeId].casing)
|
|
70
|
+
|
|
71
|
+
const casedTableName = (
|
|
72
|
+
table: Table.AnyTable,
|
|
73
|
+
state: RenderState
|
|
74
|
+
): string => {
|
|
75
|
+
const tableState = table[Table.TypeId]
|
|
76
|
+
return Casing.applyCategory(casingForTable(table, state), "tables", tableState.baseName)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const casedSchemaName = (
|
|
80
|
+
table: Table.AnyTable,
|
|
81
|
+
state: RenderState
|
|
82
|
+
): string | undefined => {
|
|
83
|
+
const schemaName = table[Table.TypeId].schemaName
|
|
84
|
+
return schemaName === undefined
|
|
85
|
+
? undefined
|
|
86
|
+
: Casing.applyCategory(casingForTable(table, state), "schemas", schemaName)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const casedColumnName = (
|
|
90
|
+
columnName: string,
|
|
91
|
+
state: RenderState,
|
|
92
|
+
tableName?: string
|
|
93
|
+
): string => {
|
|
94
|
+
if (tableName !== undefined) {
|
|
95
|
+
const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
|
|
96
|
+
if (mapped !== undefined) {
|
|
97
|
+
return mapped
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return Casing.applyCategory(state.casing, "columns", columnName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const casedTableReferenceName = (
|
|
104
|
+
tableName: string,
|
|
105
|
+
state: RenderState
|
|
106
|
+
): string =>
|
|
107
|
+
state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
|
|
108
|
+
|
|
109
|
+
const quoteColumn = (
|
|
110
|
+
columnName: string,
|
|
111
|
+
state: RenderState,
|
|
112
|
+
dialect: SqlDialect,
|
|
113
|
+
tableName?: string
|
|
114
|
+
): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
|
|
115
|
+
|
|
116
|
+
const stateWithTableCasing = (
|
|
117
|
+
state: RenderState,
|
|
118
|
+
source: unknown
|
|
119
|
+
): RenderState =>
|
|
120
|
+
typeof source === "object" && source !== null && Table.TypeId in source
|
|
121
|
+
? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
|
|
122
|
+
: state
|
|
123
|
+
|
|
124
|
+
const referenceCasing = (
|
|
125
|
+
reference: { readonly casing?: Casing.Options },
|
|
126
|
+
state: RenderState
|
|
127
|
+
): Casing.Options | undefined =>
|
|
128
|
+
Casing.merge(state.casing, reference.casing)
|
|
129
|
+
|
|
130
|
+
const renderReferenceTable = (
|
|
131
|
+
reference: {
|
|
132
|
+
readonly tableName: string
|
|
133
|
+
readonly schemaName?: string
|
|
134
|
+
readonly casing?: Casing.Options
|
|
135
|
+
},
|
|
136
|
+
state: RenderState,
|
|
137
|
+
dialect: SqlDialect
|
|
138
|
+
): string => {
|
|
139
|
+
const casing = referenceCasing(reference, state)
|
|
140
|
+
const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
|
|
141
|
+
const schemaName = reference.schemaName === undefined
|
|
142
|
+
? undefined
|
|
143
|
+
: Casing.applyCategory(casing, "schemas", reference.schemaName)
|
|
144
|
+
return dialect.renderTableReference(tableName, tableName, schemaName)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const quoteReferenceColumn = (
|
|
148
|
+
columnName: string,
|
|
149
|
+
reference: { readonly casing?: Casing.Options },
|
|
150
|
+
state: RenderState,
|
|
151
|
+
dialect: SqlDialect
|
|
152
|
+
): string =>
|
|
153
|
+
dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
|
|
154
|
+
|
|
155
|
+
const registerSourceReference = (
|
|
156
|
+
source: unknown,
|
|
157
|
+
tableName: string,
|
|
158
|
+
state: RenderState
|
|
159
|
+
): void => {
|
|
160
|
+
if (typeof source !== "object" || source === null) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (Table.TypeId in source) {
|
|
164
|
+
const table = source as Table.AnyTable
|
|
165
|
+
const tableState = table[Table.TypeId]
|
|
166
|
+
const casing = casingForTable(table, state)
|
|
167
|
+
const renderedTableName = tableState.kind === "alias"
|
|
168
|
+
? tableName
|
|
169
|
+
: Casing.applyCategory(casing, "tables", tableState.baseName)
|
|
170
|
+
const columns = new Map(
|
|
171
|
+
Object.keys(tableState.fields).map((columnName) => [
|
|
172
|
+
columnName,
|
|
173
|
+
Casing.applyCategory(casing, "columns", columnName)
|
|
174
|
+
] as const)
|
|
175
|
+
)
|
|
176
|
+
state.sourceNames?.set(tableName, {
|
|
177
|
+
tableName: renderedTableName,
|
|
178
|
+
columns
|
|
179
|
+
})
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
|
|
183
|
+
state.sourceNames?.set(tableName, {
|
|
184
|
+
tableName,
|
|
185
|
+
columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const registerQuerySources = (
|
|
191
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
192
|
+
state: RenderState
|
|
193
|
+
): void => {
|
|
194
|
+
if (ast.from !== undefined) {
|
|
195
|
+
registerSourceReference(ast.from.source, ast.from.tableName, state)
|
|
196
|
+
}
|
|
197
|
+
for (const source of ast.fromSources ?? []) {
|
|
198
|
+
registerSourceReference(source.source, source.tableName, state)
|
|
199
|
+
}
|
|
200
|
+
for (const join of ast.joins) {
|
|
201
|
+
registerSourceReference(join.source, join.tableName, state)
|
|
202
|
+
}
|
|
203
|
+
if (ast.into !== undefined) {
|
|
204
|
+
registerSourceReference(ast.into.source, ast.into.tableName, state)
|
|
205
|
+
}
|
|
206
|
+
if (ast.target !== undefined) {
|
|
207
|
+
registerSourceReference(ast.target.source, ast.target.tableName, state)
|
|
208
|
+
}
|
|
209
|
+
for (const target of ast.targets ?? []) {
|
|
210
|
+
registerSourceReference(target.source, target.tableName, state)
|
|
211
|
+
}
|
|
212
|
+
if (ast.using !== undefined) {
|
|
213
|
+
registerSourceReference(ast.using.source, ast.using.tableName, state)
|
|
57
214
|
}
|
|
58
215
|
}
|
|
59
216
|
|
|
@@ -115,18 +272,29 @@ const renderColumnDefinition = (
|
|
|
115
272
|
dialect: SqlDialect,
|
|
116
273
|
state: RenderState,
|
|
117
274
|
columnName: string,
|
|
118
|
-
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
275
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
276
|
+
tableName?: string,
|
|
277
|
+
casing?: Casing.Options
|
|
119
278
|
): string => {
|
|
279
|
+
const expressionState = { ...state, casing, rowLocalColumns: true }
|
|
280
|
+
if (dialect.name !== "postgres" && isArrayDbType(column.metadata.dbType)) {
|
|
281
|
+
throw new Error(`Unsupported ${dialect.name} array column options`)
|
|
282
|
+
}
|
|
120
283
|
const clauses = [
|
|
121
|
-
|
|
122
|
-
column.metadata.ddlType
|
|
284
|
+
quoteColumn(columnName, state, dialect, tableName),
|
|
285
|
+
column.metadata.ddlType === undefined
|
|
286
|
+
? renderDbType(dialect, column.metadata.dbType)
|
|
287
|
+
: renderDbTypeName(column.metadata.ddlType)
|
|
123
288
|
]
|
|
124
289
|
if (column.metadata.identity) {
|
|
290
|
+
if (dialect.name !== "postgres") {
|
|
291
|
+
throw new Error(`Unsupported ${dialect.name} identity column options`)
|
|
292
|
+
}
|
|
125
293
|
clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
|
|
126
294
|
} else if (column.metadata.generatedValue) {
|
|
127
|
-
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue,
|
|
295
|
+
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
|
|
128
296
|
} else if (column.metadata.defaultValue) {
|
|
129
|
-
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue,
|
|
297
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
|
|
130
298
|
}
|
|
131
299
|
if (!column.metadata.nullable) {
|
|
132
300
|
clauses.push("not null")
|
|
@@ -138,31 +306,56 @@ const renderCreateTableSql = (
|
|
|
138
306
|
targetSource: QueryAst.FromClause,
|
|
139
307
|
state: RenderState,
|
|
140
308
|
dialect: SqlDialect,
|
|
141
|
-
ifNotExists:
|
|
309
|
+
ifNotExists: unknown
|
|
142
310
|
): string => {
|
|
311
|
+
const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
|
|
312
|
+
if (dialect.name !== "postgres" && normalizedIfNotExists) {
|
|
313
|
+
throw new Error(`Unsupported ${dialect.name} create table options`)
|
|
314
|
+
}
|
|
143
315
|
const table = targetSource.source as Table.AnyTable
|
|
316
|
+
const tableCasing = casingForTable(table, state)
|
|
144
317
|
const fields = table[Table.TypeId].fields
|
|
145
318
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
146
|
-
renderColumnDefinition(dialect, state, columnName, column)
|
|
319
|
+
renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
|
|
147
320
|
)
|
|
148
|
-
|
|
321
|
+
const options = table[Table.OptionsSymbol] as unknown
|
|
322
|
+
const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
|
|
323
|
+
validateOptions(table[Table.TypeId].name, fields, tableOptions)
|
|
324
|
+
for (const option of tableOptions) {
|
|
325
|
+
if (typeof option !== "object" || option === null || !("kind" in option)) {
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
149
328
|
switch (option.kind) {
|
|
150
329
|
case "primaryKey":
|
|
151
|
-
|
|
330
|
+
if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
|
|
331
|
+
throw new Error(`Unsupported ${dialect.name} primary key constraint options`)
|
|
332
|
+
}
|
|
333
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}primary key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
152
334
|
break
|
|
153
335
|
case "unique":
|
|
154
|
-
|
|
336
|
+
if (dialect.name !== "postgres" && (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred)) {
|
|
337
|
+
throw new Error(`Unsupported ${dialect.name} unique constraint options`)
|
|
338
|
+
}
|
|
339
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
155
340
|
break
|
|
156
341
|
case "foreignKey": {
|
|
157
|
-
|
|
342
|
+
if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
|
|
343
|
+
throw new Error(`Unsupported ${dialect.name} foreign key constraint options`)
|
|
344
|
+
}
|
|
345
|
+
const reference = typeof option.references === "function"
|
|
346
|
+
? option.references()
|
|
347
|
+
: option.references
|
|
158
348
|
definitions.push(
|
|
159
|
-
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.
|
|
349
|
+
`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}foreign key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")}) references ${renderReferenceTable(reference, state, dialect)} (${reference.columns.map((column) => quoteReferenceColumn(column, reference, state, dialect)).join(", ")})${option.onDelete !== undefined ? ` on delete ${renderReferentialAction(option.onDelete)}` : ""}${option.onUpdate !== undefined ? ` on update ${renderReferentialAction(option.onUpdate)}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
|
|
160
350
|
)
|
|
161
351
|
break
|
|
162
352
|
}
|
|
163
353
|
case "check":
|
|
354
|
+
if (dialect.name !== "postgres" && option.noInherit) {
|
|
355
|
+
throw new Error(`Unsupported ${dialect.name} check constraint options`)
|
|
356
|
+
}
|
|
164
357
|
definitions.push(
|
|
165
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
358
|
+
`constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
166
359
|
)
|
|
167
360
|
break
|
|
168
361
|
case "index":
|
|
@@ -171,7 +364,7 @@ const renderCreateTableSql = (
|
|
|
171
364
|
throw new Error("Unsupported table option kind")
|
|
172
365
|
}
|
|
173
366
|
}
|
|
174
|
-
return `create table${
|
|
367
|
+
return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
175
368
|
}
|
|
176
369
|
|
|
177
370
|
const renderCreateIndexSql = (
|
|
@@ -180,8 +373,16 @@ const renderCreateIndexSql = (
|
|
|
180
373
|
state: RenderState,
|
|
181
374
|
dialect: SqlDialect
|
|
182
375
|
): string => {
|
|
183
|
-
const
|
|
184
|
-
|
|
376
|
+
const unique = normalizeStatementFlag(ddl.unique)
|
|
377
|
+
const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
|
|
378
|
+
const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
|
|
379
|
+
if (dialect.name !== "postgres" && ifNotExists) {
|
|
380
|
+
throw new Error(`Unsupported ${dialect.name} create index options`)
|
|
381
|
+
}
|
|
382
|
+
const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
|
|
383
|
+
const table = targetSource.source as Table.AnyTable
|
|
384
|
+
const tableCasing = casingForTable(table, state)
|
|
385
|
+
return `create${unique ? " unique" : ""} index${maybeIfNotExists} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${ddl.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})`
|
|
185
386
|
}
|
|
186
387
|
|
|
187
388
|
const renderDropIndexSql = (
|
|
@@ -190,18 +391,29 @@ const renderDropIndexSql = (
|
|
|
190
391
|
state: RenderState,
|
|
191
392
|
dialect: SqlDialect
|
|
192
393
|
): string => {
|
|
394
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
395
|
+
const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
|
|
396
|
+
if (dialect.name !== "postgres" && ifExists) {
|
|
397
|
+
throw new Error(`Unsupported ${dialect.name} drop index options`)
|
|
398
|
+
}
|
|
193
399
|
if (dialect.name === "postgres") {
|
|
194
|
-
const
|
|
400
|
+
const table = typeof targetSource.source === "object" &&
|
|
195
401
|
targetSource.source !== null &&
|
|
196
402
|
Table.TypeId in targetSource.source
|
|
197
|
-
?
|
|
403
|
+
? targetSource.source as Table.AnyTable
|
|
198
404
|
: undefined
|
|
405
|
+
const schemaName = table?.[Table.TypeId].schemaName
|
|
406
|
+
const tableCasing = table === undefined ? state.casing : casingForTable(table, state)
|
|
407
|
+
const renderedSchemaName = table === undefined ? schemaName : casedSchemaName(table, state)
|
|
408
|
+
const renderedIndexName = Casing.applyCategory(tableCasing, "indexes", name)
|
|
199
409
|
const indexName = schemaName === undefined || schemaName === "public"
|
|
200
|
-
? dialect.quoteIdentifier(
|
|
201
|
-
: `${dialect.quoteIdentifier(schemaName)}.${dialect.quoteIdentifier(
|
|
202
|
-
return `drop index${
|
|
410
|
+
? dialect.quoteIdentifier(renderedIndexName)
|
|
411
|
+
: `${dialect.quoteIdentifier(renderedSchemaName ?? schemaName)}.${dialect.quoteIdentifier(renderedIndexName)}`
|
|
412
|
+
return `drop index${ifExists ? " if exists" : ""} ${indexName}`
|
|
203
413
|
}
|
|
204
|
-
|
|
414
|
+
const table = targetSource.source as Table.AnyTable
|
|
415
|
+
const tableCasing = casingForTable(table, state)
|
|
416
|
+
return `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
205
417
|
}
|
|
206
418
|
|
|
207
419
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
@@ -221,6 +433,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
|
|
|
221
433
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
222
434
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
223
435
|
|
|
436
|
+
const expectValueExpression = (
|
|
437
|
+
_functionName: string,
|
|
438
|
+
value: unknown
|
|
439
|
+
): Expression.Any => value as Expression.Any
|
|
440
|
+
|
|
441
|
+
const expectBinaryExpressions = (
|
|
442
|
+
_functionName: string,
|
|
443
|
+
left: unknown,
|
|
444
|
+
right: unknown
|
|
445
|
+
): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
|
|
446
|
+
|
|
447
|
+
const renderBinaryExpression = (
|
|
448
|
+
functionName: string,
|
|
449
|
+
operator: string,
|
|
450
|
+
left: unknown,
|
|
451
|
+
right: unknown,
|
|
452
|
+
state: RenderState,
|
|
453
|
+
dialect: SqlDialect
|
|
454
|
+
): string => {
|
|
455
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
|
|
456
|
+
return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
|
|
457
|
+
}
|
|
458
|
+
|
|
224
459
|
const postgresRangeSubtypeByKind: Readonly<Record<string, string>> = {
|
|
225
460
|
int4range: "int4",
|
|
226
461
|
int8range: "int8",
|
|
@@ -280,13 +515,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
|
|
|
280
515
|
const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
|
|
281
516
|
value !== null && typeof value === "object" && JsonPath.TypeId in value
|
|
282
517
|
|
|
518
|
+
const isOptionalJsonPathNumber = (value: unknown): boolean =>
|
|
519
|
+
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
520
|
+
|
|
521
|
+
const isJsonPathSegment = (segment: unknown): boolean => {
|
|
522
|
+
if (typeof segment === "string") {
|
|
523
|
+
return true
|
|
524
|
+
}
|
|
525
|
+
if (typeof segment === "number") {
|
|
526
|
+
return Number.isFinite(segment)
|
|
527
|
+
}
|
|
528
|
+
if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
|
|
529
|
+
return false
|
|
530
|
+
}
|
|
531
|
+
switch ((segment as { readonly kind?: unknown }).kind) {
|
|
532
|
+
case "key":
|
|
533
|
+
return typeof (segment as { readonly key?: unknown }).key === "string"
|
|
534
|
+
case "index": {
|
|
535
|
+
const index = (segment as { readonly index?: unknown }).index
|
|
536
|
+
return typeof index === "number" && Number.isFinite(index)
|
|
537
|
+
}
|
|
538
|
+
case "wildcard":
|
|
539
|
+
case "descend":
|
|
540
|
+
return true
|
|
541
|
+
case "slice":
|
|
542
|
+
return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
|
|
543
|
+
isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
|
|
544
|
+
default:
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
|
|
550
|
+
if (!Array.isArray(segments)) {
|
|
551
|
+
throw new Error("JSON path expressions require a segment array")
|
|
552
|
+
}
|
|
553
|
+
if (segments.some((segment) => !isJsonPathSegment(segment))) {
|
|
554
|
+
throw new Error("JSON path segments require string, number, or path segment objects")
|
|
555
|
+
}
|
|
556
|
+
return segments as ReadonlyArray<JsonPath.AnySegment>
|
|
557
|
+
}
|
|
558
|
+
|
|
283
559
|
const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
|
|
284
560
|
const path = node.path ?? node.segments ?? node.keys
|
|
285
561
|
if (isJsonPathValue(path)) {
|
|
286
|
-
return path.segments
|
|
562
|
+
return validateJsonPathSegments(path.segments)
|
|
287
563
|
}
|
|
288
564
|
if (Array.isArray(path)) {
|
|
289
|
-
return path
|
|
565
|
+
return validateJsonPathSegments(path)
|
|
566
|
+
}
|
|
567
|
+
if (node.segments !== undefined) {
|
|
568
|
+
return validateJsonPathSegments(node.segments)
|
|
290
569
|
}
|
|
291
570
|
if ("key" in node) {
|
|
292
571
|
return [JsonPath.key(String(node.key))]
|
|
@@ -305,11 +584,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
|
|
|
305
584
|
return []
|
|
306
585
|
}
|
|
307
586
|
if ("right" in node && isJsonPathValue(node.right)) {
|
|
308
|
-
return node.right.segments
|
|
587
|
+
return validateJsonPathSegments(node.right.segments)
|
|
309
588
|
}
|
|
310
589
|
return []
|
|
311
590
|
}
|
|
312
591
|
|
|
592
|
+
const extractJsonKeys = (
|
|
593
|
+
node: Record<string, unknown>,
|
|
594
|
+
segments: ReadonlyArray<JsonPath.AnySegment>
|
|
595
|
+
): readonly unknown[] =>
|
|
596
|
+
Array.isArray(node.keys)
|
|
597
|
+
? node.keys
|
|
598
|
+
: segments.map((segment) =>
|
|
599
|
+
typeof segment === "object" && segment !== null && segment.kind === "key"
|
|
600
|
+
? segment.key
|
|
601
|
+
: segment
|
|
602
|
+
)
|
|
603
|
+
|
|
313
604
|
const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
314
605
|
node.newValue ?? node.insert ?? node.right
|
|
315
606
|
|
|
@@ -479,52 +770,64 @@ const renderJsonOpaquePath = (
|
|
|
479
770
|
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments), state)
|
|
480
771
|
}
|
|
481
772
|
if (typeof value === "string") {
|
|
773
|
+
if (value.trim().length === 0) {
|
|
774
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
775
|
+
}
|
|
482
776
|
return dialect.renderLiteral(value, state)
|
|
483
777
|
}
|
|
484
778
|
if (isExpression(value)) {
|
|
779
|
+
const ast = (value as Expression.Any & {
|
|
780
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
781
|
+
})[ExpressionAst.TypeId]
|
|
782
|
+
if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
|
|
783
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
784
|
+
}
|
|
485
785
|
return renderExpression(value, state, dialect)
|
|
486
786
|
}
|
|
487
787
|
throw new Error("Unsupported SQL/JSON path input")
|
|
488
788
|
}
|
|
489
789
|
|
|
790
|
+
const renderFunctionName = (name: unknown): string => {
|
|
791
|
+
return name as string
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const renderExtractField = (field: Expression.Any): string => {
|
|
795
|
+
const ast = (field as Expression.Any & {
|
|
796
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
797
|
+
})[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
|
|
798
|
+
return ast.value
|
|
799
|
+
}
|
|
800
|
+
|
|
490
801
|
const renderFunctionCall = (
|
|
491
|
-
name:
|
|
492
|
-
args:
|
|
802
|
+
name: unknown,
|
|
803
|
+
args: unknown,
|
|
493
804
|
state: RenderState,
|
|
494
805
|
dialect: SqlDialect
|
|
495
806
|
): string => {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
? field[Expression.TypeId].runtime
|
|
510
|
-
: undefined
|
|
511
|
-
const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
|
|
512
|
-
return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
|
|
513
|
-
}
|
|
514
|
-
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
515
|
-
if (args.length === 0) {
|
|
516
|
-
switch (name) {
|
|
807
|
+
const functionName = renderFunctionName(name)
|
|
808
|
+
const functionArgs = args as readonly Expression.Any[]
|
|
809
|
+
if (functionName === "array") {
|
|
810
|
+
return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
|
|
811
|
+
}
|
|
812
|
+
if (functionName === "extract") {
|
|
813
|
+
const field = functionArgs[0]!
|
|
814
|
+
const source = functionArgs[1]!
|
|
815
|
+
return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
|
|
816
|
+
}
|
|
817
|
+
const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
818
|
+
if (functionArgs.length === 0) {
|
|
819
|
+
switch (functionName) {
|
|
517
820
|
case "current_date":
|
|
518
821
|
case "current_time":
|
|
519
822
|
case "current_timestamp":
|
|
520
823
|
case "localtime":
|
|
521
824
|
case "localtimestamp":
|
|
522
|
-
return
|
|
825
|
+
return functionName
|
|
523
826
|
default:
|
|
524
|
-
return `${
|
|
827
|
+
return `${functionName}()`
|
|
525
828
|
}
|
|
526
829
|
}
|
|
527
|
-
return `${
|
|
830
|
+
return `${functionName}(${renderedArgs})`
|
|
528
831
|
}
|
|
529
832
|
|
|
530
833
|
const renderJsonExpression = (
|
|
@@ -588,22 +891,26 @@ const renderJsonExpression = (
|
|
|
588
891
|
const baseSql = dialect.name === "postgres"
|
|
589
892
|
? renderPostgresJsonValue(base, state, dialect)
|
|
590
893
|
: renderExpression(base, state, dialect)
|
|
591
|
-
const keys = segments
|
|
894
|
+
const keys = extractJsonKeys(ast, segments)
|
|
592
895
|
if (keys.length === 0) {
|
|
593
896
|
return undefined
|
|
594
897
|
}
|
|
898
|
+
if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
|
|
899
|
+
throw new Error("json key predicates require string keys")
|
|
900
|
+
}
|
|
901
|
+
const keyNames = keys as readonly string[]
|
|
595
902
|
if (dialect.name === "postgres") {
|
|
596
903
|
if (kind === "jsonHasAnyKeys") {
|
|
597
|
-
return `(${baseSql} ?| array[${
|
|
904
|
+
return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
598
905
|
}
|
|
599
906
|
if (kind === "jsonHasAllKeys") {
|
|
600
|
-
return `(${baseSql} ?& array[${
|
|
907
|
+
return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
601
908
|
}
|
|
602
|
-
return `(${baseSql} ? ${renderPostgresTextLiteral(
|
|
909
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
|
|
603
910
|
}
|
|
604
911
|
if (dialect.name === "mysql") {
|
|
605
912
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
606
|
-
const paths =
|
|
913
|
+
const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
|
|
607
914
|
return `json_contains_path(${baseSql}, ${dialect.renderLiteral(mode, state)}, ${paths})`
|
|
608
915
|
}
|
|
609
916
|
return undefined
|
|
@@ -622,9 +929,7 @@ const renderJsonExpression = (
|
|
|
622
929
|
return undefined
|
|
623
930
|
}
|
|
624
931
|
case "jsonBuildObject": {
|
|
625
|
-
const entries =
|
|
626
|
-
? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
627
|
-
: []
|
|
932
|
+
const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
628
933
|
const renderedEntries = entries.flatMap((entry) => [
|
|
629
934
|
dialect.renderLiteral(entry.key, state),
|
|
630
935
|
renderJsonInputExpression(entry.value, state, dialect)
|
|
@@ -638,9 +943,7 @@ const renderJsonExpression = (
|
|
|
638
943
|
return undefined
|
|
639
944
|
}
|
|
640
945
|
case "jsonBuildArray": {
|
|
641
|
-
const values =
|
|
642
|
-
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
643
|
-
: []
|
|
946
|
+
const values = (ast as { readonly values: readonly Expression.Any[] }).values
|
|
644
947
|
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
645
948
|
if (dialect.name === "postgres") {
|
|
646
949
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
@@ -818,11 +1121,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
|
|
|
818
1121
|
const renderMutationAssignment = (
|
|
819
1122
|
entry: QueryAst.AssignmentClause,
|
|
820
1123
|
state: RenderState,
|
|
821
|
-
dialect: SqlDialect
|
|
1124
|
+
dialect: SqlDialect,
|
|
1125
|
+
targetTableName?: string
|
|
822
1126
|
): string => {
|
|
823
1127
|
const column = entry.tableName && dialect.name === "mysql"
|
|
824
|
-
? `${dialect.quoteIdentifier(entry.tableName)}.${
|
|
825
|
-
:
|
|
1128
|
+
? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
|
|
1129
|
+
: quoteColumn(entry.columnName, state, dialect, targetTableName)
|
|
826
1130
|
return `${column} = ${renderExpression(entry.value, state, dialect)}`
|
|
827
1131
|
}
|
|
828
1132
|
|
|
@@ -858,15 +1162,6 @@ const renderDeleteTargets = (
|
|
|
858
1162
|
dialect: SqlDialect
|
|
859
1163
|
): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
|
|
860
1164
|
|
|
861
|
-
const assertMergeActionKind = (
|
|
862
|
-
kind: unknown,
|
|
863
|
-
allowed: readonly string[]
|
|
864
|
-
): void => {
|
|
865
|
-
if (typeof kind !== "string" || !allowed.includes(kind)) {
|
|
866
|
-
throw new Error("Unsupported merge action kind")
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
1165
|
const renderMysqlMutationLock = (
|
|
871
1166
|
lock: QueryAst.LockClause | undefined,
|
|
872
1167
|
statement: "update" | "delete"
|
|
@@ -897,7 +1192,7 @@ const renderTransactionClause = (
|
|
|
897
1192
|
if (isolationLevel) {
|
|
898
1193
|
modes.push(isolationLevel)
|
|
899
1194
|
}
|
|
900
|
-
if (clause.readOnly
|
|
1195
|
+
if (normalizeStatementFlag(clause.readOnly)) {
|
|
901
1196
|
modes.push("read only")
|
|
902
1197
|
}
|
|
903
1198
|
return modes.length > 0
|
|
@@ -909,28 +1204,21 @@ const renderTransactionClause = (
|
|
|
909
1204
|
case "rollback":
|
|
910
1205
|
return "rollback"
|
|
911
1206
|
case "savepoint":
|
|
912
|
-
return `savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1207
|
+
return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
|
|
913
1208
|
case "rollbackTo":
|
|
914
|
-
return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1209
|
+
return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
|
|
915
1210
|
case "releaseSavepoint":
|
|
916
|
-
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1211
|
+
return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
|
|
917
1212
|
}
|
|
918
|
-
|
|
1213
|
+
return "start transaction"
|
|
919
1214
|
}
|
|
920
1215
|
|
|
921
1216
|
const renderSelectionList = (
|
|
922
1217
|
selection: Record<string, unknown>,
|
|
923
1218
|
state: RenderState,
|
|
924
|
-
dialect: SqlDialect
|
|
925
|
-
validateAggregation: boolean
|
|
1219
|
+
dialect: SqlDialect
|
|
926
1220
|
): RenderedQueryAst => {
|
|
927
|
-
if (validateAggregation) {
|
|
928
|
-
validateAggregationSelection(selection as SelectionValue, [])
|
|
929
|
-
}
|
|
930
1221
|
const flattened = flattenSelection(selection)
|
|
931
|
-
if (dialect.name === "mysql" && flattened.length === 0) {
|
|
932
|
-
throw new Error("mysql select statements require at least one selected expression")
|
|
933
|
-
}
|
|
934
1222
|
const projections = selectionProjections(selection)
|
|
935
1223
|
const sql = flattened.map(({ expression, alias }) =>
|
|
936
1224
|
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
@@ -943,94 +1231,40 @@ const renderSelectionList = (
|
|
|
943
1231
|
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
944
1232
|
params: state.params,
|
|
945
1233
|
valueMappings: state.valueMappings,
|
|
1234
|
+
casing: state.casing,
|
|
946
1235
|
ctes: [],
|
|
947
1236
|
cteNames: new Set(state.cteNames),
|
|
948
|
-
cteSources: new Map(state.cteSources)
|
|
1237
|
+
cteSources: new Map(state.cteSources),
|
|
1238
|
+
sourceNames: new Map(state.sourceNames)
|
|
949
1239
|
})
|
|
950
1240
|
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
): void => {
|
|
955
|
-
const leftKeys = left.map((projection) => JSON.stringify(projection.path))
|
|
956
|
-
const rightKeys = right.map((projection) => JSON.stringify(projection.path))
|
|
957
|
-
if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
|
|
958
|
-
throw new Error("set operator operands must have matching result rows")
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const assertNoGroupedMutationClauses = (
|
|
963
|
-
ast: Pick<QueryAst.Ast, "groupBy" | "having">,
|
|
964
|
-
statement: string
|
|
1241
|
+
const assertSupportedMutationReturning = (
|
|
1242
|
+
dialect: SqlDialect,
|
|
1243
|
+
selection: Record<string, unknown>
|
|
965
1244
|
): void => {
|
|
966
|
-
if (
|
|
967
|
-
throw new Error(
|
|
968
|
-
}
|
|
969
|
-
if (ast.having.length > 0) {
|
|
970
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1245
|
+
if (dialect.name === "standard" && Object.keys(selection).length > 0) {
|
|
1246
|
+
throw new Error("Unsupported standard returning")
|
|
971
1247
|
}
|
|
972
1248
|
}
|
|
973
1249
|
|
|
974
|
-
const
|
|
975
|
-
|
|
1250
|
+
const validateDistinctOnOrdering = (
|
|
1251
|
+
distinctOn: readonly Expression.Any[] | undefined,
|
|
1252
|
+
orderBy: readonly QueryAst.OrderByClause[]
|
|
976
1253
|
): void => {
|
|
977
|
-
if (
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
if (ast.lock) {
|
|
993
|
-
throw new Error("lock(...) is not supported for insert statements")
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
const assertNoStatementQueryClauses = (
|
|
998
|
-
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
999
|
-
statement: string,
|
|
1000
|
-
options: { readonly allowSelection?: boolean } = {}
|
|
1001
|
-
): void => {
|
|
1002
|
-
if (ast.distinct) {
|
|
1003
|
-
throw new Error(`distinct(...) is not supported for ${statement} statements`)
|
|
1004
|
-
}
|
|
1005
|
-
if (ast.where.length > 0) {
|
|
1006
|
-
throw new Error(`where(...) is not supported for ${statement} statements`)
|
|
1007
|
-
}
|
|
1008
|
-
if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
|
|
1009
|
-
throw new Error(`from(...) is not supported for ${statement} statements`)
|
|
1010
|
-
}
|
|
1011
|
-
if (ast.joins.length > 0) {
|
|
1012
|
-
throw new Error(`join(...) is not supported for ${statement} statements`)
|
|
1013
|
-
}
|
|
1014
|
-
if (ast.groupBy.length > 0) {
|
|
1015
|
-
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
1016
|
-
}
|
|
1017
|
-
if (ast.having.length > 0) {
|
|
1018
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1019
|
-
}
|
|
1020
|
-
if (ast.orderBy.length > 0) {
|
|
1021
|
-
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
1022
|
-
}
|
|
1023
|
-
if (ast.limit) {
|
|
1024
|
-
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
1025
|
-
}
|
|
1026
|
-
if (ast.offset) {
|
|
1027
|
-
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
1028
|
-
}
|
|
1029
|
-
if (ast.lock) {
|
|
1030
|
-
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
1031
|
-
}
|
|
1032
|
-
if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
|
|
1033
|
-
throw new Error(`returning(...) is not supported for ${statement} statements`)
|
|
1254
|
+
if (distinctOn === undefined || distinctOn.length === 0 || orderBy.length === 0) {
|
|
1255
|
+
return
|
|
1256
|
+
}
|
|
1257
|
+
const remainingDistinctKeys = new Set(distinctOn.map(groupingKeyOfExpression))
|
|
1258
|
+
for (const order of orderBy) {
|
|
1259
|
+
const key = groupingKeyOfExpression(order.value)
|
|
1260
|
+
if (remainingDistinctKeys.has(key)) {
|
|
1261
|
+
remainingDistinctKeys.delete(key)
|
|
1262
|
+
continue
|
|
1263
|
+
}
|
|
1264
|
+
if (remainingDistinctKeys.size > 0) {
|
|
1265
|
+
throw new Error("distinctOn(...) expressions must match the leftmost orderBy(...) expressions")
|
|
1266
|
+
}
|
|
1267
|
+
return
|
|
1034
1268
|
}
|
|
1035
1269
|
}
|
|
1036
1270
|
|
|
@@ -1040,13 +1274,14 @@ export const renderQueryAst = (
|
|
|
1040
1274
|
dialect: SqlDialect,
|
|
1041
1275
|
options: { readonly emitCtes?: boolean } = {}
|
|
1042
1276
|
): RenderedQueryAst => {
|
|
1277
|
+
registerQuerySources(ast, state)
|
|
1043
1278
|
let sql = ""
|
|
1044
1279
|
let projections: readonly Projection[] = []
|
|
1045
1280
|
|
|
1046
1281
|
switch (ast.kind) {
|
|
1047
1282
|
case "select": {
|
|
1048
|
-
|
|
1049
|
-
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect
|
|
1283
|
+
validateDistinctOnOrdering(ast.distinctOn, ast.orderBy)
|
|
1284
|
+
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
|
|
1050
1285
|
projections = rendered.projections
|
|
1051
1286
|
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
1052
1287
|
const clauses = [
|
|
@@ -1058,6 +1293,9 @@ export const renderQueryAst = (
|
|
|
1058
1293
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
1059
1294
|
}
|
|
1060
1295
|
for (const join of ast.joins) {
|
|
1296
|
+
if (dialect.name === "standard" && join.kind === "full") {
|
|
1297
|
+
throw new Error("Unsupported standard full join")
|
|
1298
|
+
}
|
|
1061
1299
|
const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
|
|
1062
1300
|
clauses.push(
|
|
1063
1301
|
join.kind === "cross"
|
|
@@ -1084,8 +1322,8 @@ export const renderQueryAst = (
|
|
|
1084
1322
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
1085
1323
|
}
|
|
1086
1324
|
if (ast.lock) {
|
|
1087
|
-
if (
|
|
1088
|
-
throw new Error("
|
|
1325
|
+
if (dialect.name === "standard") {
|
|
1326
|
+
throw new Error("Unsupported standard row locking")
|
|
1089
1327
|
}
|
|
1090
1328
|
clauses.push(
|
|
1091
1329
|
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
@@ -1096,7 +1334,6 @@ export const renderQueryAst = (
|
|
|
1096
1334
|
}
|
|
1097
1335
|
case "set": {
|
|
1098
1336
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1099
|
-
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
1100
1337
|
const base = renderQueryAst(
|
|
1101
1338
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
1102
1339
|
Record<string, unknown>,
|
|
@@ -1107,7 +1344,6 @@ export const renderQueryAst = (
|
|
|
1107
1344
|
dialect
|
|
1108
1345
|
)
|
|
1109
1346
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1110
|
-
assertMatchingSetProjections(projections, base.projections)
|
|
1111
1347
|
sql = [
|
|
1112
1348
|
`(${base.sql})`,
|
|
1113
1349
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -1120,7 +1356,9 @@ export const renderQueryAst = (
|
|
|
1120
1356
|
state,
|
|
1121
1357
|
dialect
|
|
1122
1358
|
)
|
|
1123
|
-
|
|
1359
|
+
if (dialect.name === "standard" && entry.all && entry.kind !== "union") {
|
|
1360
|
+
throw new Error("Unsupported standard set operator all variant")
|
|
1361
|
+
}
|
|
1124
1362
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
1125
1363
|
})
|
|
1126
1364
|
].join(" ")
|
|
@@ -1128,24 +1366,20 @@ export const renderQueryAst = (
|
|
|
1128
1366
|
}
|
|
1129
1367
|
case "insert": {
|
|
1130
1368
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
1131
|
-
if (insertAst.distinct) {
|
|
1132
|
-
throw new Error("distinct(...) is not supported for insert statements")
|
|
1133
|
-
}
|
|
1134
|
-
assertNoGroupedMutationClauses(insertAst, "insert")
|
|
1135
|
-
assertNoInsertQueryClauses(insertAst)
|
|
1136
1369
|
const targetSource = insertAst.into!
|
|
1137
1370
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1138
|
-
const
|
|
1371
|
+
const targetCasingState = stateWithTableCasing(state, targetSource.source)
|
|
1372
|
+
const insertSource = insertAst.insertSource
|
|
1139
1373
|
const conflict = expectConflictClause(insertAst.conflict)
|
|
1140
1374
|
sql = `insert into ${target}`
|
|
1141
1375
|
if (insertSource?.kind === "values") {
|
|
1142
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1376
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1143
1377
|
const rows = insertSource.rows.map((row) =>
|
|
1144
|
-
`(${row.values.map((entry) => renderExpression(entry.value,
|
|
1378
|
+
`(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
|
|
1145
1379
|
).join(", ")
|
|
1146
1380
|
sql += ` (${columns}) values ${rows}`
|
|
1147
1381
|
} else if (insertSource?.kind === "query") {
|
|
1148
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1382
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1149
1383
|
const renderedQuery = renderQueryAst(
|
|
1150
1384
|
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
1151
1385
|
Record<string, unknown>,
|
|
@@ -1157,7 +1391,7 @@ export const renderQueryAst = (
|
|
|
1157
1391
|
)
|
|
1158
1392
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
1159
1393
|
} else if (insertSource?.kind === "unnest") {
|
|
1160
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1394
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1161
1395
|
if (dialect.name === "postgres") {
|
|
1162
1396
|
const table = targetSource.source as Table.AnyTable
|
|
1163
1397
|
const fields = table[Table.TypeId].fields
|
|
@@ -1179,38 +1413,41 @@ export const renderQueryAst = (
|
|
|
1179
1413
|
sql += ` (${columns}) values ${rows}`
|
|
1180
1414
|
}
|
|
1181
1415
|
} else {
|
|
1182
|
-
const
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1416
|
+
const insertValues = insertAst.values ?? []
|
|
1417
|
+
const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
|
|
1418
|
+
const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
|
|
1419
|
+
if (insertValues.length > 0) {
|
|
1185
1420
|
sql += ` (${columns}) values (${values})`
|
|
1186
1421
|
} else {
|
|
1187
1422
|
sql += " default values"
|
|
1188
1423
|
}
|
|
1189
1424
|
}
|
|
1190
1425
|
if (conflict) {
|
|
1191
|
-
if (
|
|
1192
|
-
throw new Error("
|
|
1426
|
+
if (dialect.name === "standard") {
|
|
1427
|
+
throw new Error("Unsupported standard insert conflict")
|
|
1193
1428
|
}
|
|
1429
|
+
const conflictValueState = { ...targetCasingState, allowExcluded: true }
|
|
1194
1430
|
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
1195
|
-
`${
|
|
1431
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
|
|
1196
1432
|
).join(", ")
|
|
1197
1433
|
if (dialect.name === "postgres") {
|
|
1198
1434
|
const targetSql = conflict.target?.kind === "constraint"
|
|
1199
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
|
|
1435
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
|
|
1200
1436
|
: conflict.target?.kind === "columns"
|
|
1201
|
-
? ` on conflict (${conflict.target.columns.map((column) => dialect.
|
|
1437
|
+
? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
|
|
1202
1438
|
: " on conflict"
|
|
1203
1439
|
sql += targetSql
|
|
1204
1440
|
sql += conflict.action === "doNothing"
|
|
1205
1441
|
? " do nothing"
|
|
1206
|
-
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where,
|
|
1442
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
|
|
1207
1443
|
} else if (conflict.action === "doNothing") {
|
|
1208
1444
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
1209
1445
|
} else {
|
|
1210
1446
|
sql += ` on duplicate key update ${updateValues}`
|
|
1211
1447
|
}
|
|
1212
1448
|
}
|
|
1213
|
-
|
|
1449
|
+
assertSupportedMutationReturning(dialect, insertAst.select as Record<string, unknown>)
|
|
1450
|
+
const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
|
|
1214
1451
|
projections = returning.projections
|
|
1215
1452
|
if (returning.sql.length > 0) {
|
|
1216
1453
|
sql += ` returning ${returning.sql}`
|
|
@@ -1219,31 +1456,15 @@ export const renderQueryAst = (
|
|
|
1219
1456
|
}
|
|
1220
1457
|
case "update": {
|
|
1221
1458
|
const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
|
|
1222
|
-
if (updateAst.distinct) {
|
|
1223
|
-
throw new Error("distinct(...) is not supported for update statements")
|
|
1224
|
-
}
|
|
1225
|
-
assertNoGroupedMutationClauses(updateAst, "update")
|
|
1226
|
-
if (updateAst.orderBy.length > 0) {
|
|
1227
|
-
throw new Error("orderBy(...) is not supported for update statements")
|
|
1228
|
-
}
|
|
1229
|
-
if (updateAst.limit) {
|
|
1230
|
-
throw new Error("limit(...) is not supported for update statements")
|
|
1231
|
-
}
|
|
1232
|
-
if (updateAst.offset) {
|
|
1233
|
-
throw new Error("offset(...) is not supported for update statements")
|
|
1234
|
-
}
|
|
1235
|
-
if (updateAst.lock) {
|
|
1236
|
-
throw new Error("lock(...) is not supported for update statements")
|
|
1237
|
-
}
|
|
1238
1459
|
const targetSource = updateAst.target!
|
|
1239
1460
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1240
1461
|
const targets = updateAst.targets ?? [targetSource]
|
|
1241
1462
|
const fromSources = updateAst.fromSources ?? []
|
|
1242
|
-
if ((
|
|
1243
|
-
throw new Error("
|
|
1463
|
+
if (dialect.name === "standard" && (targets.length > 1 || fromSources.length > 0 || updateAst.joins.length > 0)) {
|
|
1464
|
+
throw new Error("Unsupported standard joined mutation")
|
|
1244
1465
|
}
|
|
1245
1466
|
const assignments = updateAst.set!.map((entry) =>
|
|
1246
|
-
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
1467
|
+
renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
|
|
1247
1468
|
if (dialect.name === "mysql") {
|
|
1248
1469
|
const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
|
|
1249
1470
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
@@ -1282,7 +1503,8 @@ export const renderQueryAst = (
|
|
|
1282
1503
|
if (dialect.name === "mysql" && updateAst.limit) {
|
|
1283
1504
|
sql += ` limit ${renderExpression(updateAst.limit, state, dialect)}`
|
|
1284
1505
|
}
|
|
1285
|
-
|
|
1506
|
+
assertSupportedMutationReturning(dialect, updateAst.select as Record<string, unknown>)
|
|
1507
|
+
const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
|
|
1286
1508
|
projections = returning.projections
|
|
1287
1509
|
if (returning.sql.length > 0) {
|
|
1288
1510
|
sql += ` returning ${returning.sql}`
|
|
@@ -1291,25 +1513,12 @@ export const renderQueryAst = (
|
|
|
1291
1513
|
}
|
|
1292
1514
|
case "delete": {
|
|
1293
1515
|
const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
|
|
1294
|
-
if (deleteAst.distinct) {
|
|
1295
|
-
throw new Error("distinct(...) is not supported for delete statements")
|
|
1296
|
-
}
|
|
1297
|
-
assertNoGroupedMutationClauses(deleteAst, "delete")
|
|
1298
|
-
if (deleteAst.orderBy.length > 0 && dialect.name === "postgres") {
|
|
1299
|
-
throw new Error("orderBy(...) is not supported for delete statements")
|
|
1300
|
-
}
|
|
1301
|
-
if (deleteAst.limit && dialect.name === "postgres") {
|
|
1302
|
-
throw new Error("limit(...) is not supported for delete statements")
|
|
1303
|
-
}
|
|
1304
|
-
if (deleteAst.offset) {
|
|
1305
|
-
throw new Error("offset(...) is not supported for delete statements")
|
|
1306
|
-
}
|
|
1307
|
-
if (deleteAst.lock) {
|
|
1308
|
-
throw new Error("lock(...) is not supported for delete statements")
|
|
1309
|
-
}
|
|
1310
1516
|
const targetSource = deleteAst.target!
|
|
1311
1517
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1312
1518
|
const targets = deleteAst.targets ?? [targetSource]
|
|
1519
|
+
if (dialect.name === "standard" && (targets.length > 1 || deleteAst.joins.length > 0)) {
|
|
1520
|
+
throw new Error("Unsupported standard joined mutation")
|
|
1521
|
+
}
|
|
1313
1522
|
if (dialect.name === "mysql") {
|
|
1314
1523
|
const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
|
|
1315
1524
|
const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
|
|
@@ -1344,7 +1553,8 @@ export const renderQueryAst = (
|
|
|
1344
1553
|
if (dialect.name === "mysql" && deleteAst.limit) {
|
|
1345
1554
|
sql += ` limit ${renderExpression(deleteAst.limit, state, dialect)}`
|
|
1346
1555
|
}
|
|
1347
|
-
|
|
1556
|
+
assertSupportedMutationReturning(dialect, deleteAst.select as Record<string, unknown>)
|
|
1557
|
+
const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
|
|
1348
1558
|
projections = returning.projections
|
|
1349
1559
|
if (returning.sql.length > 0) {
|
|
1350
1560
|
sql += ` returning ${returning.sql}`
|
|
@@ -1353,14 +1563,18 @@ export const renderQueryAst = (
|
|
|
1353
1563
|
}
|
|
1354
1564
|
case "truncate": {
|
|
1355
1565
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1356
|
-
|
|
1566
|
+
if (dialect.name === "standard") {
|
|
1567
|
+
throw new Error("Unsupported standard truncate statement")
|
|
1568
|
+
}
|
|
1357
1569
|
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1358
1570
|
const targetSource = truncateAst.target!
|
|
1571
|
+
const restartIdentity = truncate.restartIdentity
|
|
1572
|
+
const cascade = truncate.cascade
|
|
1359
1573
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1360
|
-
if (
|
|
1574
|
+
if (restartIdentity) {
|
|
1361
1575
|
sql += " restart identity"
|
|
1362
1576
|
}
|
|
1363
|
-
if (
|
|
1577
|
+
if (cascade) {
|
|
1364
1578
|
sql += " cascade"
|
|
1365
1579
|
}
|
|
1366
1580
|
break
|
|
@@ -1373,43 +1587,28 @@ export const renderQueryAst = (
|
|
|
1373
1587
|
const targetSource = mergeAst.target!
|
|
1374
1588
|
const usingSource = mergeAst.using!
|
|
1375
1589
|
const merge = mergeAst.merge!
|
|
1376
|
-
if (merge.kind !== "merge") {
|
|
1377
|
-
throw new Error("Unsupported merge statement kind")
|
|
1378
|
-
}
|
|
1379
|
-
if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
|
|
1380
|
-
throw new Error("returning(...) is not supported for merge statements")
|
|
1381
|
-
}
|
|
1382
|
-
if (!merge.whenMatched && !merge.whenNotMatched) {
|
|
1383
|
-
throw new Error("merge statements require at least one action")
|
|
1384
|
-
}
|
|
1385
1590
|
sql = `merge into ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} using ${renderSourceReference(usingSource.source, usingSource.tableName, usingSource.baseTableName, state, dialect)} on ${renderExpression(merge.on, state, dialect)}`
|
|
1386
1591
|
if (merge.whenMatched) {
|
|
1387
|
-
|
|
1592
|
+
const matchedKind = merge.whenMatched.kind === "delete" ? "delete" : "update"
|
|
1388
1593
|
sql += " when matched"
|
|
1389
1594
|
if (merge.whenMatched.predicate) {
|
|
1390
1595
|
sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
|
|
1391
1596
|
}
|
|
1392
|
-
if (
|
|
1597
|
+
if (matchedKind === "delete") {
|
|
1393
1598
|
sql += " then delete"
|
|
1394
1599
|
} else {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
sql += ` then update set ${merge.whenMatched.values.map((entry) =>
|
|
1399
|
-
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
1600
|
+
const matchedUpdate = merge.whenMatched as Extract<QueryAst.MergeMatchedClause, { readonly kind: "update" }>
|
|
1601
|
+
sql += ` then update set ${matchedUpdate.values.map((entry) =>
|
|
1602
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
1400
1603
|
).join(", ")}`
|
|
1401
1604
|
}
|
|
1402
1605
|
}
|
|
1403
1606
|
if (merge.whenNotMatched) {
|
|
1404
|
-
assertMergeActionKind(merge.whenNotMatched.kind, ["insert"])
|
|
1405
1607
|
sql += " when not matched"
|
|
1406
1608
|
if (merge.whenNotMatched.predicate) {
|
|
1407
1609
|
sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
|
|
1408
1610
|
}
|
|
1409
|
-
|
|
1410
|
-
throw new Error("merge insert actions require at least one value")
|
|
1411
|
-
}
|
|
1412
|
-
sql += ` then insert (${merge.whenNotMatched.values.map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
1611
|
+
sql += ` then insert (${merge.whenNotMatched.values.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
1413
1612
|
}
|
|
1414
1613
|
break
|
|
1415
1614
|
}
|
|
@@ -1419,27 +1618,27 @@ export const renderQueryAst = (
|
|
|
1419
1618
|
case "savepoint":
|
|
1420
1619
|
case "rollbackTo":
|
|
1421
1620
|
case "releaseSavepoint": {
|
|
1422
|
-
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1423
1621
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1424
1622
|
break
|
|
1425
1623
|
}
|
|
1426
1624
|
case "createTable": {
|
|
1427
1625
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1428
|
-
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1429
1626
|
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1430
1627
|
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1431
1628
|
break
|
|
1432
1629
|
}
|
|
1433
1630
|
case "dropTable": {
|
|
1434
1631
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1435
|
-
assertNoStatementQueryClauses(dropTableAst, "dropTable")
|
|
1436
1632
|
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1437
|
-
|
|
1633
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
1634
|
+
if (dialect.name !== "postgres" && ifExists) {
|
|
1635
|
+
throw new Error(`Unsupported ${dialect.name} drop table options`)
|
|
1636
|
+
}
|
|
1637
|
+
sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1438
1638
|
break
|
|
1439
1639
|
}
|
|
1440
1640
|
case "createIndex": {
|
|
1441
1641
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1442
|
-
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1443
1642
|
sql = renderCreateIndexSql(
|
|
1444
1643
|
createIndexAst.target!,
|
|
1445
1644
|
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
@@ -1450,7 +1649,6 @@ export const renderQueryAst = (
|
|
|
1450
1649
|
}
|
|
1451
1650
|
case "dropIndex": {
|
|
1452
1651
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1453
|
-
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1454
1652
|
sql = renderDropIndexSql(
|
|
1455
1653
|
dropIndexAst.target!,
|
|
1456
1654
|
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
@@ -1459,8 +1657,12 @@ export const renderQueryAst = (
|
|
|
1459
1657
|
)
|
|
1460
1658
|
break
|
|
1461
1659
|
}
|
|
1462
|
-
default:
|
|
1463
|
-
|
|
1660
|
+
default: {
|
|
1661
|
+
if (ast.transaction !== undefined) {
|
|
1662
|
+
sql = renderTransactionClause(ast.transaction, dialect)
|
|
1663
|
+
}
|
|
1664
|
+
break
|
|
1665
|
+
}
|
|
1464
1666
|
}
|
|
1465
1667
|
|
|
1466
1668
|
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
@@ -1547,6 +1749,9 @@ const renderSourceReference = (
|
|
|
1547
1749
|
readonly name: string
|
|
1548
1750
|
readonly plan: Query.Plan.Any
|
|
1549
1751
|
}
|
|
1752
|
+
if (dialect.name === "standard") {
|
|
1753
|
+
throw new Error("Unsupported standard lateral source")
|
|
1754
|
+
}
|
|
1550
1755
|
return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
|
|
1551
1756
|
}
|
|
1552
1757
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
@@ -1573,13 +1778,26 @@ const renderSourceReference = (
|
|
|
1573
1778
|
if (dialect.name !== "postgres") {
|
|
1574
1779
|
throw new Error("Unsupported table function source for SQL rendering")
|
|
1575
1780
|
}
|
|
1781
|
+
const functionName = renderFunctionName(tableFunction.functionName)
|
|
1576
1782
|
const columnNames = Object.keys(tableFunction.columns)
|
|
1577
|
-
return `${
|
|
1783
|
+
return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
|
|
1578
1784
|
}
|
|
1579
1785
|
const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
|
|
1580
|
-
? (source as Table.AnyTable)
|
|
1786
|
+
? casedSchemaName(source as Table.AnyTable, state)
|
|
1581
1787
|
: undefined
|
|
1582
|
-
|
|
1788
|
+
if (typeof source === "object" && source !== null && Table.TypeId in source) {
|
|
1789
|
+
const table = source as Table.AnyTable
|
|
1790
|
+
const renderedBaseName = casedTableName(table, state)
|
|
1791
|
+
const renderedTableName = table[Table.TypeId].kind === "alias"
|
|
1792
|
+
? tableName
|
|
1793
|
+
: renderedBaseName
|
|
1794
|
+
return dialect.renderTableReference(renderedTableName, renderedBaseName, schemaName)
|
|
1795
|
+
}
|
|
1796
|
+
return dialect.renderTableReference(
|
|
1797
|
+
Casing.applyCategory(state.casing, "tables", tableName),
|
|
1798
|
+
Casing.applyCategory(state.casing, "tables", baseTableName),
|
|
1799
|
+
schemaName
|
|
1800
|
+
)
|
|
1583
1801
|
}
|
|
1584
1802
|
|
|
1585
1803
|
const renderSubqueryExpressionPlan = (
|
|
@@ -1618,162 +1836,185 @@ export const renderExpression = (
|
|
|
1618
1836
|
return jsonSql
|
|
1619
1837
|
}
|
|
1620
1838
|
const ast = rawAst as ExpressionAst.Any
|
|
1621
|
-
const renderComparisonOperator = (operator:
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
:
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1839
|
+
const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
|
|
1840
|
+
({
|
|
1841
|
+
eq: "=",
|
|
1842
|
+
neq: "<>",
|
|
1843
|
+
lt: "<",
|
|
1844
|
+
lte: "<=",
|
|
1845
|
+
gt: ">",
|
|
1846
|
+
gte: ">="
|
|
1847
|
+
} as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
|
|
1848
|
+
const renderCollation = (collation: unknown): string => {
|
|
1849
|
+
return (collation as readonly string[]).map((segment) => dialect.quoteIdentifier(segment)).join(".")
|
|
1850
|
+
}
|
|
1851
|
+
switch (ast.kind) {
|
|
1634
1852
|
case "column":
|
|
1635
1853
|
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1636
|
-
?
|
|
1637
|
-
: `${dialect.quoteIdentifier(ast.tableName)}.${
|
|
1854
|
+
? quoteColumn(ast.columnName, state, dialect, ast.tableName)
|
|
1855
|
+
: `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
|
|
1638
1856
|
case "literal":
|
|
1639
1857
|
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1640
1858
|
throw new Error("Expected a finite numeric value")
|
|
1641
1859
|
}
|
|
1642
1860
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1643
1861
|
case "excluded":
|
|
1862
|
+
if (state.allowExcluded !== true) {
|
|
1863
|
+
throw new Error("excluded(...) is only supported inside insert conflict handlers")
|
|
1864
|
+
}
|
|
1644
1865
|
return dialect.name === "mysql"
|
|
1645
|
-
? `values(${
|
|
1646
|
-
: `excluded.${
|
|
1866
|
+
? `values(${quoteColumn(ast.columnName, state, dialect)})`
|
|
1867
|
+
: `excluded.${quoteColumn(ast.columnName, state, dialect)}`
|
|
1647
1868
|
case "cast":
|
|
1648
|
-
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1869
|
+
return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1649
1870
|
case "collate":
|
|
1650
|
-
return `(${renderExpression(ast.value, state, dialect)} collate ${ast.collation
|
|
1871
|
+
return `(${renderExpression(expectValueExpression("collate", ast.value), state, dialect)} collate ${renderCollation(ast.collation)})`
|
|
1651
1872
|
case "function":
|
|
1652
|
-
return renderFunctionCall(ast.name,
|
|
1873
|
+
return renderFunctionCall(ast.name, ast.args, state, dialect)
|
|
1653
1874
|
case "eq":
|
|
1654
|
-
return
|
|
1875
|
+
return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
|
|
1655
1876
|
case "neq":
|
|
1656
|
-
return
|
|
1877
|
+
return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
|
|
1657
1878
|
case "lt":
|
|
1658
|
-
return
|
|
1879
|
+
return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
|
|
1659
1880
|
case "lte":
|
|
1660
|
-
return
|
|
1881
|
+
return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
|
|
1661
1882
|
case "gt":
|
|
1662
|
-
return
|
|
1883
|
+
return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
|
|
1663
1884
|
case "gte":
|
|
1664
|
-
return
|
|
1885
|
+
return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
|
|
1665
1886
|
case "like":
|
|
1666
|
-
return
|
|
1667
|
-
case "ilike":
|
|
1887
|
+
return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
|
|
1888
|
+
case "ilike": {
|
|
1889
|
+
const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
|
|
1668
1890
|
return dialect.name === "postgres"
|
|
1669
|
-
? `(${renderExpression(
|
|
1670
|
-
: `(lower(${renderExpression(
|
|
1671
|
-
|
|
1891
|
+
? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
|
|
1892
|
+
: `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
|
|
1893
|
+
}
|
|
1894
|
+
case "regexMatch": {
|
|
1895
|
+
const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
|
|
1896
|
+
if (dialect.name === "standard") {
|
|
1897
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1898
|
+
}
|
|
1672
1899
|
return dialect.name === "postgres"
|
|
1673
|
-
? `(${renderExpression(
|
|
1674
|
-
: `(${renderExpression(
|
|
1675
|
-
|
|
1900
|
+
? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
|
|
1901
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1902
|
+
}
|
|
1903
|
+
case "regexIMatch": {
|
|
1904
|
+
const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
|
|
1905
|
+
if (dialect.name === "standard") {
|
|
1906
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1907
|
+
}
|
|
1676
1908
|
return dialect.name === "postgres"
|
|
1677
|
-
? `(${renderExpression(
|
|
1678
|
-
: `(${renderExpression(
|
|
1679
|
-
|
|
1909
|
+
? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
|
|
1910
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1911
|
+
}
|
|
1912
|
+
case "regexNotMatch": {
|
|
1913
|
+
const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
|
|
1914
|
+
if (dialect.name === "standard") {
|
|
1915
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1916
|
+
}
|
|
1680
1917
|
return dialect.name === "postgres"
|
|
1681
|
-
? `(${renderExpression(
|
|
1682
|
-
: `(${renderExpression(
|
|
1683
|
-
|
|
1918
|
+
? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
|
|
1919
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1920
|
+
}
|
|
1921
|
+
case "regexNotIMatch": {
|
|
1922
|
+
const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
|
|
1923
|
+
if (dialect.name === "standard") {
|
|
1924
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1925
|
+
}
|
|
1684
1926
|
return dialect.name === "postgres"
|
|
1685
|
-
? `(${renderExpression(
|
|
1686
|
-
: `(${renderExpression(
|
|
1687
|
-
|
|
1927
|
+
? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
|
|
1928
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1929
|
+
}
|
|
1930
|
+
case "isDistinctFrom": {
|
|
1931
|
+
const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
|
|
1688
1932
|
return dialect.name === "mysql"
|
|
1689
|
-
? `(not (${renderExpression(
|
|
1690
|
-
: `(${renderExpression(
|
|
1691
|
-
|
|
1933
|
+
? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
|
|
1934
|
+
: `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
|
|
1935
|
+
}
|
|
1936
|
+
case "isNotDistinctFrom": {
|
|
1937
|
+
const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
|
|
1692
1938
|
return dialect.name === "mysql"
|
|
1693
|
-
? `(${renderExpression(
|
|
1694
|
-
: `(${renderExpression(
|
|
1695
|
-
|
|
1939
|
+
? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
|
|
1940
|
+
: `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
|
|
1941
|
+
}
|
|
1942
|
+
case "contains": {
|
|
1943
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
|
|
1696
1944
|
if (dialect.name === "postgres") {
|
|
1697
|
-
assertCompatiblePostgresRangeOperands(
|
|
1698
|
-
const left = isJsonExpression(
|
|
1699
|
-
? renderPostgresJsonValue(
|
|
1700
|
-
: renderExpression(
|
|
1701
|
-
const right = isJsonExpression(
|
|
1702
|
-
? renderPostgresJsonValue(
|
|
1703
|
-
: renderExpression(
|
|
1945
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1946
|
+
const left = isJsonExpression(leftExpression)
|
|
1947
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1948
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1949
|
+
const right = isJsonExpression(rightExpression)
|
|
1950
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1951
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1704
1952
|
return `(${left} @> ${right})`
|
|
1705
1953
|
}
|
|
1706
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1707
|
-
return `json_contains(${renderExpression(
|
|
1954
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1955
|
+
return `json_contains(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
|
|
1708
1956
|
}
|
|
1709
1957
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1710
|
-
|
|
1958
|
+
}
|
|
1959
|
+
case "containedBy": {
|
|
1960
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
|
|
1711
1961
|
if (dialect.name === "postgres") {
|
|
1712
|
-
assertCompatiblePostgresRangeOperands(
|
|
1713
|
-
const left = isJsonExpression(
|
|
1714
|
-
? renderPostgresJsonValue(
|
|
1715
|
-
: renderExpression(
|
|
1716
|
-
const right = isJsonExpression(
|
|
1717
|
-
? renderPostgresJsonValue(
|
|
1718
|
-
: renderExpression(
|
|
1962
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1963
|
+
const left = isJsonExpression(leftExpression)
|
|
1964
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1965
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1966
|
+
const right = isJsonExpression(rightExpression)
|
|
1967
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1968
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1719
1969
|
return `(${left} <@ ${right})`
|
|
1720
1970
|
}
|
|
1721
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1722
|
-
return `json_contains(${renderExpression(
|
|
1971
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1972
|
+
return `json_contains(${renderExpression(rightExpression, state, dialect)}, ${renderExpression(leftExpression, state, dialect)})`
|
|
1723
1973
|
}
|
|
1724
1974
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1725
|
-
|
|
1975
|
+
}
|
|
1976
|
+
case "overlaps": {
|
|
1977
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
|
|
1726
1978
|
if (dialect.name === "postgres") {
|
|
1727
|
-
assertCompatiblePostgresRangeOperands(
|
|
1728
|
-
const left = isJsonExpression(
|
|
1729
|
-
? renderPostgresJsonValue(
|
|
1730
|
-
: renderExpression(
|
|
1731
|
-
const right = isJsonExpression(
|
|
1732
|
-
? renderPostgresJsonValue(
|
|
1733
|
-
: renderExpression(
|
|
1979
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1980
|
+
const left = isJsonExpression(leftExpression)
|
|
1981
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1982
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1983
|
+
const right = isJsonExpression(rightExpression)
|
|
1984
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1985
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1734
1986
|
return `(${left} && ${right})`
|
|
1735
1987
|
}
|
|
1736
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1737
|
-
return `json_overlaps(${renderExpression(
|
|
1988
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1989
|
+
return `json_overlaps(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
|
|
1738
1990
|
}
|
|
1739
1991
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1992
|
+
}
|
|
1740
1993
|
case "isNull":
|
|
1741
|
-
return `(${renderExpression(ast.value, state, dialect)} is null)`
|
|
1994
|
+
return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
|
|
1742
1995
|
case "isNotNull":
|
|
1743
|
-
return `(${renderExpression(ast.value, state, dialect)} is not null)`
|
|
1996
|
+
return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
|
|
1744
1997
|
case "not":
|
|
1745
|
-
return `(not ${renderExpression(ast.value, state, dialect)})`
|
|
1998
|
+
return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
|
|
1746
1999
|
case "upper":
|
|
1747
|
-
return `upper(${renderExpression(ast.value, state, dialect)})`
|
|
2000
|
+
return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
|
|
1748
2001
|
case "lower":
|
|
1749
|
-
return `lower(${renderExpression(ast.value, state, dialect)})`
|
|
2002
|
+
return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
|
|
1750
2003
|
case "count":
|
|
1751
|
-
return `count(${renderExpression(ast.value, state, dialect)})`
|
|
2004
|
+
return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
|
|
1752
2005
|
case "max":
|
|
1753
|
-
return `max(${renderExpression(ast.value, state, dialect)})`
|
|
2006
|
+
return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
|
|
1754
2007
|
case "min":
|
|
1755
|
-
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
2008
|
+
return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
|
|
1756
2009
|
case "and":
|
|
1757
|
-
if (ast.values.length === 0) {
|
|
1758
|
-
throw new Error("and(...) requires at least one predicate")
|
|
1759
|
-
}
|
|
1760
2010
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1761
2011
|
case "or":
|
|
1762
|
-
if (ast.values.length === 0) {
|
|
1763
|
-
throw new Error("or(...) requires at least one predicate")
|
|
1764
|
-
}
|
|
1765
2012
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1766
2013
|
case "coalesce":
|
|
1767
2014
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1768
2015
|
case "in":
|
|
1769
|
-
if (ast.values.length < 2) {
|
|
1770
|
-
throw new Error("in(...) requires at least one candidate value")
|
|
1771
|
-
}
|
|
1772
2016
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1773
2017
|
case "notIn":
|
|
1774
|
-
if (ast.values.length < 2) {
|
|
1775
|
-
throw new Error("notIn(...) requires at least one candidate value")
|
|
1776
|
-
}
|
|
1777
2018
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1778
2019
|
case "between":
|
|
1779
2020
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1788,21 +2029,35 @@ export const renderExpression = (
|
|
|
1788
2029
|
case "scalarSubquery":
|
|
1789
2030
|
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1790
2031
|
case "inSubquery":
|
|
1791
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1792
|
-
case "comparisonAny":
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2032
|
+
return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2033
|
+
case "comparisonAny": {
|
|
2034
|
+
const left = expectValueExpression("compareAny", ast.left)
|
|
2035
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
2036
|
+
if (dialect.name === "standard") {
|
|
2037
|
+
throw new Error("Unsupported standard quantified comparison")
|
|
2038
|
+
}
|
|
2039
|
+
return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2040
|
+
}
|
|
2041
|
+
case "comparisonAll": {
|
|
2042
|
+
const left = expectValueExpression("compareAll", ast.left)
|
|
2043
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
2044
|
+
if (dialect.name === "standard") {
|
|
2045
|
+
throw new Error("Unsupported standard quantified comparison")
|
|
1799
2046
|
}
|
|
2047
|
+
return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2048
|
+
}
|
|
2049
|
+
case "window": {
|
|
2050
|
+
const partitionBy = ast.partitionBy as readonly Expression.Any[]
|
|
2051
|
+
const orderBy = ast.orderBy as readonly {
|
|
2052
|
+
readonly value: Expression.Any
|
|
2053
|
+
readonly direction: string
|
|
2054
|
+
}[]
|
|
1800
2055
|
const clauses: string[] = []
|
|
1801
|
-
if (
|
|
1802
|
-
clauses.push(`partition by ${
|
|
2056
|
+
if (partitionBy.length > 0) {
|
|
2057
|
+
clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
|
|
1803
2058
|
}
|
|
1804
|
-
if (
|
|
1805
|
-
clauses.push(`order by ${
|
|
2059
|
+
if (orderBy.length > 0) {
|
|
2060
|
+
clauses.push(`order by ${orderBy.map((entry) =>
|
|
1806
2061
|
`${renderExpression(entry.value, state, dialect)} ${entry.direction}`
|
|
1807
2062
|
).join(", ")}`)
|
|
1808
2063
|
}
|
|
@@ -1815,7 +2070,7 @@ export const renderExpression = (
|
|
|
1815
2070
|
case "denseRank":
|
|
1816
2071
|
return `dense_rank() over (${specification})`
|
|
1817
2072
|
case "over":
|
|
1818
|
-
return `${renderExpression(ast.value
|
|
2073
|
+
return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
|
|
1819
2074
|
}
|
|
1820
2075
|
break
|
|
1821
2076
|
}
|