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