effect-qb 4.0.0-beta.66 → 4.0.0-beta.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/index.js +9376 -0
- package/dist/mysql.js +3921 -2573
- package/dist/postgres/metadata.js +2491 -1373
- package/dist/postgres.js +5453 -5155
- package/dist/sqlite.js +6895 -5529
- package/dist/standard.js +9330 -0
- package/package.json +9 -2
- package/src/casing.ts +71 -0
- package/src/index.ts +2 -0
- package/src/internal/casing.ts +89 -0
- package/src/internal/coercion/rules.ts +13 -1
- package/src/internal/column-state.ts +21 -15
- package/src/internal/column.ts +44 -7
- package/src/internal/datatypes/define.ts +7 -1
- package/src/internal/datatypes/enrich.ts +23 -0
- package/src/internal/datatypes/lookup.ts +81 -25
- package/src/internal/datatypes/matrix.ts +903 -0
- package/src/internal/datatypes/shape.ts +2 -0
- package/src/internal/derived-table.ts +4 -36
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/mysql.ts} +552 -361
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +657 -400
- package/src/{sqlite/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +505 -347
- package/src/internal/dialect.ts +35 -0
- package/src/internal/dsl-mutation-runtime.ts +12 -162
- package/src/internal/dsl-plan-runtime.ts +10 -138
- package/src/internal/dsl-query-runtime.ts +5 -79
- package/src/internal/dsl-transaction-ddl-runtime.ts +41 -65
- package/src/internal/executor.ts +14 -16
- package/src/internal/grouping-key.ts +87 -20
- package/src/internal/implication-runtime.ts +1 -1
- package/src/internal/json/path-access.ts +351 -0
- package/src/internal/predicate/runtime.ts +3 -0
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +64 -25
- package/src/internal/renderer.ts +26 -14
- package/src/internal/runtime/normalize.ts +12 -5
- package/src/internal/scalar.ts +7 -1
- package/src/internal/schema-derivation.d.ts +7 -7
- package/src/internal/schema-derivation.ts +9 -9
- package/src/internal/schema-expression.ts +2 -2
- package/src/internal/sql-expression-renderer.ts +19 -0
- package/src/internal/standard-dsl.ts +6978 -0
- package/src/internal/table-options.ts +126 -66
- package/src/internal/table.ts +652 -219
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +8 -9
- package/src/mysql/datatypes/index.ts +4 -2
- package/src/mysql/datatypes/spec.ts +6 -176
- package/src/mysql/errors/normalize.ts +0 -1
- package/src/mysql/executor.ts +7 -7
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +312 -154
- package/src/mysql/internal/renderer.ts +6 -2
- package/src/mysql/json.ts +7 -2
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/renderer.ts +37 -3
- package/src/mysql/type.ts +60 -0
- package/src/mysql.ts +7 -13
- package/src/postgres/check.ts +1 -0
- package/src/postgres/column-extension.ts +28 -0
- package/src/postgres/column.ts +2 -8
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +4 -2
- package/src/postgres/datatypes/spec.ts +6 -260
- package/src/postgres/errors/normalize.ts +0 -1
- package/src/postgres/executor.ts +7 -7
- package/src/postgres/foreign-key.ts +24 -0
- package/src/postgres/function/core.ts +1 -3
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/index.ts +1 -0
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +306 -163
- package/src/postgres/internal/renderer.ts +6 -2
- package/src/postgres/internal/schema-ddl.ts +22 -10
- package/src/postgres/internal/schema-model.ts +238 -7
- package/src/postgres/json-extension.ts +7 -0
- package/src/postgres/json.ts +762 -173
- package/src/postgres/jsonb.ts +37 -0
- package/src/postgres/primary-key.ts +24 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/renderer.ts +37 -3
- package/src/postgres/schema-management.ts +12 -11
- package/src/postgres/schema.ts +106 -15
- package/src/postgres/table.ts +205 -530
- package/src/postgres/type.ts +93 -10
- package/src/postgres/unique.ts +32 -0
- package/src/postgres.ts +20 -16
- package/src/sqlite/column-extension.ts +3 -0
- package/src/sqlite/column.ts +8 -9
- package/src/sqlite/datatypes/index.ts +4 -2
- package/src/sqlite/datatypes/spec.ts +6 -94
- package/src/sqlite/errors/normalize.ts +0 -1
- package/src/sqlite/executor.ts +7 -7
- package/src/sqlite/internal/dialect.ts +9 -4
- package/src/sqlite/internal/dsl.ts +301 -154
- package/src/sqlite/internal/renderer.ts +6 -2
- package/src/sqlite/json.ts +8 -2
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/renderer.ts +37 -3
- package/src/sqlite/type.ts +40 -0
- package/src/sqlite.ts +7 -13
- package/src/standard/cast.ts +113 -0
- package/src/standard/check.ts +17 -0
- package/src/standard/column.ts +163 -0
- package/src/standard/datatypes/index.ts +83 -0
- package/src/standard/datatypes/spec.ts +12 -0
- package/src/standard/dialect.ts +40 -0
- package/src/standard/foreign-key.ts +37 -0
- package/src/standard/function/aggregate.ts +2 -0
- package/src/standard/function/core.ts +2 -0
- package/src/standard/function/index.ts +18 -0
- package/src/standard/function/string.ts +2 -0
- package/src/standard/function/temporal.ts +78 -0
- package/src/standard/function/window.ts +2 -0
- package/src/standard/index.ts +17 -0
- package/src/standard/internal/renderer.ts +45 -0
- package/src/standard/json.ts +883 -0
- package/src/standard/primary-key.ts +17 -0
- package/src/standard/query.ts +152 -0
- package/src/standard/renderer.ts +49 -0
- package/src/standard/table.ts +151 -0
- package/src/standard/unique.ts +17 -0
- package/src/standard.ts +32 -0
- package/src/internal/aggregation-validation.ts +0 -57
- package/src/internal/table.d.ts +0 -180
- package/src/mysql/table.ts +0 -186
- package/src/postgres/cast.ts +0 -45
- package/src/sqlite/table.ts +0 -175
package/src/{sqlite/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts}
RENAMED
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema"
|
|
2
2
|
|
|
3
|
-
import * as Query from "
|
|
4
|
-
import * as Expression from "
|
|
5
|
-
import * as Table from "
|
|
6
|
-
import * as QueryAst from "
|
|
7
|
-
import type
|
|
8
|
-
import
|
|
9
|
-
import * as
|
|
10
|
-
import
|
|
11
|
-
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 { expectConflictClause } from "../dsl-mutation-runtime.js"
|
|
12
|
+
import { expectDdlClauseKind, normalizeStatementFlag, normalizeStatementIdentifier } from "../dsl-transaction-ddl-runtime.js"
|
|
12
13
|
import {
|
|
13
14
|
renderJsonSelectSql,
|
|
14
15
|
renderSelectSql,
|
|
15
16
|
toDriverValue
|
|
16
|
-
} from "
|
|
17
|
-
import { normalizeDbValue } from "
|
|
18
|
-
import { flattenSelection, type Projection } from "
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
17
|
+
} from "../runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../runtime/normalize.js"
|
|
19
|
+
import { flattenSelection, type Projection } from "../projections.js"
|
|
20
|
+
import * as SchemaExpression from "../schema-expression.js"
|
|
21
|
+
import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
|
|
22
|
+
import * as Casing from "../casing.js"
|
|
22
23
|
|
|
23
24
|
const renderDbType = (
|
|
24
25
|
dialect: SqlDialect,
|
|
25
26
|
dbType: Expression.DbType.Any
|
|
26
27
|
): string => {
|
|
27
|
-
|
|
28
|
-
return "text"
|
|
29
|
-
}
|
|
30
|
-
return dbType.kind
|
|
28
|
+
return renderDbTypeName(renderPortableDatatypeDdlType(dialect.name, dbType.kind) ?? dbType.kind)
|
|
31
29
|
}
|
|
32
30
|
|
|
31
|
+
const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
32
|
+
"element" in dbType
|
|
33
|
+
|
|
33
34
|
const renderCastType = (
|
|
34
35
|
dialect: SqlDialect,
|
|
35
|
-
dbType:
|
|
36
|
+
dbType: unknown
|
|
36
37
|
): string => {
|
|
38
|
+
const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
|
|
39
|
+
const portableType = renderPortableDatatypeCastType(dialect.name, kind)
|
|
40
|
+
if (portableType !== undefined) {
|
|
41
|
+
return renderDbTypeName(portableType)
|
|
42
|
+
}
|
|
37
43
|
if (dialect.name !== "sqlite") {
|
|
38
|
-
return
|
|
44
|
+
return renderDbTypeName(kind)
|
|
39
45
|
}
|
|
40
|
-
switch (
|
|
46
|
+
switch (kind) {
|
|
41
47
|
case "text":
|
|
42
48
|
return "text"
|
|
43
49
|
case "uuid":
|
|
@@ -56,7 +62,7 @@ const renderCastType = (
|
|
|
56
62
|
case "json":
|
|
57
63
|
return "json"
|
|
58
64
|
default:
|
|
59
|
-
return
|
|
65
|
+
return renderDbTypeName(kind)
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
|
|
@@ -128,22 +134,164 @@ const renderSqliteMutationLimit = (
|
|
|
128
134
|
return renderExpression(expression, state, dialect)
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
const casingForTable = (
|
|
138
|
+
table: Table.AnyTable,
|
|
139
|
+
state: RenderState
|
|
140
|
+
): Casing.Options | undefined =>
|
|
141
|
+
Casing.merge(state.casing, table[Table.TypeId].casing)
|
|
142
|
+
|
|
143
|
+
const casedColumnName = (
|
|
144
|
+
columnName: string,
|
|
145
|
+
state: RenderState,
|
|
146
|
+
tableName?: string
|
|
147
|
+
): string => {
|
|
148
|
+
if (tableName !== undefined) {
|
|
149
|
+
const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
|
|
150
|
+
if (mapped !== undefined) {
|
|
151
|
+
return mapped
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return Casing.applyCategory(state.casing, "columns", columnName)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const casedTableReferenceName = (
|
|
158
|
+
tableName: string,
|
|
159
|
+
state: RenderState
|
|
160
|
+
): string =>
|
|
161
|
+
state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
|
|
162
|
+
|
|
163
|
+
const quoteColumn = (
|
|
164
|
+
columnName: string,
|
|
165
|
+
state: RenderState,
|
|
166
|
+
dialect: SqlDialect,
|
|
167
|
+
tableName?: string
|
|
168
|
+
): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
|
|
169
|
+
|
|
170
|
+
const stateWithTableCasing = (
|
|
171
|
+
state: RenderState,
|
|
172
|
+
source: unknown
|
|
173
|
+
): RenderState =>
|
|
174
|
+
typeof source === "object" && source !== null && Table.TypeId in source
|
|
175
|
+
? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
|
|
176
|
+
: state
|
|
177
|
+
|
|
178
|
+
const referenceCasing = (
|
|
179
|
+
reference: { readonly casing?: Casing.Options },
|
|
180
|
+
state: RenderState
|
|
181
|
+
): Casing.Options | undefined =>
|
|
182
|
+
Casing.merge(state.casing, reference.casing)
|
|
183
|
+
|
|
184
|
+
const renderReferenceTable = (
|
|
185
|
+
reference: {
|
|
186
|
+
readonly tableName: string
|
|
187
|
+
readonly schemaName?: string
|
|
188
|
+
readonly casing?: Casing.Options
|
|
189
|
+
},
|
|
190
|
+
state: RenderState,
|
|
191
|
+
dialect: SqlDialect
|
|
192
|
+
): string => {
|
|
193
|
+
const casing = referenceCasing(reference, state)
|
|
194
|
+
const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
|
|
195
|
+
const schemaName = reference.schemaName === undefined
|
|
196
|
+
? undefined
|
|
197
|
+
: Casing.applyCategory(casing, "schemas", reference.schemaName)
|
|
198
|
+
return dialect.renderTableReference(tableName, tableName, schemaName)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const quoteReferenceColumn = (
|
|
202
|
+
columnName: string,
|
|
203
|
+
reference: { readonly casing?: Casing.Options },
|
|
204
|
+
state: RenderState,
|
|
205
|
+
dialect: SqlDialect
|
|
206
|
+
): string =>
|
|
207
|
+
dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
|
|
208
|
+
|
|
209
|
+
const registerSourceReference = (
|
|
210
|
+
source: unknown,
|
|
211
|
+
tableName: string,
|
|
212
|
+
state: RenderState
|
|
213
|
+
): void => {
|
|
214
|
+
if (typeof source !== "object" || source === null) {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
if (Table.TypeId in source) {
|
|
218
|
+
const table = source as Table.AnyTable
|
|
219
|
+
const tableState = table[Table.TypeId]
|
|
220
|
+
const casing = casingForTable(table, state)
|
|
221
|
+
const renderedTableName = tableState.kind === "alias"
|
|
222
|
+
? tableName
|
|
223
|
+
: Casing.applyCategory(casing, "tables", tableState.baseName)
|
|
224
|
+
const columns = new Map(
|
|
225
|
+
Object.keys(tableState.fields).map((columnName) => [
|
|
226
|
+
columnName,
|
|
227
|
+
Casing.applyCategory(casing, "columns", columnName)
|
|
228
|
+
] as const)
|
|
229
|
+
)
|
|
230
|
+
state.sourceNames?.set(tableName, {
|
|
231
|
+
tableName: renderedTableName,
|
|
232
|
+
columns
|
|
233
|
+
})
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
|
|
237
|
+
state.sourceNames?.set(tableName, {
|
|
238
|
+
tableName,
|
|
239
|
+
columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const registerQuerySources = (
|
|
245
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
246
|
+
state: RenderState
|
|
247
|
+
): void => {
|
|
248
|
+
if (ast.from !== undefined) {
|
|
249
|
+
registerSourceReference(ast.from.source, ast.from.tableName, state)
|
|
250
|
+
}
|
|
251
|
+
for (const source of ast.fromSources ?? []) {
|
|
252
|
+
registerSourceReference(source.source, source.tableName, state)
|
|
253
|
+
}
|
|
254
|
+
for (const join of ast.joins) {
|
|
255
|
+
registerSourceReference(join.source, join.tableName, state)
|
|
256
|
+
}
|
|
257
|
+
if (ast.into !== undefined) {
|
|
258
|
+
registerSourceReference(ast.into.source, ast.into.tableName, state)
|
|
259
|
+
}
|
|
260
|
+
if (ast.target !== undefined) {
|
|
261
|
+
registerSourceReference(ast.target.source, ast.target.tableName, state)
|
|
262
|
+
}
|
|
263
|
+
for (const target of ast.targets ?? []) {
|
|
264
|
+
registerSourceReference(target.source, target.tableName, state)
|
|
265
|
+
}
|
|
266
|
+
if (ast.using !== undefined) {
|
|
267
|
+
registerSourceReference(ast.using.source, ast.using.tableName, state)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
131
271
|
const renderColumnDefinition = (
|
|
132
272
|
dialect: SqlDialect,
|
|
133
273
|
state: RenderState,
|
|
134
274
|
columnName: string,
|
|
135
|
-
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
275
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
276
|
+
tableName?: string,
|
|
277
|
+
casing?: Casing.Options
|
|
136
278
|
): string => {
|
|
279
|
+
const expressionState = { ...state, casing, rowLocalColumns: true }
|
|
280
|
+
if (isArrayDbType(column.metadata.dbType)) {
|
|
281
|
+
throw new Error("Unsupported sqlite array column options")
|
|
282
|
+
}
|
|
137
283
|
const clauses = [
|
|
138
|
-
|
|
139
|
-
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)
|
|
140
288
|
]
|
|
141
289
|
if (column.metadata.identity) {
|
|
142
|
-
|
|
290
|
+
throw new Error("Unsupported sqlite identity column options")
|
|
143
291
|
} else if (column.metadata.generatedValue) {
|
|
144
|
-
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue,
|
|
292
|
+
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
|
|
145
293
|
} else if (column.metadata.defaultValue) {
|
|
146
|
-
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue,
|
|
294
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
|
|
147
295
|
}
|
|
148
296
|
if (!column.metadata.nullable) {
|
|
149
297
|
clauses.push("not null")
|
|
@@ -155,34 +303,50 @@ const renderCreateTableSql = (
|
|
|
155
303
|
targetSource: QueryAst.FromClause,
|
|
156
304
|
state: RenderState,
|
|
157
305
|
dialect: SqlDialect,
|
|
158
|
-
ifNotExists:
|
|
306
|
+
ifNotExists: unknown
|
|
159
307
|
): string => {
|
|
308
|
+
const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
|
|
160
309
|
const table = targetSource.source as Table.AnyTable
|
|
310
|
+
const tableCasing = casingForTable(table, state)
|
|
161
311
|
const fields = table[Table.TypeId].fields
|
|
162
312
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
163
|
-
renderColumnDefinition(dialect, state, columnName, column)
|
|
313
|
+
renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
|
|
164
314
|
)
|
|
165
|
-
|
|
315
|
+
const options = table[Table.OptionsSymbol] as unknown
|
|
316
|
+
const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
|
|
317
|
+
validateOptions(table[Table.TypeId].name, fields, tableOptions)
|
|
318
|
+
for (const option of tableOptions) {
|
|
319
|
+
if (typeof option !== "object" || option === null || !("kind" in option)) {
|
|
320
|
+
continue
|
|
321
|
+
}
|
|
166
322
|
switch (option.kind) {
|
|
167
323
|
case "primaryKey":
|
|
168
|
-
|
|
324
|
+
if (option.deferrable || option.initiallyDeferred) {
|
|
325
|
+
throw new Error("Unsupported sqlite primary key constraint options")
|
|
326
|
+
}
|
|
327
|
+
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" : ""}` : ""}`)
|
|
169
328
|
break
|
|
170
329
|
case "unique":
|
|
171
330
|
if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
|
|
172
331
|
throw new Error("Unsupported sqlite unique constraint options")
|
|
173
332
|
}
|
|
174
|
-
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => dialect.
|
|
333
|
+
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" : ""}` : ""}`)
|
|
175
334
|
break
|
|
176
335
|
case "foreignKey": {
|
|
177
|
-
const reference = option.references
|
|
336
|
+
const reference = typeof option.references === "function"
|
|
337
|
+
? option.references()
|
|
338
|
+
: option.references
|
|
178
339
|
definitions.push(
|
|
179
|
-
`${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" : ""}` : ""}`
|
|
180
341
|
)
|
|
181
342
|
break
|
|
182
343
|
}
|
|
183
344
|
case "check":
|
|
345
|
+
if (option.noInherit) {
|
|
346
|
+
throw new Error("Unsupported sqlite check constraint options")
|
|
347
|
+
}
|
|
184
348
|
definitions.push(
|
|
185
|
-
`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" : ""}`
|
|
186
350
|
)
|
|
187
351
|
break
|
|
188
352
|
case "index":
|
|
@@ -191,7 +355,7 @@ const renderCreateTableSql = (
|
|
|
191
355
|
throw new Error("Unsupported table option kind")
|
|
192
356
|
}
|
|
193
357
|
}
|
|
194
|
-
return `create table${
|
|
358
|
+
return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
195
359
|
}
|
|
196
360
|
|
|
197
361
|
const renderCreateIndexSql = (
|
|
@@ -200,8 +364,13 @@ const renderCreateIndexSql = (
|
|
|
200
364
|
state: RenderState,
|
|
201
365
|
dialect: SqlDialect
|
|
202
366
|
): string => {
|
|
203
|
-
const
|
|
204
|
-
|
|
367
|
+
const unique = normalizeStatementFlag(ddl.unique)
|
|
368
|
+
const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
|
|
369
|
+
const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
|
|
370
|
+
const maybeIfNotExists = (dialect.name === "postgres" || dialect.name === "sqlite") && ifNotExists ? " if not exists" : ""
|
|
371
|
+
const table = targetSource.source as Table.AnyTable
|
|
372
|
+
const tableCasing = casingForTable(table, state)
|
|
373
|
+
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
374
|
}
|
|
206
375
|
|
|
207
376
|
const renderDropIndexSql = (
|
|
@@ -209,10 +378,15 @@ const renderDropIndexSql = (
|
|
|
209
378
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
210
379
|
state: RenderState,
|
|
211
380
|
dialect: SqlDialect
|
|
212
|
-
): string =>
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
381
|
+
): string => {
|
|
382
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
383
|
+
const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
|
|
384
|
+
const table = targetSource.source as Table.AnyTable
|
|
385
|
+
const tableCasing = casingForTable(table, state)
|
|
386
|
+
return dialect.name === "postgres" || dialect.name === "sqlite"
|
|
387
|
+
? `drop index${ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))}`
|
|
388
|
+
: `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
389
|
+
}
|
|
216
390
|
|
|
217
391
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
218
392
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
@@ -223,6 +397,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
|
223
397
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
224
398
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
225
399
|
|
|
400
|
+
const expectValueExpression = (
|
|
401
|
+
_functionName: string,
|
|
402
|
+
value: unknown
|
|
403
|
+
): Expression.Any => value as Expression.Any
|
|
404
|
+
|
|
405
|
+
const expectBinaryExpressions = (
|
|
406
|
+
_functionName: string,
|
|
407
|
+
left: unknown,
|
|
408
|
+
right: unknown
|
|
409
|
+
): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
|
|
410
|
+
|
|
411
|
+
const renderBinaryExpression = (
|
|
412
|
+
functionName: string,
|
|
413
|
+
operator: string,
|
|
414
|
+
left: unknown,
|
|
415
|
+
right: unknown,
|
|
416
|
+
state: RenderState,
|
|
417
|
+
dialect: SqlDialect
|
|
418
|
+
): string => {
|
|
419
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
|
|
420
|
+
return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
|
|
421
|
+
}
|
|
422
|
+
|
|
226
423
|
const unsupportedJsonFeature = (
|
|
227
424
|
dialect: SqlDialect,
|
|
228
425
|
feature: string
|
|
@@ -246,13 +443,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
|
|
|
246
443
|
const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
|
|
247
444
|
value !== null && typeof value === "object" && JsonPath.TypeId in value
|
|
248
445
|
|
|
446
|
+
const isOptionalJsonPathNumber = (value: unknown): boolean =>
|
|
447
|
+
value === undefined || (typeof value === "number" && Number.isFinite(value))
|
|
448
|
+
|
|
449
|
+
const isJsonPathSegment = (segment: unknown): boolean => {
|
|
450
|
+
if (typeof segment === "string") {
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
if (typeof segment === "number") {
|
|
454
|
+
return Number.isFinite(segment)
|
|
455
|
+
}
|
|
456
|
+
if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
|
|
457
|
+
return false
|
|
458
|
+
}
|
|
459
|
+
switch ((segment as { readonly kind?: unknown }).kind) {
|
|
460
|
+
case "key":
|
|
461
|
+
return typeof (segment as { readonly key?: unknown }).key === "string"
|
|
462
|
+
case "index": {
|
|
463
|
+
const index = (segment as { readonly index?: unknown }).index
|
|
464
|
+
return typeof index === "number" && Number.isFinite(index)
|
|
465
|
+
}
|
|
466
|
+
case "wildcard":
|
|
467
|
+
case "descend":
|
|
468
|
+
return true
|
|
469
|
+
case "slice":
|
|
470
|
+
return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
|
|
471
|
+
isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
|
|
472
|
+
default:
|
|
473
|
+
return false
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
|
|
478
|
+
if (!Array.isArray(segments)) {
|
|
479
|
+
throw new Error("JSON path expressions require a segment array")
|
|
480
|
+
}
|
|
481
|
+
if (segments.some((segment) => !isJsonPathSegment(segment))) {
|
|
482
|
+
throw new Error("JSON path segments require string, number, or path segment objects")
|
|
483
|
+
}
|
|
484
|
+
return segments as ReadonlyArray<JsonPath.AnySegment>
|
|
485
|
+
}
|
|
486
|
+
|
|
249
487
|
const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
|
|
250
488
|
const path = node.path ?? node.segments ?? node.keys
|
|
251
489
|
if (isJsonPathValue(path)) {
|
|
252
|
-
return path.segments
|
|
490
|
+
return validateJsonPathSegments(path.segments)
|
|
253
491
|
}
|
|
254
492
|
if (Array.isArray(path)) {
|
|
255
|
-
return path
|
|
493
|
+
return validateJsonPathSegments(path)
|
|
494
|
+
}
|
|
495
|
+
if (node.segments !== undefined) {
|
|
496
|
+
return validateJsonPathSegments(node.segments)
|
|
256
497
|
}
|
|
257
498
|
if ("key" in node) {
|
|
258
499
|
return [JsonPath.key(String(node.key))]
|
|
@@ -271,11 +512,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
|
|
|
271
512
|
return []
|
|
272
513
|
}
|
|
273
514
|
if ("right" in node && isJsonPathValue(node.right)) {
|
|
274
|
-
return node.right.segments
|
|
515
|
+
return validateJsonPathSegments(node.right.segments)
|
|
275
516
|
}
|
|
276
517
|
return []
|
|
277
518
|
}
|
|
278
519
|
|
|
520
|
+
const extractJsonKeys = (
|
|
521
|
+
node: Record<string, unknown>,
|
|
522
|
+
segments: ReadonlyArray<JsonPath.AnySegment>
|
|
523
|
+
): readonly unknown[] =>
|
|
524
|
+
Array.isArray(node.keys)
|
|
525
|
+
? node.keys
|
|
526
|
+
: segments.map((segment) =>
|
|
527
|
+
typeof segment === "object" && segment !== null && segment.kind === "key"
|
|
528
|
+
? segment.key
|
|
529
|
+
: segment
|
|
530
|
+
)
|
|
531
|
+
|
|
279
532
|
const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
280
533
|
node.newValue ?? node.insert ?? node.right
|
|
281
534
|
|
|
@@ -501,45 +754,57 @@ const renderJsonOpaquePath = (
|
|
|
501
754
|
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
|
|
502
755
|
}
|
|
503
756
|
if (typeof value === "string") {
|
|
757
|
+
if (value.trim().length === 0) {
|
|
758
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
759
|
+
}
|
|
504
760
|
return dialect.renderLiteral(value, state)
|
|
505
761
|
}
|
|
506
762
|
if (isExpression(value)) {
|
|
763
|
+
const ast = (value as Expression.Any & {
|
|
764
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
765
|
+
})[ExpressionAst.TypeId]
|
|
766
|
+
if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
|
|
767
|
+
throw new Error("SQL/JSON path input must be a non-empty string")
|
|
768
|
+
}
|
|
507
769
|
return renderExpression(value, state, dialect)
|
|
508
770
|
}
|
|
509
771
|
throw new Error("Unsupported SQL/JSON path input")
|
|
510
772
|
}
|
|
511
773
|
|
|
774
|
+
const renderFunctionName = (name: unknown): string => {
|
|
775
|
+
return name as string
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const renderExtractField = (field: Expression.Any): string => {
|
|
779
|
+
const ast = (field as Expression.Any & {
|
|
780
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
781
|
+
})[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
|
|
782
|
+
return ast.value
|
|
783
|
+
}
|
|
784
|
+
|
|
512
785
|
const renderFunctionCall = (
|
|
513
|
-
name:
|
|
514
|
-
args:
|
|
786
|
+
name: unknown,
|
|
787
|
+
args: unknown,
|
|
515
788
|
state: RenderState,
|
|
516
789
|
dialect: SqlDialect
|
|
517
790
|
): string => {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
? field[Expression.TypeId].runtime
|
|
532
|
-
: undefined
|
|
533
|
-
const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
|
|
534
|
-
return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
|
|
535
|
-
}
|
|
536
|
-
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
537
|
-
if (args.length === 0) {
|
|
538
|
-
switch (name) {
|
|
791
|
+
const functionName = renderFunctionName(name)
|
|
792
|
+
const functionArgs = args as readonly Expression.Any[]
|
|
793
|
+
if (functionName === "array") {
|
|
794
|
+
return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
|
|
795
|
+
}
|
|
796
|
+
if (functionName === "extract") {
|
|
797
|
+
const field = functionArgs[0]!
|
|
798
|
+
const source = functionArgs[1]!
|
|
799
|
+
return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
|
|
800
|
+
}
|
|
801
|
+
const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
802
|
+
if (functionArgs.length === 0) {
|
|
803
|
+
switch (functionName) {
|
|
539
804
|
case "current_date":
|
|
540
805
|
case "current_time":
|
|
541
806
|
case "current_timestamp":
|
|
542
|
-
return
|
|
807
|
+
return functionName
|
|
543
808
|
case "localtime":
|
|
544
809
|
return "time('now', 'localtime')"
|
|
545
810
|
case "localtimestamp":
|
|
@@ -547,10 +812,10 @@ const renderFunctionCall = (
|
|
|
547
812
|
case "now":
|
|
548
813
|
return "current_timestamp"
|
|
549
814
|
default:
|
|
550
|
-
return `${
|
|
815
|
+
return `${functionName}()`
|
|
551
816
|
}
|
|
552
817
|
}
|
|
553
|
-
return `${
|
|
818
|
+
return `${functionName}(${renderedArgs})`
|
|
554
819
|
}
|
|
555
820
|
|
|
556
821
|
const renderJsonExpression = (
|
|
@@ -614,22 +879,26 @@ const renderJsonExpression = (
|
|
|
614
879
|
const baseSql = dialect.name === "postgres"
|
|
615
880
|
? renderPostgresJsonValue(base, state, dialect)
|
|
616
881
|
: renderExpression(base, state, dialect)
|
|
617
|
-
const keys = segments
|
|
882
|
+
const keys = extractJsonKeys(ast, segments)
|
|
618
883
|
if (keys.length === 0) {
|
|
619
884
|
return undefined
|
|
620
885
|
}
|
|
886
|
+
if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
|
|
887
|
+
throw new Error("json key predicates require string keys")
|
|
888
|
+
}
|
|
889
|
+
const keyNames = keys as readonly string[]
|
|
621
890
|
if (dialect.name === "postgres") {
|
|
622
891
|
if (kind === "jsonHasAnyKeys") {
|
|
623
|
-
return `(${baseSql} ?| array[${
|
|
892
|
+
return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
624
893
|
}
|
|
625
894
|
if (kind === "jsonHasAllKeys") {
|
|
626
|
-
return `(${baseSql} ?& array[${
|
|
895
|
+
return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
627
896
|
}
|
|
628
|
-
return `(${baseSql} ? ${renderPostgresTextLiteral(
|
|
897
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
|
|
629
898
|
}
|
|
630
899
|
if (dialect.name === "sqlite") {
|
|
631
900
|
const renderBase = () => renderExpression(base, state, dialect)
|
|
632
|
-
const checks =
|
|
901
|
+
const checks = keyNames.map((segment) => `json_type(${renderBase()}, ${renderSqliteJsonPath([segment], state, dialect)}) is not null`)
|
|
633
902
|
return `(${checks.join(kind === "jsonHasAllKeys" ? " and " : " or ")})`
|
|
634
903
|
}
|
|
635
904
|
return undefined
|
|
@@ -648,9 +917,7 @@ const renderJsonExpression = (
|
|
|
648
917
|
return undefined
|
|
649
918
|
}
|
|
650
919
|
case "jsonBuildObject": {
|
|
651
|
-
const entries =
|
|
652
|
-
? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
653
|
-
: []
|
|
920
|
+
const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
|
|
654
921
|
const renderedEntries = entries.flatMap((entry) => [
|
|
655
922
|
dialect.renderLiteral(entry.key, state),
|
|
656
923
|
renderJsonInputExpression(entry.value, state, dialect)
|
|
@@ -664,9 +931,7 @@ const renderJsonExpression = (
|
|
|
664
931
|
return undefined
|
|
665
932
|
}
|
|
666
933
|
case "jsonBuildArray": {
|
|
667
|
-
const values =
|
|
668
|
-
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
669
|
-
: []
|
|
934
|
+
const values = (ast as { readonly values: readonly Expression.Any[] }).values
|
|
670
935
|
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
671
936
|
if (dialect.name === "postgres") {
|
|
672
937
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
@@ -850,11 +1115,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
|
|
|
850
1115
|
const renderMutationAssignment = (
|
|
851
1116
|
entry: QueryAst.AssignmentClause,
|
|
852
1117
|
state: RenderState,
|
|
853
|
-
dialect: SqlDialect
|
|
1118
|
+
dialect: SqlDialect,
|
|
1119
|
+
targetTableName?: string
|
|
854
1120
|
): string => {
|
|
855
1121
|
const column = entry.tableName && dialect.name === "sqlite"
|
|
856
|
-
? `${dialect.quoteIdentifier(entry.tableName)}.${
|
|
857
|
-
:
|
|
1122
|
+
? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
|
|
1123
|
+
: quoteColumn(entry.columnName, state, dialect, targetTableName)
|
|
858
1124
|
return `${column} = ${renderExpression(entry.value, state, dialect)}`
|
|
859
1125
|
}
|
|
860
1126
|
|
|
@@ -896,6 +1162,9 @@ const renderTransactionClause = (
|
|
|
896
1162
|
): string => {
|
|
897
1163
|
switch (clause.kind) {
|
|
898
1164
|
case "transaction": {
|
|
1165
|
+
if (clause.readOnly !== undefined) {
|
|
1166
|
+
normalizeStatementFlag(clause.readOnly)
|
|
1167
|
+
}
|
|
899
1168
|
if (clause.isolationLevel !== undefined || clause.readOnly !== undefined) {
|
|
900
1169
|
throw new Error("Unsupported sqlite transaction options")
|
|
901
1170
|
}
|
|
@@ -906,24 +1175,20 @@ const renderTransactionClause = (
|
|
|
906
1175
|
case "rollback":
|
|
907
1176
|
return "rollback"
|
|
908
1177
|
case "savepoint":
|
|
909
|
-
return `savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1178
|
+
return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
|
|
910
1179
|
case "rollbackTo":
|
|
911
|
-
return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1180
|
+
return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
|
|
912
1181
|
case "releaseSavepoint":
|
|
913
|
-
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
1182
|
+
return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
|
|
914
1183
|
}
|
|
915
|
-
|
|
1184
|
+
return "begin"
|
|
916
1185
|
}
|
|
917
1186
|
|
|
918
1187
|
const renderSelectionList = (
|
|
919
1188
|
selection: Record<string, unknown>,
|
|
920
1189
|
state: RenderState,
|
|
921
|
-
dialect: SqlDialect
|
|
922
|
-
validateAggregation: boolean
|
|
1190
|
+
dialect: SqlDialect
|
|
923
1191
|
): RenderedQueryAst => {
|
|
924
|
-
if (validateAggregation) {
|
|
925
|
-
validateAggregationSelection(selection as SelectionValue, [])
|
|
926
|
-
}
|
|
927
1192
|
const flattened = flattenSelection(selection)
|
|
928
1193
|
const projections = selectionProjections(selection)
|
|
929
1194
|
const sql = flattened.map(({ expression, alias }) =>
|
|
@@ -937,131 +1202,26 @@ const renderSelectionList = (
|
|
|
937
1202
|
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
938
1203
|
params: state.params,
|
|
939
1204
|
valueMappings: state.valueMappings,
|
|
1205
|
+
casing: state.casing,
|
|
940
1206
|
ctes: [],
|
|
941
1207
|
cteNames: new Set(state.cteNames),
|
|
942
|
-
cteSources: new Map(state.cteSources)
|
|
1208
|
+
cteSources: new Map(state.cteSources),
|
|
1209
|
+
sourceNames: new Map(state.sourceNames)
|
|
943
1210
|
})
|
|
944
1211
|
|
|
945
|
-
const assertMatchingSetProjections = (
|
|
946
|
-
left: readonly Projection[],
|
|
947
|
-
right: readonly Projection[]
|
|
948
|
-
): void => {
|
|
949
|
-
const leftKeys = left.map((projection) => JSON.stringify(projection.path))
|
|
950
|
-
const rightKeys = right.map((projection) => JSON.stringify(projection.path))
|
|
951
|
-
if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
|
|
952
|
-
throw new Error("set operator operands must have matching result rows")
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
const assertNoGroupedMutationClauses = (
|
|
957
|
-
ast: Pick<QueryAst.Ast, "groupBy" | "having">,
|
|
958
|
-
statement: string
|
|
959
|
-
): void => {
|
|
960
|
-
if (ast.groupBy.length > 0) {
|
|
961
|
-
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
962
|
-
}
|
|
963
|
-
if (ast.having.length > 0) {
|
|
964
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
const assertNoSqliteMutationModifiers = (
|
|
969
|
-
ast: Pick<QueryAst.Ast, "orderBy" | "limit" | "offset" | "lock">,
|
|
970
|
-
statement: string
|
|
971
|
-
): void => {
|
|
972
|
-
if (ast.orderBy.length > 0) {
|
|
973
|
-
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
974
|
-
}
|
|
975
|
-
if (ast.limit) {
|
|
976
|
-
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
977
|
-
}
|
|
978
|
-
if (ast.offset) {
|
|
979
|
-
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
980
|
-
}
|
|
981
|
-
if (ast.lock) {
|
|
982
|
-
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
const assertNoInsertQueryClauses = (
|
|
987
|
-
ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
|
|
988
|
-
): void => {
|
|
989
|
-
if (ast.where.length > 0) {
|
|
990
|
-
throw new Error("where(...) is not supported for insert statements")
|
|
991
|
-
}
|
|
992
|
-
if (ast.joins.length > 0) {
|
|
993
|
-
throw new Error("join(...) is not supported for insert statements")
|
|
994
|
-
}
|
|
995
|
-
if (ast.orderBy.length > 0) {
|
|
996
|
-
throw new Error("orderBy(...) is not supported for insert statements")
|
|
997
|
-
}
|
|
998
|
-
if (ast.limit) {
|
|
999
|
-
throw new Error("limit(...) is not supported for insert statements")
|
|
1000
|
-
}
|
|
1001
|
-
if (ast.offset) {
|
|
1002
|
-
throw new Error("offset(...) is not supported for insert statements")
|
|
1003
|
-
}
|
|
1004
|
-
if (ast.lock) {
|
|
1005
|
-
throw new Error("lock(...) is not supported for insert statements")
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
const assertNoStatementQueryClauses = (
|
|
1010
|
-
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1011
|
-
statement: string,
|
|
1012
|
-
options: { readonly allowSelection?: boolean } = {}
|
|
1013
|
-
): void => {
|
|
1014
|
-
if (ast.distinct) {
|
|
1015
|
-
throw new Error(`distinct(...) is not supported for ${statement} statements`)
|
|
1016
|
-
}
|
|
1017
|
-
if (ast.where.length > 0) {
|
|
1018
|
-
throw new Error(`where(...) is not supported for ${statement} statements`)
|
|
1019
|
-
}
|
|
1020
|
-
if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
|
|
1021
|
-
throw new Error(`from(...) is not supported for ${statement} statements`)
|
|
1022
|
-
}
|
|
1023
|
-
if (ast.joins.length > 0) {
|
|
1024
|
-
throw new Error(`join(...) is not supported for ${statement} statements`)
|
|
1025
|
-
}
|
|
1026
|
-
if (ast.groupBy.length > 0) {
|
|
1027
|
-
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
1028
|
-
}
|
|
1029
|
-
if (ast.having.length > 0) {
|
|
1030
|
-
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1031
|
-
}
|
|
1032
|
-
if (ast.orderBy.length > 0) {
|
|
1033
|
-
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
1034
|
-
}
|
|
1035
|
-
if (ast.limit) {
|
|
1036
|
-
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
1037
|
-
}
|
|
1038
|
-
if (ast.offset) {
|
|
1039
|
-
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
1040
|
-
}
|
|
1041
|
-
if (ast.lock) {
|
|
1042
|
-
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
1043
|
-
}
|
|
1044
|
-
if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
|
|
1045
|
-
throw new Error(`returning(...) is not supported for ${statement} statements`)
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
1212
|
export const renderQueryAst = (
|
|
1050
1213
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1051
1214
|
state: RenderState,
|
|
1052
1215
|
dialect: SqlDialect,
|
|
1053
1216
|
options: { readonly emitCtes?: boolean } = {}
|
|
1054
1217
|
): RenderedQueryAst => {
|
|
1218
|
+
registerQuerySources(ast, state)
|
|
1055
1219
|
let sql = ""
|
|
1056
1220
|
let projections: readonly Projection[] = []
|
|
1057
1221
|
|
|
1058
1222
|
switch (ast.kind) {
|
|
1059
1223
|
case "select": {
|
|
1060
|
-
|
|
1061
|
-
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
1062
|
-
if (rendered.projections.length === 0) {
|
|
1063
|
-
throw new Error("sqlite select statements require at least one selected expression")
|
|
1064
|
-
}
|
|
1224
|
+
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
|
|
1065
1225
|
projections = rendered.projections
|
|
1066
1226
|
const clauses = [
|
|
1067
1227
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
@@ -1105,7 +1265,6 @@ export const renderQueryAst = (
|
|
|
1105
1265
|
}
|
|
1106
1266
|
case "set": {
|
|
1107
1267
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1108
|
-
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
1109
1268
|
const base = renderQueryAst(
|
|
1110
1269
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
1111
1270
|
Record<string, unknown>,
|
|
@@ -1116,7 +1275,6 @@ export const renderQueryAst = (
|
|
|
1116
1275
|
dialect
|
|
1117
1276
|
)
|
|
1118
1277
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1119
|
-
assertMatchingSetProjections(projections, base.projections)
|
|
1120
1278
|
sql = [
|
|
1121
1279
|
base.sql,
|
|
1122
1280
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -1132,7 +1290,6 @@ export const renderQueryAst = (
|
|
|
1132
1290
|
state,
|
|
1133
1291
|
dialect
|
|
1134
1292
|
)
|
|
1135
|
-
assertMatchingSetProjections(projections, rendered.projections)
|
|
1136
1293
|
return `${entry.kind}${entry.all ? " all" : ""} ${rendered.sql}`
|
|
1137
1294
|
})
|
|
1138
1295
|
].join(" ")
|
|
@@ -1140,24 +1297,20 @@ export const renderQueryAst = (
|
|
|
1140
1297
|
}
|
|
1141
1298
|
case "insert": {
|
|
1142
1299
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
1143
|
-
if (insertAst.distinct) {
|
|
1144
|
-
throw new Error("distinct(...) is not supported for insert statements")
|
|
1145
|
-
}
|
|
1146
|
-
assertNoGroupedMutationClauses(insertAst, "insert")
|
|
1147
|
-
assertNoInsertQueryClauses(insertAst)
|
|
1148
1300
|
const targetSource = insertAst.into!
|
|
1149
1301
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1150
|
-
const
|
|
1302
|
+
const targetCasingState = stateWithTableCasing(state, targetSource.source)
|
|
1303
|
+
const insertSource = insertAst.insertSource
|
|
1151
1304
|
const conflict = expectConflictClause(insertAst.conflict)
|
|
1152
1305
|
sql = `insert into ${target}`
|
|
1153
1306
|
if (insertSource?.kind === "values") {
|
|
1154
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1307
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1155
1308
|
const rows = insertSource.rows.map((row) =>
|
|
1156
|
-
`(${row.values.map((entry) => renderExpression(entry.value,
|
|
1309
|
+
`(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
|
|
1157
1310
|
).join(", ")
|
|
1158
1311
|
sql += ` (${columns}) values ${rows}`
|
|
1159
1312
|
} else if (insertSource?.kind === "query") {
|
|
1160
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1313
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1161
1314
|
const renderedQuery = renderQueryAst(
|
|
1162
1315
|
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
1163
1316
|
Record<string, unknown>,
|
|
@@ -1169,7 +1322,7 @@ export const renderQueryAst = (
|
|
|
1169
1322
|
)
|
|
1170
1323
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
1171
1324
|
} else if (insertSource?.kind === "unnest") {
|
|
1172
|
-
const columns = insertSource.columns.map((column) => dialect.
|
|
1325
|
+
const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
|
|
1173
1326
|
if (dialect.name === "postgres") {
|
|
1174
1327
|
const table = targetSource.source as Table.AnyTable
|
|
1175
1328
|
const fields = table[Table.TypeId].fields
|
|
@@ -1191,41 +1344,37 @@ export const renderQueryAst = (
|
|
|
1191
1344
|
sql += ` (${columns}) values ${rows}`
|
|
1192
1345
|
}
|
|
1193
1346
|
} else {
|
|
1194
|
-
const
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1347
|
+
const insertValues = insertAst.values ?? []
|
|
1348
|
+
const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
|
|
1349
|
+
const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
|
|
1350
|
+
if (insertValues.length > 0) {
|
|
1197
1351
|
sql += ` (${columns}) values (${values})`
|
|
1198
1352
|
} else {
|
|
1199
1353
|
sql += " default values"
|
|
1200
1354
|
}
|
|
1201
1355
|
}
|
|
1202
1356
|
if (conflict) {
|
|
1203
|
-
|
|
1204
|
-
throw new Error("conflict action predicates require update assignments")
|
|
1205
|
-
}
|
|
1357
|
+
const conflictValueState = { ...targetCasingState, allowExcluded: true }
|
|
1206
1358
|
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
1207
|
-
`${
|
|
1359
|
+
`${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
|
|
1208
1360
|
).join(", ")
|
|
1209
1361
|
if (dialect.name === "postgres" || dialect.name === "sqlite") {
|
|
1210
|
-
if (dialect.name === "sqlite" && conflict.target?.kind === "constraint") {
|
|
1211
|
-
throw new Error("Unsupported sqlite named conflict constraint")
|
|
1212
|
-
}
|
|
1213
1362
|
const targetSql = conflict.target?.kind === "constraint"
|
|
1214
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
|
|
1363
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
|
|
1215
1364
|
: conflict.target?.kind === "columns"
|
|
1216
|
-
? ` on conflict (${conflict.target.columns.map((column) => dialect.
|
|
1365
|
+
? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
|
|
1217
1366
|
: " on conflict"
|
|
1218
1367
|
sql += targetSql
|
|
1219
1368
|
sql += conflict.action === "doNothing"
|
|
1220
1369
|
? " do nothing"
|
|
1221
|
-
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where,
|
|
1370
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
|
|
1222
1371
|
} else if (conflict.action === "doNothing") {
|
|
1223
1372
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
1224
1373
|
} else {
|
|
1225
1374
|
sql += ` on duplicate key update ${updateValues}`
|
|
1226
1375
|
}
|
|
1227
1376
|
}
|
|
1228
|
-
const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect
|
|
1377
|
+
const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
|
|
1229
1378
|
projections = returning.projections
|
|
1230
1379
|
if (returning.sql.length > 0) {
|
|
1231
1380
|
sql += ` returning ${returning.sql}`
|
|
@@ -1234,11 +1383,6 @@ export const renderQueryAst = (
|
|
|
1234
1383
|
}
|
|
1235
1384
|
case "update": {
|
|
1236
1385
|
const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
|
|
1237
|
-
if (updateAst.distinct) {
|
|
1238
|
-
throw new Error("distinct(...) is not supported for update statements")
|
|
1239
|
-
}
|
|
1240
|
-
assertNoGroupedMutationClauses(updateAst, "update")
|
|
1241
|
-
assertNoSqliteMutationModifiers(updateAst, "update")
|
|
1242
1386
|
const targetSource = updateAst.target!
|
|
1243
1387
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1244
1388
|
const targets = updateAst.targets ?? [targetSource]
|
|
@@ -1246,11 +1390,8 @@ export const renderQueryAst = (
|
|
|
1246
1390
|
if (targets.length > 1) {
|
|
1247
1391
|
throw new Error("Unsupported sqlite multi-table update")
|
|
1248
1392
|
}
|
|
1249
|
-
if ((updateAst.set ?? []).length === 0) {
|
|
1250
|
-
throw new Error("update statements require at least one assignment")
|
|
1251
|
-
}
|
|
1252
1393
|
const assignments = updateAst.set!.map((entry) =>
|
|
1253
|
-
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
1394
|
+
renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
|
|
1254
1395
|
if (dialect.name === "mysql") {
|
|
1255
1396
|
const modifiers = ""
|
|
1256
1397
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
@@ -1293,7 +1434,7 @@ export const renderQueryAst = (
|
|
|
1293
1434
|
if (dialect.name === "mysql" && updateAst.limit) {
|
|
1294
1435
|
sql += ` limit ${renderSqliteMutationLimit(updateAst.limit, state, dialect)}`
|
|
1295
1436
|
}
|
|
1296
|
-
const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect
|
|
1437
|
+
const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
|
|
1297
1438
|
projections = returning.projections
|
|
1298
1439
|
if (returning.sql.length > 0) {
|
|
1299
1440
|
sql += ` returning ${returning.sql}`
|
|
@@ -1302,11 +1443,6 @@ export const renderQueryAst = (
|
|
|
1302
1443
|
}
|
|
1303
1444
|
case "delete": {
|
|
1304
1445
|
const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
|
|
1305
|
-
if (deleteAst.distinct) {
|
|
1306
|
-
throw new Error("distinct(...) is not supported for delete statements")
|
|
1307
|
-
}
|
|
1308
|
-
assertNoGroupedMutationClauses(deleteAst, "delete")
|
|
1309
|
-
assertNoSqliteMutationModifiers(deleteAst, "delete")
|
|
1310
1446
|
const targetSource = deleteAst.target!
|
|
1311
1447
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1312
1448
|
const targets = deleteAst.targets ?? [targetSource]
|
|
@@ -1354,7 +1490,7 @@ export const renderQueryAst = (
|
|
|
1354
1490
|
if (dialect.name === "mysql" && deleteAst.limit) {
|
|
1355
1491
|
sql += ` limit ${renderSqliteMutationLimit(deleteAst.limit, state, dialect)}`
|
|
1356
1492
|
}
|
|
1357
|
-
const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect
|
|
1493
|
+
const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
|
|
1358
1494
|
projections = returning.projections
|
|
1359
1495
|
if (returning.sql.length > 0) {
|
|
1360
1496
|
sql += ` returning ${returning.sql}`
|
|
@@ -1363,7 +1499,6 @@ export const renderQueryAst = (
|
|
|
1363
1499
|
}
|
|
1364
1500
|
case "truncate": {
|
|
1365
1501
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1366
|
-
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1367
1502
|
throw new Error("Unsupported sqlite truncate statement")
|
|
1368
1503
|
break
|
|
1369
1504
|
}
|
|
@@ -1375,9 +1510,6 @@ export const renderQueryAst = (
|
|
|
1375
1510
|
const targetSource = mergeAst.target!
|
|
1376
1511
|
const usingSource = mergeAst.using!
|
|
1377
1512
|
const merge = mergeAst.merge!
|
|
1378
|
-
if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
|
|
1379
|
-
throw new Error("returning(...) is not supported for merge statements")
|
|
1380
|
-
}
|
|
1381
1513
|
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)}`
|
|
1382
1514
|
if (merge.whenMatched) {
|
|
1383
1515
|
sql += " when matched"
|
|
@@ -1407,27 +1539,24 @@ export const renderQueryAst = (
|
|
|
1407
1539
|
case "savepoint":
|
|
1408
1540
|
case "rollbackTo":
|
|
1409
1541
|
case "releaseSavepoint": {
|
|
1410
|
-
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1411
1542
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1412
1543
|
break
|
|
1413
1544
|
}
|
|
1414
1545
|
case "createTable": {
|
|
1415
1546
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1416
|
-
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1417
1547
|
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1418
1548
|
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1419
1549
|
break
|
|
1420
1550
|
}
|
|
1421
1551
|
case "dropTable": {
|
|
1422
1552
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1423
|
-
assertNoStatementQueryClauses(dropTableAst, "dropTable")
|
|
1424
1553
|
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1425
|
-
|
|
1554
|
+
const ifExists = normalizeStatementFlag(ddl.ifExists)
|
|
1555
|
+
sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1426
1556
|
break
|
|
1427
1557
|
}
|
|
1428
1558
|
case "createIndex": {
|
|
1429
1559
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1430
|
-
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1431
1560
|
sql = renderCreateIndexSql(
|
|
1432
1561
|
createIndexAst.target!,
|
|
1433
1562
|
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
@@ -1438,7 +1567,6 @@ export const renderQueryAst = (
|
|
|
1438
1567
|
}
|
|
1439
1568
|
case "dropIndex": {
|
|
1440
1569
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1441
|
-
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1442
1570
|
sql = renderDropIndexSql(
|
|
1443
1571
|
dropIndexAst.target!,
|
|
1444
1572
|
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
@@ -1447,8 +1575,12 @@ export const renderQueryAst = (
|
|
|
1447
1575
|
)
|
|
1448
1576
|
break
|
|
1449
1577
|
}
|
|
1450
|
-
default:
|
|
1451
|
-
|
|
1578
|
+
default: {
|
|
1579
|
+
if (ast.transaction !== undefined) {
|
|
1580
|
+
sql = renderTransactionClause(ast.transaction, dialect)
|
|
1581
|
+
}
|
|
1582
|
+
break
|
|
1583
|
+
}
|
|
1452
1584
|
}
|
|
1453
1585
|
|
|
1454
1586
|
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
@@ -1572,13 +1704,31 @@ const renderSourceReference = (
|
|
|
1572
1704
|
if (dialect.name !== "postgres") {
|
|
1573
1705
|
throw new Error("Unsupported table function source for SQL rendering")
|
|
1574
1706
|
}
|
|
1707
|
+
const functionName = renderFunctionName(tableFunction.functionName)
|
|
1575
1708
|
const columnNames = Object.keys(tableFunction.columns)
|
|
1576
|
-
return `${
|
|
1709
|
+
return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
|
|
1577
1710
|
}
|
|
1578
1711
|
const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
|
|
1579
1712
|
? (source as Table.AnyTable)[Table.TypeId].schemaName
|
|
1580
1713
|
: undefined
|
|
1581
|
-
|
|
1714
|
+
if (typeof source === "object" && source !== null && Table.TypeId in source) {
|
|
1715
|
+
const table = source as Table.AnyTable
|
|
1716
|
+
const tableState = table[Table.TypeId]
|
|
1717
|
+
const casing = casingForTable(table, state)
|
|
1718
|
+
const renderedTableName = tableState.kind === "alias"
|
|
1719
|
+
? tableName
|
|
1720
|
+
: Casing.applyCategory(casing, "tables", baseTableName)
|
|
1721
|
+
const renderedBaseName = Casing.applyCategory(casing, "tables", baseTableName)
|
|
1722
|
+
const renderedSchemaName = schemaName === undefined
|
|
1723
|
+
? undefined
|
|
1724
|
+
: Casing.applyCategory(casing, "schemas", schemaName)
|
|
1725
|
+
return dialect.renderTableReference(renderedTableName, renderedBaseName, renderedSchemaName)
|
|
1726
|
+
}
|
|
1727
|
+
return dialect.renderTableReference(
|
|
1728
|
+
Casing.applyCategory(state.casing, "tables", tableName),
|
|
1729
|
+
Casing.applyCategory(state.casing, "tables", baseTableName),
|
|
1730
|
+
schemaName === undefined ? undefined : Casing.applyCategory(state.casing, "schemas", schemaName)
|
|
1731
|
+
)
|
|
1582
1732
|
}
|
|
1583
1733
|
|
|
1584
1734
|
const renderSubqueryExpressionPlan = (
|
|
@@ -1617,53 +1767,56 @@ export const renderExpression = (
|
|
|
1617
1767
|
return jsonSql
|
|
1618
1768
|
}
|
|
1619
1769
|
const ast = rawAst as ExpressionAst.Any
|
|
1620
|
-
const renderComparisonOperator = (operator:
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
:
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
? ">"
|
|
1631
|
-
: ">="
|
|
1632
|
-
switch (ast.kind) {
|
|
1770
|
+
const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
|
|
1771
|
+
({
|
|
1772
|
+
eq: "=",
|
|
1773
|
+
neq: "<>",
|
|
1774
|
+
lt: "<",
|
|
1775
|
+
lte: "<=",
|
|
1776
|
+
gt: ">",
|
|
1777
|
+
gte: ">="
|
|
1778
|
+
} as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
|
|
1779
|
+
switch (ast.kind) {
|
|
1633
1780
|
case "column":
|
|
1634
1781
|
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1635
|
-
?
|
|
1636
|
-
: `${dialect.quoteIdentifier(ast.tableName)}.${
|
|
1782
|
+
? quoteColumn(ast.columnName, state, dialect, ast.tableName)
|
|
1783
|
+
: `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
|
|
1637
1784
|
case "literal":
|
|
1638
1785
|
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1639
1786
|
throw new Error("Expected a finite numeric value")
|
|
1640
1787
|
}
|
|
1641
1788
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1642
1789
|
case "excluded":
|
|
1643
|
-
|
|
1790
|
+
if (state.allowExcluded !== true) {
|
|
1791
|
+
throw new Error("excluded(...) is only supported inside insert conflict handlers")
|
|
1792
|
+
}
|
|
1793
|
+
return `excluded.${quoteColumn(ast.columnName, state, dialect)}`
|
|
1644
1794
|
case "cast":
|
|
1645
|
-
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1795
|
+
return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1646
1796
|
case "function":
|
|
1647
|
-
return renderFunctionCall(ast.name,
|
|
1797
|
+
return renderFunctionCall(ast.name, ast.args, state, dialect)
|
|
1648
1798
|
case "eq":
|
|
1649
|
-
return
|
|
1799
|
+
return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
|
|
1650
1800
|
case "neq":
|
|
1651
|
-
return
|
|
1801
|
+
return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
|
|
1652
1802
|
case "lt":
|
|
1653
|
-
return
|
|
1803
|
+
return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
|
|
1654
1804
|
case "lte":
|
|
1655
|
-
return
|
|
1805
|
+
return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
|
|
1656
1806
|
case "gt":
|
|
1657
|
-
return
|
|
1807
|
+
return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
|
|
1658
1808
|
case "gte":
|
|
1659
|
-
return
|
|
1809
|
+
return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
|
|
1660
1810
|
case "like":
|
|
1661
|
-
return
|
|
1662
|
-
case "ilike":
|
|
1811
|
+
return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
|
|
1812
|
+
case "ilike": {
|
|
1813
|
+
const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
|
|
1663
1814
|
return dialect.name === "postgres"
|
|
1664
|
-
? `(${renderExpression(
|
|
1665
|
-
: `(lower(${renderExpression(
|
|
1815
|
+
? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
|
|
1816
|
+
: `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
|
|
1817
|
+
}
|
|
1666
1818
|
case "regexMatch":
|
|
1819
|
+
expectBinaryExpressions("regexMatch", ast.left, ast.right)
|
|
1667
1820
|
if (dialect.name === "sqlite") {
|
|
1668
1821
|
throw new Error("Unsupported sqlite regex operator")
|
|
1669
1822
|
}
|
|
@@ -1671,6 +1824,7 @@ export const renderExpression = (
|
|
|
1671
1824
|
? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
|
|
1672
1825
|
: `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1673
1826
|
case "regexIMatch":
|
|
1827
|
+
expectBinaryExpressions("regexIMatch", ast.left, ast.right)
|
|
1674
1828
|
if (dialect.name === "sqlite") {
|
|
1675
1829
|
throw new Error("Unsupported sqlite regex operator")
|
|
1676
1830
|
}
|
|
@@ -1678,6 +1832,7 @@ export const renderExpression = (
|
|
|
1678
1832
|
? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
|
|
1679
1833
|
: `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1680
1834
|
case "regexNotMatch":
|
|
1835
|
+
expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
|
|
1681
1836
|
if (dialect.name === "sqlite") {
|
|
1682
1837
|
throw new Error("Unsupported sqlite regex operator")
|
|
1683
1838
|
}
|
|
@@ -1685,6 +1840,7 @@ export const renderExpression = (
|
|
|
1685
1840
|
? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
|
|
1686
1841
|
: `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1687
1842
|
case "regexNotIMatch":
|
|
1843
|
+
expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
|
|
1688
1844
|
if (dialect.name === "sqlite") {
|
|
1689
1845
|
throw new Error("Unsupported sqlite regex operator")
|
|
1690
1846
|
}
|
|
@@ -1692,79 +1848,73 @@ export const renderExpression = (
|
|
|
1692
1848
|
? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
|
|
1693
1849
|
: `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1694
1850
|
case "isDistinctFrom":
|
|
1695
|
-
return
|
|
1851
|
+
return renderBinaryExpression("isDistinctFrom", "is distinct from", ast.left, ast.right, state, dialect)
|
|
1696
1852
|
case "isNotDistinctFrom":
|
|
1697
|
-
return
|
|
1698
|
-
case "contains":
|
|
1853
|
+
return renderBinaryExpression("isNotDistinctFrom", "is not distinct from", ast.left, ast.right, state, dialect)
|
|
1854
|
+
case "contains": {
|
|
1855
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
|
|
1699
1856
|
if (dialect.name === "postgres") {
|
|
1700
|
-
const left = isJsonExpression(
|
|
1701
|
-
? renderPostgresJsonValue(
|
|
1702
|
-
: renderExpression(
|
|
1703
|
-
const right = isJsonExpression(
|
|
1704
|
-
? renderPostgresJsonValue(
|
|
1705
|
-
: renderExpression(
|
|
1857
|
+
const left = isJsonExpression(leftExpression)
|
|
1858
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1859
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1860
|
+
const right = isJsonExpression(rightExpression)
|
|
1861
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1862
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1706
1863
|
return `(${left} @> ${right})`
|
|
1707
1864
|
}
|
|
1708
1865
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1709
|
-
|
|
1866
|
+
}
|
|
1867
|
+
case "containedBy": {
|
|
1868
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
|
|
1710
1869
|
if (dialect.name === "postgres") {
|
|
1711
|
-
const left = isJsonExpression(
|
|
1712
|
-
? renderPostgresJsonValue(
|
|
1713
|
-
: renderExpression(
|
|
1714
|
-
const right = isJsonExpression(
|
|
1715
|
-
? renderPostgresJsonValue(
|
|
1716
|
-
: renderExpression(
|
|
1870
|
+
const left = isJsonExpression(leftExpression)
|
|
1871
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1872
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1873
|
+
const right = isJsonExpression(rightExpression)
|
|
1874
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1875
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1717
1876
|
return `(${left} <@ ${right})`
|
|
1718
1877
|
}
|
|
1719
1878
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1720
|
-
|
|
1879
|
+
}
|
|
1880
|
+
case "overlaps": {
|
|
1881
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
|
|
1721
1882
|
if (dialect.name === "postgres") {
|
|
1722
|
-
const left = isJsonExpression(
|
|
1723
|
-
? renderPostgresJsonValue(
|
|
1724
|
-
: renderExpression(
|
|
1725
|
-
const right = isJsonExpression(
|
|
1726
|
-
? renderPostgresJsonValue(
|
|
1727
|
-
: renderExpression(
|
|
1883
|
+
const left = isJsonExpression(leftExpression)
|
|
1884
|
+
? renderPostgresJsonValue(leftExpression, state, dialect)
|
|
1885
|
+
: renderExpression(leftExpression, state, dialect)
|
|
1886
|
+
const right = isJsonExpression(rightExpression)
|
|
1887
|
+
? renderPostgresJsonValue(rightExpression, state, dialect)
|
|
1888
|
+
: renderExpression(rightExpression, state, dialect)
|
|
1728
1889
|
return `(${left} && ${right})`
|
|
1729
1890
|
}
|
|
1730
1891
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1892
|
+
}
|
|
1731
1893
|
case "isNull":
|
|
1732
|
-
return `(${renderExpression(ast.value, state, dialect)} is null)`
|
|
1894
|
+
return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
|
|
1733
1895
|
case "isNotNull":
|
|
1734
|
-
return `(${renderExpression(ast.value, state, dialect)} is not null)`
|
|
1896
|
+
return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
|
|
1735
1897
|
case "not":
|
|
1736
|
-
return `(not ${renderExpression(ast.value, state, dialect)})`
|
|
1898
|
+
return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
|
|
1737
1899
|
case "upper":
|
|
1738
|
-
return `upper(${renderExpression(ast.value, state, dialect)})`
|
|
1900
|
+
return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
|
|
1739
1901
|
case "lower":
|
|
1740
|
-
return `lower(${renderExpression(ast.value, state, dialect)})`
|
|
1902
|
+
return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
|
|
1741
1903
|
case "count":
|
|
1742
|
-
return `count(${renderExpression(ast.value, state, dialect)})`
|
|
1904
|
+
return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
|
|
1743
1905
|
case "max":
|
|
1744
|
-
return `max(${renderExpression(ast.value, state, dialect)})`
|
|
1906
|
+
return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
|
|
1745
1907
|
case "min":
|
|
1746
|
-
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1908
|
+
return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
|
|
1747
1909
|
case "and":
|
|
1748
|
-
if (ast.values.length === 0) {
|
|
1749
|
-
throw new Error("and(...) requires at least one predicate")
|
|
1750
|
-
}
|
|
1751
1910
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1752
1911
|
case "or":
|
|
1753
|
-
if (ast.values.length === 0) {
|
|
1754
|
-
throw new Error("or(...) requires at least one predicate")
|
|
1755
|
-
}
|
|
1756
1912
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1757
1913
|
case "coalesce":
|
|
1758
1914
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1759
1915
|
case "in":
|
|
1760
|
-
if (ast.values.length < 2) {
|
|
1761
|
-
throw new Error("in(...) requires at least one candidate value")
|
|
1762
|
-
}
|
|
1763
1916
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1764
1917
|
case "notIn":
|
|
1765
|
-
if (ast.values.length < 2) {
|
|
1766
|
-
throw new Error("notIn(...) requires at least one candidate value")
|
|
1767
|
-
}
|
|
1768
1918
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1769
1919
|
case "between":
|
|
1770
1920
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1779,27 +1929,35 @@ export const renderExpression = (
|
|
|
1779
1929
|
case "scalarSubquery":
|
|
1780
1930
|
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1781
1931
|
case "inSubquery":
|
|
1782
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1783
|
-
case "comparisonAny":
|
|
1932
|
+
return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1933
|
+
case "comparisonAny": {
|
|
1934
|
+
const left = expectValueExpression("compareAny", ast.left)
|
|
1935
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
1784
1936
|
if (dialect.name === "sqlite") {
|
|
1785
1937
|
throw new Error("Unsupported sqlite quantified comparison")
|
|
1786
1938
|
}
|
|
1787
|
-
return `(${renderExpression(
|
|
1788
|
-
|
|
1939
|
+
return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1940
|
+
}
|
|
1941
|
+
case "comparisonAll": {
|
|
1942
|
+
const left = expectValueExpression("compareAll", ast.left)
|
|
1943
|
+
const operator = renderComparisonOperator(ast.operator)
|
|
1789
1944
|
if (dialect.name === "sqlite") {
|
|
1790
1945
|
throw new Error("Unsupported sqlite quantified comparison")
|
|
1791
1946
|
}
|
|
1792
|
-
return `(${renderExpression(
|
|
1947
|
+
return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1948
|
+
}
|
|
1793
1949
|
case "window": {
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1950
|
+
const partitionBy = ast.partitionBy as readonly Expression.Any[]
|
|
1951
|
+
const orderBy = ast.orderBy as readonly {
|
|
1952
|
+
readonly value: Expression.Any
|
|
1953
|
+
readonly direction: string
|
|
1954
|
+
}[]
|
|
1797
1955
|
const clauses: string[] = []
|
|
1798
|
-
if (
|
|
1799
|
-
clauses.push(`partition by ${
|
|
1956
|
+
if (partitionBy.length > 0) {
|
|
1957
|
+
clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
|
|
1800
1958
|
}
|
|
1801
|
-
if (
|
|
1802
|
-
clauses.push(`order by ${
|
|
1959
|
+
if (orderBy.length > 0) {
|
|
1960
|
+
clauses.push(`order by ${orderBy.map((entry) =>
|
|
1803
1961
|
`${renderExpression(entry.value, state, dialect)} ${entry.direction}`
|
|
1804
1962
|
).join(", ")}`)
|
|
1805
1963
|
}
|
|
@@ -1812,7 +1970,7 @@ export const renderExpression = (
|
|
|
1812
1970
|
case "denseRank":
|
|
1813
1971
|
return `dense_rank() over (${specification})`
|
|
1814
1972
|
case "over":
|
|
1815
|
-
return `${renderExpression(ast.value
|
|
1973
|
+
return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
|
|
1816
1974
|
}
|
|
1817
1975
|
break
|
|
1818
1976
|
}
|