effect-qb 0.16.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 +1661 -591
- package/dist/postgres/metadata.js +1930 -135
- package/dist/postgres.js +7808 -6718
- package/dist/sqlite.js +8360 -0
- package/package.json +6 -1
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +2 -0
- 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 +47 -9
- 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/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +27 -3
- package/src/internal/query.ts +252 -30
- package/src/internal/renderer.ts +35 -2
- package/src/internal/runtime/driver-value-mapping.ts +58 -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/table-options.ts +108 -1
- package/src/internal/table.ts +87 -29
- package/src/mysql/column.ts +18 -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/internal/dsl.ts +736 -218
- package/src/mysql/internal/renderer.ts +2 -1
- package/src/mysql/internal/sql-expression-renderer.ts +486 -130
- package/src/mysql/query.ts +9 -2
- package/src/mysql/table.ts +38 -12
- package/src/postgres/column.ts +4 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +48 -5
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/internal/dsl.ts +683 -240
- package/src/postgres/internal/renderer.ts +2 -1
- 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 +420 -91
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -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/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,19 +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"
|
|
8
13
|
import {
|
|
9
14
|
renderJsonSelectSql,
|
|
10
15
|
renderSelectSql,
|
|
11
16
|
toDriverValue
|
|
12
17
|
} from "../../internal/runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../../internal/runtime/normalize.js"
|
|
13
19
|
import { flattenSelection, type Projection } from "../../internal/projections.js"
|
|
14
20
|
import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
|
|
15
21
|
import * as SchemaExpression from "../../internal/schema-expression.js"
|
|
16
|
-
import type
|
|
22
|
+
import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
|
|
17
23
|
|
|
18
24
|
const renderDbType = (
|
|
19
25
|
dialect: SqlDialect,
|
|
@@ -51,14 +57,59 @@ const renderCastType = (
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
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
|
+
|
|
54
100
|
const renderDdlExpression = (
|
|
55
101
|
expression: DdlExpressionLike,
|
|
56
102
|
state: RenderState,
|
|
57
103
|
dialect: SqlDialect
|
|
58
|
-
): string =>
|
|
59
|
-
SchemaExpression.isSchemaExpression(expression)
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
}
|
|
62
113
|
|
|
63
114
|
const renderColumnDefinition = (
|
|
64
115
|
dialect: SqlDialect,
|
|
@@ -105,17 +156,19 @@ const renderCreateTableSql = (
|
|
|
105
156
|
case "foreignKey": {
|
|
106
157
|
const reference = option.references()
|
|
107
158
|
definitions.push(
|
|
108
|
-
`${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" : ""}` : ""}`
|
|
109
160
|
)
|
|
110
161
|
break
|
|
111
162
|
}
|
|
112
163
|
case "check":
|
|
113
164
|
definitions.push(
|
|
114
|
-
`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" : ""}`
|
|
115
166
|
)
|
|
116
167
|
break
|
|
117
168
|
case "index":
|
|
118
169
|
break
|
|
170
|
+
default:
|
|
171
|
+
throw new Error("Unsupported table option kind")
|
|
119
172
|
}
|
|
120
173
|
}
|
|
121
174
|
return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
@@ -136,20 +189,74 @@ const renderDropIndexSql = (
|
|
|
136
189
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
137
190
|
state: RenderState,
|
|
138
191
|
dialect: SqlDialect
|
|
139
|
-
): string =>
|
|
140
|
-
dialect.name === "postgres"
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
206
|
|
|
144
207
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
145
208
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
146
209
|
|
|
147
|
-
const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
148
|
-
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
|
+
}
|
|
149
220
|
|
|
150
221
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
151
222
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
152
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
|
+
|
|
153
260
|
const unsupportedJsonFeature = (
|
|
154
261
|
dialect: SqlDialect,
|
|
155
262
|
feature: string
|
|
@@ -207,19 +314,19 @@ const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
|
207
314
|
node.newValue ?? node.insert ?? node.right
|
|
208
315
|
|
|
209
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)}`
|
|
210
321
|
if (typeof segment === "string") {
|
|
211
|
-
return
|
|
212
|
-
? `.${segment}`
|
|
213
|
-
: `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
322
|
+
return renderKey(segment)
|
|
214
323
|
}
|
|
215
324
|
if (typeof segment === "number") {
|
|
216
325
|
return `[${segment}]`
|
|
217
326
|
}
|
|
218
327
|
switch (segment.kind) {
|
|
219
328
|
case "key":
|
|
220
|
-
return
|
|
221
|
-
? `.${segment.key}`
|
|
222
|
-
: `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
329
|
+
return renderKey(segment.key)
|
|
223
330
|
case "index":
|
|
224
331
|
return `[${segment.index}]`
|
|
225
332
|
case "wildcard":
|
|
@@ -284,7 +391,7 @@ const renderPostgresJsonAccessStep = (
|
|
|
284
391
|
case "key":
|
|
285
392
|
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.key, state)}`
|
|
286
393
|
case "index":
|
|
287
|
-
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(
|
|
394
|
+
return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.index, state)}`
|
|
288
395
|
default:
|
|
289
396
|
throw new Error("Postgres exact JSON access requires key/index segments")
|
|
290
397
|
}
|
|
@@ -338,14 +445,26 @@ const encodeArrayValues = (
|
|
|
338
445
|
state: RenderState,
|
|
339
446
|
dialect: SqlDialect
|
|
340
447
|
): readonly unknown[] =>
|
|
341
|
-
values.map((value) =>
|
|
342
|
-
|
|
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, {
|
|
343
461
|
dialect: dialect.name,
|
|
344
462
|
valueMappings: state.valueMappings,
|
|
345
463
|
dbType: column.metadata.dbType,
|
|
346
464
|
runtimeSchema: column.schema,
|
|
347
465
|
driverValueMapping: column.metadata.driverValueMapping
|
|
348
|
-
})
|
|
466
|
+
})
|
|
467
|
+
})
|
|
349
468
|
|
|
350
469
|
const renderPostgresJsonKind = (
|
|
351
470
|
value: Expression.Any
|
|
@@ -588,7 +707,7 @@ const renderJsonExpression = (
|
|
|
588
707
|
const baseSql = renderExpression(base, state, dialect)
|
|
589
708
|
const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
|
|
590
709
|
const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
|
|
591
|
-
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)`
|
|
592
711
|
}
|
|
593
712
|
if (dialect.name === "mysql") {
|
|
594
713
|
return `json_keys(${renderExpression(base, state, dialect)})`
|
|
@@ -615,7 +734,7 @@ const renderJsonExpression = (
|
|
|
615
734
|
const segment = segments[0]!
|
|
616
735
|
return `(${baseSql} - ${segment.kind === "key"
|
|
617
736
|
? dialect.renderLiteral(segment.key, state)
|
|
618
|
-
: dialect.renderLiteral(
|
|
737
|
+
: dialect.renderLiteral(segment.index, state)})`
|
|
619
738
|
}
|
|
620
739
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
621
740
|
}
|
|
@@ -739,6 +858,15 @@ const renderDeleteTargets = (
|
|
|
739
858
|
dialect: SqlDialect
|
|
740
859
|
): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
|
|
741
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
|
+
|
|
742
870
|
const renderMysqlMutationLock = (
|
|
743
871
|
lock: QueryAst.LockClause | undefined,
|
|
744
872
|
statement: "update" | "delete"
|
|
@@ -765,8 +893,9 @@ const renderTransactionClause = (
|
|
|
765
893
|
switch (clause.kind) {
|
|
766
894
|
case "transaction": {
|
|
767
895
|
const modes: string[] = []
|
|
768
|
-
|
|
769
|
-
|
|
896
|
+
const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
|
|
897
|
+
if (isolationLevel) {
|
|
898
|
+
modes.push(isolationLevel)
|
|
770
899
|
}
|
|
771
900
|
if (clause.readOnly === true) {
|
|
772
901
|
modes.push("read only")
|
|
@@ -786,7 +915,7 @@ const renderTransactionClause = (
|
|
|
786
915
|
case "releaseSavepoint":
|
|
787
916
|
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
788
917
|
}
|
|
789
|
-
|
|
918
|
+
throw new Error("Unsupported transaction statement kind")
|
|
790
919
|
}
|
|
791
920
|
|
|
792
921
|
const renderSelectionList = (
|
|
@@ -799,6 +928,9 @@ const renderSelectionList = (
|
|
|
799
928
|
validateAggregationSelection(selection as SelectionValue, [])
|
|
800
929
|
}
|
|
801
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
|
+
}
|
|
802
934
|
const projections = selectionProjections(selection)
|
|
803
935
|
const sql = flattened.map(({ expression, alias }) =>
|
|
804
936
|
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
@@ -808,10 +940,105 @@ const renderSelectionList = (
|
|
|
808
940
|
}
|
|
809
941
|
}
|
|
810
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
|
+
|
|
811
1037
|
export const renderQueryAst = (
|
|
812
1038
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
813
1039
|
state: RenderState,
|
|
814
|
-
dialect: SqlDialect
|
|
1040
|
+
dialect: SqlDialect,
|
|
1041
|
+
options: { readonly emitCtes?: boolean } = {}
|
|
815
1042
|
): RenderedQueryAst => {
|
|
816
1043
|
let sql = ""
|
|
817
1044
|
let projections: readonly Projection[] = []
|
|
@@ -821,10 +1048,11 @@ export const renderQueryAst = (
|
|
|
821
1048
|
validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
|
|
822
1049
|
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
823
1050
|
projections = rendered.projections
|
|
1051
|
+
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
824
1052
|
const clauses = [
|
|
825
1053
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
826
|
-
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})
|
|
827
|
-
: `select${ast.distinct ? " distinct" : ""}
|
|
1054
|
+
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
|
|
1055
|
+
: `select${ast.distinct ? " distinct" : ""}${selectList}`
|
|
828
1056
|
]
|
|
829
1057
|
if (ast.from) {
|
|
830
1058
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
@@ -856,8 +1084,11 @@ export const renderQueryAst = (
|
|
|
856
1084
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
857
1085
|
}
|
|
858
1086
|
if (ast.lock) {
|
|
1087
|
+
if (ast.lock.nowait && ast.lock.skipLocked) {
|
|
1088
|
+
throw new Error("lock(...) cannot specify both nowait and skipLocked")
|
|
1089
|
+
}
|
|
859
1090
|
clauses.push(
|
|
860
|
-
`${ast.lock.mode
|
|
1091
|
+
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
861
1092
|
)
|
|
862
1093
|
}
|
|
863
1094
|
sql = clauses.join(" ")
|
|
@@ -865,6 +1096,7 @@ export const renderQueryAst = (
|
|
|
865
1096
|
}
|
|
866
1097
|
case "set": {
|
|
867
1098
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1099
|
+
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
868
1100
|
const base = renderQueryAst(
|
|
869
1101
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
870
1102
|
Record<string, unknown>,
|
|
@@ -875,6 +1107,7 @@ export const renderQueryAst = (
|
|
|
875
1107
|
dialect
|
|
876
1108
|
)
|
|
877
1109
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1110
|
+
assertMatchingSetProjections(projections, base.projections)
|
|
878
1111
|
sql = [
|
|
879
1112
|
`(${base.sql})`,
|
|
880
1113
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -887,6 +1120,7 @@ export const renderQueryAst = (
|
|
|
887
1120
|
state,
|
|
888
1121
|
dialect
|
|
889
1122
|
)
|
|
1123
|
+
assertMatchingSetProjections(projections, rendered.projections)
|
|
890
1124
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
891
1125
|
})
|
|
892
1126
|
].join(" ")
|
|
@@ -894,19 +1128,26 @@ export const renderQueryAst = (
|
|
|
894
1128
|
}
|
|
895
1129
|
case "insert": {
|
|
896
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)
|
|
897
1136
|
const targetSource = insertAst.into!
|
|
898
1137
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1138
|
+
const insertSource = expectInsertSourceKind(insertAst.insertSource)
|
|
1139
|
+
const conflict = expectConflictClause(insertAst.conflict)
|
|
899
1140
|
sql = `insert into ${target}`
|
|
900
|
-
if (
|
|
901
|
-
const columns =
|
|
902
|
-
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) =>
|
|
903
1144
|
`(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
904
1145
|
).join(", ")
|
|
905
1146
|
sql += ` (${columns}) values ${rows}`
|
|
906
|
-
} else if (
|
|
907
|
-
const columns =
|
|
1147
|
+
} else if (insertSource?.kind === "query") {
|
|
1148
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
908
1149
|
const renderedQuery = renderQueryAst(
|
|
909
|
-
Query.getAst(
|
|
1150
|
+
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
910
1151
|
Record<string, unknown>,
|
|
911
1152
|
any,
|
|
912
1153
|
QueryAst.QueryStatement
|
|
@@ -915,20 +1156,19 @@ export const renderQueryAst = (
|
|
|
915
1156
|
dialect
|
|
916
1157
|
)
|
|
917
1158
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
918
|
-
} else if (
|
|
919
|
-
const
|
|
920
|
-
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(", ")
|
|
921
1161
|
if (dialect.name === "postgres") {
|
|
922
1162
|
const table = targetSource.source as Table.AnyTable
|
|
923
1163
|
const fields = table[Table.TypeId].fields
|
|
924
|
-
const rendered =
|
|
1164
|
+
const rendered = insertSource.values.map((entry) =>
|
|
925
1165
|
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
926
1166
|
).join(", ")
|
|
927
1167
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
928
1168
|
} else {
|
|
929
|
-
const rowCount =
|
|
1169
|
+
const rowCount = insertSource.values[0]?.values.length ?? 0
|
|
930
1170
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
931
|
-
`(${
|
|
1171
|
+
`(${insertSource.values.map((entry) =>
|
|
932
1172
|
dialect.renderLiteral(
|
|
933
1173
|
entry.values[index],
|
|
934
1174
|
state,
|
|
@@ -947,21 +1187,24 @@ export const renderQueryAst = (
|
|
|
947
1187
|
sql += " default values"
|
|
948
1188
|
}
|
|
949
1189
|
}
|
|
950
|
-
if (
|
|
951
|
-
|
|
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) =>
|
|
952
1195
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
953
1196
|
).join(", ")
|
|
954
1197
|
if (dialect.name === "postgres") {
|
|
955
|
-
const targetSql =
|
|
956
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
957
|
-
:
|
|
958
|
-
? ` 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)}` : ""}`
|
|
959
1202
|
: " on conflict"
|
|
960
1203
|
sql += targetSql
|
|
961
|
-
sql +=
|
|
1204
|
+
sql += conflict.action === "doNothing"
|
|
962
1205
|
? " do nothing"
|
|
963
|
-
: ` do update set ${updateValues}${
|
|
964
|
-
} else if (
|
|
1206
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
|
|
1207
|
+
} else if (conflict.action === "doNothing") {
|
|
965
1208
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
966
1209
|
} else {
|
|
967
1210
|
sql += ` on duplicate key update ${updateValues}`
|
|
@@ -976,10 +1219,29 @@ export const renderQueryAst = (
|
|
|
976
1219
|
}
|
|
977
1220
|
case "update": {
|
|
978
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
|
+
}
|
|
979
1238
|
const targetSource = updateAst.target!
|
|
980
1239
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
981
1240
|
const targets = updateAst.targets ?? [targetSource]
|
|
982
1241
|
const fromSources = updateAst.fromSources ?? []
|
|
1242
|
+
if ((updateAst.set ?? []).length === 0) {
|
|
1243
|
+
throw new Error("update statements require at least one assignment")
|
|
1244
|
+
}
|
|
983
1245
|
const assignments = updateAst.set!.map((entry) =>
|
|
984
1246
|
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
985
1247
|
if (dialect.name === "mysql") {
|
|
@@ -1029,6 +1291,22 @@ export const renderQueryAst = (
|
|
|
1029
1291
|
}
|
|
1030
1292
|
case "delete": {
|
|
1031
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
|
+
}
|
|
1032
1310
|
const targetSource = deleteAst.target!
|
|
1033
1311
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1034
1312
|
const targets = deleteAst.targets ?? [targetSource]
|
|
@@ -1075,12 +1353,14 @@ export const renderQueryAst = (
|
|
|
1075
1353
|
}
|
|
1076
1354
|
case "truncate": {
|
|
1077
1355
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1356
|
+
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1357
|
+
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1078
1358
|
const targetSource = truncateAst.target!
|
|
1079
1359
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1080
|
-
if (
|
|
1360
|
+
if (truncate.restartIdentity) {
|
|
1081
1361
|
sql += " restart identity"
|
|
1082
1362
|
}
|
|
1083
|
-
if (
|
|
1363
|
+
if (truncate.cascade) {
|
|
1084
1364
|
sql += " cascade"
|
|
1085
1365
|
}
|
|
1086
1366
|
break
|
|
@@ -1093,8 +1373,18 @@ export const renderQueryAst = (
|
|
|
1093
1373
|
const targetSource = mergeAst.target!
|
|
1094
1374
|
const usingSource = mergeAst.using!
|
|
1095
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
|
+
}
|
|
1096
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)}`
|
|
1097
1386
|
if (merge.whenMatched) {
|
|
1387
|
+
assertMergeActionKind(merge.whenMatched.kind, ["update", "delete"])
|
|
1098
1388
|
sql += " when matched"
|
|
1099
1389
|
if (merge.whenMatched.predicate) {
|
|
1100
1390
|
sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
|
|
@@ -1102,16 +1392,23 @@ export const renderQueryAst = (
|
|
|
1102
1392
|
if (merge.whenMatched.kind === "delete") {
|
|
1103
1393
|
sql += " then delete"
|
|
1104
1394
|
} else {
|
|
1395
|
+
if (merge.whenMatched.values.length === 0) {
|
|
1396
|
+
throw new Error("merge update actions require at least one assignment")
|
|
1397
|
+
}
|
|
1105
1398
|
sql += ` then update set ${merge.whenMatched.values.map((entry) =>
|
|
1106
1399
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
1107
1400
|
).join(", ")}`
|
|
1108
1401
|
}
|
|
1109
1402
|
}
|
|
1110
1403
|
if (merge.whenNotMatched) {
|
|
1404
|
+
assertMergeActionKind(merge.whenNotMatched.kind, ["insert"])
|
|
1111
1405
|
sql += " when not matched"
|
|
1112
1406
|
if (merge.whenNotMatched.predicate) {
|
|
1113
1407
|
sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
|
|
1114
1408
|
}
|
|
1409
|
+
if (merge.whenNotMatched.values.length === 0) {
|
|
1410
|
+
throw new Error("merge insert actions require at least one value")
|
|
1411
|
+
}
|
|
1115
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(", ")})`
|
|
1116
1413
|
}
|
|
1117
1414
|
break
|
|
@@ -1122,25 +1419,30 @@ export const renderQueryAst = (
|
|
|
1122
1419
|
case "savepoint":
|
|
1123
1420
|
case "rollbackTo":
|
|
1124
1421
|
case "releaseSavepoint": {
|
|
1422
|
+
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1125
1423
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1126
1424
|
break
|
|
1127
1425
|
}
|
|
1128
1426
|
case "createTable": {
|
|
1129
1427
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1130
|
-
|
|
1428
|
+
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1429
|
+
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1430
|
+
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1131
1431
|
break
|
|
1132
1432
|
}
|
|
1133
1433
|
case "dropTable": {
|
|
1134
1434
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1135
|
-
|
|
1136
|
-
|
|
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)}`
|
|
1137
1438
|
break
|
|
1138
1439
|
}
|
|
1139
1440
|
case "createIndex": {
|
|
1140
1441
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1442
|
+
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1141
1443
|
sql = renderCreateIndexSql(
|
|
1142
1444
|
createIndexAst.target!,
|
|
1143
|
-
createIndexAst.ddl
|
|
1445
|
+
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
1144
1446
|
state,
|
|
1145
1447
|
dialect
|
|
1146
1448
|
)
|
|
@@ -1148,17 +1450,20 @@ export const renderQueryAst = (
|
|
|
1148
1450
|
}
|
|
1149
1451
|
case "dropIndex": {
|
|
1150
1452
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1453
|
+
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1151
1454
|
sql = renderDropIndexSql(
|
|
1152
1455
|
dropIndexAst.target!,
|
|
1153
|
-
dropIndexAst.ddl
|
|
1456
|
+
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
1154
1457
|
state,
|
|
1155
1458
|
dialect
|
|
1156
1459
|
)
|
|
1157
1460
|
break
|
|
1158
1461
|
}
|
|
1462
|
+
default:
|
|
1463
|
+
throw new Error("Unsupported query statement kind")
|
|
1159
1464
|
}
|
|
1160
1465
|
|
|
1161
|
-
if (state.ctes.length === 0) {
|
|
1466
|
+
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
1162
1467
|
return {
|
|
1163
1468
|
sql,
|
|
1164
1469
|
projections
|
|
@@ -1206,9 +1511,19 @@ const renderSourceReference = (
|
|
|
1206
1511
|
readonly plan: Query.Plan.Any
|
|
1207
1512
|
readonly recursive?: boolean
|
|
1208
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
|
+
}
|
|
1209
1518
|
if (!state.cteNames.has(cte.name)) {
|
|
1210
1519
|
state.cteNames.add(cte.name)
|
|
1211
|
-
|
|
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
|
+
)
|
|
1212
1527
|
state.ctes.push({
|
|
1213
1528
|
name: cte.name,
|
|
1214
1529
|
sql: rendered.sql,
|
|
@@ -1225,14 +1540,14 @@ const renderSourceReference = (
|
|
|
1225
1540
|
if (!state.cteNames.has(derived.name)) {
|
|
1226
1541
|
// derived tables are inlined, so no CTE registration is needed
|
|
1227
1542
|
}
|
|
1228
|
-
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)}`
|
|
1229
1544
|
}
|
|
1230
1545
|
if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
|
|
1231
1546
|
const lateral = source as unknown as {
|
|
1232
1547
|
readonly name: string
|
|
1233
1548
|
readonly plan: Query.Plan.Any
|
|
1234
1549
|
}
|
|
1235
|
-
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)}`
|
|
1236
1551
|
}
|
|
1237
1552
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
1238
1553
|
const values = source as unknown as {
|
|
@@ -1267,6 +1582,22 @@ const renderSourceReference = (
|
|
|
1267
1582
|
return dialect.renderTableReference(tableName, baseTableName, schemaName)
|
|
1268
1583
|
}
|
|
1269
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
|
+
|
|
1270
1601
|
/**
|
|
1271
1602
|
* Renders a scalar expression AST into SQL text.
|
|
1272
1603
|
*
|
|
@@ -1301,10 +1632,13 @@ export const renderExpression = (
|
|
|
1301
1632
|
: ">="
|
|
1302
1633
|
switch (ast.kind) {
|
|
1303
1634
|
case "column":
|
|
1304
|
-
return ast.tableName.length === 0
|
|
1635
|
+
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1305
1636
|
? dialect.quoteIdentifier(ast.columnName)
|
|
1306
1637
|
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1307
1638
|
case "literal":
|
|
1639
|
+
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1640
|
+
throw new Error("Expected a finite numeric value")
|
|
1641
|
+
}
|
|
1308
1642
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1309
1643
|
case "excluded":
|
|
1310
1644
|
return dialect.name === "mysql"
|
|
@@ -1360,6 +1694,7 @@ export const renderExpression = (
|
|
|
1360
1694
|
: `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
|
|
1361
1695
|
case "contains":
|
|
1362
1696
|
if (dialect.name === "postgres") {
|
|
1697
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1363
1698
|
const left = isJsonExpression(ast.left)
|
|
1364
1699
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1365
1700
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1374,6 +1709,7 @@ export const renderExpression = (
|
|
|
1374
1709
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1375
1710
|
case "containedBy":
|
|
1376
1711
|
if (dialect.name === "postgres") {
|
|
1712
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1377
1713
|
const left = isJsonExpression(ast.left)
|
|
1378
1714
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1379
1715
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1388,6 +1724,7 @@ export const renderExpression = (
|
|
|
1388
1724
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1389
1725
|
case "overlaps":
|
|
1390
1726
|
if (dialect.name === "postgres") {
|
|
1727
|
+
assertCompatiblePostgresRangeOperands(ast.left, ast.right)
|
|
1391
1728
|
const left = isJsonExpression(ast.left)
|
|
1392
1729
|
? renderPostgresJsonValue(ast.left, state, dialect)
|
|
1393
1730
|
: renderExpression(ast.left, state, dialect)
|
|
@@ -1417,14 +1754,26 @@ export const renderExpression = (
|
|
|
1417
1754
|
case "min":
|
|
1418
1755
|
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1419
1756
|
case "and":
|
|
1757
|
+
if (ast.values.length === 0) {
|
|
1758
|
+
throw new Error("and(...) requires at least one predicate")
|
|
1759
|
+
}
|
|
1420
1760
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1421
1761
|
case "or":
|
|
1762
|
+
if (ast.values.length === 0) {
|
|
1763
|
+
throw new Error("or(...) requires at least one predicate")
|
|
1764
|
+
}
|
|
1422
1765
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1423
1766
|
case "coalesce":
|
|
1424
1767
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1425
1768
|
case "in":
|
|
1769
|
+
if (ast.values.length < 2) {
|
|
1770
|
+
throw new Error("in(...) requires at least one candidate value")
|
|
1771
|
+
}
|
|
1426
1772
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1427
1773
|
case "notIn":
|
|
1774
|
+
if (ast.values.length < 2) {
|
|
1775
|
+
throw new Error("notIn(...) requires at least one candidate value")
|
|
1776
|
+
}
|
|
1428
1777
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1429
1778
|
case "between":
|
|
1430
1779
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1435,35 +1784,15 @@ export const renderExpression = (
|
|
|
1435
1784
|
`when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
|
|
1436
1785
|
).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
|
|
1437
1786
|
case "exists":
|
|
1438
|
-
return `exists (${
|
|
1439
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1440
|
-
state,
|
|
1441
|
-
dialect
|
|
1442
|
-
).sql})`
|
|
1787
|
+
return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1443
1788
|
case "scalarSubquery":
|
|
1444
|
-
return `(${
|
|
1445
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1446
|
-
state,
|
|
1447
|
-
dialect
|
|
1448
|
-
).sql})`
|
|
1789
|
+
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1449
1790
|
case "inSubquery":
|
|
1450
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${
|
|
1451
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1452
|
-
state,
|
|
1453
|
-
dialect
|
|
1454
|
-
).sql}))`
|
|
1791
|
+
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1455
1792
|
case "comparisonAny":
|
|
1456
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${
|
|
1457
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1458
|
-
state,
|
|
1459
|
-
dialect
|
|
1460
|
-
).sql}))`
|
|
1793
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1461
1794
|
case "comparisonAll":
|
|
1462
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${
|
|
1463
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1464
|
-
state,
|
|
1465
|
-
dialect
|
|
1466
|
-
).sql}))`
|
|
1795
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1467
1796
|
case "window": {
|
|
1468
1797
|
if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
|
|
1469
1798
|
break
|