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