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