effect-qb 0.16.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 +4036 -2418
- package/dist/postgres/metadata.js +2536 -625
- package/dist/postgres.js +8248 -7857
- package/dist/sqlite.js +8854 -0
- package/dist/standard.js +8019 -0
- package/package.json +15 -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 +7 -13
- package/src/internal/dialect-renderers/mysql.ts +2046 -0
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
- package/src/internal/dialect.ts +37 -0
- package/src/internal/dsl-mutation-runtime.ts +29 -10
- package/src/internal/dsl-plan-runtime.ts +41 -24
- package/src/internal/dsl-query-runtime.ts +11 -31
- package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
- package/src/internal/executor.ts +57 -15
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +216 -9
- package/src/internal/implication-runtime.ts +3 -2
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +30 -3
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +315 -54
- package/src/internal/renderer.ts +51 -6
- package/src/internal/runtime/driver-value-mapping.ts +58 -0
- package/src/internal/runtime/normalize.ts +74 -43
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- 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 +229 -62
- package/src/internal/table.d.ts +33 -32
- package/src/internal/table.ts +469 -160
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +27 -12
- package/src/mysql/datatypes/index.ts +24 -2
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +7 -5
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +906 -324
- package/src/mysql/internal/renderer.ts +7 -2
- package/src/mysql/json.ts +37 -0
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/query.ts +9 -2
- 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 +9 -13
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +3 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +55 -10
- package/src/postgres/function/core.ts +20 -4
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +850 -359
- package/src/postgres/internal/renderer.ts +7 -2
- package/src/postgres/internal/schema-ddl.ts +22 -9
- package/src/postgres/internal/schema-model.ts +244 -10
- package/src/postgres/json.ts +100 -24
- package/src/postgres/jsonb.ts +38 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +31 -4
- package/src/postgres/schema-management.ts +108 -16
- package/src/postgres/schema.ts +98 -15
- package/src/postgres/table.ts +203 -398
- 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 +127 -0
- package/src/sqlite/datatypes/index.ts +80 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +229 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +42 -0
- package/src/sqlite/internal/dsl.ts +6979 -0
- package/src/sqlite/internal/renderer.ts +51 -0
- package/src/sqlite/json.ts +39 -0
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +51 -0
- package/src/sqlite.ts +14 -0
- 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 -157
package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts}
RENAMED
|
@@ -1,38 +1,49 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
1
|
+
import * as Schema from "effect/Schema"
|
|
2
|
+
|
|
3
|
+
import * as Query from "../query.js"
|
|
4
|
+
import * as Expression from "../scalar.js"
|
|
5
|
+
import * as Table from "../table.js"
|
|
6
|
+
import * as QueryAst from "../query-ast.js"
|
|
7
|
+
import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
|
|
8
|
+
import * as ExpressionAst from "../expression-ast.js"
|
|
9
|
+
import * as JsonPath from "../json/path.js"
|
|
10
|
+
import { renderSelectLockMode } from "../dsl-plan-runtime.js"
|
|
11
|
+
import { expectConflictClause } from "../dsl-mutation-runtime.js"
|
|
12
|
+
import { expectDdlClauseKind, expectTruncateClause, normalizeStatementFlag, normalizeStatementIdentifier, renderTransactionIsolationLevel } from "../dsl-transaction-ddl-runtime.js"
|
|
8
13
|
import {
|
|
9
14
|
renderJsonSelectSql,
|
|
10
15
|
renderSelectSql,
|
|
11
16
|
toDriverValue
|
|
12
|
-
} from "
|
|
13
|
-
import {
|
|
14
|
-
import { type
|
|
15
|
-
import
|
|
16
|
-
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 { groupingKeyOfExpression } from "../grouping-key.js"
|
|
21
|
+
import * as SchemaExpression from "../schema-expression.js"
|
|
22
|
+
import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
|
|
23
|
+
import * as Casing from "../casing.js"
|
|
17
24
|
|
|
18
25
|
const renderDbType = (
|
|
19
26
|
dialect: SqlDialect,
|
|
20
27
|
dbType: Expression.DbType.Any
|
|
21
28
|
): string => {
|
|
22
|
-
if (dialect.name === "
|
|
23
|
-
return "
|
|
29
|
+
if (dialect.name === "postgres" && dbType.kind === "blob") {
|
|
30
|
+
return "bytea"
|
|
24
31
|
}
|
|
25
|
-
return dbType.kind
|
|
32
|
+
return renderDbTypeName(dbType.kind)
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
36
|
+
"element" in dbType
|
|
37
|
+
|
|
28
38
|
const renderCastType = (
|
|
29
39
|
dialect: SqlDialect,
|
|
30
|
-
dbType:
|
|
40
|
+
dbType: unknown
|
|
31
41
|
): string => {
|
|
42
|
+
const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
|
|
32
43
|
if (dialect.name !== "mysql") {
|
|
33
|
-
return
|
|
44
|
+
return renderDbTypeName(kind)
|
|
34
45
|
}
|
|
35
|
-
switch (
|
|
46
|
+
switch (kind) {
|
|
36
47
|
case "text":
|
|
37
48
|
return "char"
|
|
38
49
|
case "uuid":
|
|
@@ -47,35 +58,243 @@ const renderCastType = (
|
|
|
47
58
|
case "json":
|
|
48
59
|
return "json"
|
|
49
60
|
default:
|
|
50
|
-
return
|
|
61
|
+
return renderDbTypeName(kind)
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
const casingForTable = (
|
|
66
|
+
table: Table.AnyTable,
|
|
67
|
+
state: RenderState
|
|
68
|
+
): Casing.Options | undefined =>
|
|
69
|
+
Casing.merge(state.casing, table[Table.TypeId].casing)
|
|
70
|
+
|
|
71
|
+
const casedTableName = (
|
|
72
|
+
table: Table.AnyTable,
|
|
73
|
+
state: RenderState
|
|
74
|
+
): string => {
|
|
75
|
+
const tableState = table[Table.TypeId]
|
|
76
|
+
return Casing.applyCategory(casingForTable(table, state), "tables", tableState.baseName)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const casedSchemaName = (
|
|
80
|
+
table: Table.AnyTable,
|
|
81
|
+
state: RenderState
|
|
82
|
+
): string | undefined => {
|
|
83
|
+
const schemaName = table[Table.TypeId].schemaName
|
|
84
|
+
return schemaName === undefined
|
|
85
|
+
? undefined
|
|
86
|
+
: Casing.applyCategory(casingForTable(table, state), "schemas", schemaName)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const casedColumnName = (
|
|
90
|
+
columnName: string,
|
|
91
|
+
state: RenderState,
|
|
92
|
+
tableName?: string
|
|
93
|
+
): string => {
|
|
94
|
+
if (tableName !== undefined) {
|
|
95
|
+
const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
|
|
96
|
+
if (mapped !== undefined) {
|
|
97
|
+
return mapped
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return Casing.applyCategory(state.casing, "columns", columnName)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const casedTableReferenceName = (
|
|
104
|
+
tableName: string,
|
|
105
|
+
state: RenderState
|
|
106
|
+
): string =>
|
|
107
|
+
state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
|
|
108
|
+
|
|
109
|
+
const quoteColumn = (
|
|
110
|
+
columnName: string,
|
|
111
|
+
state: RenderState,
|
|
112
|
+
dialect: SqlDialect,
|
|
113
|
+
tableName?: string
|
|
114
|
+
): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
|
|
115
|
+
|
|
116
|
+
const stateWithTableCasing = (
|
|
117
|
+
state: RenderState,
|
|
118
|
+
source: unknown
|
|
119
|
+
): RenderState =>
|
|
120
|
+
typeof source === "object" && source !== null && Table.TypeId in source
|
|
121
|
+
? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
|
|
122
|
+
: state
|
|
123
|
+
|
|
124
|
+
const referenceCasing = (
|
|
125
|
+
reference: { readonly casing?: Casing.Options },
|
|
126
|
+
state: RenderState
|
|
127
|
+
): Casing.Options | undefined =>
|
|
128
|
+
Casing.merge(state.casing, reference.casing)
|
|
129
|
+
|
|
130
|
+
const renderReferenceTable = (
|
|
131
|
+
reference: {
|
|
132
|
+
readonly tableName: string
|
|
133
|
+
readonly schemaName?: string
|
|
134
|
+
readonly casing?: Casing.Options
|
|
135
|
+
},
|
|
136
|
+
state: RenderState,
|
|
137
|
+
dialect: SqlDialect
|
|
138
|
+
): string => {
|
|
139
|
+
const casing = referenceCasing(reference, state)
|
|
140
|
+
const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
|
|
141
|
+
const schemaName = reference.schemaName === undefined
|
|
142
|
+
? undefined
|
|
143
|
+
: Casing.applyCategory(casing, "schemas", reference.schemaName)
|
|
144
|
+
return dialect.renderTableReference(tableName, tableName, schemaName)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const quoteReferenceColumn = (
|
|
148
|
+
columnName: string,
|
|
149
|
+
reference: { readonly casing?: Casing.Options },
|
|
150
|
+
state: RenderState,
|
|
151
|
+
dialect: SqlDialect
|
|
152
|
+
): string =>
|
|
153
|
+
dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
|
|
154
|
+
|
|
155
|
+
const registerSourceReference = (
|
|
156
|
+
source: unknown,
|
|
157
|
+
tableName: string,
|
|
158
|
+
state: RenderState
|
|
159
|
+
): void => {
|
|
160
|
+
if (typeof source !== "object" || source === null) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (Table.TypeId in source) {
|
|
164
|
+
const table = source as Table.AnyTable
|
|
165
|
+
const tableState = table[Table.TypeId]
|
|
166
|
+
const casing = casingForTable(table, state)
|
|
167
|
+
const renderedTableName = tableState.kind === "alias"
|
|
168
|
+
? tableName
|
|
169
|
+
: Casing.applyCategory(casing, "tables", tableState.baseName)
|
|
170
|
+
const columns = new Map(
|
|
171
|
+
Object.keys(tableState.fields).map((columnName) => [
|
|
172
|
+
columnName,
|
|
173
|
+
Casing.applyCategory(casing, "columns", columnName)
|
|
174
|
+
] as const)
|
|
175
|
+
)
|
|
176
|
+
state.sourceNames?.set(tableName, {
|
|
177
|
+
tableName: renderedTableName,
|
|
178
|
+
columns
|
|
179
|
+
})
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
|
|
183
|
+
state.sourceNames?.set(tableName, {
|
|
184
|
+
tableName,
|
|
185
|
+
columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const registerQuerySources = (
|
|
191
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
192
|
+
state: RenderState
|
|
193
|
+
): void => {
|
|
194
|
+
if (ast.from !== undefined) {
|
|
195
|
+
registerSourceReference(ast.from.source, ast.from.tableName, state)
|
|
196
|
+
}
|
|
197
|
+
for (const source of ast.fromSources ?? []) {
|
|
198
|
+
registerSourceReference(source.source, source.tableName, state)
|
|
199
|
+
}
|
|
200
|
+
for (const join of ast.joins) {
|
|
201
|
+
registerSourceReference(join.source, join.tableName, state)
|
|
202
|
+
}
|
|
203
|
+
if (ast.into !== undefined) {
|
|
204
|
+
registerSourceReference(ast.into.source, ast.into.tableName, state)
|
|
205
|
+
}
|
|
206
|
+
if (ast.target !== undefined) {
|
|
207
|
+
registerSourceReference(ast.target.source, ast.target.tableName, state)
|
|
208
|
+
}
|
|
209
|
+
for (const target of ast.targets ?? []) {
|
|
210
|
+
registerSourceReference(target.source, target.tableName, state)
|
|
211
|
+
}
|
|
212
|
+
if (ast.using !== undefined) {
|
|
213
|
+
registerSourceReference(ast.using.source, ast.using.tableName, state)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const renderPostgresDdlString = (value: string): string =>
|
|
218
|
+
`'${value.replaceAll("'", "''")}'`
|
|
219
|
+
|
|
220
|
+
const renderPostgresDdlBytes = (value: Uint8Array): string =>
|
|
221
|
+
`decode('${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}', 'hex')`
|
|
222
|
+
|
|
223
|
+
const renderPostgresDdlLiteral = (
|
|
224
|
+
value: unknown,
|
|
225
|
+
state: RenderState,
|
|
226
|
+
context: RenderValueContext = {}
|
|
227
|
+
): string => {
|
|
228
|
+
const driverValue = toDriverValue(value, {
|
|
229
|
+
dialect: "postgres",
|
|
230
|
+
valueMappings: state.valueMappings,
|
|
231
|
+
...context
|
|
232
|
+
})
|
|
233
|
+
if (driverValue === null) {
|
|
234
|
+
return "null"
|
|
235
|
+
}
|
|
236
|
+
switch (typeof driverValue) {
|
|
237
|
+
case "boolean":
|
|
238
|
+
return driverValue ? "true" : "false"
|
|
239
|
+
case "number":
|
|
240
|
+
if (!Number.isFinite(driverValue)) {
|
|
241
|
+
throw new Error("Expected a finite numeric value")
|
|
242
|
+
}
|
|
243
|
+
return String(driverValue)
|
|
244
|
+
case "bigint":
|
|
245
|
+
return driverValue.toString()
|
|
246
|
+
case "string":
|
|
247
|
+
return renderPostgresDdlString(driverValue)
|
|
248
|
+
case "object":
|
|
249
|
+
if (driverValue instanceof Uint8Array) {
|
|
250
|
+
return renderPostgresDdlBytes(driverValue)
|
|
251
|
+
}
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
throw new Error("Unsupported postgres DDL literal value")
|
|
255
|
+
}
|
|
256
|
+
|
|
54
257
|
const renderDdlExpression = (
|
|
55
258
|
expression: DdlExpressionLike,
|
|
56
259
|
state: RenderState,
|
|
57
260
|
dialect: SqlDialect
|
|
58
|
-
): string =>
|
|
59
|
-
SchemaExpression.isSchemaExpression(expression)
|
|
60
|
-
|
|
61
|
-
|
|
261
|
+
): string => {
|
|
262
|
+
if (SchemaExpression.isSchemaExpression(expression)) {
|
|
263
|
+
return SchemaExpression.render(expression)
|
|
264
|
+
}
|
|
265
|
+
return renderExpression(expression, state, {
|
|
266
|
+
...dialect,
|
|
267
|
+
renderLiteral: renderPostgresDdlLiteral
|
|
268
|
+
})
|
|
269
|
+
}
|
|
62
270
|
|
|
63
271
|
const renderColumnDefinition = (
|
|
64
272
|
dialect: SqlDialect,
|
|
65
273
|
state: RenderState,
|
|
66
274
|
columnName: string,
|
|
67
|
-
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
275
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
276
|
+
tableName?: string,
|
|
277
|
+
casing?: Casing.Options
|
|
68
278
|
): string => {
|
|
279
|
+
const expressionState = { ...state, casing, rowLocalColumns: true }
|
|
280
|
+
if (dialect.name !== "postgres" && isArrayDbType(column.metadata.dbType)) {
|
|
281
|
+
throw new Error(`Unsupported ${dialect.name} array column options`)
|
|
282
|
+
}
|
|
69
283
|
const clauses = [
|
|
70
|
-
|
|
71
|
-
column.metadata.ddlType
|
|
284
|
+
quoteColumn(columnName, state, dialect, tableName),
|
|
285
|
+
column.metadata.ddlType === undefined
|
|
286
|
+
? renderDbType(dialect, column.metadata.dbType)
|
|
287
|
+
: renderDbTypeName(column.metadata.ddlType)
|
|
72
288
|
]
|
|
73
289
|
if (column.metadata.identity) {
|
|
290
|
+
if (dialect.name !== "postgres") {
|
|
291
|
+
throw new Error(`Unsupported ${dialect.name} identity column options`)
|
|
292
|
+
}
|
|
74
293
|
clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
|
|
75
294
|
} else if (column.metadata.generatedValue) {
|
|
76
|
-
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue,
|
|
295
|
+
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
|
|
77
296
|
} else if (column.metadata.defaultValue) {
|
|
78
|
-
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue,
|
|
297
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
|
|
79
298
|
}
|
|
80
299
|
if (!column.metadata.nullable) {
|
|
81
300
|
clauses.push("not null")
|
|
@@ -87,38 +306,65 @@ const renderCreateTableSql = (
|
|
|
87
306
|
targetSource: QueryAst.FromClause,
|
|
88
307
|
state: RenderState,
|
|
89
308
|
dialect: SqlDialect,
|
|
90
|
-
ifNotExists:
|
|
309
|
+
ifNotExists: unknown
|
|
91
310
|
): string => {
|
|
311
|
+
const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
|
|
312
|
+
if (dialect.name !== "postgres" && normalizedIfNotExists) {
|
|
313
|
+
throw new Error(`Unsupported ${dialect.name} create table options`)
|
|
314
|
+
}
|
|
92
315
|
const table = targetSource.source as Table.AnyTable
|
|
316
|
+
const tableCasing = casingForTable(table, state)
|
|
93
317
|
const fields = table[Table.TypeId].fields
|
|
94
318
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
95
|
-
renderColumnDefinition(dialect, state, columnName, column)
|
|
319
|
+
renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
|
|
96
320
|
)
|
|
97
|
-
|
|
321
|
+
const options = table[Table.OptionsSymbol] as unknown
|
|
322
|
+
const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
|
|
323
|
+
validateOptions(table[Table.TypeId].name, fields, tableOptions)
|
|
324
|
+
for (const option of tableOptions) {
|
|
325
|
+
if (typeof option !== "object" || option === null || !("kind" in option)) {
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
98
328
|
switch (option.kind) {
|
|
99
329
|
case "primaryKey":
|
|
100
|
-
|
|
330
|
+
if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
|
|
331
|
+
throw new Error(`Unsupported ${dialect.name} primary key constraint options`)
|
|
332
|
+
}
|
|
333
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}primary key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
101
334
|
break
|
|
102
335
|
case "unique":
|
|
103
|
-
|
|
336
|
+
if (dialect.name !== "postgres" && (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred)) {
|
|
337
|
+
throw new Error(`Unsupported ${dialect.name} unique constraint options`)
|
|
338
|
+
}
|
|
339
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
104
340
|
break
|
|
105
341
|
case "foreignKey": {
|
|
106
|
-
|
|
342
|
+
if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
|
|
343
|
+
throw new Error(`Unsupported ${dialect.name} foreign key constraint options`)
|
|
344
|
+
}
|
|
345
|
+
const reference = typeof option.references === "function"
|
|
346
|
+
? option.references()
|
|
347
|
+
: option.references
|
|
107
348
|
definitions.push(
|
|
108
|
-
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.
|
|
349
|
+
`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}foreign key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")}) references ${renderReferenceTable(reference, state, dialect)} (${reference.columns.map((column) => quoteReferenceColumn(column, reference, state, dialect)).join(", ")})${option.onDelete !== undefined ? ` on delete ${renderReferentialAction(option.onDelete)}` : ""}${option.onUpdate !== undefined ? ` on update ${renderReferentialAction(option.onUpdate)}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
|
|
109
350
|
)
|
|
110
351
|
break
|
|
111
352
|
}
|
|
112
353
|
case "check":
|
|
354
|
+
if (dialect.name !== "postgres" && option.noInherit) {
|
|
355
|
+
throw new Error(`Unsupported ${dialect.name} check constraint options`)
|
|
356
|
+
}
|
|
113
357
|
definitions.push(
|
|
114
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
358
|
+
`constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
115
359
|
)
|
|
116
360
|
break
|
|
117
361
|
case "index":
|
|
118
362
|
break
|
|
363
|
+
default:
|
|
364
|
+
throw new Error("Unsupported table option kind")
|
|
119
365
|
}
|
|
120
366
|
}
|
|
121
|
-
return `create table${
|
|
367
|
+
return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
122
368
|
}
|
|
123
369
|
|
|
124
370
|
const renderCreateIndexSql = (
|
|
@@ -127,8 +373,16 @@ const renderCreateIndexSql = (
|
|
|
127
373
|
state: RenderState,
|
|
128
374
|
dialect: SqlDialect
|
|
129
375
|
): string => {
|
|
130
|
-
const
|
|
131
|
-
|
|
376
|
+
const unique = normalizeStatementFlag(ddl.unique)
|
|
377
|
+
const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
|
|
378
|
+
const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
|
|
379
|
+
if (dialect.name !== "postgres" && ifNotExists) {
|
|
380
|
+
throw new Error(`Unsupported ${dialect.name} create index options`)
|
|
381
|
+
}
|
|
382
|
+
const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
|
|
383
|
+
const table = targetSource.source as Table.AnyTable
|
|
384
|
+
const tableCasing = casingForTable(table, state)
|
|
385
|
+
return `create${unique ? " unique" : ""} index${maybeIfNotExists} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${ddl.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})`
|
|
132
386
|
}
|
|
133
387
|
|
|
134
388
|
const renderDropIndexSql = (
|
|
@@ -136,20 +390,108 @@ const renderDropIndexSql = (
|
|
|
136
390
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
137
391
|
state: RenderState,
|
|
138
392
|
dialect: SqlDialect
|
|
139
|
-
): string =>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
393
|
+
): string => {
|
|
394
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
395
|
+
const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
|
|
396
|
+
if (dialect.name !== "postgres" && ifExists) {
|
|
397
|
+
throw new Error(`Unsupported ${dialect.name} drop index options`)
|
|
398
|
+
}
|
|
399
|
+
if (dialect.name === "postgres") {
|
|
400
|
+
const table = typeof targetSource.source === "object" &&
|
|
401
|
+
targetSource.source !== null &&
|
|
402
|
+
Table.TypeId in targetSource.source
|
|
403
|
+
? targetSource.source as Table.AnyTable
|
|
404
|
+
: undefined
|
|
405
|
+
const schemaName = table?.[Table.TypeId].schemaName
|
|
406
|
+
const tableCasing = table === undefined ? state.casing : casingForTable(table, state)
|
|
407
|
+
const renderedSchemaName = table === undefined ? schemaName : casedSchemaName(table, state)
|
|
408
|
+
const renderedIndexName = Casing.applyCategory(tableCasing, "indexes", name)
|
|
409
|
+
const indexName = schemaName === undefined || schemaName === "public"
|
|
410
|
+
? dialect.quoteIdentifier(renderedIndexName)
|
|
411
|
+
: `${dialect.quoteIdentifier(renderedSchemaName ?? schemaName)}.${dialect.quoteIdentifier(renderedIndexName)}`
|
|
412
|
+
return `drop index${ifExists ? " if exists" : ""} ${indexName}`
|
|
413
|
+
}
|
|
414
|
+
const table = targetSource.source as Table.AnyTable
|
|
415
|
+
const tableCasing = casingForTable(table, state)
|
|
416
|
+
return `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
417
|
+
}
|
|
143
418
|
|
|
144
419
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
145
420
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
146
421
|
|
|
147
|
-
const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
148
|
-
dbType.kind === "jsonb" || dbType.kind === "json"
|
|
422
|
+
const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
|
|
423
|
+
if (dbType.kind === "jsonb" || dbType.kind === "json") {
|
|
424
|
+
return true
|
|
425
|
+
}
|
|
426
|
+
if (!("variant" in dbType)) {
|
|
427
|
+
return false
|
|
428
|
+
}
|
|
429
|
+
const variant = dbType.variant as string
|
|
430
|
+
return variant === "json" || variant === "jsonb"
|
|
431
|
+
}
|
|
149
432
|
|
|
150
433
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
151
434
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
152
435
|
|
|
436
|
+
const expectValueExpression = (
|
|
437
|
+
_functionName: string,
|
|
438
|
+
value: unknown
|
|
439
|
+
): Expression.Any => value as Expression.Any
|
|
440
|
+
|
|
441
|
+
const expectBinaryExpressions = (
|
|
442
|
+
_functionName: string,
|
|
443
|
+
left: unknown,
|
|
444
|
+
right: unknown
|
|
445
|
+
): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
|
|
446
|
+
|
|
447
|
+
const renderBinaryExpression = (
|
|
448
|
+
functionName: string,
|
|
449
|
+
operator: string,
|
|
450
|
+
left: unknown,
|
|
451
|
+
right: unknown,
|
|
452
|
+
state: RenderState,
|
|
453
|
+
dialect: SqlDialect
|
|
454
|
+
): string => {
|
|
455
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
|
|
456
|
+
return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const postgresRangeSubtypeByKind: Readonly<Record<string, string>> = {
|
|
460
|
+
int4range: "int4",
|
|
461
|
+
int8range: "int8",
|
|
462
|
+
numrange: "numeric",
|
|
463
|
+
tsrange: "timestamp",
|
|
464
|
+
tstzrange: "timestamptz",
|
|
465
|
+
daterange: "date",
|
|
466
|
+
int4multirange: "int4",
|
|
467
|
+
int8multirange: "int8",
|
|
468
|
+
nummultirange: "numeric",
|
|
469
|
+
tsmultirange: "timestamp",
|
|
470
|
+
tstzmultirange: "timestamptz",
|
|
471
|
+
datemultirange: "date"
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const postgresRangeSubtypeKey = (dbType: Expression.DbType.Any): string | undefined => {
|
|
475
|
+
if ("base" in dbType) {
|
|
476
|
+
return postgresRangeSubtypeKey(dbType.base)
|
|
477
|
+
}
|
|
478
|
+
if ("subtype" in dbType) {
|
|
479
|
+
return postgresRangeSubtypeKey(dbType.subtype) ?? dbType.subtype.kind
|
|
480
|
+
}
|
|
481
|
+
return postgresRangeSubtypeByKind[dbType.kind]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const assertCompatiblePostgresRangeOperands = (
|
|
485
|
+
left: Expression.Any,
|
|
486
|
+
right: Expression.Any
|
|
487
|
+
): void => {
|
|
488
|
+
const leftKey = postgresRangeSubtypeKey(left[Expression.TypeId].dbType)
|
|
489
|
+
const rightKey = postgresRangeSubtypeKey(right[Expression.TypeId].dbType)
|
|
490
|
+
if (leftKey !== undefined && rightKey !== undefined && leftKey !== rightKey) {
|
|
491
|
+
throw new Error("Incompatible postgres range operands")
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
153
495
|
const unsupportedJsonFeature = (
|
|
154
496
|
dialect: SqlDialect,
|
|
155
497
|
feature: string
|
|
@@ -173,13 +515,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
|
|
|
173
515
|
const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
|
|
174
516
|
value !== null && typeof value === "object" && JsonPath.TypeId in value
|
|
175
517
|
|
|
518
|
+
const isOptionalJsonPathNumber = (value: unknown): boolean =>
|
|
519
|
+
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
520
|
+
|
|
521
|
+
const isJsonPathSegment = (segment: unknown): boolean => {
|
|
522
|
+
if (typeof segment === "string") {
|
|
523
|
+
return true
|
|
524
|
+
}
|
|
525
|
+
if (typeof segment === "number") {
|
|
526
|
+
return Number.isFinite(segment)
|
|
527
|
+
}
|
|
528
|
+
if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
|
|
529
|
+
return false
|
|
530
|
+
}
|
|
531
|
+
switch ((segment as { readonly kind?: unknown }).kind) {
|
|
532
|
+
case "key":
|
|
533
|
+
return typeof (segment as { readonly key?: unknown }).key === "string"
|
|
534
|
+
case "index": {
|
|
535
|
+
const index = (segment as { readonly index?: unknown }).index
|
|
536
|
+
return typeof index === "number" && Number.isFinite(index)
|
|
537
|
+
}
|
|
538
|
+
case "wildcard":
|
|
539
|
+
case "descend":
|
|
540
|
+
return true
|
|
541
|
+
case "slice":
|
|
542
|
+
return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
|
|
543
|
+
isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
|
|
544
|
+
default:
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
|
|
550
|
+
if (!Array.isArray(segments)) {
|
|
551
|
+
throw new Error("JSON path expressions require a segment array")
|
|
552
|
+
}
|
|
553
|
+
if (segments.some((segment) => !isJsonPathSegment(segment))) {
|
|
554
|
+
throw new Error("JSON path segments require string, number, or path segment objects")
|
|
555
|
+
}
|
|
556
|
+
return segments as ReadonlyArray<JsonPath.AnySegment>
|
|
557
|
+
}
|
|
558
|
+
|
|
176
559
|
const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
|
|
177
560
|
const path = node.path ?? node.segments ?? node.keys
|
|
178
561
|
if (isJsonPathValue(path)) {
|
|
179
|
-
return path.segments
|
|
562
|
+
return validateJsonPathSegments(path.segments)
|
|
180
563
|
}
|
|
181
564
|
if (Array.isArray(path)) {
|
|
182
|
-
return path
|
|
565
|
+
return validateJsonPathSegments(path)
|
|
566
|
+
}
|
|
567
|
+
if (node.segments !== undefined) {
|
|
568
|
+
return validateJsonPathSegments(node.segments)
|
|
183
569
|
}
|
|
184
570
|
if ("key" in node) {
|
|
185
571
|
return [JsonPath.key(String(node.key))]
|
|
@@ -198,28 +584,40 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
|
|
|
198
584
|
return []
|
|
199
585
|
}
|
|
200
586
|
if ("right" in node && isJsonPathValue(node.right)) {
|
|
201
|
-
return node.right.segments
|
|
587
|
+
return validateJsonPathSegments(node.right.segments)
|
|
202
588
|
}
|
|
203
589
|
return []
|
|
204
590
|
}
|
|
205
591
|
|
|
592
|
+
const extractJsonKeys = (
|
|
593
|
+
node: Record<string, unknown>,
|
|
594
|
+
segments: ReadonlyArray<JsonPath.AnySegment>
|
|
595
|
+
): readonly unknown[] =>
|
|
596
|
+
Array.isArray(node.keys)
|
|
597
|
+
? node.keys
|
|
598
|
+
: segments.map((segment) =>
|
|
599
|
+
typeof segment === "object" && segment !== null && segment.kind === "key"
|
|
600
|
+
? segment.key
|
|
601
|
+
: segment
|
|
602
|
+
)
|
|
603
|
+
|
|
206
604
|
const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
207
605
|
node.newValue ?? node.insert ?? node.right
|
|
208
606
|
|
|
209
607
|
const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
|
|
608
|
+
const renderKey = (value: string): string =>
|
|
609
|
+
/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
|
|
610
|
+
? `.${value}`
|
|
611
|
+
: `.${JSON.stringify(value)}`
|
|
210
612
|
if (typeof segment === "string") {
|
|
211
|
-
return
|
|
212
|
-
? `.${segment}`
|
|
213
|
-
: `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
613
|
+
return renderKey(segment)
|
|
214
614
|
}
|
|
215
615
|
if (typeof segment === "number") {
|
|
216
616
|
return `[${segment}]`
|
|
217
617
|
}
|
|
218
618
|
switch (segment.kind) {
|
|
219
619
|
case "key":
|
|
220
|
-
return
|
|
221
|
-
? `.${segment.key}`
|
|
222
|
-
: `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
620
|
+
return renderKey(segment.key)
|
|
223
621
|
case "index":
|
|
224
622
|
return `[${segment.index}]`
|
|
225
623
|
case "wildcard":
|
|
@@ -284,7 +682,7 @@ const renderPostgresJsonAccessStep = (
|
|
|
284
682
|
case "key":
|
|
285
683
|
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.key, state)}`
|
|
286
684
|
case "index":
|
|
287
|
-
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(
|
|
685
|
+
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.index, state)}`
|
|
288
686
|
default:
|
|
289
687
|
throw new Error("Postgres exact JSON access requires key/index segments")
|
|
290
688
|
}
|
|
@@ -338,14 +736,26 @@ const encodeArrayValues = (
|
|
|
338
736
|
state: RenderState,
|
|
339
737
|
dialect: SqlDialect
|
|
340
738
|
): readonly unknown[] =>
|
|
341
|
-
values.map((value) =>
|
|
342
|
-
|
|
739
|
+
values.map((value) => {
|
|
740
|
+
if (value === null && column.metadata.nullable) {
|
|
741
|
+
return null
|
|
742
|
+
}
|
|
743
|
+
const runtimeSchemaAccepts = column.schema !== undefined &&
|
|
744
|
+
(Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
|
|
745
|
+
const normalizedValue = runtimeSchemaAccepts
|
|
746
|
+
? value
|
|
747
|
+
: normalizeDbValue(column.metadata.dbType, value)
|
|
748
|
+
const encodedValue = column.schema === undefined || runtimeSchemaAccepts
|
|
749
|
+
? normalizedValue
|
|
750
|
+
: (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
|
|
751
|
+
return toDriverValue(encodedValue, {
|
|
343
752
|
dialect: dialect.name,
|
|
344
753
|
valueMappings: state.valueMappings,
|
|
345
754
|
dbType: column.metadata.dbType,
|
|
346
755
|
runtimeSchema: column.schema,
|
|
347
756
|
driverValueMapping: column.metadata.driverValueMapping
|
|
348
|
-
})
|
|
757
|
+
})
|
|
758
|
+
})
|
|
349
759
|
|
|
350
760
|
const renderPostgresJsonKind = (
|
|
351
761
|
value: Expression.Any
|
|
@@ -360,52 +770,64 @@ const renderJsonOpaquePath = (
|
|
|
360
770
|
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments), state)
|
|
361
771
|
}
|
|
362
772
|
if (typeof value === "string") {
|
|
773
|
+
if (value.trim().length === 0) {
|
|
774
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
775
|
+
}
|
|
363
776
|
return dialect.renderLiteral(value, state)
|
|
364
777
|
}
|
|
365
778
|
if (isExpression(value)) {
|
|
779
|
+
const ast = (value as Expression.Any & {
|
|
780
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
781
|
+
})[ExpressionAst.TypeId]
|
|
782
|
+
if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
|
|
783
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
784
|
+
}
|
|
366
785
|
return renderExpression(value, state, dialect)
|
|
367
786
|
}
|
|
368
787
|
throw new Error("Unsupported SQL/JSON path input")
|
|
369
788
|
}
|
|
370
789
|
|
|
790
|
+
const renderFunctionName = (name: unknown): string => {
|
|
791
|
+
return name as string
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const renderExtractField = (field: Expression.Any): string => {
|
|
795
|
+
const ast = (field as Expression.Any & {
|
|
796
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
797
|
+
})[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
|
|
798
|
+
return ast.value
|
|
799
|
+
}
|
|
800
|
+
|
|
371
801
|
const renderFunctionCall = (
|
|
372
|
-
name:
|
|
373
|
-
args:
|
|
802
|
+
name: unknown,
|
|
803
|
+
args: unknown,
|
|
374
804
|
state: RenderState,
|
|
375
805
|
dialect: SqlDialect
|
|
376
806
|
): string => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
if (source === undefined) {
|
|
387
|
-
throw new Error("Unsupported SQL extract expression")
|
|
388
|
-
}
|
|
389
|
-
const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
|
|
390
|
-
? field[Expression.TypeId].runtime
|
|
391
|
-
: undefined
|
|
392
|
-
const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
|
|
393
|
-
return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
|
|
807
|
+
const functionName = renderFunctionName(name)
|
|
808
|
+
const functionArgs = args as readonly Expression.Any[]
|
|
809
|
+
if (functionName === "array") {
|
|
810
|
+
return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
|
|
811
|
+
}
|
|
812
|
+
if (functionName === "extract") {
|
|
813
|
+
const field = functionArgs[0]!
|
|
814
|
+
const source = functionArgs[1]!
|
|
815
|
+
return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
|
|
394
816
|
}
|
|
395
|
-
const renderedArgs =
|
|
396
|
-
if (
|
|
397
|
-
switch (
|
|
817
|
+
const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
818
|
+
if (functionArgs.length === 0) {
|
|
819
|
+
switch (functionName) {
|
|
398
820
|
case "current_date":
|
|
399
821
|
case "current_time":
|
|
400
822
|
case "current_timestamp":
|
|
401
823
|
case "localtime":
|
|
402
824
|
case "localtimestamp":
|
|
403
|
-
return
|
|
825
|
+
return functionName
|
|
404
826
|
default:
|
|
405
|
-
return `${
|
|
827
|
+
return `${functionName}()`
|
|
406
828
|
}
|
|
407
829
|
}
|
|
408
|
-
return `${
|
|
830
|
+
return `${functionName}(${renderedArgs})`
|
|
409
831
|
}
|
|
410
832
|
|
|
411
833
|
const renderJsonExpression = (
|
|
@@ -469,22 +891,26 @@ const renderJsonExpression = (
|
|
|
469
891
|
const baseSql = dialect.name === "postgres"
|
|
470
892
|
? renderPostgresJsonValue(base, state, dialect)
|
|
471
893
|
: renderExpression(base, state, dialect)
|
|
472
|
-
const keys = segments
|
|
894
|
+
const keys = extractJsonKeys(ast, segments)
|
|
473
895
|
if (keys.length === 0) {
|
|
474
896
|
return undefined
|
|
475
897
|
}
|
|
898
|
+
if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
|
|
899
|
+
throw new Error("json key predicates require string keys")
|
|
900
|
+
}
|
|
901
|
+
const keyNames = keys as readonly string[]
|
|
476
902
|
if (dialect.name === "postgres") {
|
|
477
903
|
if (kind === "jsonHasAnyKeys") {
|
|
478
|
-
return `(${baseSql} ?| array[${
|
|
904
|
+
return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
479
905
|
}
|
|
480
906
|
if (kind === "jsonHasAllKeys") {
|
|
481
|
-
return `(${baseSql} ?& array[${
|
|
907
|
+
return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
482
908
|
}
|
|
483
|
-
return `(${baseSql} ? ${renderPostgresTextLiteral(
|
|
909
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
|
|
484
910
|
}
|
|
485
911
|
if (dialect.name === "mysql") {
|
|
486
912
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
487
|
-
const paths =
|
|
913
|
+
const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
|
|
488
914
|
return `json_contains_path(${baseSql}, ${dialect.renderLiteral(mode, state)}, ${paths})`
|
|
489
915
|
}
|
|
490
916
|
return undefined
|
|
@@ -503,9 +929,7 @@ const renderJsonExpression = (
|
|
|
503
929
|
return undefined
|
|
504
930
|
}
|
|
505
931
|
case "jsonBuildObject": {
|
|
506
|
-
const entries =
|
|
507
|
-
? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
508
|
-
: []
|
|
932
|
+
const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
509
933
|
const renderedEntries = entries.flatMap((entry) => [
|
|
510
934
|
dialect.renderLiteral(entry.key, state),
|
|
511
935
|
renderJsonInputExpression(entry.value, state, dialect)
|
|
@@ -519,9 +943,7 @@ const renderJsonExpression = (
|
|
|
519
943
|
return undefined
|
|
520
944
|
}
|
|
521
945
|
case "jsonBuildArray": {
|
|
522
|
-
const values =
|
|
523
|
-
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
524
|
-
: []
|
|
946
|
+
const values = (ast as { readonly values: readonly Expression.Any[] }).values
|
|
525
947
|
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
526
948
|
if (dialect.name === "postgres") {
|
|
527
949
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
@@ -588,7 +1010,7 @@ const renderJsonExpression = (
|
|
|
588
1010
|
const baseSql = renderExpression(base, state, dialect)
|
|
589
1011
|
const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
|
|
590
1012
|
const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
|
|
591
|
-
return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
|
|
1013
|
+
return `(case when ${typeOf}(${baseSql}) = 'object' then to_json(array(select ${objectKeys}(${baseSql}))) else null end)`
|
|
592
1014
|
}
|
|
593
1015
|
if (dialect.name === "mysql") {
|
|
594
1016
|
return `json_keys(${renderExpression(base, state, dialect)})`
|
|
@@ -615,7 +1037,7 @@ const renderJsonExpression = (
|
|
|
615
1037
|
const segment = segments[0]!
|
|
616
1038
|
return `(${baseSql} - ${segment.kind === "key"
|
|
617
1039
|
? dialect.renderLiteral(segment.key, state)
|
|
618
|
-
: dialect.renderLiteral(
|
|
1040
|
+
: dialect.renderLiteral(segment.index, state)})`
|
|
619
1041
|
}
|
|
620
1042
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
621
1043
|
}
|
|
@@ -699,11 +1121,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
|
|
|
699
1121
|
const renderMutationAssignment = (
|
|
700
1122
|
entry: QueryAst.AssignmentClause,
|
|
701
1123
|
state: RenderState,
|
|
702
|
-
dialect: SqlDialect
|
|
1124
|
+
dialect: SqlDialect,
|
|
1125
|
+
targetTableName?: string
|
|
703
1126
|
): string => {
|
|
704
1127
|
const column = entry.tableName && dialect.name === "mysql"
|
|
705
|
-
? `${dialect.quoteIdentifier(entry.tableName)}.${
|
|
706
|
-
:
|
|
1128
|
+
? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
|
|
1129
|
+
: quoteColumn(entry.columnName, state, dialect, targetTableName)
|
|
707
1130
|
return `${column} = ${renderExpression(entry.value, state, dialect)}`
|
|
708
1131
|
}
|
|
709
1132
|
|
|
@@ -765,10 +1188,11 @@ const renderTransactionClause = (
|
|
|
765
1188
|
switch (clause.kind) {
|
|
766
1189
|
case "transaction": {
|
|
767
1190
|
const modes: string[] = []
|
|
768
|
-
|
|
769
|
-
|
|
1191
|
+
const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
|
|
1192
|
+
if (isolationLevel) {
|
|
1193
|
+
modes.push(isolationLevel)
|
|
770
1194
|
}
|
|
771
|
-
if (clause.readOnly
|
|
1195
|
+
if (normalizeStatementFlag(clause.readOnly)) {
|
|
772
1196
|
modes.push("read only")
|
|
773
1197
|
}
|
|
774
1198
|
return modes.length > 0
|
|
@@ -780,24 +1204,20 @@ const renderTransactionClause = (
|
|
|
780
1204
|
case "rollback":
|
|
781
1205
|
return "rollback"
|
|
782
1206
|
case "savepoint":
|
|
783
|
-
return `savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1207
|
+
return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
|
|
784
1208
|
case "rollbackTo":
|
|
785
|
-
return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1209
|
+
return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
|
|
786
1210
|
case "releaseSavepoint":
|
|
787
|
-
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1211
|
+
return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
|
|
788
1212
|
}
|
|
789
|
-
return ""
|
|
1213
|
+
return "start transaction"
|
|
790
1214
|
}
|
|
791
1215
|
|
|
792
1216
|
const renderSelectionList = (
|
|
793
1217
|
selection: Record<string, unknown>,
|
|
794
1218
|
state: RenderState,
|
|
795
|
-
dialect: SqlDialect
|
|
796
|
-
validateAggregation: boolean
|
|
1219
|
+
dialect: SqlDialect
|
|
797
1220
|
): RenderedQueryAst => {
|
|
798
|
-
if (validateAggregation) {
|
|
799
|
-
validateAggregationSelection(selection as SelectionValue, [])
|
|
800
|
-
}
|
|
801
1221
|
const flattened = flattenSelection(selection)
|
|
802
1222
|
const projections = selectionProjections(selection)
|
|
803
1223
|
const sql = flattened.map(({ expression, alias }) =>
|
|
@@ -808,28 +1228,74 @@ const renderSelectionList = (
|
|
|
808
1228
|
}
|
|
809
1229
|
}
|
|
810
1230
|
|
|
1231
|
+
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
1232
|
+
params: state.params,
|
|
1233
|
+
valueMappings: state.valueMappings,
|
|
1234
|
+
casing: state.casing,
|
|
1235
|
+
ctes: [],
|
|
1236
|
+
cteNames: new Set(state.cteNames),
|
|
1237
|
+
cteSources: new Map(state.cteSources),
|
|
1238
|
+
sourceNames: new Map(state.sourceNames)
|
|
1239
|
+
})
|
|
1240
|
+
|
|
1241
|
+
const assertSupportedMutationReturning = (
|
|
1242
|
+
dialect: SqlDialect,
|
|
1243
|
+
selection: Record<string, unknown>
|
|
1244
|
+
): void => {
|
|
1245
|
+
if (dialect.name === "standard" && Object.keys(selection).length > 0) {
|
|
1246
|
+
throw new Error("Unsupported standard returning")
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
const validateDistinctOnOrdering = (
|
|
1251
|
+
distinctOn: readonly Expression.Any[] | undefined,
|
|
1252
|
+
orderBy: readonly QueryAst.OrderByClause[]
|
|
1253
|
+
): void => {
|
|
1254
|
+
if (distinctOn === undefined || distinctOn.length === 0 || orderBy.length === 0) {
|
|
1255
|
+
return
|
|
1256
|
+
}
|
|
1257
|
+
const remainingDistinctKeys = new Set(distinctOn.map(groupingKeyOfExpression))
|
|
1258
|
+
for (const order of orderBy) {
|
|
1259
|
+
const key = groupingKeyOfExpression(order.value)
|
|
1260
|
+
if (remainingDistinctKeys.has(key)) {
|
|
1261
|
+
remainingDistinctKeys.delete(key)
|
|
1262
|
+
continue
|
|
1263
|
+
}
|
|
1264
|
+
if (remainingDistinctKeys.size > 0) {
|
|
1265
|
+
throw new Error("distinctOn(...) expressions must match the leftmost orderBy(...) expressions")
|
|
1266
|
+
}
|
|
1267
|
+
return
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
811
1271
|
export const renderQueryAst = (
|
|
812
1272
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
813
1273
|
state: RenderState,
|
|
814
|
-
dialect: SqlDialect
|
|
1274
|
+
dialect: SqlDialect,
|
|
1275
|
+
options: { readonly emitCtes?: boolean } = {}
|
|
815
1276
|
): RenderedQueryAst => {
|
|
1277
|
+
registerQuerySources(ast, state)
|
|
816
1278
|
let sql = ""
|
|
817
1279
|
let projections: readonly Projection[] = []
|
|
818
1280
|
|
|
819
1281
|
switch (ast.kind) {
|
|
820
1282
|
case "select": {
|
|
821
|
-
|
|
822
|
-
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect
|
|
1283
|
+
validateDistinctOnOrdering(ast.distinctOn, ast.orderBy)
|
|
1284
|
+
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
|
|
823
1285
|
projections = rendered.projections
|
|
1286
|
+
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
824
1287
|
const clauses = [
|
|
825
1288
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
826
|
-
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})
|
|
827
|
-
: `select${ast.distinct ? " distinct" : ""}
|
|
1289
|
+
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
|
|
1290
|
+
: `select${ast.distinct ? " distinct" : ""}${selectList}`
|
|
828
1291
|
]
|
|
829
1292
|
if (ast.from) {
|
|
830
1293
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
831
1294
|
}
|
|
832
1295
|
for (const join of ast.joins) {
|
|
1296
|
+
if (dialect.name === "standard" && join.kind === "full") {
|
|
1297
|
+
throw new Error("Unsupported standard full join")
|
|
1298
|
+
}
|
|
833
1299
|
const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
|
|
834
1300
|
clauses.push(
|
|
835
1301
|
join.kind === "cross"
|
|
@@ -856,8 +1322,11 @@ export const renderQueryAst = (
|
|
|
856
1322
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
857
1323
|
}
|
|
858
1324
|
if (ast.lock) {
|
|
1325
|
+
if (dialect.name === "standard") {
|
|
1326
|
+
throw new Error("Unsupported standard row locking")
|
|
1327
|
+
}
|
|
859
1328
|
clauses.push(
|
|
860
|
-
`${ast.lock.mode
|
|
1329
|
+
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
861
1330
|
)
|
|
862
1331
|
}
|
|
863
1332
|
sql = clauses.join(" ")
|
|
@@ -887,6 +1356,9 @@ export const renderQueryAst = (
|
|
|
887
1356
|
state,
|
|
888
1357
|
dialect
|
|
889
1358
|
)
|
|
1359
|
+
if (dialect.name === "standard" && entry.all && entry.kind !== "union") {
|
|
1360
|
+
throw new Error("Unsupported standard set operator all variant")
|
|
1361
|
+
}
|
|
890
1362
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
891
1363
|
})
|
|
892
1364
|
].join(" ")
|
|
@@ -896,17 +1368,20 @@ export const renderQueryAst = (
|
|
|
896
1368
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
897
1369
|
const targetSource = insertAst.into!
|
|
898
1370
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1371
|
+
const targetCasingState = stateWithTableCasing(state, targetSource.source)
|
|
1372
|
+
const insertSource = insertAst.insertSource
|
|
1373
|
+
const conflict = expectConflictClause(insertAst.conflict)
|
|
899
1374
|
sql = `insert into ${target}`
|
|
900
|
-
if (
|
|
901
|
-
const columns =
|
|
902
|
-
const rows =
|
|
903
|
-
`(${row.values.map((entry) => renderExpression(entry.value,
|
|
1375
|
+
if (insertSource?.kind === "values") {
|
|
1376
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1377
|
+
const rows = insertSource.rows.map((row) =>
|
|
1378
|
+
`(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
|
|
904
1379
|
).join(", ")
|
|
905
1380
|
sql += ` (${columns}) values ${rows}`
|
|
906
|
-
} else if (
|
|
907
|
-
const columns =
|
|
1381
|
+
} else if (insertSource?.kind === "query") {
|
|
1382
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
908
1383
|
const renderedQuery = renderQueryAst(
|
|
909
|
-
Query.getAst(
|
|
1384
|
+
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
910
1385
|
Record<string, unknown>,
|
|
911
1386
|
any,
|
|
912
1387
|
QueryAst.QueryStatement
|
|
@@ -915,20 +1390,19 @@ export const renderQueryAst = (
|
|
|
915
1390
|
dialect
|
|
916
1391
|
)
|
|
917
1392
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
918
|
-
} else if (
|
|
919
|
-
const
|
|
920
|
-
const columns = unnestSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
1393
|
+
} else if (insertSource?.kind === "unnest") {
|
|
1394
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
921
1395
|
if (dialect.name === "postgres") {
|
|
922
1396
|
const table = targetSource.source as Table.AnyTable
|
|
923
1397
|
const fields = table[Table.TypeId].fields
|
|
924
|
-
const rendered =
|
|
1398
|
+
const rendered = insertSource.values.map((entry) =>
|
|
925
1399
|
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
926
1400
|
).join(", ")
|
|
927
1401
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
928
1402
|
} else {
|
|
929
|
-
const rowCount =
|
|
1403
|
+
const rowCount = insertSource.values[0]?.values.length ?? 0
|
|
930
1404
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
931
|
-
`(${
|
|
1405
|
+
`(${insertSource.values.map((entry) =>
|
|
932
1406
|
dialect.renderLiteral(
|
|
933
1407
|
entry.values[index],
|
|
934
1408
|
state,
|
|
@@ -939,35 +1413,41 @@ export const renderQueryAst = (
|
|
|
939
1413
|
sql += ` (${columns}) values ${rows}`
|
|
940
1414
|
}
|
|
941
1415
|
} else {
|
|
942
|
-
const
|
|
943
|
-
const
|
|
944
|
-
|
|
1416
|
+
const insertValues = insertAst.values ?? []
|
|
1417
|
+
const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
|
|
1418
|
+
const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
|
|
1419
|
+
if (insertValues.length > 0) {
|
|
945
1420
|
sql += ` (${columns}) values (${values})`
|
|
946
1421
|
} else {
|
|
947
1422
|
sql += " default values"
|
|
948
1423
|
}
|
|
949
1424
|
}
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
1425
|
+
if (conflict) {
|
|
1426
|
+
if (dialect.name === "standard") {
|
|
1427
|
+
throw new Error("Unsupported standard insert conflict")
|
|
1428
|
+
}
|
|
1429
|
+
const conflictValueState = { ...targetCasingState, allowExcluded: true }
|
|
1430
|
+
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
1431
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
|
|
953
1432
|
).join(", ")
|
|
954
1433
|
if (dialect.name === "postgres") {
|
|
955
|
-
const targetSql =
|
|
956
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
957
|
-
:
|
|
958
|
-
? ` on conflict (${
|
|
1434
|
+
const targetSql = conflict.target?.kind === "constraint"
|
|
1435
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
|
|
1436
|
+
: conflict.target?.kind === "columns"
|
|
1437
|
+
? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
|
|
959
1438
|
: " on conflict"
|
|
960
1439
|
sql += targetSql
|
|
961
|
-
sql +=
|
|
1440
|
+
sql += conflict.action === "doNothing"
|
|
962
1441
|
? " do nothing"
|
|
963
|
-
: ` do update set ${updateValues}${
|
|
964
|
-
} else if (
|
|
1442
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
|
|
1443
|
+
} else if (conflict.action === "doNothing") {
|
|
965
1444
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
966
1445
|
} else {
|
|
967
1446
|
sql += ` on duplicate key update ${updateValues}`
|
|
968
1447
|
}
|
|
969
1448
|
}
|
|
970
|
-
|
|
1449
|
+
assertSupportedMutationReturning(dialect, insertAst.select as Record<string, unknown>)
|
|
1450
|
+
const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
|
|
971
1451
|
projections = returning.projections
|
|
972
1452
|
if (returning.sql.length > 0) {
|
|
973
1453
|
sql += ` returning ${returning.sql}`
|
|
@@ -980,8 +1460,11 @@ export const renderQueryAst = (
|
|
|
980
1460
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
981
1461
|
const targets = updateAst.targets ?? [targetSource]
|
|
982
1462
|
const fromSources = updateAst.fromSources ?? []
|
|
1463
|
+
if (dialect.name === "standard" && (targets.length > 1 || fromSources.length > 0 || updateAst.joins.length > 0)) {
|
|
1464
|
+
throw new Error("Unsupported standard joined mutation")
|
|
1465
|
+
}
|
|
983
1466
|
const assignments = updateAst.set!.map((entry) =>
|
|
984
|
-
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
1467
|
+
renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
|
|
985
1468
|
if (dialect.name === "mysql") {
|
|
986
1469
|
const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
|
|
987
1470
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
@@ -1020,7 +1503,8 @@ export const renderQueryAst = (
|
|
|
1020
1503
|
if (dialect.name === "mysql" && updateAst.limit) {
|
|
1021
1504
|
sql += ` limit ${renderExpression(updateAst.limit, state, dialect)}`
|
|
1022
1505
|
}
|
|
1023
|
-
|
|
1506
|
+
assertSupportedMutationReturning(dialect, updateAst.select as Record<string, unknown>)
|
|
1507
|
+
const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
|
|
1024
1508
|
projections = returning.projections
|
|
1025
1509
|
if (returning.sql.length > 0) {
|
|
1026
1510
|
sql += ` returning ${returning.sql}`
|
|
@@ -1032,6 +1516,9 @@ export const renderQueryAst = (
|
|
|
1032
1516
|
const targetSource = deleteAst.target!
|
|
1033
1517
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1034
1518
|
const targets = deleteAst.targets ?? [targetSource]
|
|
1519
|
+
if (dialect.name === "standard" && (targets.length > 1 || deleteAst.joins.length > 0)) {
|
|
1520
|
+
throw new Error("Unsupported standard joined mutation")
|
|
1521
|
+
}
|
|
1035
1522
|
if (dialect.name === "mysql") {
|
|
1036
1523
|
const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
|
|
1037
1524
|
const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
|
|
@@ -1066,7 +1553,8 @@ export const renderQueryAst = (
|
|
|
1066
1553
|
if (dialect.name === "mysql" && deleteAst.limit) {
|
|
1067
1554
|
sql += ` limit ${renderExpression(deleteAst.limit, state, dialect)}`
|
|
1068
1555
|
}
|
|
1069
|
-
|
|
1556
|
+
assertSupportedMutationReturning(dialect, deleteAst.select as Record<string, unknown>)
|
|
1557
|
+
const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
|
|
1070
1558
|
projections = returning.projections
|
|
1071
1559
|
if (returning.sql.length > 0) {
|
|
1072
1560
|
sql += ` returning ${returning.sql}`
|
|
@@ -1075,12 +1563,18 @@ export const renderQueryAst = (
|
|
|
1075
1563
|
}
|
|
1076
1564
|
case "truncate": {
|
|
1077
1565
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1566
|
+
if (dialect.name === "standard") {
|
|
1567
|
+
throw new Error("Unsupported standard truncate statement")
|
|
1568
|
+
}
|
|
1569
|
+
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1078
1570
|
const targetSource = truncateAst.target!
|
|
1571
|
+
const restartIdentity = truncate.restartIdentity
|
|
1572
|
+
const cascade = truncate.cascade
|
|
1079
1573
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1080
|
-
if (
|
|
1574
|
+
if (restartIdentity) {
|
|
1081
1575
|
sql += " restart identity"
|
|
1082
1576
|
}
|
|
1083
|
-
if (
|
|
1577
|
+
if (cascade) {
|
|
1084
1578
|
sql += " cascade"
|
|
1085
1579
|
}
|
|
1086
1580
|
break
|
|
@@ -1095,15 +1589,17 @@ export const renderQueryAst = (
|
|
|
1095
1589
|
const merge = mergeAst.merge!
|
|
1096
1590
|
sql = `merge into ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} using ${renderSourceReference(usingSource.source, usingSource.tableName, usingSource.baseTableName, state, dialect)} on ${renderExpression(merge.on, state, dialect)}`
|
|
1097
1591
|
if (merge.whenMatched) {
|
|
1592
|
+
const matchedKind = merge.whenMatched.kind === "delete" ? "delete" : "update"
|
|
1098
1593
|
sql += " when matched"
|
|
1099
1594
|
if (merge.whenMatched.predicate) {
|
|
1100
1595
|
sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
|
|
1101
1596
|
}
|
|
1102
|
-
if (
|
|
1597
|
+
if (matchedKind === "delete") {
|
|
1103
1598
|
sql += " then delete"
|
|
1104
1599
|
} else {
|
|
1105
|
-
|
|
1106
|
-
|
|
1600
|
+
const matchedUpdate = merge.whenMatched as Extract<QueryAst.MergeMatchedClause, { readonly kind: "update" }>
|
|
1601
|
+
sql += ` then update set ${matchedUpdate.values.map((entry) =>
|
|
1602
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
1107
1603
|
).join(", ")}`
|
|
1108
1604
|
}
|
|
1109
1605
|
}
|
|
@@ -1112,7 +1608,7 @@ export const renderQueryAst = (
|
|
|
1112
1608
|
if (merge.whenNotMatched.predicate) {
|
|
1113
1609
|
sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
|
|
1114
1610
|
}
|
|
1115
|
-
sql += ` then insert (${merge.whenNotMatched.values.map((entry) =>
|
|
1611
|
+
sql += ` then insert (${merge.whenNotMatched.values.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
1116
1612
|
}
|
|
1117
1613
|
break
|
|
1118
1614
|
}
|
|
@@ -1127,12 +1623,17 @@ export const renderQueryAst = (
|
|
|
1127
1623
|
}
|
|
1128
1624
|
case "createTable": {
|
|
1129
1625
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1130
|
-
|
|
1626
|
+
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1627
|
+
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1131
1628
|
break
|
|
1132
1629
|
}
|
|
1133
1630
|
case "dropTable": {
|
|
1134
1631
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1135
|
-
const
|
|
1632
|
+
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1633
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
1634
|
+
if (dialect.name !== "postgres" && ifExists) {
|
|
1635
|
+
throw new Error(`Unsupported ${dialect.name} drop table options`)
|
|
1636
|
+
}
|
|
1136
1637
|
sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1137
1638
|
break
|
|
1138
1639
|
}
|
|
@@ -1140,7 +1641,7 @@ export const renderQueryAst = (
|
|
|
1140
1641
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1141
1642
|
sql = renderCreateIndexSql(
|
|
1142
1643
|
createIndexAst.target!,
|
|
1143
|
-
createIndexAst.ddl
|
|
1644
|
+
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
1144
1645
|
state,
|
|
1145
1646
|
dialect
|
|
1146
1647
|
)
|
|
@@ -1150,15 +1651,21 @@ export const renderQueryAst = (
|
|
|
1150
1651
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1151
1652
|
sql = renderDropIndexSql(
|
|
1152
1653
|
dropIndexAst.target!,
|
|
1153
|
-
dropIndexAst.ddl
|
|
1654
|
+
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
1154
1655
|
state,
|
|
1155
1656
|
dialect
|
|
1156
1657
|
)
|
|
1157
1658
|
break
|
|
1158
1659
|
}
|
|
1660
|
+
default: {
|
|
1661
|
+
if (ast.transaction !== undefined) {
|
|
1662
|
+
sql = renderTransactionClause(ast.transaction, dialect)
|
|
1663
|
+
}
|
|
1664
|
+
break
|
|
1665
|
+
}
|
|
1159
1666
|
}
|
|
1160
1667
|
|
|
1161
|
-
if (state.ctes.length === 0) {
|
|
1668
|
+
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
1162
1669
|
return {
|
|
1163
1670
|
sql,
|
|
1164
1671
|
projections
|
|
@@ -1206,9 +1713,19 @@ const renderSourceReference = (
|
|
|
1206
1713
|
readonly plan: Query.Plan.Any
|
|
1207
1714
|
readonly recursive?: boolean
|
|
1208
1715
|
}
|
|
1716
|
+
const registeredCteSource = state.cteSources.get(cte.name)
|
|
1717
|
+
if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
|
|
1718
|
+
throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
|
|
1719
|
+
}
|
|
1209
1720
|
if (!state.cteNames.has(cte.name)) {
|
|
1210
1721
|
state.cteNames.add(cte.name)
|
|
1211
|
-
|
|
1722
|
+
state.cteSources.set(cte.name, cte.plan)
|
|
1723
|
+
const rendered = renderQueryAst(
|
|
1724
|
+
Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1725
|
+
state,
|
|
1726
|
+
dialect,
|
|
1727
|
+
{ emitCtes: false }
|
|
1728
|
+
)
|
|
1212
1729
|
state.ctes.push({
|
|
1213
1730
|
name: cte.name,
|
|
1214
1731
|
sql: rendered.sql,
|
|
@@ -1225,14 +1742,17 @@ const renderSourceReference = (
|
|
|
1225
1742
|
if (!state.cteNames.has(derived.name)) {
|
|
1226
1743
|
// derived tables are inlined, so no CTE registration is needed
|
|
1227
1744
|
}
|
|
1228
|
-
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1745
|
+
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1229
1746
|
}
|
|
1230
1747
|
if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
|
|
1231
1748
|
const lateral = source as unknown as {
|
|
1232
1749
|
readonly name: string
|
|
1233
1750
|
readonly plan: Query.Plan.Any
|
|
1234
1751
|
}
|
|
1235
|
-
|
|
1752
|
+
if (dialect.name === "standard") {
|
|
1753
|
+
throw new Error("Unsupported standard lateral source")
|
|
1754
|
+
}
|
|
1755
|
+
return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
|
|
1236
1756
|
}
|
|
1237
1757
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
1238
1758
|
const values = source as unknown as {
|
|
@@ -1258,13 +1778,42 @@ const renderSourceReference = (
|
|
|
1258
1778
|
if (dialect.name !== "postgres") {
|
|
1259
1779
|
throw new Error("Unsupported table function source for SQL rendering")
|
|
1260
1780
|
}
|
|
1781
|
+
const functionName = renderFunctionName(tableFunction.functionName)
|
|
1261
1782
|
const columnNames = Object.keys(tableFunction.columns)
|
|
1262
|
-
return `${
|
|
1783
|
+
return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
|
|
1263
1784
|
}
|
|
1264
1785
|
const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
|
|
1265
|
-
? (source as Table.AnyTable)
|
|
1786
|
+
? casedSchemaName(source as Table.AnyTable, state)
|
|
1266
1787
|
: undefined
|
|
1267
|
-
|
|
1788
|
+
if (typeof source === "object" && source !== null && Table.TypeId in source) {
|
|
1789
|
+
const table = source as Table.AnyTable
|
|
1790
|
+
const renderedBaseName = casedTableName(table, state)
|
|
1791
|
+
const renderedTableName = table[Table.TypeId].kind === "alias"
|
|
1792
|
+
? tableName
|
|
1793
|
+
: renderedBaseName
|
|
1794
|
+
return dialect.renderTableReference(renderedTableName, renderedBaseName, schemaName)
|
|
1795
|
+
}
|
|
1796
|
+
return dialect.renderTableReference(
|
|
1797
|
+
Casing.applyCategory(state.casing, "tables", tableName),
|
|
1798
|
+
Casing.applyCategory(state.casing, "tables", baseTableName),
|
|
1799
|
+
schemaName
|
|
1800
|
+
)
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
const renderSubqueryExpressionPlan = (
|
|
1804
|
+
plan: Query.Plan.Any,
|
|
1805
|
+
state: RenderState,
|
|
1806
|
+
dialect: SqlDialect
|
|
1807
|
+
): string => {
|
|
1808
|
+
const statement = Query.getQueryState(plan).statement
|
|
1809
|
+
if (statement !== "select" && statement !== "set") {
|
|
1810
|
+
throw new Error("subquery expressions only accept select-like query plans")
|
|
1811
|
+
}
|
|
1812
|
+
return renderQueryAst(
|
|
1813
|
+
Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1814
|
+
state,
|
|
1815
|
+
dialect
|
|
1816
|
+
).sql
|
|
1268
1817
|
}
|
|
1269
1818
|
|
|
1270
1819
|
/**
|
|
@@ -1287,135 +1836,176 @@ export const renderExpression = (
|
|
|
1287
1836
|
return jsonSql
|
|
1288
1837
|
}
|
|
1289
1838
|
const ast = rawAst as ExpressionAst.Any
|
|
1290
|
-
const renderComparisonOperator = (operator:
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
:
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1839
|
+
const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
|
|
1840
|
+
({
|
|
1841
|
+
eq: "=",
|
|
1842
|
+
neq: "<>",
|
|
1843
|
+
lt: "<",
|
|
1844
|
+
lte: "<=",
|
|
1845
|
+
gt: ">",
|
|
1846
|
+
gte: ">="
|
|
1847
|
+
} as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
|
|
1848
|
+
const renderCollation = (collation: unknown): string => {
|
|
1849
|
+
return (collation as readonly string[]).map((segment) => dialect.quoteIdentifier(segment)).join(".")
|
|
1850
|
+
}
|
|
1851
|
+
switch (ast.kind) {
|
|
1303
1852
|
case "column":
|
|
1304
|
-
return ast.tableName.length === 0
|
|
1305
|
-
?
|
|
1306
|
-
: `${dialect.quoteIdentifier(ast.tableName)}.${
|
|
1853
|
+
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1854
|
+
? quoteColumn(ast.columnName, state, dialect, ast.tableName)
|
|
1855
|
+
: `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
|
|
1307
1856
|
case "literal":
|
|
1857
|
+
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1858
|
+
throw new Error("Expected a finite numeric value")
|
|
1859
|
+
}
|
|
1308
1860
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1309
1861
|
case "excluded":
|
|
1862
|
+
if (state.allowExcluded !== true) {
|
|
1863
|
+
throw new Error("excluded(...) is only supported inside insert conflict handlers")
|
|
1864
|
+
}
|
|
1310
1865
|
return dialect.name === "mysql"
|
|
1311
|
-
? `values(${
|
|
1312
|
-
: `excluded.${
|
|
1866
|
+
? `values(${quoteColumn(ast.columnName, state, dialect)})`
|
|
1867
|
+
: `excluded.${quoteColumn(ast.columnName, state, dialect)}`
|
|
1313
1868
|
case "cast":
|
|
1314
|
-
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1869
|
+
return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1315
1870
|
case "collate":
|
|
1316
|
-
return `(${renderExpression(ast.value, state, dialect)} collate ${ast.collation
|
|
1871
|
+
return `(${renderExpression(expectValueExpression("collate", ast.value), state, dialect)} collate ${renderCollation(ast.collation)})`
|
|
1317
1872
|
case "function":
|
|
1318
|
-
return renderFunctionCall(ast.name,
|
|
1873
|
+
return renderFunctionCall(ast.name, ast.args, state, dialect)
|
|
1319
1874
|
case "eq":
|
|
1320
|
-
return
|
|
1875
|
+
return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
|
|
1321
1876
|
case "neq":
|
|
1322
|
-
return
|
|
1877
|
+
return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
|
|
1323
1878
|
case "lt":
|
|
1324
|
-
return
|
|
1879
|
+
return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
|
|
1325
1880
|
case "lte":
|
|
1326
|
-
return
|
|
1881
|
+
return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
|
|
1327
1882
|
case "gt":
|
|
1328
|
-
return
|
|
1883
|
+
return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
|
|
1329
1884
|
case "gte":
|
|
1330
|
-
return
|
|
1885
|
+
return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
|
|
1331
1886
|
case "like":
|
|
1332
|
-
return
|
|
1333
|
-
case "ilike":
|
|
1887
|
+
return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
|
|
1888
|
+
case "ilike": {
|
|
1889
|
+
const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
|
|
1334
1890
|
return dialect.name === "postgres"
|
|
1335
|
-
? `(${renderExpression(
|
|
1336
|
-
: `(lower(${renderExpression(
|
|
1337
|
-
|
|
1891
|
+
? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
|
|
1892
|
+
: `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
|
|
1893
|
+
}
|
|
1894
|
+
case "regexMatch": {
|
|
1895
|
+
const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
|
|
1896
|
+
if (dialect.name === "standard") {
|
|
1897
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1898
|
+
}
|
|
1338
1899
|
return dialect.name === "postgres"
|
|
1339
|
-
? `(${renderExpression(
|
|
1340
|
-
: `(${renderExpression(
|
|
1341
|
-
|
|
1900
|
+
? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
|
|
1901
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1902
|
+
}
|
|
1903
|
+
case "regexIMatch": {
|
|
1904
|
+
const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
|
|
1905
|
+
if (dialect.name === "standard") {
|
|
1906
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1907
|
+
}
|
|
1342
1908
|
return dialect.name === "postgres"
|
|
1343
|
-
? `(${renderExpression(
|
|
1344
|
-
: `(${renderExpression(
|
|
1345
|
-
|
|
1909
|
+
? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
|
|
1910
|
+
: `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
|
|
1911
|
+
}
|
|
1912
|
+
case "regexNotMatch": {
|
|
1913
|
+
const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
|
|
1914
|
+
if (dialect.name === "standard") {
|
|
1915
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1916
|
+
}
|
|
1346
1917
|
return dialect.name === "postgres"
|
|
1347
|
-
? `(${renderExpression(
|
|
1348
|
-
: `(${renderExpression(
|
|
1349
|
-
|
|
1918
|
+
? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
|
|
1919
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1920
|
+
}
|
|
1921
|
+
case "regexNotIMatch": {
|
|
1922
|
+
const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
|
|
1923
|
+
if (dialect.name === "standard") {
|
|
1924
|
+
throw new Error("Unsupported standard regular-expression predicates")
|
|
1925
|
+
}
|
|
1350
1926
|
return dialect.name === "postgres"
|
|
1351
|
-
? `(${renderExpression(
|
|
1352
|
-
: `(${renderExpression(
|
|
1353
|
-
|
|
1927
|
+
? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
|
|
1928
|
+
: `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
|
|
1929
|
+
}
|
|
1930
|
+
case "isDistinctFrom": {
|
|
1931
|
+
const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
|
|
1354
1932
|
return dialect.name === "mysql"
|
|
1355
|
-
? `(not (${renderExpression(
|
|
1356
|
-
: `(${renderExpression(
|
|
1357
|
-
|
|
1933
|
+
? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
|
|
1934
|
+
: `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
|
|
1935
|
+
}
|
|
1936
|
+
case "isNotDistinctFrom": {
|
|
1937
|
+
const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
|
|
1358
1938
|
return dialect.name === "mysql"
|
|
1359
|
-
? `(${renderExpression(
|
|
1360
|
-
: `(${renderExpression(
|
|
1361
|
-
|
|
1939
|
+
? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
|
|
1940
|
+
: `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
|
|
1941
|
+
}
|
|
1942
|
+
case "contains": {
|
|
1943
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
|
|
1362
1944
|
if (dialect.name === "postgres") {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1945
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1946
|
+
const left = isJsonExpression(leftExpression)
|
|
1947
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1948
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1949
|
+
const right = isJsonExpression(rightExpression)
|
|
1950
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1951
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1369
1952
|
return `(${left} @> ${right})`
|
|
1370
1953
|
}
|
|
1371
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1372
|
-
return `json_contains(${renderExpression(
|
|
1954
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1955
|
+
return `json_contains(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
|
|
1373
1956
|
}
|
|
1374
1957
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1375
|
-
|
|
1958
|
+
}
|
|
1959
|
+
case "containedBy": {
|
|
1960
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
|
|
1376
1961
|
if (dialect.name === "postgres") {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1962
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1963
|
+
const left = isJsonExpression(leftExpression)
|
|
1964
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1965
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1966
|
+
const right = isJsonExpression(rightExpression)
|
|
1967
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1968
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1383
1969
|
return `(${left} <@ ${right})`
|
|
1384
1970
|
}
|
|
1385
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1386
|
-
return `json_contains(${renderExpression(
|
|
1971
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1972
|
+
return `json_contains(${renderExpression(rightExpression, state, dialect)}, ${renderExpression(leftExpression, state, dialect)})`
|
|
1387
1973
|
}
|
|
1388
1974
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1389
|
-
|
|
1975
|
+
}
|
|
1976
|
+
case "overlaps": {
|
|
1977
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
|
|
1390
1978
|
if (dialect.name === "postgres") {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1979
|
+
assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
|
|
1980
|
+
const left = isJsonExpression(leftExpression)
|
|
1981
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1982
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1983
|
+
const right = isJsonExpression(rightExpression)
|
|
1984
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1985
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1397
1986
|
return `(${left} && ${right})`
|
|
1398
1987
|
}
|
|
1399
|
-
if (dialect.name === "mysql" && isJsonExpression(
|
|
1400
|
-
return `json_overlaps(${renderExpression(
|
|
1988
|
+
if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
|
|
1989
|
+
return `json_overlaps(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
|
|
1401
1990
|
}
|
|
1402
1991
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1992
|
+
}
|
|
1403
1993
|
case "isNull":
|
|
1404
|
-
return `(${renderExpression(ast.value, state, dialect)} is null)`
|
|
1994
|
+
return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
|
|
1405
1995
|
case "isNotNull":
|
|
1406
|
-
return `(${renderExpression(ast.value, state, dialect)} is not null)`
|
|
1996
|
+
return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
|
|
1407
1997
|
case "not":
|
|
1408
|
-
return `(not ${renderExpression(ast.value, state, dialect)})`
|
|
1998
|
+
return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
|
|
1409
1999
|
case "upper":
|
|
1410
|
-
return `upper(${renderExpression(ast.value, state, dialect)})`
|
|
2000
|
+
return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
|
|
1411
2001
|
case "lower":
|
|
1412
|
-
return `lower(${renderExpression(ast.value, state, dialect)})`
|
|
2002
|
+
return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
|
|
1413
2003
|
case "count":
|
|
1414
|
-
return `count(${renderExpression(ast.value, state, dialect)})`
|
|
2004
|
+
return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
|
|
1415
2005
|
case "max":
|
|
1416
|
-
return `max(${renderExpression(ast.value, state, dialect)})`
|
|
2006
|
+
return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
|
|
1417
2007
|
case "min":
|
|
1418
|
-
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
2008
|
+
return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
|
|
1419
2009
|
case "and":
|
|
1420
2010
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1421
2011
|
case "or":
|
|
@@ -1435,45 +2025,39 @@ export const renderExpression = (
|
|
|
1435
2025
|
`when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
|
|
1436
2026
|
).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
|
|
1437
2027
|
case "exists":
|
|
1438
|
-
return `exists (${
|
|
1439
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1440
|
-
state,
|
|
1441
|
-
dialect
|
|
1442
|
-
).sql})`
|
|
2028
|
+
return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1443
2029
|
case "scalarSubquery":
|
|
1444
|
-
return `(${
|
|
1445
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1446
|
-
state,
|
|
1447
|
-
dialect
|
|
1448
|
-
).sql})`
|
|
2030
|
+
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1449
2031
|
case "inSubquery":
|
|
1450
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
state,
|
|
1465
|
-
dialect
|
|
1466
|
-
).sql}))`
|
|
1467
|
-
case "window": {
|
|
1468
|
-
if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
|
|
1469
|
-
break
|
|
2032
|
+
return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2033
|
+
case "comparisonAny": {
|
|
2034
|
+
const left = expectValueExpression("compareAny", ast.left)
|
|
2035
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
2036
|
+
if (dialect.name === "standard") {
|
|
2037
|
+
throw new Error("Unsupported standard quantified comparison")
|
|
2038
|
+
}
|
|
2039
|
+
return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2040
|
+
}
|
|
2041
|
+
case "comparisonAll": {
|
|
2042
|
+
const left = expectValueExpression("compareAll", ast.left)
|
|
2043
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
2044
|
+
if (dialect.name === "standard") {
|
|
2045
|
+
throw new Error("Unsupported standard quantified comparison")
|
|
1470
2046
|
}
|
|
2047
|
+
return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
2048
|
+
}
|
|
2049
|
+
case "window": {
|
|
2050
|
+
const partitionBy = ast.partitionBy as readonly Expression.Any[]
|
|
2051
|
+
const orderBy = ast.orderBy as readonly {
|
|
2052
|
+
readonly value: Expression.Any
|
|
2053
|
+
readonly direction: string
|
|
2054
|
+
}[]
|
|
1471
2055
|
const clauses: string[] = []
|
|
1472
|
-
if (
|
|
1473
|
-
clauses.push(`partition by ${
|
|
2056
|
+
if (partitionBy.length > 0) {
|
|
2057
|
+
clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
|
|
1474
2058
|
}
|
|
1475
|
-
if (
|
|
1476
|
-
clauses.push(`order by ${
|
|
2059
|
+
if (orderBy.length > 0) {
|
|
2060
|
+
clauses.push(`order by ${orderBy.map((entry) =>
|
|
1477
2061
|
`${renderExpression(entry.value, state, dialect)} ${entry.direction}`
|
|
1478
2062
|
).join(", ")}`)
|
|
1479
2063
|
}
|
|
@@ -1486,7 +2070,7 @@ export const renderExpression = (
|
|
|
1486
2070
|
case "denseRank":
|
|
1487
2071
|
return `dense_rank() over (${specification})`
|
|
1488
2072
|
case "over":
|
|
1489
|
-
return `${renderExpression(ast.value
|
|
2073
|
+
return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
|
|
1490
2074
|
}
|
|
1491
2075
|
break
|
|
1492
2076
|
}
|