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/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/mysql.ts}
RENAMED
|
@@ -1,44 +1,48 @@
|
|
|
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 { renderMysqlMutationLockMode, 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 { renderMysqlMutationLockMode, 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
|
|
22
|
-
import
|
|
17
|
+
} from "../runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../runtime/normalize.js"
|
|
19
|
+
import { flattenSelection, type Projection } from "../projections.js"
|
|
20
|
+
import * as SchemaExpression from "../schema-expression.js"
|
|
21
|
+
import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
|
|
22
|
+
import * as Casing from "../casing.js"
|
|
23
23
|
|
|
24
24
|
const renderDbType = (
|
|
25
25
|
dialect: SqlDialect,
|
|
26
26
|
dbType: Expression.DbType.Any
|
|
27
27
|
): string => {
|
|
28
|
-
if (dialect.name === "mysql" && dbType.
|
|
28
|
+
if (dialect.name === "mysql" && dbType.kind === "uuid") {
|
|
29
29
|
return "char(36)"
|
|
30
30
|
}
|
|
31
|
-
return dbType.kind
|
|
31
|
+
return renderDbTypeName(dbType.kind)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
35
|
+
"element" in dbType
|
|
36
|
+
|
|
34
37
|
const renderCastType = (
|
|
35
38
|
dialect: SqlDialect,
|
|
36
|
-
dbType:
|
|
39
|
+
dbType: unknown
|
|
37
40
|
): string => {
|
|
41
|
+
const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
|
|
38
42
|
if (dialect.name !== "mysql") {
|
|
39
|
-
return
|
|
43
|
+
return renderDbTypeName(kind)
|
|
40
44
|
}
|
|
41
|
-
switch (
|
|
45
|
+
switch (kind) {
|
|
42
46
|
case "text":
|
|
43
47
|
return "char"
|
|
44
48
|
case "uuid":
|
|
@@ -53,7 +57,7 @@ const renderCastType = (
|
|
|
53
57
|
case "json":
|
|
54
58
|
return "json"
|
|
55
59
|
default:
|
|
56
|
-
return
|
|
60
|
+
return renderDbTypeName(kind)
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -125,22 +129,164 @@ const renderMysqlMutationLimit = (
|
|
|
125
129
|
return renderExpression(expression, state, dialect)
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
const casingForTable = (
|
|
133
|
+
table: Table.AnyTable,
|
|
134
|
+
state: RenderState
|
|
135
|
+
): Casing.Options | undefined =>
|
|
136
|
+
Casing.merge(state.casing, table[Table.TypeId].casing)
|
|
137
|
+
|
|
138
|
+
const casedColumnName = (
|
|
139
|
+
columnName: string,
|
|
140
|
+
state: RenderState,
|
|
141
|
+
tableName?: string
|
|
142
|
+
): string => {
|
|
143
|
+
if (tableName !== undefined) {
|
|
144
|
+
const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
|
|
145
|
+
if (mapped !== undefined) {
|
|
146
|
+
return mapped
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return Casing.applyCategory(state.casing, "columns", columnName)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const casedTableReferenceName = (
|
|
153
|
+
tableName: string,
|
|
154
|
+
state: RenderState
|
|
155
|
+
): string =>
|
|
156
|
+
state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
|
|
157
|
+
|
|
158
|
+
const quoteColumn = (
|
|
159
|
+
columnName: string,
|
|
160
|
+
state: RenderState,
|
|
161
|
+
dialect: SqlDialect,
|
|
162
|
+
tableName?: string
|
|
163
|
+
): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
|
|
164
|
+
|
|
165
|
+
const stateWithTableCasing = (
|
|
166
|
+
state: RenderState,
|
|
167
|
+
source: unknown
|
|
168
|
+
): RenderState =>
|
|
169
|
+
typeof source === "object" && source !== null && Table.TypeId in source
|
|
170
|
+
? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
|
|
171
|
+
: state
|
|
172
|
+
|
|
173
|
+
const referenceCasing = (
|
|
174
|
+
reference: { readonly casing?: Casing.Options },
|
|
175
|
+
state: RenderState
|
|
176
|
+
): Casing.Options | undefined =>
|
|
177
|
+
Casing.merge(state.casing, reference.casing)
|
|
178
|
+
|
|
179
|
+
const renderReferenceTable = (
|
|
180
|
+
reference: {
|
|
181
|
+
readonly tableName: string
|
|
182
|
+
readonly schemaName?: string
|
|
183
|
+
readonly casing?: Casing.Options
|
|
184
|
+
},
|
|
185
|
+
state: RenderState,
|
|
186
|
+
dialect: SqlDialect
|
|
187
|
+
): string => {
|
|
188
|
+
const casing = referenceCasing(reference, state)
|
|
189
|
+
const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
|
|
190
|
+
const schemaName = reference.schemaName === undefined
|
|
191
|
+
? undefined
|
|
192
|
+
: Casing.applyCategory(casing, "schemas", reference.schemaName)
|
|
193
|
+
return dialect.renderTableReference(tableName, tableName, schemaName)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const quoteReferenceColumn = (
|
|
197
|
+
columnName: string,
|
|
198
|
+
reference: { readonly casing?: Casing.Options },
|
|
199
|
+
state: RenderState,
|
|
200
|
+
dialect: SqlDialect
|
|
201
|
+
): string =>
|
|
202
|
+
dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
|
|
203
|
+
|
|
204
|
+
const registerSourceReference = (
|
|
205
|
+
source: unknown,
|
|
206
|
+
tableName: string,
|
|
207
|
+
state: RenderState
|
|
208
|
+
): void => {
|
|
209
|
+
if (typeof source !== "object" || source === null) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
if (Table.TypeId in source) {
|
|
213
|
+
const table = source as Table.AnyTable
|
|
214
|
+
const tableState = table[Table.TypeId]
|
|
215
|
+
const casing = casingForTable(table, state)
|
|
216
|
+
const renderedTableName = tableState.kind === "alias"
|
|
217
|
+
? tableName
|
|
218
|
+
: Casing.applyCategory(casing, "tables", tableState.baseName)
|
|
219
|
+
const columns = new Map(
|
|
220
|
+
Object.keys(tableState.fields).map((columnName) => [
|
|
221
|
+
columnName,
|
|
222
|
+
Casing.applyCategory(casing, "columns", columnName)
|
|
223
|
+
] as const)
|
|
224
|
+
)
|
|
225
|
+
state.sourceNames?.set(tableName, {
|
|
226
|
+
tableName: renderedTableName,
|
|
227
|
+
columns
|
|
228
|
+
})
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
|
|
232
|
+
state.sourceNames?.set(tableName, {
|
|
233
|
+
tableName,
|
|
234
|
+
columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const registerQuerySources = (
|
|
240
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
241
|
+
state: RenderState
|
|
242
|
+
): void => {
|
|
243
|
+
if (ast.from !== undefined) {
|
|
244
|
+
registerSourceReference(ast.from.source, ast.from.tableName, state)
|
|
245
|
+
}
|
|
246
|
+
for (const source of ast.fromSources ?? []) {
|
|
247
|
+
registerSourceReference(source.source, source.tableName, state)
|
|
248
|
+
}
|
|
249
|
+
for (const join of ast.joins) {
|
|
250
|
+
registerSourceReference(join.source, join.tableName, state)
|
|
251
|
+
}
|
|
252
|
+
if (ast.into !== undefined) {
|
|
253
|
+
registerSourceReference(ast.into.source, ast.into.tableName, state)
|
|
254
|
+
}
|
|
255
|
+
if (ast.target !== undefined) {
|
|
256
|
+
registerSourceReference(ast.target.source, ast.target.tableName, state)
|
|
257
|
+
}
|
|
258
|
+
for (const target of ast.targets ?? []) {
|
|
259
|
+
registerSourceReference(target.source, target.tableName, state)
|
|
260
|
+
}
|
|
261
|
+
if (ast.using !== undefined) {
|
|
262
|
+
registerSourceReference(ast.using.source, ast.using.tableName, state)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
128
266
|
const renderColumnDefinition = (
|
|
129
267
|
dialect: SqlDialect,
|
|
130
268
|
state: RenderState,
|
|
131
269
|
columnName: string,
|
|
132
|
-
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
270
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
271
|
+
tableName?: string,
|
|
272
|
+
casing?: Casing.Options
|
|
133
273
|
): string => {
|
|
274
|
+
const expressionState = { ...state, casing, rowLocalColumns: true }
|
|
275
|
+
if (isArrayDbType(column.metadata.dbType)) {
|
|
276
|
+
throw new Error("Unsupported mysql array column options")
|
|
277
|
+
}
|
|
134
278
|
const clauses = [
|
|
135
|
-
|
|
136
|
-
column.metadata.ddlType
|
|
279
|
+
quoteColumn(columnName, state, dialect, tableName),
|
|
280
|
+
column.metadata.ddlType === undefined
|
|
281
|
+
? renderDbType(dialect, column.metadata.dbType)
|
|
282
|
+
: renderDbTypeName(column.metadata.ddlType)
|
|
137
283
|
]
|
|
138
284
|
if (column.metadata.identity) {
|
|
139
|
-
|
|
285
|
+
throw new Error("Unsupported mysql identity column options")
|
|
140
286
|
} else if (column.metadata.generatedValue) {
|
|
141
|
-
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue,
|
|
287
|
+
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
|
|
142
288
|
} else if (column.metadata.defaultValue) {
|
|
143
|
-
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue,
|
|
289
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
|
|
144
290
|
}
|
|
145
291
|
if (!column.metadata.nullable) {
|
|
146
292
|
clauses.push("not null")
|
|
@@ -152,34 +298,53 @@ const renderCreateTableSql = (
|
|
|
152
298
|
targetSource: QueryAst.FromClause,
|
|
153
299
|
state: RenderState,
|
|
154
300
|
dialect: SqlDialect,
|
|
155
|
-
ifNotExists:
|
|
301
|
+
ifNotExists: unknown
|
|
156
302
|
): string => {
|
|
303
|
+
const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
|
|
157
304
|
const table = targetSource.source as Table.AnyTable
|
|
305
|
+
const tableCasing = casingForTable(table, state)
|
|
158
306
|
const fields = table[Table.TypeId].fields
|
|
159
307
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
160
|
-
renderColumnDefinition(dialect, state, columnName, column)
|
|
308
|
+
renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
|
|
161
309
|
)
|
|
162
|
-
|
|
310
|
+
const options = table[Table.OptionsSymbol] as unknown
|
|
311
|
+
const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
|
|
312
|
+
validateOptions(table[Table.TypeId].name, fields, tableOptions)
|
|
313
|
+
for (const option of tableOptions) {
|
|
314
|
+
if (typeof option !== "object" || option === null || !("kind" in option)) {
|
|
315
|
+
continue
|
|
316
|
+
}
|
|
163
317
|
switch (option.kind) {
|
|
164
318
|
case "primaryKey":
|
|
165
|
-
|
|
319
|
+
if (option.deferrable || option.initiallyDeferred) {
|
|
320
|
+
throw new Error("Unsupported mysql primary key constraint options")
|
|
321
|
+
}
|
|
322
|
+
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" : ""}` : ""}`)
|
|
166
323
|
break
|
|
167
324
|
case "unique":
|
|
168
325
|
if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
|
|
169
326
|
throw new Error("Unsupported mysql unique constraint options")
|
|
170
327
|
}
|
|
171
|
-
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => dialect.
|
|
328
|
+
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" : ""}` : ""}`)
|
|
172
329
|
break
|
|
173
330
|
case "foreignKey": {
|
|
174
|
-
|
|
331
|
+
if (option.deferrable || option.initiallyDeferred) {
|
|
332
|
+
throw new Error("Unsupported mysql foreign key constraint options")
|
|
333
|
+
}
|
|
334
|
+
const reference = typeof option.references === "function"
|
|
335
|
+
? option.references()
|
|
336
|
+
: option.references
|
|
175
337
|
definitions.push(
|
|
176
|
-
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.
|
|
338
|
+
`${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" : ""}` : ""}`
|
|
177
339
|
)
|
|
178
340
|
break
|
|
179
341
|
}
|
|
180
342
|
case "check":
|
|
343
|
+
if (option.noInherit) {
|
|
344
|
+
throw new Error("Unsupported mysql check constraint options")
|
|
345
|
+
}
|
|
181
346
|
definitions.push(
|
|
182
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
347
|
+
`constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
183
348
|
)
|
|
184
349
|
break
|
|
185
350
|
case "index":
|
|
@@ -188,7 +353,7 @@ const renderCreateTableSql = (
|
|
|
188
353
|
throw new Error("Unsupported table option kind")
|
|
189
354
|
}
|
|
190
355
|
}
|
|
191
|
-
return `create table${
|
|
356
|
+
return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
192
357
|
}
|
|
193
358
|
|
|
194
359
|
const renderCreateIndexSql = (
|
|
@@ -197,11 +362,16 @@ const renderCreateIndexSql = (
|
|
|
197
362
|
state: RenderState,
|
|
198
363
|
dialect: SqlDialect
|
|
199
364
|
): string => {
|
|
200
|
-
|
|
365
|
+
const unique = normalizeStatementFlag(ddl.unique)
|
|
366
|
+
const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
|
|
367
|
+
const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
|
|
368
|
+
if (ifNotExists) {
|
|
201
369
|
throw new Error("Unsupported mysql create index options")
|
|
202
370
|
}
|
|
203
|
-
const maybeIfNotExists = dialect.name === "postgres" &&
|
|
204
|
-
|
|
371
|
+
const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
|
|
372
|
+
const table = targetSource.source as Table.AnyTable
|
|
373
|
+
const tableCasing = casingForTable(table, state)
|
|
374
|
+
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(", ")})`
|
|
205
375
|
}
|
|
206
376
|
|
|
207
377
|
const renderDropIndexSql = (
|
|
@@ -210,12 +380,16 @@ const renderDropIndexSql = (
|
|
|
210
380
|
state: RenderState,
|
|
211
381
|
dialect: SqlDialect
|
|
212
382
|
): string => {
|
|
213
|
-
|
|
383
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
384
|
+
const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
|
|
385
|
+
if (ifExists) {
|
|
214
386
|
throw new Error("Unsupported mysql drop index options")
|
|
215
387
|
}
|
|
388
|
+
const table = targetSource.source as Table.AnyTable
|
|
389
|
+
const tableCasing = casingForTable(table, state)
|
|
216
390
|
return dialect.name === "postgres"
|
|
217
|
-
? `drop index${
|
|
218
|
-
: `drop index ${dialect.quoteIdentifier(
|
|
391
|
+
? `drop index${ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))}`
|
|
392
|
+
: `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
219
393
|
}
|
|
220
394
|
|
|
221
395
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
@@ -235,6 +409,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
|
|
|
235
409
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
236
410
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
237
411
|
|
|
412
|
+
const expectValueExpression = (
|
|
413
|
+
_functionName: string,
|
|
414
|
+
value: unknown
|
|
415
|
+
): Expression.Any => value as Expression.Any
|
|
416
|
+
|
|
417
|
+
const expectBinaryExpressions = (
|
|
418
|
+
_functionName: string,
|
|
419
|
+
left: unknown,
|
|
420
|
+
right: unknown
|
|
421
|
+
): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
|
|
422
|
+
|
|
423
|
+
const renderBinaryExpression = (
|
|
424
|
+
functionName: string,
|
|
425
|
+
operator: string,
|
|
426
|
+
left: unknown,
|
|
427
|
+
right: unknown,
|
|
428
|
+
state: RenderState,
|
|
429
|
+
dialect: SqlDialect
|
|
430
|
+
): string => {
|
|
431
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
|
|
432
|
+
return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
|
|
433
|
+
}
|
|
434
|
+
|
|
238
435
|
const unsupportedJsonFeature = (
|
|
239
436
|
dialect: SqlDialect,
|
|
240
437
|
feature: string
|
|
@@ -258,13 +455,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
|
|
|
258
455
|
const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
|
|
259
456
|
value !== null && typeof value === "object" && JsonPath.TypeId in value
|
|
260
457
|
|
|
458
|
+
const isOptionalJsonPathNumber = (value: unknown): boolean =>
|
|
459
|
+
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
460
|
+
|
|
461
|
+
const isJsonPathSegment = (segment: unknown): boolean => {
|
|
462
|
+
if (typeof segment === "string") {
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
if (typeof segment === "number") {
|
|
466
|
+
return Number.isFinite(segment)
|
|
467
|
+
}
|
|
468
|
+
if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
|
|
469
|
+
return false
|
|
470
|
+
}
|
|
471
|
+
switch ((segment as { readonly kind?: unknown }).kind) {
|
|
472
|
+
case "key":
|
|
473
|
+
return typeof (segment as { readonly key?: unknown }).key === "string"
|
|
474
|
+
case "index": {
|
|
475
|
+
const index = (segment as { readonly index?: unknown }).index
|
|
476
|
+
return typeof index === "number" && Number.isFinite(index)
|
|
477
|
+
}
|
|
478
|
+
case "wildcard":
|
|
479
|
+
case "descend":
|
|
480
|
+
return true
|
|
481
|
+
case "slice":
|
|
482
|
+
return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
|
|
483
|
+
isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
|
|
484
|
+
default:
|
|
485
|
+
return false
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
|
|
490
|
+
if (!Array.isArray(segments)) {
|
|
491
|
+
throw new Error("JSON path expressions require a segment array")
|
|
492
|
+
}
|
|
493
|
+
if (segments.some((segment) => !isJsonPathSegment(segment))) {
|
|
494
|
+
throw new Error("JSON path segments require string, number, or path segment objects")
|
|
495
|
+
}
|
|
496
|
+
return segments as ReadonlyArray<JsonPath.AnySegment>
|
|
497
|
+
}
|
|
498
|
+
|
|
261
499
|
const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
|
|
262
500
|
const path = node.path ?? node.segments ?? node.keys
|
|
263
501
|
if (isJsonPathValue(path)) {
|
|
264
|
-
return path.segments
|
|
502
|
+
return validateJsonPathSegments(path.segments)
|
|
265
503
|
}
|
|
266
504
|
if (Array.isArray(path)) {
|
|
267
|
-
return path
|
|
505
|
+
return validateJsonPathSegments(path)
|
|
506
|
+
}
|
|
507
|
+
if (node.segments !== undefined) {
|
|
508
|
+
return validateJsonPathSegments(node.segments)
|
|
268
509
|
}
|
|
269
510
|
if ("key" in node) {
|
|
270
511
|
return [JsonPath.key(String(node.key))]
|
|
@@ -283,11 +524,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
|
|
|
283
524
|
return []
|
|
284
525
|
}
|
|
285
526
|
if ("right" in node && isJsonPathValue(node.right)) {
|
|
286
|
-
return node.right.segments
|
|
527
|
+
return validateJsonPathSegments(node.right.segments)
|
|
287
528
|
}
|
|
288
529
|
return []
|
|
289
530
|
}
|
|
290
531
|
|
|
532
|
+
const extractJsonKeys = (
|
|
533
|
+
node: Record<string, unknown>,
|
|
534
|
+
segments: ReadonlyArray<JsonPath.AnySegment>
|
|
535
|
+
): readonly unknown[] =>
|
|
536
|
+
Array.isArray(node.keys)
|
|
537
|
+
? node.keys
|
|
538
|
+
: segments.map((segment) =>
|
|
539
|
+
typeof segment === "object" && segment !== null && segment.kind === "key"
|
|
540
|
+
? segment.key
|
|
541
|
+
: segment
|
|
542
|
+
)
|
|
543
|
+
|
|
291
544
|
const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
292
545
|
node.newValue ?? node.insert ?? node.right
|
|
293
546
|
|
|
@@ -357,6 +610,19 @@ const renderMySqlJsonPath = (
|
|
|
357
610
|
const isJsonArrayIndexSegment = (segment: JsonPath.AnySegment | string | number | undefined): boolean =>
|
|
358
611
|
typeof segment === "number" || (typeof segment === "object" && segment !== null && segment.kind === "index")
|
|
359
612
|
|
|
613
|
+
const isExactJsonPathSegment = (segment: JsonPath.AnySegment | string | number): boolean =>
|
|
614
|
+
typeof segment === "string" ||
|
|
615
|
+
typeof segment === "number" ||
|
|
616
|
+
(typeof segment === "object" && segment !== null && (segment.kind === "key" || segment.kind === "index"))
|
|
617
|
+
|
|
618
|
+
const assertMySqlJsonMutationPath = (
|
|
619
|
+
segments: ReadonlyArray<JsonPath.AnySegment | string | number>
|
|
620
|
+
): void => {
|
|
621
|
+
if (!segments.every(isExactJsonPathSegment)) {
|
|
622
|
+
throw new Error("MySQL JSON mutation paths require key/index segments")
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
360
626
|
const renderMySqlJsonInsertPath = (
|
|
361
627
|
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
362
628
|
insertAfter: boolean,
|
|
@@ -520,52 +786,64 @@ const renderJsonOpaquePath = (
|
|
|
520
786
|
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
|
|
521
787
|
}
|
|
522
788
|
if (typeof value === "string") {
|
|
789
|
+
if (value.trim().length === 0) {
|
|
790
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
791
|
+
}
|
|
523
792
|
return dialect.renderLiteral(value, state)
|
|
524
793
|
}
|
|
525
794
|
if (isExpression(value)) {
|
|
795
|
+
const ast = (value as Expression.Any & {
|
|
796
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
797
|
+
})[ExpressionAst.TypeId]
|
|
798
|
+
if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
|
|
799
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
800
|
+
}
|
|
526
801
|
return renderExpression(value, state, dialect)
|
|
527
802
|
}
|
|
528
803
|
throw new Error("Unsupported SQL/JSON path input")
|
|
529
804
|
}
|
|
530
805
|
|
|
806
|
+
const renderFunctionName = (name: unknown): string => {
|
|
807
|
+
return name as string
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const renderExtractField = (field: Expression.Any): string => {
|
|
811
|
+
const ast = (field as Expression.Any & {
|
|
812
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
813
|
+
})[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
|
|
814
|
+
return ast.value
|
|
815
|
+
}
|
|
816
|
+
|
|
531
817
|
const renderFunctionCall = (
|
|
532
|
-
name:
|
|
533
|
-
args:
|
|
818
|
+
name: unknown,
|
|
819
|
+
args: unknown,
|
|
534
820
|
state: RenderState,
|
|
535
821
|
dialect: SqlDialect
|
|
536
822
|
): string => {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
? field[Expression.TypeId].runtime
|
|
551
|
-
: undefined
|
|
552
|
-
const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
|
|
553
|
-
return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
|
|
554
|
-
}
|
|
555
|
-
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
556
|
-
if (args.length === 0) {
|
|
557
|
-
switch (name) {
|
|
823
|
+
const functionName = renderFunctionName(name)
|
|
824
|
+
const functionArgs = args as readonly Expression.Any[]
|
|
825
|
+
if (functionName === "array") {
|
|
826
|
+
return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
|
|
827
|
+
}
|
|
828
|
+
if (functionName === "extract") {
|
|
829
|
+
const field = functionArgs[0]!
|
|
830
|
+
const source = functionArgs[1]!
|
|
831
|
+
return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
|
|
832
|
+
}
|
|
833
|
+
const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
834
|
+
if (functionArgs.length === 0) {
|
|
835
|
+
switch (functionName) {
|
|
558
836
|
case "current_date":
|
|
559
837
|
case "current_time":
|
|
560
838
|
case "current_timestamp":
|
|
561
839
|
case "localtime":
|
|
562
840
|
case "localtimestamp":
|
|
563
|
-
return
|
|
841
|
+
return functionName
|
|
564
842
|
default:
|
|
565
|
-
return `${
|
|
843
|
+
return `${functionName}()`
|
|
566
844
|
}
|
|
567
845
|
}
|
|
568
|
-
return `${
|
|
846
|
+
return `${functionName}(${renderedArgs})`
|
|
569
847
|
}
|
|
570
848
|
|
|
571
849
|
const renderJsonExpression = (
|
|
@@ -629,23 +907,27 @@ const renderJsonExpression = (
|
|
|
629
907
|
const baseSql = dialect.name === "postgres"
|
|
630
908
|
? renderPostgresJsonValue(base, state, dialect)
|
|
631
909
|
: renderExpression(base, state, dialect)
|
|
632
|
-
const keys = segments
|
|
910
|
+
const keys = extractJsonKeys(ast, segments)
|
|
633
911
|
if (keys.length === 0) {
|
|
634
912
|
return undefined
|
|
635
913
|
}
|
|
914
|
+
if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
|
|
915
|
+
throw new Error("json key predicates require string keys")
|
|
916
|
+
}
|
|
917
|
+
const keyNames = keys as readonly string[]
|
|
636
918
|
if (dialect.name === "postgres") {
|
|
637
919
|
if (kind === "jsonHasAnyKeys") {
|
|
638
|
-
return `(${baseSql} ?| array[${
|
|
920
|
+
return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
639
921
|
}
|
|
640
922
|
if (kind === "jsonHasAllKeys") {
|
|
641
|
-
return `(${baseSql} ?& array[${
|
|
923
|
+
return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
642
924
|
}
|
|
643
|
-
return `(${baseSql} ? ${renderPostgresTextLiteral(
|
|
925
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
|
|
644
926
|
}
|
|
645
927
|
if (dialect.name === "mysql") {
|
|
646
928
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
647
929
|
const modeSql = dialect.renderLiteral(mode, state)
|
|
648
|
-
const paths =
|
|
930
|
+
const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
|
|
649
931
|
return `json_contains_path(${baseSql}, ${modeSql}, ${paths})`
|
|
650
932
|
}
|
|
651
933
|
return undefined
|
|
@@ -664,9 +946,7 @@ const renderJsonExpression = (
|
|
|
664
946
|
return undefined
|
|
665
947
|
}
|
|
666
948
|
case "jsonBuildObject": {
|
|
667
|
-
const entries =
|
|
668
|
-
? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
669
|
-
: []
|
|
949
|
+
const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
670
950
|
const renderedEntries = entries.flatMap((entry) => [
|
|
671
951
|
dialect.renderLiteral(entry.key, state),
|
|
672
952
|
renderJsonInputExpression(entry.value, state, dialect)
|
|
@@ -680,9 +960,7 @@ const renderJsonExpression = (
|
|
|
680
960
|
return undefined
|
|
681
961
|
}
|
|
682
962
|
case "jsonBuildArray": {
|
|
683
|
-
const values =
|
|
684
|
-
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
685
|
-
: []
|
|
963
|
+
const values = (ast as { readonly values: readonly Expression.Any[] }).values
|
|
686
964
|
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
687
965
|
if (dialect.name === "postgres") {
|
|
688
966
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
@@ -781,6 +1059,7 @@ const renderJsonExpression = (
|
|
|
781
1059
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
782
1060
|
}
|
|
783
1061
|
if (dialect.name === "mysql") {
|
|
1062
|
+
assertMySqlJsonMutationPath(segments)
|
|
784
1063
|
return `json_remove(${renderExpression(base, state, dialect)}, ${renderMySqlJsonPath(segments, state, dialect)})`
|
|
785
1064
|
}
|
|
786
1065
|
return undefined
|
|
@@ -805,6 +1084,7 @@ const renderJsonExpression = (
|
|
|
805
1084
|
return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
|
|
806
1085
|
}
|
|
807
1086
|
if (dialect.name === "mysql") {
|
|
1087
|
+
assertMySqlJsonMutationPath(segments)
|
|
808
1088
|
const renderedBase = renderExpression(base, state, dialect)
|
|
809
1089
|
if (kind === "jsonInsert" && isJsonArrayIndexSegment(segments[segments.length - 1])) {
|
|
810
1090
|
const renderedPath = renderMySqlJsonInsertPath(segments, insertAfter, state, dialect)
|
|
@@ -866,11 +1146,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
|
|
|
866
1146
|
const renderMutationAssignment = (
|
|
867
1147
|
entry: QueryAst.AssignmentClause,
|
|
868
1148
|
state: RenderState,
|
|
869
|
-
dialect: SqlDialect
|
|
1149
|
+
dialect: SqlDialect,
|
|
1150
|
+
targetTableName?: string
|
|
870
1151
|
): string => {
|
|
871
1152
|
const column = entry.tableName && dialect.name === "mysql"
|
|
872
|
-
? `${dialect.quoteIdentifier(entry.tableName)}.${
|
|
873
|
-
:
|
|
1153
|
+
? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
|
|
1154
|
+
: quoteColumn(entry.columnName, state, dialect, targetTableName)
|
|
874
1155
|
return `${column} = ${renderExpression(entry.value, state, dialect)}`
|
|
875
1156
|
}
|
|
876
1157
|
|
|
@@ -903,8 +1184,9 @@ const renderJoinPredicatesForMutation = (
|
|
|
903
1184
|
|
|
904
1185
|
const renderDeleteTargets = (
|
|
905
1186
|
targets: readonly QueryAst.FromClause[],
|
|
1187
|
+
state: RenderState,
|
|
906
1188
|
dialect: SqlDialect
|
|
907
|
-
): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
|
|
1189
|
+
): string => targets.map((target) => dialect.quoteIdentifier(casedTableReferenceName(target.tableName, state))).join(", ")
|
|
908
1190
|
|
|
909
1191
|
const renderMysqlMutationLock = (
|
|
910
1192
|
lock: QueryAst.LockClause | undefined,
|
|
@@ -927,7 +1209,7 @@ const renderTransactionClause = (
|
|
|
927
1209
|
if (isolationLevel) {
|
|
928
1210
|
modes.push(isolationLevel)
|
|
929
1211
|
}
|
|
930
|
-
if (clause.readOnly
|
|
1212
|
+
if (normalizeStatementFlag(clause.readOnly)) {
|
|
931
1213
|
modes.push("read only")
|
|
932
1214
|
}
|
|
933
1215
|
return modes.length > 0
|
|
@@ -939,28 +1221,21 @@ const renderTransactionClause = (
|
|
|
939
1221
|
case "rollback":
|
|
940
1222
|
return "rollback"
|
|
941
1223
|
case "savepoint":
|
|
942
|
-
return `savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1224
|
+
return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
|
|
943
1225
|
case "rollbackTo":
|
|
944
|
-
return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1226
|
+
return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
|
|
945
1227
|
case "releaseSavepoint":
|
|
946
|
-
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1228
|
+
return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
|
|
947
1229
|
}
|
|
948
|
-
|
|
1230
|
+
return "start transaction"
|
|
949
1231
|
}
|
|
950
1232
|
|
|
951
1233
|
const renderSelectionList = (
|
|
952
1234
|
selection: Record<string, unknown>,
|
|
953
1235
|
state: RenderState,
|
|
954
|
-
dialect: SqlDialect
|
|
955
|
-
validateAggregation: boolean
|
|
1236
|
+
dialect: SqlDialect
|
|
956
1237
|
): RenderedQueryAst => {
|
|
957
|
-
if (validateAggregation) {
|
|
958
|
-
validateAggregationSelection(selection as SelectionValue, [])
|
|
959
|
-
}
|
|
960
1238
|
const flattened = flattenSelection(selection)
|
|
961
|
-
if (dialect.name === "mysql" && flattened.length === 0) {
|
|
962
|
-
throw new Error("mysql select statements require at least one selected expression")
|
|
963
|
-
}
|
|
964
1239
|
const projections = selectionProjections(selection)
|
|
965
1240
|
const sql = flattened.map(({ expression, alias }) =>
|
|
966
1241
|
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
@@ -973,110 +1248,26 @@ const renderSelectionList = (
|
|
|
973
1248
|
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
974
1249
|
params: state.params,
|
|
975
1250
|
valueMappings: state.valueMappings,
|
|
1251
|
+
casing: state.casing,
|
|
976
1252
|
ctes: [],
|
|
977
1253
|
cteNames: new Set(state.cteNames),
|
|
978
|
-
cteSources: new Map(state.cteSources)
|
|
1254
|
+
cteSources: new Map(state.cteSources),
|
|
1255
|
+
sourceNames: new Map(state.sourceNames)
|
|
979
1256
|
})
|
|
980
1257
|
|
|
981
|
-
const assertMatchingSetProjections = (
|
|
982
|
-
left: readonly Projection[],
|
|
983
|
-
right: readonly Projection[]
|
|
984
|
-
): void => {
|
|
985
|
-
const leftKeys = left.map((projection) => JSON.stringify(projection.path))
|
|
986
|
-
const rightKeys = right.map((projection) => JSON.stringify(projection.path))
|
|
987
|
-
if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
|
|
988
|
-
throw new Error("set operator operands must have matching result rows")
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
const assertNoGroupedMutationClauses = (
|
|
993
|
-
ast: Pick<QueryAst.Ast, "groupBy" | "having">,
|
|
994
|
-
statement: string
|
|
995
|
-
): void => {
|
|
996
|
-
if (ast.groupBy.length > 0) {
|
|
997
|
-
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
998
|
-
}
|
|
999
|
-
if (ast.having.length > 0) {
|
|
1000
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
const assertNoInsertQueryClauses = (
|
|
1005
|
-
ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
|
|
1006
|
-
): void => {
|
|
1007
|
-
if (ast.where.length > 0) {
|
|
1008
|
-
throw new Error("where(...) is not supported for insert statements")
|
|
1009
|
-
}
|
|
1010
|
-
if (ast.joins.length > 0) {
|
|
1011
|
-
throw new Error("join(...) is not supported for insert statements")
|
|
1012
|
-
}
|
|
1013
|
-
if (ast.orderBy.length > 0) {
|
|
1014
|
-
throw new Error("orderBy(...) is not supported for insert statements")
|
|
1015
|
-
}
|
|
1016
|
-
if (ast.limit) {
|
|
1017
|
-
throw new Error("limit(...) is not supported for insert statements")
|
|
1018
|
-
}
|
|
1019
|
-
if (ast.offset) {
|
|
1020
|
-
throw new Error("offset(...) is not supported for insert statements")
|
|
1021
|
-
}
|
|
1022
|
-
if (ast.lock) {
|
|
1023
|
-
throw new Error("lock(...) is not supported for insert statements")
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
const assertNoStatementQueryClauses = (
|
|
1028
|
-
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1029
|
-
statement: string,
|
|
1030
|
-
options: { readonly allowSelection?: boolean } = {}
|
|
1031
|
-
): void => {
|
|
1032
|
-
if (ast.distinct) {
|
|
1033
|
-
throw new Error(`distinct(...) is not supported for ${statement} statements`)
|
|
1034
|
-
}
|
|
1035
|
-
if (ast.where.length > 0) {
|
|
1036
|
-
throw new Error(`where(...) is not supported for ${statement} statements`)
|
|
1037
|
-
}
|
|
1038
|
-
if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
|
|
1039
|
-
throw new Error(`from(...) is not supported for ${statement} statements`)
|
|
1040
|
-
}
|
|
1041
|
-
if (ast.joins.length > 0) {
|
|
1042
|
-
throw new Error(`join(...) is not supported for ${statement} statements`)
|
|
1043
|
-
}
|
|
1044
|
-
if (ast.groupBy.length > 0) {
|
|
1045
|
-
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
1046
|
-
}
|
|
1047
|
-
if (ast.having.length > 0) {
|
|
1048
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1049
|
-
}
|
|
1050
|
-
if (ast.orderBy.length > 0) {
|
|
1051
|
-
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
1052
|
-
}
|
|
1053
|
-
if (ast.limit) {
|
|
1054
|
-
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
1055
|
-
}
|
|
1056
|
-
if (ast.offset) {
|
|
1057
|
-
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
1058
|
-
}
|
|
1059
|
-
if (ast.lock) {
|
|
1060
|
-
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
1061
|
-
}
|
|
1062
|
-
if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
|
|
1063
|
-
throw new Error(`returning(...) is not supported for ${statement} statements`)
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1258
|
export const renderQueryAst = (
|
|
1068
1259
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1069
1260
|
state: RenderState,
|
|
1070
1261
|
dialect: SqlDialect,
|
|
1071
1262
|
options: { readonly emitCtes?: boolean } = {}
|
|
1072
1263
|
): RenderedQueryAst => {
|
|
1264
|
+
registerQuerySources(ast, state)
|
|
1073
1265
|
let sql = ""
|
|
1074
1266
|
let projections: readonly Projection[] = []
|
|
1075
1267
|
|
|
1076
1268
|
switch (ast.kind) {
|
|
1077
1269
|
case "select": {
|
|
1078
|
-
|
|
1079
|
-
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
1270
|
+
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
|
|
1080
1271
|
projections = rendered.projections
|
|
1081
1272
|
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
1082
1273
|
const clauses = [
|
|
@@ -1117,9 +1308,6 @@ export const renderQueryAst = (
|
|
|
1117
1308
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
1118
1309
|
}
|
|
1119
1310
|
if (ast.lock) {
|
|
1120
|
-
if (ast.lock.nowait && ast.lock.skipLocked) {
|
|
1121
|
-
throw new Error("lock(...) cannot specify both nowait and skipLocked")
|
|
1122
|
-
}
|
|
1123
1311
|
clauses.push(
|
|
1124
1312
|
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
1125
1313
|
)
|
|
@@ -1129,7 +1317,6 @@ export const renderQueryAst = (
|
|
|
1129
1317
|
}
|
|
1130
1318
|
case "set": {
|
|
1131
1319
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1132
|
-
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
1133
1320
|
const base = renderQueryAst(
|
|
1134
1321
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
1135
1322
|
Record<string, unknown>,
|
|
@@ -1140,7 +1327,6 @@ export const renderQueryAst = (
|
|
|
1140
1327
|
dialect
|
|
1141
1328
|
)
|
|
1142
1329
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1143
|
-
assertMatchingSetProjections(projections, base.projections)
|
|
1144
1330
|
sql = [
|
|
1145
1331
|
`(${base.sql})`,
|
|
1146
1332
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -1153,7 +1339,6 @@ export const renderQueryAst = (
|
|
|
1153
1339
|
state,
|
|
1154
1340
|
dialect
|
|
1155
1341
|
)
|
|
1156
|
-
assertMatchingSetProjections(projections, rendered.projections)
|
|
1157
1342
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
1158
1343
|
})
|
|
1159
1344
|
].join(" ")
|
|
@@ -1161,24 +1346,20 @@ export const renderQueryAst = (
|
|
|
1161
1346
|
}
|
|
1162
1347
|
case "insert": {
|
|
1163
1348
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
1164
|
-
if (insertAst.distinct) {
|
|
1165
|
-
throw new Error("distinct(...) is not supported for insert statements")
|
|
1166
|
-
}
|
|
1167
|
-
assertNoGroupedMutationClauses(insertAst, "insert")
|
|
1168
|
-
assertNoInsertQueryClauses(insertAst)
|
|
1169
1349
|
const targetSource = insertAst.into!
|
|
1170
1350
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1171
|
-
const
|
|
1351
|
+
const targetCasingState = stateWithTableCasing(state, targetSource.source)
|
|
1352
|
+
const insertSource = insertAst.insertSource
|
|
1172
1353
|
const conflict = expectConflictClause(insertAst.conflict)
|
|
1173
1354
|
sql = `insert into ${target}`
|
|
1174
1355
|
if (insertSource?.kind === "values") {
|
|
1175
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1356
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1176
1357
|
const rows = insertSource.rows.map((row) =>
|
|
1177
|
-
`(${row.values.map((entry) => renderExpression(entry.value,
|
|
1358
|
+
`(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
|
|
1178
1359
|
).join(", ")
|
|
1179
1360
|
sql += ` (${columns}) values ${rows}`
|
|
1180
1361
|
} else if (insertSource?.kind === "query") {
|
|
1181
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1362
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1182
1363
|
const renderedQuery = renderQueryAst(
|
|
1183
1364
|
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
1184
1365
|
Record<string, unknown>,
|
|
@@ -1190,7 +1371,7 @@ export const renderQueryAst = (
|
|
|
1190
1371
|
)
|
|
1191
1372
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
1192
1373
|
} else if (insertSource?.kind === "unnest") {
|
|
1193
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1374
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1194
1375
|
if (dialect.name === "postgres") {
|
|
1195
1376
|
const table = targetSource.source as Table.AnyTable
|
|
1196
1377
|
const fields = table[Table.TypeId].fields
|
|
@@ -1212,31 +1393,30 @@ export const renderQueryAst = (
|
|
|
1212
1393
|
sql += ` (${columns}) values ${rows}`
|
|
1213
1394
|
}
|
|
1214
1395
|
} else {
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1396
|
+
const insertValues = insertAst.values ?? []
|
|
1397
|
+
const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
|
|
1398
|
+
const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
|
|
1399
|
+
if (insertValues.length > 0) {
|
|
1218
1400
|
sql += ` (${columns}) values (${values})`
|
|
1219
1401
|
} else {
|
|
1220
1402
|
sql += " () values ()"
|
|
1221
1403
|
}
|
|
1222
1404
|
}
|
|
1223
1405
|
if (conflict) {
|
|
1224
|
-
|
|
1225
|
-
throw new Error("Unsupported mysql conflict action predicates")
|
|
1226
|
-
}
|
|
1406
|
+
const conflictValueState = { ...targetCasingState, allowExcluded: true }
|
|
1227
1407
|
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
1228
|
-
`${
|
|
1408
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
|
|
1229
1409
|
).join(", ")
|
|
1230
1410
|
if (dialect.name === "postgres") {
|
|
1231
1411
|
const targetSql = conflict.target?.kind === "constraint"
|
|
1232
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
|
|
1412
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
|
|
1233
1413
|
: conflict.target?.kind === "columns"
|
|
1234
|
-
? ` on conflict (${conflict.target.columns.map((column) => dialect.
|
|
1414
|
+
? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
|
|
1235
1415
|
: " on conflict"
|
|
1236
1416
|
sql += targetSql
|
|
1237
1417
|
sql += conflict.action === "doNothing"
|
|
1238
1418
|
? " do nothing"
|
|
1239
|
-
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where,
|
|
1419
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
|
|
1240
1420
|
} else if (conflict.action === "doNothing") {
|
|
1241
1421
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
1242
1422
|
} else {
|
|
@@ -1245,7 +1425,7 @@ export const renderQueryAst = (
|
|
|
1245
1425
|
}
|
|
1246
1426
|
const hasReturning = Object.keys(insertAst.select as Record<string, unknown>).length > 0
|
|
1247
1427
|
const returning = hasReturning
|
|
1248
|
-
? renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect
|
|
1428
|
+
? renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
|
|
1249
1429
|
: { sql: "", projections: [] }
|
|
1250
1430
|
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1251
1431
|
throw new Error("Unsupported mysql returning")
|
|
@@ -1258,22 +1438,12 @@ export const renderQueryAst = (
|
|
|
1258
1438
|
}
|
|
1259
1439
|
case "update": {
|
|
1260
1440
|
const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
|
|
1261
|
-
if (updateAst.distinct) {
|
|
1262
|
-
throw new Error("distinct(...) is not supported for update statements")
|
|
1263
|
-
}
|
|
1264
|
-
assertNoGroupedMutationClauses(updateAst, "update")
|
|
1265
|
-
if (updateAst.offset) {
|
|
1266
|
-
throw new Error("offset(...) is not supported for update statements")
|
|
1267
|
-
}
|
|
1268
1441
|
const targetSource = updateAst.target!
|
|
1269
1442
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1270
1443
|
const targets = updateAst.targets ?? [targetSource]
|
|
1271
1444
|
const fromSources = updateAst.fromSources ?? []
|
|
1272
|
-
if ((updateAst.set ?? []).length === 0) {
|
|
1273
|
-
throw new Error("update statements require at least one assignment")
|
|
1274
|
-
}
|
|
1275
1445
|
const assignments = updateAst.set!.map((entry) =>
|
|
1276
|
-
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
1446
|
+
renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
|
|
1277
1447
|
if (dialect.name === "mysql") {
|
|
1278
1448
|
const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
|
|
1279
1449
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
@@ -1318,7 +1488,7 @@ export const renderQueryAst = (
|
|
|
1318
1488
|
}
|
|
1319
1489
|
const hasReturning = Object.keys(updateAst.select as Record<string, unknown>).length > 0
|
|
1320
1490
|
const returning = hasReturning
|
|
1321
|
-
? renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect
|
|
1491
|
+
? renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
|
|
1322
1492
|
: { sql: "", projections: [] }
|
|
1323
1493
|
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1324
1494
|
throw new Error("Unsupported mysql returning")
|
|
@@ -1331,20 +1501,13 @@ export const renderQueryAst = (
|
|
|
1331
1501
|
}
|
|
1332
1502
|
case "delete": {
|
|
1333
1503
|
const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
|
|
1334
|
-
if (deleteAst.distinct) {
|
|
1335
|
-
throw new Error("distinct(...) is not supported for delete statements")
|
|
1336
|
-
}
|
|
1337
|
-
assertNoGroupedMutationClauses(deleteAst, "delete")
|
|
1338
|
-
if (deleteAst.offset) {
|
|
1339
|
-
throw new Error("offset(...) is not supported for delete statements")
|
|
1340
|
-
}
|
|
1341
1504
|
const targetSource = deleteAst.target!
|
|
1342
1505
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1343
1506
|
const targets = deleteAst.targets ?? [targetSource]
|
|
1344
1507
|
if (dialect.name === "mysql") {
|
|
1345
1508
|
const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
|
|
1346
1509
|
const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
|
|
1347
|
-
const targetList = renderDeleteTargets(targets, dialect)
|
|
1510
|
+
const targetList = renderDeleteTargets(targets, state, dialect)
|
|
1348
1511
|
const fromSources = targets.map((entry) =>
|
|
1349
1512
|
renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
|
|
1350
1513
|
).join(", ")
|
|
@@ -1381,7 +1544,7 @@ export const renderQueryAst = (
|
|
|
1381
1544
|
}
|
|
1382
1545
|
const hasReturning = Object.keys(deleteAst.select as Record<string, unknown>).length > 0
|
|
1383
1546
|
const returning = hasReturning
|
|
1384
|
-
? renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect
|
|
1547
|
+
? renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
|
|
1385
1548
|
: { sql: "", projections: [] }
|
|
1386
1549
|
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1387
1550
|
throw new Error("Unsupported mysql returning")
|
|
@@ -1394,17 +1557,18 @@ export const renderQueryAst = (
|
|
|
1394
1557
|
}
|
|
1395
1558
|
case "truncate": {
|
|
1396
1559
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1397
|
-
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1398
1560
|
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1399
1561
|
const targetSource = truncateAst.target!
|
|
1400
|
-
|
|
1562
|
+
const restartIdentity = truncate.restartIdentity
|
|
1563
|
+
const cascade = truncate.cascade
|
|
1564
|
+
if (dialect.name === "mysql" && (restartIdentity || cascade)) {
|
|
1401
1565
|
throw new Error("Unsupported mysql truncate options")
|
|
1402
1566
|
}
|
|
1403
1567
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1404
|
-
if (
|
|
1568
|
+
if (restartIdentity) {
|
|
1405
1569
|
sql += " restart identity"
|
|
1406
1570
|
}
|
|
1407
|
-
if (
|
|
1571
|
+
if (cascade) {
|
|
1408
1572
|
sql += " cascade"
|
|
1409
1573
|
}
|
|
1410
1574
|
break
|
|
@@ -1417,9 +1581,6 @@ export const renderQueryAst = (
|
|
|
1417
1581
|
const targetSource = mergeAst.target!
|
|
1418
1582
|
const usingSource = mergeAst.using!
|
|
1419
1583
|
const merge = mergeAst.merge!
|
|
1420
|
-
if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
|
|
1421
|
-
throw new Error("returning(...) is not supported for merge statements")
|
|
1422
|
-
}
|
|
1423
1584
|
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)}`
|
|
1424
1585
|
if (merge.whenMatched) {
|
|
1425
1586
|
sql += " when matched"
|
|
@@ -1449,27 +1610,24 @@ export const renderQueryAst = (
|
|
|
1449
1610
|
case "savepoint":
|
|
1450
1611
|
case "rollbackTo":
|
|
1451
1612
|
case "releaseSavepoint": {
|
|
1452
|
-
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1453
1613
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1454
1614
|
break
|
|
1455
1615
|
}
|
|
1456
1616
|
case "createTable": {
|
|
1457
1617
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1458
|
-
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1459
1618
|
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1460
1619
|
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1461
1620
|
break
|
|
1462
1621
|
}
|
|
1463
1622
|
case "dropTable": {
|
|
1464
1623
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1465
|
-
assertNoStatementQueryClauses(dropTableAst, "dropTable")
|
|
1466
1624
|
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1467
|
-
|
|
1625
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
1626
|
+
sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1468
1627
|
break
|
|
1469
1628
|
}
|
|
1470
1629
|
case "createIndex": {
|
|
1471
1630
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1472
|
-
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1473
1631
|
sql = renderCreateIndexSql(
|
|
1474
1632
|
createIndexAst.target!,
|
|
1475
1633
|
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
@@ -1480,7 +1638,6 @@ export const renderQueryAst = (
|
|
|
1480
1638
|
}
|
|
1481
1639
|
case "dropIndex": {
|
|
1482
1640
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1483
|
-
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1484
1641
|
sql = renderDropIndexSql(
|
|
1485
1642
|
dropIndexAst.target!,
|
|
1486
1643
|
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
@@ -1489,8 +1646,12 @@ export const renderQueryAst = (
|
|
|
1489
1646
|
)
|
|
1490
1647
|
break
|
|
1491
1648
|
}
|
|
1492
|
-
default:
|
|
1493
|
-
|
|
1649
|
+
default: {
|
|
1650
|
+
if (ast.transaction !== undefined) {
|
|
1651
|
+
sql = renderTransactionClause(ast.transaction, dialect)
|
|
1652
|
+
}
|
|
1653
|
+
break
|
|
1654
|
+
}
|
|
1494
1655
|
}
|
|
1495
1656
|
|
|
1496
1657
|
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
@@ -1611,13 +1772,31 @@ const renderSourceReference = (
|
|
|
1611
1772
|
if (dialect.name !== "postgres") {
|
|
1612
1773
|
throw new Error("Unsupported table function source for SQL rendering")
|
|
1613
1774
|
}
|
|
1775
|
+
const functionName = renderFunctionName(tableFunction.functionName)
|
|
1614
1776
|
const columnNames = Object.keys(tableFunction.columns)
|
|
1615
|
-
return `${
|
|
1777
|
+
return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
|
|
1616
1778
|
}
|
|
1617
1779
|
const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
|
|
1618
1780
|
? (source as Table.AnyTable)[Table.TypeId].schemaName
|
|
1619
1781
|
: undefined
|
|
1620
|
-
|
|
1782
|
+
if (typeof source === "object" && source !== null && Table.TypeId in source) {
|
|
1783
|
+
const table = source as Table.AnyTable
|
|
1784
|
+
const tableState = table[Table.TypeId]
|
|
1785
|
+
const casing = casingForTable(table, state)
|
|
1786
|
+
const renderedTableName = tableState.kind === "alias"
|
|
1787
|
+
? tableName
|
|
1788
|
+
: Casing.applyCategory(casing, "tables", baseTableName)
|
|
1789
|
+
const renderedBaseName = Casing.applyCategory(casing, "tables", baseTableName)
|
|
1790
|
+
const renderedSchemaName = schemaName === undefined
|
|
1791
|
+
? undefined
|
|
1792
|
+
: Casing.applyCategory(casing, "schemas", schemaName)
|
|
1793
|
+
return dialect.renderTableReference(renderedTableName, renderedBaseName, renderedSchemaName)
|
|
1794
|
+
}
|
|
1795
|
+
return dialect.renderTableReference(
|
|
1796
|
+
Casing.applyCategory(state.casing, "tables", tableName),
|
|
1797
|
+
Casing.applyCategory(state.casing, "tables", baseTableName),
|
|
1798
|
+
schemaName === undefined ? undefined : Casing.applyCategory(state.casing, "schemas", schemaName)
|
|
1799
|
+
)
|
|
1621
1800
|
}
|
|
1622
1801
|
|
|
1623
1802
|
const renderSubqueryExpressionPlan = (
|
|
@@ -1656,157 +1835,165 @@ export const renderExpression = (
|
|
|
1656
1835
|
return jsonSql
|
|
1657
1836
|
}
|
|
1658
1837
|
const ast = rawAst as ExpressionAst.Any
|
|
1659
|
-
const renderComparisonOperator = (operator:
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
:
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
? ">"
|
|
1670
|
-
: ">="
|
|
1671
|
-
switch (ast.kind) {
|
|
1838
|
+
const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
|
|
1839
|
+
({
|
|
1840
|
+
eq: "=",
|
|
1841
|
+
neq: "<>",
|
|
1842
|
+
lt: "<",
|
|
1843
|
+
lte: "<=",
|
|
1844
|
+
gt: ">",
|
|
1845
|
+
gte: ">="
|
|
1846
|
+
} as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
|
|
1847
|
+
switch (ast.kind) {
|
|
1672
1848
|
case "column":
|
|
1673
1849
|
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1674
|
-
?
|
|
1675
|
-
: `${dialect.quoteIdentifier(ast.tableName)}.${
|
|
1850
|
+
? quoteColumn(ast.columnName, state, dialect, ast.tableName)
|
|
1851
|
+
: `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
|
|
1676
1852
|
case "literal":
|
|
1677
1853
|
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1678
1854
|
throw new Error("Expected a finite numeric value")
|
|
1679
1855
|
}
|
|
1680
1856
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1681
1857
|
case "excluded":
|
|
1858
|
+
if (state.allowExcluded !== true) {
|
|
1859
|
+
throw new Error("excluded(...) is only supported inside insert conflict handlers")
|
|
1860
|
+
}
|
|
1682
1861
|
return dialect.name === "mysql"
|
|
1683
|
-
? `values(${
|
|
1684
|
-
: `excluded.${
|
|
1862
|
+
? `values(${quoteColumn(ast.columnName, state, dialect)})`
|
|
1863
|
+
: `excluded.${quoteColumn(ast.columnName, state, dialect)}`
|
|
1685
1864
|
case "cast":
|
|
1686
|
-
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1865
|
+
return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1687
1866
|
case "function":
|
|
1688
|
-
return renderFunctionCall(ast.name,
|
|
1867
|
+
return renderFunctionCall(ast.name, ast.args, state, dialect)
|
|
1689
1868
|
case "eq":
|
|
1690
|
-
return
|
|
1869
|
+
return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
|
|
1691
1870
|
case "neq":
|
|
1692
|
-
return
|
|
1871
|
+
return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
|
|
1693
1872
|
case "lt":
|
|
1694
|
-
return
|
|
1873
|
+
return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
|
|
1695
1874
|
case "lte":
|
|
1696
|
-
return
|
|
1875
|
+
return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
|
|
1697
1876
|
case "gt":
|
|
1698
|
-
return
|
|
1877
|
+
return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
|
|
1699
1878
|
case "gte":
|
|
1700
|
-
return
|
|
1879
|
+
return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
|
|
1701
1880
|
case "like":
|
|
1702
|
-
return
|
|
1703
|
-
case "ilike":
|
|
1881
|
+
return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
|
|
1882
|
+
case "ilike": {
|
|
1883
|
+
const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
|
|
1704
1884
|
return dialect.name === "postgres"
|
|
1705
|
-
? `(${renderExpression(
|
|
1706
|
-
: `(lower(${renderExpression(
|
|
1707
|
-
|
|
1885
|
+
? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
|
|
1886
|
+
: `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
|
|
1887
|
+
}
|
|
1888
|
+
case "regexMatch": {
|
|
1889
|
+
const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
|
|
1708
1890
|
return dialect.name === "postgres"
|
|
1709
|
-
? `(${renderExpression(
|
|
1710
|
-
: `(${renderExpression(
|
|
1711
|
-
|
|
1891
|
+
? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
|
|
1892
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1893
|
+
}
|
|
1894
|
+
case "regexIMatch": {
|
|
1895
|
+
const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
|
|
1712
1896
|
return dialect.name === "postgres"
|
|
1713
|
-
? `(${renderExpression(
|
|
1714
|
-
: `(${renderExpression(
|
|
1715
|
-
|
|
1897
|
+
? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
|
|
1898
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1899
|
+
}
|
|
1900
|
+
case "regexNotMatch": {
|
|
1901
|
+
const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
|
|
1716
1902
|
return dialect.name === "postgres"
|
|
1717
|
-
? `(${renderExpression(
|
|
1718
|
-
: `(${renderExpression(
|
|
1719
|
-
|
|
1903
|
+
? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
|
|
1904
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1905
|
+
}
|
|
1906
|
+
case "regexNotIMatch": {
|
|
1907
|
+
const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
|
|
1720
1908
|
return dialect.name === "postgres"
|
|
1721
|
-
? `(${renderExpression(
|
|
1722
|
-
: `(${renderExpression(
|
|
1723
|
-
|
|
1909
|
+
? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
|
|
1910
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1911
|
+
}
|
|
1912
|
+
case "isDistinctFrom": {
|
|
1913
|
+
const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
|
|
1724
1914
|
return dialect.name === "mysql"
|
|
1725
|
-
? `(not (${renderExpression(
|
|
1726
|
-
: `(${renderExpression(
|
|
1727
|
-
|
|
1915
|
+
? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
|
|
1916
|
+
: `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
|
|
1917
|
+
}
|
|
1918
|
+
case "isNotDistinctFrom": {
|
|
1919
|
+
const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
|
|
1728
1920
|
return dialect.name === "mysql"
|
|
1729
|
-
? `(${renderExpression(
|
|
1730
|
-
: `(${renderExpression(
|
|
1731
|
-
|
|
1921
|
+
? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
|
|
1922
|
+
: `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
|
|
1923
|
+
}
|
|
1924
|
+
case "contains": {
|
|
1925
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
|
|
1732
1926
|
if (dialect.name === "postgres") {
|
|
1733
|
-
const left = isJsonExpression(
|
|
1734
|
-
? renderPostgresJsonValue(
|
|
1735
|
-
: renderExpression(
|
|
1736
|
-
const right = isJsonExpression(
|
|
1737
|
-
? renderPostgresJsonValue(
|
|
1738
|
-
: renderExpression(
|
|
1927
|
+
const left = isJsonExpression(leftExpression)
|
|
1928
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1929
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1930
|
+
const right = isJsonExpression(rightExpression)
|
|
1931
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1932
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1739
1933
|
return `(${left} @> ${right})`
|
|
1740
1934
|
}
|
|
1741
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1742
|
-
return `json_contains(${renderJsonInputExpression(
|
|
1935
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1936
|
+
return `json_contains(${renderJsonInputExpression(leftExpression, state, dialect)}, ${renderJsonInputExpression(rightExpression, state, dialect)})`
|
|
1743
1937
|
}
|
|
1744
1938
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1745
|
-
|
|
1939
|
+
}
|
|
1940
|
+
case "containedBy": {
|
|
1941
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
|
|
1746
1942
|
if (dialect.name === "postgres") {
|
|
1747
|
-
const left = isJsonExpression(
|
|
1748
|
-
? renderPostgresJsonValue(
|
|
1749
|
-
: renderExpression(
|
|
1750
|
-
const right = isJsonExpression(
|
|
1751
|
-
? renderPostgresJsonValue(
|
|
1752
|
-
: renderExpression(
|
|
1943
|
+
const left = isJsonExpression(leftExpression)
|
|
1944
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1945
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1946
|
+
const right = isJsonExpression(rightExpression)
|
|
1947
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1948
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1753
1949
|
return `(${left} <@ ${right})`
|
|
1754
1950
|
}
|
|
1755
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1756
|
-
return `json_contains(${renderJsonInputExpression(
|
|
1951
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1952
|
+
return `json_contains(${renderJsonInputExpression(rightExpression, state, dialect)}, ${renderJsonInputExpression(leftExpression, state, dialect)})`
|
|
1757
1953
|
}
|
|
1758
1954
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1759
|
-
|
|
1955
|
+
}
|
|
1956
|
+
case "overlaps": {
|
|
1957
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
|
|
1760
1958
|
if (dialect.name === "postgres") {
|
|
1761
|
-
const left = isJsonExpression(
|
|
1762
|
-
? renderPostgresJsonValue(
|
|
1763
|
-
: renderExpression(
|
|
1764
|
-
const right = isJsonExpression(
|
|
1765
|
-
? renderPostgresJsonValue(
|
|
1766
|
-
: renderExpression(
|
|
1959
|
+
const left = isJsonExpression(leftExpression)
|
|
1960
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1961
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1962
|
+
const right = isJsonExpression(rightExpression)
|
|
1963
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1964
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1767
1965
|
return `(${left} && ${right})`
|
|
1768
1966
|
}
|
|
1769
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1770
|
-
return `json_overlaps(${renderJsonInputExpression(
|
|
1967
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1968
|
+
return `json_overlaps(${renderJsonInputExpression(leftExpression, state, dialect)}, ${renderJsonInputExpression(rightExpression, state, dialect)})`
|
|
1771
1969
|
}
|
|
1772
1970
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1971
|
+
}
|
|
1773
1972
|
case "isNull":
|
|
1774
|
-
return `(${renderExpression(ast.value, state, dialect)} is null)`
|
|
1973
|
+
return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
|
|
1775
1974
|
case "isNotNull":
|
|
1776
|
-
return `(${renderExpression(ast.value, state, dialect)} is not null)`
|
|
1975
|
+
return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
|
|
1777
1976
|
case "not":
|
|
1778
|
-
return `(not ${renderExpression(ast.value, state, dialect)})`
|
|
1977
|
+
return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
|
|
1779
1978
|
case "upper":
|
|
1780
|
-
return `upper(${renderExpression(ast.value, state, dialect)})`
|
|
1979
|
+
return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
|
|
1781
1980
|
case "lower":
|
|
1782
|
-
return `lower(${renderExpression(ast.value, state, dialect)})`
|
|
1981
|
+
return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
|
|
1783
1982
|
case "count":
|
|
1784
|
-
return `count(${renderExpression(ast.value, state, dialect)})`
|
|
1983
|
+
return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
|
|
1785
1984
|
case "max":
|
|
1786
|
-
return `max(${renderExpression(ast.value, state, dialect)})`
|
|
1985
|
+
return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
|
|
1787
1986
|
case "min":
|
|
1788
|
-
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1987
|
+
return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
|
|
1789
1988
|
case "and":
|
|
1790
|
-
if (ast.values.length === 0) {
|
|
1791
|
-
throw new Error("and(...) requires at least one predicate")
|
|
1792
|
-
}
|
|
1793
1989
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1794
1990
|
case "or":
|
|
1795
|
-
if (ast.values.length === 0) {
|
|
1796
|
-
throw new Error("or(...) requires at least one predicate")
|
|
1797
|
-
}
|
|
1798
1991
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1799
1992
|
case "coalesce":
|
|
1800
1993
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1801
1994
|
case "in":
|
|
1802
|
-
if (ast.values.length < 2) {
|
|
1803
|
-
throw new Error("in(...) requires at least one candidate value")
|
|
1804
|
-
}
|
|
1805
1995
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1806
1996
|
case "notIn":
|
|
1807
|
-
if (ast.values.length < 2) {
|
|
1808
|
-
throw new Error("notIn(...) requires at least one candidate value")
|
|
1809
|
-
}
|
|
1810
1997
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1811
1998
|
case "between":
|
|
1812
1999
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1821,21 +2008,23 @@ export const renderExpression = (
|
|
|
1821
2008
|
case "scalarSubquery":
|
|
1822
2009
|
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1823
2010
|
case "inSubquery":
|
|
1824
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2011
|
+
return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1825
2012
|
case "comparisonAny":
|
|
1826
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2013
|
+
return `(${renderExpression(expectValueExpression("compareAny", ast.left), state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1827
2014
|
case "comparisonAll":
|
|
1828
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2015
|
+
return `(${renderExpression(expectValueExpression("compareAll", ast.left), state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1829
2016
|
case "window": {
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2017
|
+
const partitionBy = ast.partitionBy as readonly Expression.Any[]
|
|
2018
|
+
const orderBy = ast.orderBy as readonly {
|
|
2019
|
+
readonly value: Expression.Any
|
|
2020
|
+
readonly direction: string
|
|
2021
|
+
}[]
|
|
1833
2022
|
const clauses: string[] = []
|
|
1834
|
-
if (
|
|
1835
|
-
clauses.push(`partition by ${
|
|
2023
|
+
if (partitionBy.length > 0) {
|
|
2024
|
+
clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
|
|
1836
2025
|
}
|
|
1837
|
-
if (
|
|
1838
|
-
clauses.push(`order by ${
|
|
2026
|
+
if (orderBy.length > 0) {
|
|
2027
|
+
clauses.push(`order by ${orderBy.map((entry) =>
|
|
1839
2028
|
`${renderExpression(entry.value, state, dialect)} ${entry.direction}`
|
|
1840
2029
|
).join(", ")}`)
|
|
1841
2030
|
}
|
|
@@ -1848,7 +2037,7 @@ export const renderExpression = (
|
|
|
1848
2037
|
case "denseRank":
|
|
1849
2038
|
return `dense_rank() over (${specification})`
|
|
1850
2039
|
case "over":
|
|
1851
|
-
return `${renderExpression(ast.value
|
|
2040
|
+
return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
|
|
1852
2041
|
}
|
|
1853
2042
|
break
|
|
1854
2043
|
}
|