effect-qb 0.15.0 → 0.17.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/dist/mysql.js +1957 -595
- package/dist/postgres/metadata.js +2507 -182
- package/dist/postgres.js +9587 -8201
- package/dist/sqlite.js +8360 -0
- package/package.json +7 -2
- package/src/internal/column-state.ts +7 -0
- package/src/internal/column.ts +22 -0
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +14 -1
- package/src/internal/dsl-mutation-runtime.ts +173 -4
- package/src/internal/dsl-plan-runtime.ts +165 -20
- package/src/internal/dsl-query-runtime.ts +60 -6
- package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
- package/src/internal/executor.ts +62 -13
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +141 -1
- package/src/internal/implication-runtime.ts +2 -1
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/analysis.ts +103 -1
- package/src/internal/predicate/atom.ts +7 -0
- package/src/internal/predicate/context.ts +170 -17
- package/src/internal/predicate/key.ts +64 -2
- package/src/internal/predicate/normalize.ts +115 -34
- package/src/internal/predicate/runtime.ts +144 -13
- package/src/internal/query.ts +563 -103
- package/src/internal/renderer.ts +39 -2
- package/src/internal/runtime/driver-value-mapping.ts +244 -0
- package/src/internal/runtime/normalize.ts +62 -38
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- package/src/internal/scalar.ts +11 -0
- package/src/internal/table-options.ts +108 -1
- package/src/internal/table.ts +87 -29
- package/src/mysql/column.ts +19 -2
- package/src/mysql/datatypes/index.ts +21 -0
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +20 -5
- package/src/mysql/internal/dialect.ts +12 -6
- package/src/mysql/internal/dsl.ts +995 -263
- package/src/mysql/internal/renderer.ts +13 -3
- package/src/mysql/internal/sql-expression-renderer.ts +530 -128
- package/src/mysql/query.ts +9 -2
- package/src/mysql/renderer.ts +7 -2
- package/src/mysql/table.ts +38 -12
- package/src/postgres/cast.ts +22 -7
- package/src/postgres/column.ts +5 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +68 -10
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/internal/dialect.ts +12 -6
- package/src/postgres/internal/dsl.ts +958 -288
- package/src/postgres/internal/renderer.ts +13 -3
- package/src/postgres/internal/schema-ddl.ts +2 -1
- package/src/postgres/internal/schema-model.ts +6 -3
- package/src/postgres/internal/sql-expression-renderer.ts +477 -96
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +7 -2
- package/src/postgres/schema-management.ts +91 -4
- package/src/postgres/schema.ts +1 -1
- package/src/postgres/table.ts +189 -53
- package/src/postgres/type.ts +4 -0
- package/src/sqlite/column.ts +128 -0
- package/src/sqlite/datatypes/index.ts +79 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +227 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +37 -0
- package/src/sqlite/internal/dsl.ts +6926 -0
- package/src/sqlite/internal/renderer.ts +47 -0
- package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
- package/src/sqlite/json.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +24 -0
- package/src/sqlite/table.ts +183 -0
- package/src/sqlite.ts +22 -0
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema"
|
|
2
|
+
|
|
1
3
|
import * as Query from "../../internal/query.js"
|
|
2
4
|
import * as Expression from "../../internal/scalar.js"
|
|
3
5
|
import * as Table from "../../internal/table.js"
|
|
4
6
|
import * as QueryAst from "../../internal/query-ast.js"
|
|
5
|
-
import type { RenderState, SqlDialect } from "../../internal/dialect.js"
|
|
7
|
+
import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
|
|
6
8
|
import * as ExpressionAst from "../../internal/expression-ast.js"
|
|
7
9
|
import * as JsonPath from "../../internal/json/path.js"
|
|
10
|
+
import { renderSelectLockMode } from "../../internal/dsl-plan-runtime.js"
|
|
11
|
+
import { expectConflictClause, expectInsertSourceKind } from "../../internal/dsl-mutation-runtime.js"
|
|
12
|
+
import { expectDdlClauseKind, expectTruncateClause, renderTransactionIsolationLevel } from "../../internal/dsl-transaction-ddl-runtime.js"
|
|
13
|
+
import {
|
|
14
|
+
renderJsonSelectSql,
|
|
15
|
+
renderSelectSql,
|
|
16
|
+
toDriverValue
|
|
17
|
+
} from "../../internal/runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../../internal/runtime/normalize.js"
|
|
8
19
|
import { flattenSelection, type Projection } from "../../internal/projections.js"
|
|
9
20
|
import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
|
|
10
21
|
import * as SchemaExpression from "../../internal/schema-expression.js"
|
|
11
|
-
import type
|
|
22
|
+
import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
|
|
12
23
|
|
|
13
24
|
const renderDbType = (
|
|
14
25
|
dialect: SqlDialect,
|
|
@@ -46,14 +57,59 @@ const renderCastType = (
|
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
const renderPostgresDdlString = (value: string): string =>
|
|
61
|
+
`'${value.replaceAll("'", "''")}'`
|
|
62
|
+
|
|
63
|
+
const renderPostgresDdlBytes = (value: Uint8Array): string =>
|
|
64
|
+
`decode('${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}', 'hex')`
|
|
65
|
+
|
|
66
|
+
const renderPostgresDdlLiteral = (
|
|
67
|
+
value: unknown,
|
|
68
|
+
state: RenderState,
|
|
69
|
+
context: RenderValueContext = {}
|
|
70
|
+
): string => {
|
|
71
|
+
const driverValue = toDriverValue(value, {
|
|
72
|
+
dialect: "postgres",
|
|
73
|
+
valueMappings: state.valueMappings,
|
|
74
|
+
...context
|
|
75
|
+
})
|
|
76
|
+
if (driverValue === null) {
|
|
77
|
+
return "null"
|
|
78
|
+
}
|
|
79
|
+
switch (typeof driverValue) {
|
|
80
|
+
case "boolean":
|
|
81
|
+
return driverValue ? "true" : "false"
|
|
82
|
+
case "number":
|
|
83
|
+
if (!Number.isFinite(driverValue)) {
|
|
84
|
+
throw new Error("Expected a finite numeric value")
|
|
85
|
+
}
|
|
86
|
+
return String(driverValue)
|
|
87
|
+
case "bigint":
|
|
88
|
+
return driverValue.toString()
|
|
89
|
+
case "string":
|
|
90
|
+
return renderPostgresDdlString(driverValue)
|
|
91
|
+
case "object":
|
|
92
|
+
if (driverValue instanceof Uint8Array) {
|
|
93
|
+
return renderPostgresDdlBytes(driverValue)
|
|
94
|
+
}
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Unsupported postgres DDL literal value")
|
|
98
|
+
}
|
|
99
|
+
|
|
49
100
|
const renderDdlExpression = (
|
|
50
101
|
expression: DdlExpressionLike,
|
|
51
102
|
state: RenderState,
|
|
52
103
|
dialect: SqlDialect
|
|
53
|
-
): string =>
|
|
54
|
-
SchemaExpression.isSchemaExpression(expression)
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
): string => {
|
|
105
|
+
if (SchemaExpression.isSchemaExpression(expression)) {
|
|
106
|
+
return SchemaExpression.render(expression)
|
|
107
|
+
}
|
|
108
|
+
return renderExpression(expression, state, {
|
|
109
|
+
...dialect,
|
|
110
|
+
renderLiteral: renderPostgresDdlLiteral
|
|
111
|
+
})
|
|
112
|
+
}
|
|
57
113
|
|
|
58
114
|
const renderColumnDefinition = (
|
|
59
115
|
dialect: SqlDialect,
|
|
@@ -100,17 +156,19 @@ const renderCreateTableSql = (
|
|
|
100
156
|
case "foreignKey": {
|
|
101
157
|
const reference = option.references()
|
|
102
158
|
definitions.push(
|
|
103
|
-
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.onDelete ? ` on delete ${option.onDelete
|
|
159
|
+
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.onDelete !== undefined ? ` on delete ${renderReferentialAction(option.onDelete)}` : ""}${option.onUpdate !== undefined ? ` on update ${renderReferentialAction(option.onUpdate)}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
|
|
104
160
|
)
|
|
105
161
|
break
|
|
106
162
|
}
|
|
107
163
|
case "check":
|
|
108
164
|
definitions.push(
|
|
109
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
165
|
+
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
110
166
|
)
|
|
111
167
|
break
|
|
112
168
|
case "index":
|
|
113
169
|
break
|
|
170
|
+
default:
|
|
171
|
+
throw new Error("Unsupported table option kind")
|
|
114
172
|
}
|
|
115
173
|
}
|
|
116
174
|
return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
@@ -131,20 +189,74 @@ const renderDropIndexSql = (
|
|
|
131
189
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
132
190
|
state: RenderState,
|
|
133
191
|
dialect: SqlDialect
|
|
134
|
-
): string =>
|
|
135
|
-
dialect.name === "postgres"
|
|
136
|
-
|
|
137
|
-
|
|
192
|
+
): string => {
|
|
193
|
+
if (dialect.name === "postgres") {
|
|
194
|
+
const schemaName = typeof targetSource.source === "object" &&
|
|
195
|
+
targetSource.source !== null &&
|
|
196
|
+
Table.TypeId in targetSource.source
|
|
197
|
+
? (targetSource.source as Table.AnyTable)[Table.TypeId].schemaName
|
|
198
|
+
: undefined
|
|
199
|
+
const indexName = schemaName === undefined || schemaName === "public"
|
|
200
|
+
? dialect.quoteIdentifier(ddl.name)
|
|
201
|
+
: `${dialect.quoteIdentifier(schemaName)}.${dialect.quoteIdentifier(ddl.name)}`
|
|
202
|
+
return `drop index${ddl.ifExists ? " if exists" : ""} ${indexName}`
|
|
203
|
+
}
|
|
204
|
+
return `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
205
|
+
}
|
|
138
206
|
|
|
139
207
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
140
208
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
141
209
|
|
|
142
|
-
const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
143
|
-
dbType.kind === "jsonb" || dbType.kind === "json"
|
|
210
|
+
const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
|
|
211
|
+
if (dbType.kind === "jsonb" || dbType.kind === "json") {
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
if (!("variant" in dbType)) {
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
const variant = dbType.variant as string
|
|
218
|
+
return variant === "json" || variant === "jsonb"
|
|
219
|
+
}
|
|
144
220
|
|
|
145
221
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
146
222
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
147
223
|
|
|
224
|
+
const postgresRangeSubtypeByKind: Readonly<Record<string, string>> = {
|
|
225
|
+
int4range: "int4",
|
|
226
|
+
int8range: "int8",
|
|
227
|
+
numrange: "numeric",
|
|
228
|
+
tsrange: "timestamp",
|
|
229
|
+
tstzrange: "timestamptz",
|
|
230
|
+
daterange: "date",
|
|
231
|
+
int4multirange: "int4",
|
|
232
|
+
int8multirange: "int8",
|
|
233
|
+
nummultirange: "numeric",
|
|
234
|
+
tsmultirange: "timestamp",
|
|
235
|
+
tstzmultirange: "timestamptz",
|
|
236
|
+
datemultirange: "date"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const postgresRangeSubtypeKey = (dbType: Expression.DbType.Any): string | undefined => {
|
|
240
|
+
if ("base" in dbType) {
|
|
241
|
+
return postgresRangeSubtypeKey(dbType.base)
|
|
242
|
+
}
|
|
243
|
+
if ("subtype" in dbType) {
|
|
244
|
+
return postgresRangeSubtypeKey(dbType.subtype) ?? dbType.subtype.kind
|
|
245
|
+
}
|
|
246
|
+
return postgresRangeSubtypeByKind[dbType.kind]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const assertCompatiblePostgresRangeOperands = (
|
|
250
|
+
left: Expression.Any,
|
|
251
|
+
right: Expression.Any
|
|
252
|
+
): void => {
|
|
253
|
+
const leftKey = postgresRangeSubtypeKey(left[Expression.TypeId].dbType)
|
|
254
|
+
const rightKey = postgresRangeSubtypeKey(right[Expression.TypeId].dbType)
|
|
255
|
+
if (leftKey !== undefined && rightKey !== undefined && leftKey !== rightKey) {
|
|
256
|
+
throw new Error("Incompatible postgres range operands")
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
148
260
|
const unsupportedJsonFeature = (
|
|
149
261
|
dialect: SqlDialect,
|
|
150
262
|
feature: string
|
|
@@ -202,19 +314,19 @@ const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
|
202
314
|
node.newValue ?? node.insert ?? node.right
|
|
203
315
|
|
|
204
316
|
const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
|
|
317
|
+
const renderKey = (value: string): string =>
|
|
318
|
+
/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
|
|
319
|
+
? `.${value}`
|
|
320
|
+
: `.${JSON.stringify(value)}`
|
|
205
321
|
if (typeof segment === "string") {
|
|
206
|
-
return
|
|
207
|
-
? `.${segment}`
|
|
208
|
-
: `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
322
|
+
return renderKey(segment)
|
|
209
323
|
}
|
|
210
324
|
if (typeof segment === "number") {
|
|
211
325
|
return `[${segment}]`
|
|
212
326
|
}
|
|
213
327
|
switch (segment.kind) {
|
|
214
328
|
case "key":
|
|
215
|
-
return
|
|
216
|
-
? `.${segment.key}`
|
|
217
|
-
: `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
329
|
+
return renderKey(segment.key)
|
|
218
330
|
case "index":
|
|
219
331
|
return `[${segment.index}]`
|
|
220
332
|
case "wildcard":
|
|
@@ -279,7 +391,7 @@ const renderPostgresJsonAccessStep = (
|
|
|
279
391
|
case "key":
|
|
280
392
|
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.key, state)}`
|
|
281
393
|
case "index":
|
|
282
|
-
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(
|
|
394
|
+
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.index, state)}`
|
|
283
395
|
default:
|
|
284
396
|
throw new Error("Postgres exact JSON access requires key/index segments")
|
|
285
397
|
}
|
|
@@ -294,11 +406,66 @@ const renderPostgresJsonValue = (
|
|
|
294
406
|
throw new Error("Expected a JSON expression")
|
|
295
407
|
}
|
|
296
408
|
const rendered = renderExpression(value, state, dialect)
|
|
409
|
+
const ast = (value as Expression.Any & {
|
|
410
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
411
|
+
})[ExpressionAst.TypeId]
|
|
412
|
+
if (ast.kind === "literal") {
|
|
413
|
+
return `cast(${rendered} as jsonb)`
|
|
414
|
+
}
|
|
297
415
|
return value[Expression.TypeId].dbType.kind === "jsonb"
|
|
298
416
|
? rendered
|
|
299
417
|
: `cast(${rendered} as jsonb)`
|
|
300
418
|
}
|
|
301
419
|
|
|
420
|
+
const expressionDriverContext = (
|
|
421
|
+
expression: Expression.Any,
|
|
422
|
+
state: RenderState,
|
|
423
|
+
dialect: SqlDialect
|
|
424
|
+
) => ({
|
|
425
|
+
dialect: dialect.name,
|
|
426
|
+
valueMappings: state.valueMappings,
|
|
427
|
+
dbType: expression[Expression.TypeId].dbType,
|
|
428
|
+
runtimeSchema: expression[Expression.TypeId].runtimeSchema,
|
|
429
|
+
driverValueMapping: expression[Expression.TypeId].driverValueMapping
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
const renderJsonInputExpression = (
|
|
433
|
+
expression: Expression.Any,
|
|
434
|
+
state: RenderState,
|
|
435
|
+
dialect: SqlDialect
|
|
436
|
+
): string =>
|
|
437
|
+
renderJsonSelectSql(
|
|
438
|
+
renderExpression(expression, state, dialect),
|
|
439
|
+
expressionDriverContext(expression, state, dialect)
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
const encodeArrayValues = (
|
|
443
|
+
values: readonly unknown[],
|
|
444
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
445
|
+
state: RenderState,
|
|
446
|
+
dialect: SqlDialect
|
|
447
|
+
): readonly unknown[] =>
|
|
448
|
+
values.map((value) => {
|
|
449
|
+
if (value === null && column.metadata.nullable) {
|
|
450
|
+
return null
|
|
451
|
+
}
|
|
452
|
+
const runtimeSchemaAccepts = column.schema !== undefined &&
|
|
453
|
+
(Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
|
|
454
|
+
const normalizedValue = runtimeSchemaAccepts
|
|
455
|
+
? value
|
|
456
|
+
: normalizeDbValue(column.metadata.dbType, value)
|
|
457
|
+
const encodedValue = column.schema === undefined || runtimeSchemaAccepts
|
|
458
|
+
? normalizedValue
|
|
459
|
+
: (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
|
|
460
|
+
return toDriverValue(encodedValue, {
|
|
461
|
+
dialect: dialect.name,
|
|
462
|
+
valueMappings: state.valueMappings,
|
|
463
|
+
dbType: column.metadata.dbType,
|
|
464
|
+
runtimeSchema: column.schema,
|
|
465
|
+
driverValueMapping: column.metadata.driverValueMapping
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
302
469
|
const renderPostgresJsonKind = (
|
|
303
470
|
value: Expression.Any
|
|
304
471
|
): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
|
|
@@ -460,7 +627,7 @@ const renderJsonExpression = (
|
|
|
460
627
|
: []
|
|
461
628
|
const renderedEntries = entries.flatMap((entry) => [
|
|
462
629
|
dialect.renderLiteral(entry.key, state),
|
|
463
|
-
|
|
630
|
+
renderJsonInputExpression(entry.value, state, dialect)
|
|
464
631
|
])
|
|
465
632
|
if (dialect.name === "postgres") {
|
|
466
633
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
|
|
@@ -474,7 +641,7 @@ const renderJsonExpression = (
|
|
|
474
641
|
const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
|
|
475
642
|
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
476
643
|
: []
|
|
477
|
-
const renderedValues = values.map((value) =>
|
|
644
|
+
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
478
645
|
if (dialect.name === "postgres") {
|
|
479
646
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
480
647
|
}
|
|
@@ -488,7 +655,7 @@ const renderJsonExpression = (
|
|
|
488
655
|
return undefined
|
|
489
656
|
}
|
|
490
657
|
if (dialect.name === "postgres") {
|
|
491
|
-
return `to_json(${
|
|
658
|
+
return `to_json(${renderJsonInputExpression(base, state, dialect)})`
|
|
492
659
|
}
|
|
493
660
|
if (dialect.name === "mysql") {
|
|
494
661
|
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
@@ -499,7 +666,7 @@ const renderJsonExpression = (
|
|
|
499
666
|
return undefined
|
|
500
667
|
}
|
|
501
668
|
if (dialect.name === "postgres") {
|
|
502
|
-
return `to_jsonb(${
|
|
669
|
+
return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
|
|
503
670
|
}
|
|
504
671
|
if (dialect.name === "mysql") {
|
|
505
672
|
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
@@ -540,7 +707,7 @@ const renderJsonExpression = (
|
|
|
540
707
|
const baseSql = renderExpression(base, state, dialect)
|
|
541
708
|
const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
|
|
542
709
|
const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
|
|
543
|
-
return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
|
|
710
|
+
return `(case when ${typeOf}(${baseSql}) = 'object' then to_json(array(select ${objectKeys}(${baseSql}))) else null end)`
|
|
544
711
|
}
|
|
545
712
|
if (dialect.name === "mysql") {
|
|
546
713
|
return `json_keys(${renderExpression(base, state, dialect)})`
|
|
@@ -567,7 +734,7 @@ const renderJsonExpression = (
|
|
|
567
734
|
const segment = segments[0]!
|
|
568
735
|
return `(${baseSql} - ${segment.kind === "key"
|
|
569
736
|
? dialect.renderLiteral(segment.key, state)
|
|
570
|
-
: dialect.renderLiteral(
|
|
737
|
+
: dialect.renderLiteral(segment.index, state)})`
|
|
571
738
|
}
|
|
572
739
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
573
740
|
}
|
|
@@ -691,6 +858,15 @@ const renderDeleteTargets = (
|
|
|
691
858
|
dialect: SqlDialect
|
|
692
859
|
): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
|
|
693
860
|
|
|
861
|
+
const assertMergeActionKind = (
|
|
862
|
+
kind: unknown,
|
|
863
|
+
allowed: readonly string[]
|
|
864
|
+
): void => {
|
|
865
|
+
if (typeof kind !== "string" || !allowed.includes(kind)) {
|
|
866
|
+
throw new Error("Unsupported merge action kind")
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
694
870
|
const renderMysqlMutationLock = (
|
|
695
871
|
lock: QueryAst.LockClause | undefined,
|
|
696
872
|
statement: "update" | "delete"
|
|
@@ -717,8 +893,9 @@ const renderTransactionClause = (
|
|
|
717
893
|
switch (clause.kind) {
|
|
718
894
|
case "transaction": {
|
|
719
895
|
const modes: string[] = []
|
|
720
|
-
|
|
721
|
-
|
|
896
|
+
const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
|
|
897
|
+
if (isolationLevel) {
|
|
898
|
+
modes.push(isolationLevel)
|
|
722
899
|
}
|
|
723
900
|
if (clause.readOnly === true) {
|
|
724
901
|
modes.push("read only")
|
|
@@ -738,7 +915,7 @@ const renderTransactionClause = (
|
|
|
738
915
|
case "releaseSavepoint":
|
|
739
916
|
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
740
917
|
}
|
|
741
|
-
|
|
918
|
+
throw new Error("Unsupported transaction statement kind")
|
|
742
919
|
}
|
|
743
920
|
|
|
744
921
|
const renderSelectionList = (
|
|
@@ -751,19 +928,117 @@ const renderSelectionList = (
|
|
|
751
928
|
validateAggregationSelection(selection as SelectionValue, [])
|
|
752
929
|
}
|
|
753
930
|
const flattened = flattenSelection(selection)
|
|
931
|
+
if (dialect.name === "mysql" && flattened.length === 0) {
|
|
932
|
+
throw new Error("mysql select statements require at least one selected expression")
|
|
933
|
+
}
|
|
754
934
|
const projections = selectionProjections(selection)
|
|
755
935
|
const sql = flattened.map(({ expression, alias }) =>
|
|
756
|
-
`${renderExpression(expression, state, dialect)} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
936
|
+
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
757
937
|
return {
|
|
758
938
|
sql,
|
|
759
939
|
projections
|
|
760
940
|
}
|
|
761
941
|
}
|
|
762
942
|
|
|
943
|
+
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
944
|
+
params: state.params,
|
|
945
|
+
valueMappings: state.valueMappings,
|
|
946
|
+
ctes: [],
|
|
947
|
+
cteNames: new Set(state.cteNames),
|
|
948
|
+
cteSources: new Map(state.cteSources)
|
|
949
|
+
})
|
|
950
|
+
|
|
951
|
+
const assertMatchingSetProjections = (
|
|
952
|
+
left: readonly Projection[],
|
|
953
|
+
right: readonly Projection[]
|
|
954
|
+
): void => {
|
|
955
|
+
const leftKeys = left.map((projection) => JSON.stringify(projection.path))
|
|
956
|
+
const rightKeys = right.map((projection) => JSON.stringify(projection.path))
|
|
957
|
+
if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
|
|
958
|
+
throw new Error("set operator operands must have matching result rows")
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const assertNoGroupedMutationClauses = (
|
|
963
|
+
ast: Pick<QueryAst.Ast, "groupBy" | "having">,
|
|
964
|
+
statement: string
|
|
965
|
+
): void => {
|
|
966
|
+
if (ast.groupBy.length > 0) {
|
|
967
|
+
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
968
|
+
}
|
|
969
|
+
if (ast.having.length > 0) {
|
|
970
|
+
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const assertNoInsertQueryClauses = (
|
|
975
|
+
ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
|
|
976
|
+
): void => {
|
|
977
|
+
if (ast.where.length > 0) {
|
|
978
|
+
throw new Error("where(...) is not supported for insert statements")
|
|
979
|
+
}
|
|
980
|
+
if (ast.joins.length > 0) {
|
|
981
|
+
throw new Error("join(...) is not supported for insert statements")
|
|
982
|
+
}
|
|
983
|
+
if (ast.orderBy.length > 0) {
|
|
984
|
+
throw new Error("orderBy(...) is not supported for insert statements")
|
|
985
|
+
}
|
|
986
|
+
if (ast.limit) {
|
|
987
|
+
throw new Error("limit(...) is not supported for insert statements")
|
|
988
|
+
}
|
|
989
|
+
if (ast.offset) {
|
|
990
|
+
throw new Error("offset(...) is not supported for insert statements")
|
|
991
|
+
}
|
|
992
|
+
if (ast.lock) {
|
|
993
|
+
throw new Error("lock(...) is not supported for insert statements")
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const assertNoStatementQueryClauses = (
|
|
998
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
999
|
+
statement: string,
|
|
1000
|
+
options: { readonly allowSelection?: boolean } = {}
|
|
1001
|
+
): void => {
|
|
1002
|
+
if (ast.distinct) {
|
|
1003
|
+
throw new Error(`distinct(...) is not supported for ${statement} statements`)
|
|
1004
|
+
}
|
|
1005
|
+
if (ast.where.length > 0) {
|
|
1006
|
+
throw new Error(`where(...) is not supported for ${statement} statements`)
|
|
1007
|
+
}
|
|
1008
|
+
if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
|
|
1009
|
+
throw new Error(`from(...) is not supported for ${statement} statements`)
|
|
1010
|
+
}
|
|
1011
|
+
if (ast.joins.length > 0) {
|
|
1012
|
+
throw new Error(`join(...) is not supported for ${statement} statements`)
|
|
1013
|
+
}
|
|
1014
|
+
if (ast.groupBy.length > 0) {
|
|
1015
|
+
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
1016
|
+
}
|
|
1017
|
+
if (ast.having.length > 0) {
|
|
1018
|
+
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1019
|
+
}
|
|
1020
|
+
if (ast.orderBy.length > 0) {
|
|
1021
|
+
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
1022
|
+
}
|
|
1023
|
+
if (ast.limit) {
|
|
1024
|
+
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
1025
|
+
}
|
|
1026
|
+
if (ast.offset) {
|
|
1027
|
+
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
1028
|
+
}
|
|
1029
|
+
if (ast.lock) {
|
|
1030
|
+
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
1031
|
+
}
|
|
1032
|
+
if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
|
|
1033
|
+
throw new Error(`returning(...) is not supported for ${statement} statements`)
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
763
1037
|
export const renderQueryAst = (
|
|
764
1038
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
765
1039
|
state: RenderState,
|
|
766
|
-
dialect: SqlDialect
|
|
1040
|
+
dialect: SqlDialect,
|
|
1041
|
+
options: { readonly emitCtes?: boolean } = {}
|
|
767
1042
|
): RenderedQueryAst => {
|
|
768
1043
|
let sql = ""
|
|
769
1044
|
let projections: readonly Projection[] = []
|
|
@@ -773,10 +1048,11 @@ export const renderQueryAst = (
|
|
|
773
1048
|
validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
|
|
774
1049
|
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
775
1050
|
projections = rendered.projections
|
|
1051
|
+
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
776
1052
|
const clauses = [
|
|
777
1053
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
778
|
-
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})
|
|
779
|
-
: `select${ast.distinct ? " distinct" : ""}
|
|
1054
|
+
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
|
|
1055
|
+
: `select${ast.distinct ? " distinct" : ""}${selectList}`
|
|
780
1056
|
]
|
|
781
1057
|
if (ast.from) {
|
|
782
1058
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
@@ -808,8 +1084,11 @@ export const renderQueryAst = (
|
|
|
808
1084
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
809
1085
|
}
|
|
810
1086
|
if (ast.lock) {
|
|
1087
|
+
if (ast.lock.nowait && ast.lock.skipLocked) {
|
|
1088
|
+
throw new Error("lock(...) cannot specify both nowait and skipLocked")
|
|
1089
|
+
}
|
|
811
1090
|
clauses.push(
|
|
812
|
-
`${ast.lock.mode
|
|
1091
|
+
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
813
1092
|
)
|
|
814
1093
|
}
|
|
815
1094
|
sql = clauses.join(" ")
|
|
@@ -817,6 +1096,7 @@ export const renderQueryAst = (
|
|
|
817
1096
|
}
|
|
818
1097
|
case "set": {
|
|
819
1098
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1099
|
+
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
820
1100
|
const base = renderQueryAst(
|
|
821
1101
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
822
1102
|
Record<string, unknown>,
|
|
@@ -827,6 +1107,7 @@ export const renderQueryAst = (
|
|
|
827
1107
|
dialect
|
|
828
1108
|
)
|
|
829
1109
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1110
|
+
assertMatchingSetProjections(projections, base.projections)
|
|
830
1111
|
sql = [
|
|
831
1112
|
`(${base.sql})`,
|
|
832
1113
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -839,6 +1120,7 @@ export const renderQueryAst = (
|
|
|
839
1120
|
state,
|
|
840
1121
|
dialect
|
|
841
1122
|
)
|
|
1123
|
+
assertMatchingSetProjections(projections, rendered.projections)
|
|
842
1124
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
843
1125
|
})
|
|
844
1126
|
].join(" ")
|
|
@@ -846,19 +1128,26 @@ export const renderQueryAst = (
|
|
|
846
1128
|
}
|
|
847
1129
|
case "insert": {
|
|
848
1130
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
1131
|
+
if (insertAst.distinct) {
|
|
1132
|
+
throw new Error("distinct(...) is not supported for insert statements")
|
|
1133
|
+
}
|
|
1134
|
+
assertNoGroupedMutationClauses(insertAst, "insert")
|
|
1135
|
+
assertNoInsertQueryClauses(insertAst)
|
|
849
1136
|
const targetSource = insertAst.into!
|
|
850
1137
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1138
|
+
const insertSource = expectInsertSourceKind(insertAst.insertSource)
|
|
1139
|
+
const conflict = expectConflictClause(insertAst.conflict)
|
|
851
1140
|
sql = `insert into ${target}`
|
|
852
|
-
if (
|
|
853
|
-
const columns =
|
|
854
|
-
const rows =
|
|
1141
|
+
if (insertSource?.kind === "values") {
|
|
1142
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
1143
|
+
const rows = insertSource.rows.map((row) =>
|
|
855
1144
|
`(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
856
1145
|
).join(", ")
|
|
857
1146
|
sql += ` (${columns}) values ${rows}`
|
|
858
|
-
} else if (
|
|
859
|
-
const columns =
|
|
1147
|
+
} else if (insertSource?.kind === "query") {
|
|
1148
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
860
1149
|
const renderedQuery = renderQueryAst(
|
|
861
|
-
Query.getAst(
|
|
1150
|
+
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
862
1151
|
Record<string, unknown>,
|
|
863
1152
|
any,
|
|
864
1153
|
QueryAst.QueryStatement
|
|
@@ -867,21 +1156,24 @@ export const renderQueryAst = (
|
|
|
867
1156
|
dialect
|
|
868
1157
|
)
|
|
869
1158
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
870
|
-
} else if (
|
|
871
|
-
const
|
|
872
|
-
const columns = unnestSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
1159
|
+
} else if (insertSource?.kind === "unnest") {
|
|
1160
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
873
1161
|
if (dialect.name === "postgres") {
|
|
874
1162
|
const table = targetSource.source as Table.AnyTable
|
|
875
1163
|
const fields = table[Table.TypeId].fields
|
|
876
|
-
const rendered =
|
|
877
|
-
`cast(${dialect.renderLiteral(entry.values, state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
1164
|
+
const rendered = insertSource.values.map((entry) =>
|
|
1165
|
+
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
878
1166
|
).join(", ")
|
|
879
1167
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
880
1168
|
} else {
|
|
881
|
-
const rowCount =
|
|
1169
|
+
const rowCount = insertSource.values[0]?.values.length ?? 0
|
|
882
1170
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
883
|
-
`(${
|
|
884
|
-
dialect.renderLiteral(
|
|
1171
|
+
`(${insertSource.values.map((entry) =>
|
|
1172
|
+
dialect.renderLiteral(
|
|
1173
|
+
entry.values[index],
|
|
1174
|
+
state,
|
|
1175
|
+
(targetSource.source as Table.AnyTable)[Table.TypeId].fields[entry.columnName]![Expression.TypeId]
|
|
1176
|
+
)
|
|
885
1177
|
).join(", ")})`
|
|
886
1178
|
).join(", ")
|
|
887
1179
|
sql += ` (${columns}) values ${rows}`
|
|
@@ -895,21 +1187,24 @@ export const renderQueryAst = (
|
|
|
895
1187
|
sql += " default values"
|
|
896
1188
|
}
|
|
897
1189
|
}
|
|
898
|
-
if (
|
|
899
|
-
|
|
1190
|
+
if (conflict) {
|
|
1191
|
+
if (conflict.action === "doNothing" && conflict.where) {
|
|
1192
|
+
throw new Error("conflict action predicates require update assignments")
|
|
1193
|
+
}
|
|
1194
|
+
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
900
1195
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
901
1196
|
).join(", ")
|
|
902
1197
|
if (dialect.name === "postgres") {
|
|
903
|
-
const targetSql =
|
|
904
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
905
|
-
:
|
|
906
|
-
? ` on conflict (${
|
|
1198
|
+
const targetSql = conflict.target?.kind === "constraint"
|
|
1199
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
|
|
1200
|
+
: conflict.target?.kind === "columns"
|
|
1201
|
+
? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
|
|
907
1202
|
: " on conflict"
|
|
908
1203
|
sql += targetSql
|
|
909
|
-
sql +=
|
|
1204
|
+
sql += conflict.action === "doNothing"
|
|
910
1205
|
? " do nothing"
|
|
911
|
-
: ` do update set ${updateValues}${
|
|
912
|
-
} else if (
|
|
1206
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
|
|
1207
|
+
} else if (conflict.action === "doNothing") {
|
|
913
1208
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
914
1209
|
} else {
|
|
915
1210
|
sql += ` on duplicate key update ${updateValues}`
|
|
@@ -924,10 +1219,29 @@ export const renderQueryAst = (
|
|
|
924
1219
|
}
|
|
925
1220
|
case "update": {
|
|
926
1221
|
const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
|
|
1222
|
+
if (updateAst.distinct) {
|
|
1223
|
+
throw new Error("distinct(...) is not supported for update statements")
|
|
1224
|
+
}
|
|
1225
|
+
assertNoGroupedMutationClauses(updateAst, "update")
|
|
1226
|
+
if (updateAst.orderBy.length > 0) {
|
|
1227
|
+
throw new Error("orderBy(...) is not supported for update statements")
|
|
1228
|
+
}
|
|
1229
|
+
if (updateAst.limit) {
|
|
1230
|
+
throw new Error("limit(...) is not supported for update statements")
|
|
1231
|
+
}
|
|
1232
|
+
if (updateAst.offset) {
|
|
1233
|
+
throw new Error("offset(...) is not supported for update statements")
|
|
1234
|
+
}
|
|
1235
|
+
if (updateAst.lock) {
|
|
1236
|
+
throw new Error("lock(...) is not supported for update statements")
|
|
1237
|
+
}
|
|
927
1238
|
const targetSource = updateAst.target!
|
|
928
1239
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
929
1240
|
const targets = updateAst.targets ?? [targetSource]
|
|
930
1241
|
const fromSources = updateAst.fromSources ?? []
|
|
1242
|
+
if ((updateAst.set ?? []).length === 0) {
|
|
1243
|
+
throw new Error("update statements require at least one assignment")
|
|
1244
|
+
}
|
|
931
1245
|
const assignments = updateAst.set!.map((entry) =>
|
|
932
1246
|
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
933
1247
|
if (dialect.name === "mysql") {
|
|
@@ -977,6 +1291,22 @@ export const renderQueryAst = (
|
|
|
977
1291
|
}
|
|
978
1292
|
case "delete": {
|
|
979
1293
|
const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
|
|
1294
|
+
if (deleteAst.distinct) {
|
|
1295
|
+
throw new Error("distinct(...) is not supported for delete statements")
|
|
1296
|
+
}
|
|
1297
|
+
assertNoGroupedMutationClauses(deleteAst, "delete")
|
|
1298
|
+
if (deleteAst.orderBy.length > 0 && dialect.name === "postgres") {
|
|
1299
|
+
throw new Error("orderBy(...) is not supported for delete statements")
|
|
1300
|
+
}
|
|
1301
|
+
if (deleteAst.limit && dialect.name === "postgres") {
|
|
1302
|
+
throw new Error("limit(...) is not supported for delete statements")
|
|
1303
|
+
}
|
|
1304
|
+
if (deleteAst.offset) {
|
|
1305
|
+
throw new Error("offset(...) is not supported for delete statements")
|
|
1306
|
+
}
|
|
1307
|
+
if (deleteAst.lock) {
|
|
1308
|
+
throw new Error("lock(...) is not supported for delete statements")
|
|
1309
|
+
}
|
|
980
1310
|
const targetSource = deleteAst.target!
|
|
981
1311
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
982
1312
|
const targets = deleteAst.targets ?? [targetSource]
|
|
@@ -1023,12 +1353,14 @@ export const renderQueryAst = (
|
|
|
1023
1353
|
}
|
|
1024
1354
|
case "truncate": {
|
|
1025
1355
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1356
|
+
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1357
|
+
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1026
1358
|
const targetSource = truncateAst.target!
|
|
1027
1359
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1028
|
-
if (
|
|
1360
|
+
if (truncate.restartIdentity) {
|
|
1029
1361
|
sql += " restart identity"
|
|
1030
1362
|
}
|
|
1031
|
-
if (
|
|
1363
|
+
if (truncate.cascade) {
|
|
1032
1364
|
sql += " cascade"
|
|
1033
1365
|
}
|
|
1034
1366
|
break
|
|
@@ -1041,8 +1373,18 @@ export const renderQueryAst = (
|
|
|
1041
1373
|
const targetSource = mergeAst.target!
|
|
1042
1374
|
const usingSource = mergeAst.using!
|
|
1043
1375
|
const merge = mergeAst.merge!
|
|
1376
|
+
if (merge.kind !== "merge") {
|
|
1377
|
+
throw new Error("Unsupported merge statement kind")
|
|
1378
|
+
}
|
|
1379
|
+
if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
|
|
1380
|
+
throw new Error("returning(...) is not supported for merge statements")
|
|
1381
|
+
}
|
|
1382
|
+
if (!merge.whenMatched && !merge.whenNotMatched) {
|
|
1383
|
+
throw new Error("merge statements require at least one action")
|
|
1384
|
+
}
|
|
1044
1385
|
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)}`
|
|
1045
1386
|
if (merge.whenMatched) {
|
|
1387
|
+
assertMergeActionKind(merge.whenMatched.kind, ["update", "delete"])
|
|
1046
1388
|
sql += " when matched"
|
|
1047
1389
|
if (merge.whenMatched.predicate) {
|
|
1048
1390
|
sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
|
|
@@ -1050,16 +1392,23 @@ export const renderQueryAst = (
|
|
|
1050
1392
|
if (merge.whenMatched.kind === "delete") {
|
|
1051
1393
|
sql += " then delete"
|
|
1052
1394
|
} else {
|
|
1395
|
+
if (merge.whenMatched.values.length === 0) {
|
|
1396
|
+
throw new Error("merge update actions require at least one assignment")
|
|
1397
|
+
}
|
|
1053
1398
|
sql += ` then update set ${merge.whenMatched.values.map((entry) =>
|
|
1054
1399
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
1055
1400
|
).join(", ")}`
|
|
1056
1401
|
}
|
|
1057
1402
|
}
|
|
1058
1403
|
if (merge.whenNotMatched) {
|
|
1404
|
+
assertMergeActionKind(merge.whenNotMatched.kind, ["insert"])
|
|
1059
1405
|
sql += " when not matched"
|
|
1060
1406
|
if (merge.whenNotMatched.predicate) {
|
|
1061
1407
|
sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
|
|
1062
1408
|
}
|
|
1409
|
+
if (merge.whenNotMatched.values.length === 0) {
|
|
1410
|
+
throw new Error("merge insert actions require at least one value")
|
|
1411
|
+
}
|
|
1063
1412
|
sql += ` then insert (${merge.whenNotMatched.values.map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
1064
1413
|
}
|
|
1065
1414
|
break
|
|
@@ -1070,25 +1419,30 @@ export const renderQueryAst = (
|
|
|
1070
1419
|
case "savepoint":
|
|
1071
1420
|
case "rollbackTo":
|
|
1072
1421
|
case "releaseSavepoint": {
|
|
1422
|
+
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1073
1423
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1074
1424
|
break
|
|
1075
1425
|
}
|
|
1076
1426
|
case "createTable": {
|
|
1077
1427
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1078
|
-
|
|
1428
|
+
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1429
|
+
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1430
|
+
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1079
1431
|
break
|
|
1080
1432
|
}
|
|
1081
1433
|
case "dropTable": {
|
|
1082
1434
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1083
|
-
|
|
1084
|
-
|
|
1435
|
+
assertNoStatementQueryClauses(dropTableAst, "dropTable")
|
|
1436
|
+
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1437
|
+
sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1085
1438
|
break
|
|
1086
1439
|
}
|
|
1087
1440
|
case "createIndex": {
|
|
1088
1441
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1442
|
+
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1089
1443
|
sql = renderCreateIndexSql(
|
|
1090
1444
|
createIndexAst.target!,
|
|
1091
|
-
createIndexAst.ddl
|
|
1445
|
+
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
1092
1446
|
state,
|
|
1093
1447
|
dialect
|
|
1094
1448
|
)
|
|
@@ -1096,17 +1450,20 @@ export const renderQueryAst = (
|
|
|
1096
1450
|
}
|
|
1097
1451
|
case "dropIndex": {
|
|
1098
1452
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1453
|
+
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1099
1454
|
sql = renderDropIndexSql(
|
|
1100
1455
|
dropIndexAst.target!,
|
|
1101
|
-
dropIndexAst.ddl
|
|
1456
|
+
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
1102
1457
|
state,
|
|
1103
1458
|
dialect
|
|
1104
1459
|
)
|
|
1105
1460
|
break
|
|
1106
1461
|
}
|
|
1462
|
+
default:
|
|
1463
|
+
throw new Error("Unsupported query statement kind")
|
|
1107
1464
|
}
|
|
1108
1465
|
|
|
1109
|
-
if (state.ctes.length === 0) {
|
|
1466
|
+
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
1110
1467
|
return {
|
|
1111
1468
|
sql,
|
|
1112
1469
|
projections
|
|
@@ -1154,9 +1511,19 @@ const renderSourceReference = (
|
|
|
1154
1511
|
readonly plan: Query.Plan.Any
|
|
1155
1512
|
readonly recursive?: boolean
|
|
1156
1513
|
}
|
|
1514
|
+
const registeredCteSource = state.cteSources.get(cte.name)
|
|
1515
|
+
if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
|
|
1516
|
+
throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
|
|
1517
|
+
}
|
|
1157
1518
|
if (!state.cteNames.has(cte.name)) {
|
|
1158
1519
|
state.cteNames.add(cte.name)
|
|
1159
|
-
|
|
1520
|
+
state.cteSources.set(cte.name, cte.plan)
|
|
1521
|
+
const rendered = renderQueryAst(
|
|
1522
|
+
Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1523
|
+
state,
|
|
1524
|
+
dialect,
|
|
1525
|
+
{ emitCtes: false }
|
|
1526
|
+
)
|
|
1160
1527
|
state.ctes.push({
|
|
1161
1528
|
name: cte.name,
|
|
1162
1529
|
sql: rendered.sql,
|
|
@@ -1173,14 +1540,14 @@ const renderSourceReference = (
|
|
|
1173
1540
|
if (!state.cteNames.has(derived.name)) {
|
|
1174
1541
|
// derived tables are inlined, so no CTE registration is needed
|
|
1175
1542
|
}
|
|
1176
|
-
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1543
|
+
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1177
1544
|
}
|
|
1178
1545
|
if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
|
|
1179
1546
|
const lateral = source as unknown as {
|
|
1180
1547
|
readonly name: string
|
|
1181
1548
|
readonly plan: Query.Plan.Any
|
|
1182
1549
|
}
|
|
1183
|
-
return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
|
|
1550
|
+
return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
|
|
1184
1551
|
}
|
|
1185
1552
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
1186
1553
|
const values = source as unknown as {
|
|
@@ -1215,6 +1582,22 @@ const renderSourceReference = (
|
|
|
1215
1582
|
return dialect.renderTableReference(tableName, baseTableName, schemaName)
|
|
1216
1583
|
}
|
|
1217
1584
|
|
|
1585
|
+
const renderSubqueryExpressionPlan = (
|
|
1586
|
+
plan: Query.Plan.Any,
|
|
1587
|
+
state: RenderState,
|
|
1588
|
+
dialect: SqlDialect
|
|
1589
|
+
): string => {
|
|
1590
|
+
const statement = Query.getQueryState(plan).statement
|
|
1591
|
+
if (statement !== "select" && statement !== "set") {
|
|
1592
|
+
throw new Error("subquery expressions only accept select-like query plans")
|
|
1593
|
+
}
|
|
1594
|
+
return renderQueryAst(
|
|
1595
|
+
Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1596
|
+
state,
|
|
1597
|
+
dialect
|
|
1598
|
+
).sql
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1218
1601
|
/**
|
|
1219
1602
|
* Renders a scalar expression AST into SQL text.
|
|
1220
1603
|
*
|
|
@@ -1249,11 +1632,14 @@ export const renderExpression = (
|
|
|
1249
1632
|
: ">="
|
|
1250
1633
|
switch (ast.kind) {
|
|
1251
1634
|
case "column":
|
|
1252
|
-
return ast.tableName.length === 0
|
|
1635
|
+
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1253
1636
|
? dialect.quoteIdentifier(ast.columnName)
|
|
1254
1637
|
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1255
1638
|
case "literal":
|
|
1256
|
-
|
|
1639
|
+
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1640
|
+
throw new Error("Expected a finite numeric value")
|
|
1641
|
+
}
|
|
1642
|
+
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1257
1643
|
case "excluded":
|
|
1258
1644
|
return dialect.name === "mysql"
|
|
1259
1645
|
? `values(${dialect.quoteIdentifier(ast.columnName)})`
|
|
@@ -1308,6 +1694,7 @@ export const renderExpression = (
|
|
|
1308
1694
|
: `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
|
|
1309
1695
|
case "contains":
|
|
1310
1696
|
if (dialect.name === "postgres") {
|
|
1697
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1311
1698
|
const left = isJsonExpression(ast.left)
|
|
1312
1699
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1313
1700
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1322,6 +1709,7 @@ export const renderExpression = (
|
|
|
1322
1709
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1323
1710
|
case "containedBy":
|
|
1324
1711
|
if (dialect.name === "postgres") {
|
|
1712
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1325
1713
|
const left = isJsonExpression(ast.left)
|
|
1326
1714
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1327
1715
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1336,6 +1724,7 @@ export const renderExpression = (
|
|
|
1336
1724
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1337
1725
|
case "overlaps":
|
|
1338
1726
|
if (dialect.name === "postgres") {
|
|
1727
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1339
1728
|
const left = isJsonExpression(ast.left)
|
|
1340
1729
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1341
1730
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1365,14 +1754,26 @@ export const renderExpression = (
|
|
|
1365
1754
|
case "min":
|
|
1366
1755
|
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1367
1756
|
case "and":
|
|
1757
|
+
if (ast.values.length === 0) {
|
|
1758
|
+
throw new Error("and(...) requires at least one predicate")
|
|
1759
|
+
}
|
|
1368
1760
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1369
1761
|
case "or":
|
|
1762
|
+
if (ast.values.length === 0) {
|
|
1763
|
+
throw new Error("or(...) requires at least one predicate")
|
|
1764
|
+
}
|
|
1370
1765
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1371
1766
|
case "coalesce":
|
|
1372
1767
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1373
1768
|
case "in":
|
|
1769
|
+
if (ast.values.length < 2) {
|
|
1770
|
+
throw new Error("in(...) requires at least one candidate value")
|
|
1771
|
+
}
|
|
1374
1772
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1375
1773
|
case "notIn":
|
|
1774
|
+
if (ast.values.length < 2) {
|
|
1775
|
+
throw new Error("notIn(...) requires at least one candidate value")
|
|
1776
|
+
}
|
|
1376
1777
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1377
1778
|
case "between":
|
|
1378
1779
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1383,35 +1784,15 @@ export const renderExpression = (
|
|
|
1383
1784
|
`when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
|
|
1384
1785
|
).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
|
|
1385
1786
|
case "exists":
|
|
1386
|
-
return `exists (${
|
|
1387
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1388
|
-
state,
|
|
1389
|
-
dialect
|
|
1390
|
-
).sql})`
|
|
1787
|
+
return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1391
1788
|
case "scalarSubquery":
|
|
1392
|
-
return `(${
|
|
1393
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1394
|
-
state,
|
|
1395
|
-
dialect
|
|
1396
|
-
).sql})`
|
|
1789
|
+
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1397
1790
|
case "inSubquery":
|
|
1398
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${
|
|
1399
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1400
|
-
state,
|
|
1401
|
-
dialect
|
|
1402
|
-
).sql}))`
|
|
1791
|
+
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1403
1792
|
case "comparisonAny":
|
|
1404
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${
|
|
1405
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1406
|
-
state,
|
|
1407
|
-
dialect
|
|
1408
|
-
).sql}))`
|
|
1793
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1409
1794
|
case "comparisonAll":
|
|
1410
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${
|
|
1411
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1412
|
-
state,
|
|
1413
|
-
dialect
|
|
1414
|
-
).sql}))`
|
|
1795
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1415
1796
|
case "window": {
|
|
1416
1797
|
if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
|
|
1417
1798
|
break
|