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 { renderMysqlMutationLockMode, 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 renderMysqlDdlString = (value: string): string =>
|
|
61
|
+
`'${value.replaceAll("'", "''")}'`
|
|
62
|
+
|
|
63
|
+
const renderMysqlDdlBytes = (value: Uint8Array): string =>
|
|
64
|
+
`x'${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}'`
|
|
65
|
+
|
|
66
|
+
const renderMysqlDdlLiteral = (
|
|
67
|
+
value: unknown,
|
|
68
|
+
state: RenderState,
|
|
69
|
+
context: RenderValueContext = {}
|
|
70
|
+
): string => {
|
|
71
|
+
const driverValue = toDriverValue(value, {
|
|
72
|
+
dialect: "mysql",
|
|
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 renderMysqlDdlString(driverValue)
|
|
91
|
+
case "object":
|
|
92
|
+
if (driverValue instanceof Uint8Array) {
|
|
93
|
+
return renderMysqlDdlBytes(driverValue)
|
|
94
|
+
}
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Unsupported mysql 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: renderMysqlDdlLiteral
|
|
111
|
+
})
|
|
112
|
+
}
|
|
62
113
|
|
|
63
114
|
const renderMysqlMutationLimit = (
|
|
64
115
|
expression: Expression.Any,
|
|
@@ -114,22 +165,27 @@ const renderCreateTableSql = (
|
|
|
114
165
|
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}primary key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
115
166
|
break
|
|
116
167
|
case "unique":
|
|
168
|
+
if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
|
|
169
|
+
throw new Error("Unsupported mysql unique constraint options")
|
|
170
|
+
}
|
|
117
171
|
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
118
172
|
break
|
|
119
173
|
case "foreignKey": {
|
|
120
174
|
const reference = option.references()
|
|
121
175
|
definitions.push(
|
|
122
|
-
`${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
|
|
176
|
+
`${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" : ""}` : ""}`
|
|
123
177
|
)
|
|
124
178
|
break
|
|
125
179
|
}
|
|
126
180
|
case "check":
|
|
127
181
|
definitions.push(
|
|
128
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
182
|
+
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
129
183
|
)
|
|
130
184
|
break
|
|
131
185
|
case "index":
|
|
132
186
|
break
|
|
187
|
+
default:
|
|
188
|
+
throw new Error("Unsupported table option kind")
|
|
133
189
|
}
|
|
134
190
|
}
|
|
135
191
|
return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
@@ -141,6 +197,9 @@ const renderCreateIndexSql = (
|
|
|
141
197
|
state: RenderState,
|
|
142
198
|
dialect: SqlDialect
|
|
143
199
|
): string => {
|
|
200
|
+
if (ddl.ifNotExists) {
|
|
201
|
+
throw new Error("Unsupported mysql create index options")
|
|
202
|
+
}
|
|
144
203
|
const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
|
|
145
204
|
return `create${ddl.unique ? " unique" : ""} index${maybeIfNotExists} ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${ddl.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`
|
|
146
205
|
}
|
|
@@ -150,16 +209,28 @@ const renderDropIndexSql = (
|
|
|
150
209
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
151
210
|
state: RenderState,
|
|
152
211
|
dialect: SqlDialect
|
|
153
|
-
): string =>
|
|
154
|
-
|
|
212
|
+
): string => {
|
|
213
|
+
if (ddl.ifExists) {
|
|
214
|
+
throw new Error("Unsupported mysql drop index options")
|
|
215
|
+
}
|
|
216
|
+
return dialect.name === "postgres"
|
|
155
217
|
? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
|
|
156
218
|
: `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
219
|
+
}
|
|
157
220
|
|
|
158
221
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
159
222
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
160
223
|
|
|
161
|
-
const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
162
|
-
dbType.kind === "jsonb" || dbType.kind === "json"
|
|
224
|
+
const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
|
|
225
|
+
if (dbType.kind === "jsonb" || dbType.kind === "json") {
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
if (!("variant" in dbType)) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
const variant = dbType.variant as string
|
|
232
|
+
return variant === "json" || variant === "jsonb"
|
|
233
|
+
}
|
|
163
234
|
|
|
164
235
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
165
236
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
@@ -221,19 +292,19 @@ const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
|
221
292
|
node.newValue ?? node.insert ?? node.right
|
|
222
293
|
|
|
223
294
|
const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
|
|
295
|
+
const renderKey = (value: string): string =>
|
|
296
|
+
/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
|
|
297
|
+
? `.${value}`
|
|
298
|
+
: `.${JSON.stringify(value)}`
|
|
224
299
|
if (typeof segment === "string") {
|
|
225
|
-
return
|
|
226
|
-
? `.${segment}`
|
|
227
|
-
: `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
300
|
+
return renderKey(segment)
|
|
228
301
|
}
|
|
229
302
|
if (typeof segment === "number") {
|
|
230
303
|
return `[${segment}]`
|
|
231
304
|
}
|
|
232
305
|
switch (segment.kind) {
|
|
233
306
|
case "key":
|
|
234
|
-
return
|
|
235
|
-
? `.${segment.key}`
|
|
236
|
-
: `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
307
|
+
return renderKey(segment.key)
|
|
237
308
|
case "index":
|
|
238
309
|
return `[${segment.index}]`
|
|
239
310
|
case "wildcard":
|
|
@@ -247,10 +318,32 @@ const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number):
|
|
|
247
318
|
}
|
|
248
319
|
}
|
|
249
320
|
|
|
250
|
-
const
|
|
321
|
+
const renderMySqlJsonIndex = (index: number): string =>
|
|
322
|
+
index >= 0 ? String(index) : index === -1 ? "last" : `last-${Math.abs(index) - 1}`
|
|
323
|
+
|
|
324
|
+
const renderMySqlJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
|
|
325
|
+
if (typeof segment === "number") {
|
|
326
|
+
return `[${renderMySqlJsonIndex(segment)}]`
|
|
327
|
+
}
|
|
328
|
+
if (typeof segment === "object" && segment !== null && segment.kind === "index") {
|
|
329
|
+
return `[${renderMySqlJsonIndex(segment.index)}]`
|
|
330
|
+
}
|
|
331
|
+
if (typeof segment === "object" && segment !== null && segment.kind === "slice") {
|
|
332
|
+
return `[${renderMySqlJsonIndex(segment.start ?? 0)} to ${segment.end === undefined ? "last" : renderMySqlJsonIndex(segment.end)}]`
|
|
333
|
+
}
|
|
334
|
+
if (typeof segment === "object" && segment !== null && segment.kind === "descend") {
|
|
335
|
+
return "**"
|
|
336
|
+
}
|
|
337
|
+
return renderJsonPathSegment(segment)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const renderJsonPathStringLiteral = (
|
|
341
|
+
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
342
|
+
renderSegment: (segment: JsonPath.AnySegment | string | number) => string = renderJsonPathSegment
|
|
343
|
+
): string => {
|
|
251
344
|
let path = "$"
|
|
252
345
|
for (const segment of segments) {
|
|
253
|
-
path +=
|
|
346
|
+
path += renderSegment(segment)
|
|
254
347
|
}
|
|
255
348
|
return path
|
|
256
349
|
}
|
|
@@ -259,7 +352,30 @@ const renderMySqlJsonPath = (
|
|
|
259
352
|
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
260
353
|
state: RenderState,
|
|
261
354
|
dialect: SqlDialect
|
|
262
|
-
): string => dialect.renderLiteral(renderJsonPathStringLiteral(segments), state)
|
|
355
|
+
): string => dialect.renderLiteral(renderJsonPathStringLiteral(segments, renderMySqlJsonPathSegment), state)
|
|
356
|
+
|
|
357
|
+
const isJsonArrayIndexSegment = (segment: JsonPath.AnySegment | string | number | undefined): boolean =>
|
|
358
|
+
typeof segment === "number" || (typeof segment === "object" && segment !== null && segment.kind === "index")
|
|
359
|
+
|
|
360
|
+
const renderMySqlJsonInsertPath = (
|
|
361
|
+
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
362
|
+
insertAfter: boolean,
|
|
363
|
+
state: RenderState,
|
|
364
|
+
dialect: SqlDialect
|
|
365
|
+
): string => {
|
|
366
|
+
if (!insertAfter || segments.length === 0) {
|
|
367
|
+
return renderMySqlJsonPath(segments, state, dialect)
|
|
368
|
+
}
|
|
369
|
+
const last = segments[segments.length - 1]
|
|
370
|
+
const nextSegments = segments.slice(0, -1)
|
|
371
|
+
if (typeof last === "number") {
|
|
372
|
+
return renderMySqlJsonPath([...nextSegments, last + 1], state, dialect)
|
|
373
|
+
}
|
|
374
|
+
if (typeof last === "object" && last !== null && last.kind === "index") {
|
|
375
|
+
return renderMySqlJsonPath([...nextSegments, { ...last, index: last.index + 1 }], state, dialect)
|
|
376
|
+
}
|
|
377
|
+
return renderMySqlJsonPath(segments, state, dialect)
|
|
378
|
+
}
|
|
263
379
|
|
|
264
380
|
const renderPostgresJsonPathArray = (
|
|
265
381
|
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
@@ -330,15 +446,36 @@ const expressionDriverContext = (
|
|
|
330
446
|
driverValueMapping: expression[Expression.TypeId].driverValueMapping
|
|
331
447
|
})
|
|
332
448
|
|
|
449
|
+
const renderMySqlStructuredJsonLiteral = (
|
|
450
|
+
expression: Expression.Any,
|
|
451
|
+
state: RenderState
|
|
452
|
+
): string | undefined => {
|
|
453
|
+
const ast = (expression as Expression.Any & {
|
|
454
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
455
|
+
})[ExpressionAst.TypeId]
|
|
456
|
+
if (ast.kind !== "literal" || ast.value === null || typeof ast.value !== "object") {
|
|
457
|
+
return undefined
|
|
458
|
+
}
|
|
459
|
+
state.params.push(JSON.stringify(ast.value))
|
|
460
|
+
return "cast(? as json)"
|
|
461
|
+
}
|
|
462
|
+
|
|
333
463
|
const renderJsonInputExpression = (
|
|
334
464
|
expression: Expression.Any,
|
|
335
465
|
state: RenderState,
|
|
336
466
|
dialect: SqlDialect
|
|
337
|
-
): string =>
|
|
338
|
-
|
|
467
|
+
): string => {
|
|
468
|
+
if (dialect.name === "mysql") {
|
|
469
|
+
const jsonLiteral = renderMySqlStructuredJsonLiteral(expression, state)
|
|
470
|
+
if (jsonLiteral !== undefined) {
|
|
471
|
+
return jsonLiteral
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return renderJsonSelectSql(
|
|
339
475
|
renderExpression(expression, state, dialect),
|
|
340
476
|
expressionDriverContext(expression, state, dialect)
|
|
341
477
|
)
|
|
478
|
+
}
|
|
342
479
|
|
|
343
480
|
const encodeArrayValues = (
|
|
344
481
|
values: readonly unknown[],
|
|
@@ -346,14 +483,26 @@ const encodeArrayValues = (
|
|
|
346
483
|
state: RenderState,
|
|
347
484
|
dialect: SqlDialect
|
|
348
485
|
): readonly unknown[] =>
|
|
349
|
-
values.map((value) =>
|
|
350
|
-
|
|
486
|
+
values.map((value) => {
|
|
487
|
+
if (value === null && column.metadata.nullable) {
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
const runtimeSchemaAccepts = column.schema !== undefined &&
|
|
491
|
+
(Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
|
|
492
|
+
const normalizedValue = runtimeSchemaAccepts
|
|
493
|
+
? value
|
|
494
|
+
: normalizeDbValue(column.metadata.dbType, value)
|
|
495
|
+
const encodedValue = column.schema === undefined || runtimeSchemaAccepts
|
|
496
|
+
? normalizedValue
|
|
497
|
+
: (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
|
|
498
|
+
return toDriverValue(encodedValue, {
|
|
351
499
|
dialect: dialect.name,
|
|
352
500
|
valueMappings: state.valueMappings,
|
|
353
501
|
dbType: column.metadata.dbType,
|
|
354
502
|
runtimeSchema: column.schema,
|
|
355
503
|
driverValueMapping: column.metadata.driverValueMapping
|
|
356
|
-
})
|
|
504
|
+
})
|
|
505
|
+
})
|
|
357
506
|
|
|
358
507
|
const renderPostgresJsonKind = (
|
|
359
508
|
value: Expression.Any
|
|
@@ -365,7 +514,10 @@ const renderJsonOpaquePath = (
|
|
|
365
514
|
dialect: SqlDialect
|
|
366
515
|
): string => {
|
|
367
516
|
if (isJsonPathValue(value)) {
|
|
368
|
-
|
|
517
|
+
const renderSegment = dialect.name === "mysql"
|
|
518
|
+
? renderMySqlJsonPathSegment
|
|
519
|
+
: renderJsonPathSegment
|
|
520
|
+
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
|
|
369
521
|
}
|
|
370
522
|
if (typeof value === "string") {
|
|
371
523
|
return dialect.renderLiteral(value, state)
|
|
@@ -492,8 +644,9 @@ const renderJsonExpression = (
|
|
|
492
644
|
}
|
|
493
645
|
if (dialect.name === "mysql") {
|
|
494
646
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
647
|
+
const modeSql = dialect.renderLiteral(mode, state)
|
|
495
648
|
const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
|
|
496
|
-
return `json_contains_path(${baseSql}, ${
|
|
649
|
+
return `json_contains_path(${baseSql}, ${modeSql}, ${paths})`
|
|
497
650
|
}
|
|
498
651
|
return undefined
|
|
499
652
|
}
|
|
@@ -506,7 +659,7 @@ const renderJsonExpression = (
|
|
|
506
659
|
return `(${renderPostgresJsonValue(ast.left, state, dialect)} || ${renderPostgresJsonValue(ast.right, state, dialect)})`
|
|
507
660
|
}
|
|
508
661
|
if (dialect.name === "mysql") {
|
|
509
|
-
return `json_merge_preserve(${
|
|
662
|
+
return `json_merge_preserve(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
510
663
|
}
|
|
511
664
|
return undefined
|
|
512
665
|
}
|
|
@@ -547,7 +700,7 @@ const renderJsonExpression = (
|
|
|
547
700
|
return `to_json(${renderJsonInputExpression(base, state, dialect)})`
|
|
548
701
|
}
|
|
549
702
|
if (dialect.name === "mysql") {
|
|
550
|
-
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
703
|
+
return renderMySqlStructuredJsonLiteral(base, state) ?? `cast(${renderExpression(base, state, dialect)} as json)`
|
|
551
704
|
}
|
|
552
705
|
return undefined
|
|
553
706
|
case "jsonToJsonb":
|
|
@@ -558,7 +711,7 @@ const renderJsonExpression = (
|
|
|
558
711
|
return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
|
|
559
712
|
}
|
|
560
713
|
if (dialect.name === "mysql") {
|
|
561
|
-
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
714
|
+
return renderMySqlStructuredJsonLiteral(base, state) ?? `cast(${renderExpression(base, state, dialect)} as json)`
|
|
562
715
|
}
|
|
563
716
|
return undefined
|
|
564
717
|
case "jsonTypeOf":
|
|
@@ -628,9 +781,7 @@ const renderJsonExpression = (
|
|
|
628
781
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
629
782
|
}
|
|
630
783
|
if (dialect.name === "mysql") {
|
|
631
|
-
return `json_remove(${renderExpression(base, state, dialect)}, ${segments
|
|
632
|
-
renderMySqlJsonPath([segment], state, dialect)
|
|
633
|
-
).join(", ")})`
|
|
784
|
+
return `json_remove(${renderExpression(base, state, dialect)}, ${renderMySqlJsonPath(segments, state, dialect)})`
|
|
634
785
|
}
|
|
635
786
|
return undefined
|
|
636
787
|
}
|
|
@@ -654,8 +805,16 @@ const renderJsonExpression = (
|
|
|
654
805
|
return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
|
|
655
806
|
}
|
|
656
807
|
if (dialect.name === "mysql") {
|
|
657
|
-
const
|
|
658
|
-
|
|
808
|
+
const renderedBase = renderExpression(base, state, dialect)
|
|
809
|
+
if (kind === "jsonInsert" && isJsonArrayIndexSegment(segments[segments.length - 1])) {
|
|
810
|
+
const renderedPath = renderMySqlJsonInsertPath(segments, insertAfter, state, dialect)
|
|
811
|
+
const renderedValue = renderJsonInputExpression(nextValue, state, dialect)
|
|
812
|
+
return `json_array_insert(${renderedBase}, ${renderedPath}, ${renderedValue})`
|
|
813
|
+
}
|
|
814
|
+
const functionName = kind === "jsonInsert" ? "json_insert" : createMissing ? "json_set" : "json_replace"
|
|
815
|
+
const renderedPath = renderMySqlJsonPath(segments, state, dialect)
|
|
816
|
+
const renderedValue = renderJsonInputExpression(nextValue, state, dialect)
|
|
817
|
+
return `${functionName}(${renderedBase}, ${renderedPath}, ${renderedValue})`
|
|
659
818
|
}
|
|
660
819
|
return undefined
|
|
661
820
|
}
|
|
@@ -754,16 +913,7 @@ const renderMysqlMutationLock = (
|
|
|
754
913
|
if (!lock) {
|
|
755
914
|
return ""
|
|
756
915
|
}
|
|
757
|
-
|
|
758
|
-
case "lowPriority":
|
|
759
|
-
return " low_priority"
|
|
760
|
-
case "ignore":
|
|
761
|
-
return " ignore"
|
|
762
|
-
case "quick":
|
|
763
|
-
return statement === "delete" ? " quick" : ""
|
|
764
|
-
default:
|
|
765
|
-
return ""
|
|
766
|
-
}
|
|
916
|
+
return renderMysqlMutationLockMode(lock.mode, statement)
|
|
767
917
|
}
|
|
768
918
|
|
|
769
919
|
const renderTransactionClause = (
|
|
@@ -773,8 +923,9 @@ const renderTransactionClause = (
|
|
|
773
923
|
switch (clause.kind) {
|
|
774
924
|
case "transaction": {
|
|
775
925
|
const modes: string[] = []
|
|
776
|
-
|
|
777
|
-
|
|
926
|
+
const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
|
|
927
|
+
if (isolationLevel) {
|
|
928
|
+
modes.push(isolationLevel)
|
|
778
929
|
}
|
|
779
930
|
if (clause.readOnly === true) {
|
|
780
931
|
modes.push("read only")
|
|
@@ -794,7 +945,7 @@ const renderTransactionClause = (
|
|
|
794
945
|
case "releaseSavepoint":
|
|
795
946
|
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
796
947
|
}
|
|
797
|
-
|
|
948
|
+
throw new Error("Unsupported transaction statement kind")
|
|
798
949
|
}
|
|
799
950
|
|
|
800
951
|
const renderSelectionList = (
|
|
@@ -807,6 +958,9 @@ const renderSelectionList = (
|
|
|
807
958
|
validateAggregationSelection(selection as SelectionValue, [])
|
|
808
959
|
}
|
|
809
960
|
const flattened = flattenSelection(selection)
|
|
961
|
+
if (dialect.name === "mysql" && flattened.length === 0) {
|
|
962
|
+
throw new Error("mysql select statements require at least one selected expression")
|
|
963
|
+
}
|
|
810
964
|
const projections = selectionProjections(selection)
|
|
811
965
|
const sql = flattened.map(({ expression, alias }) =>
|
|
812
966
|
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
@@ -816,10 +970,105 @@ const renderSelectionList = (
|
|
|
816
970
|
}
|
|
817
971
|
}
|
|
818
972
|
|
|
973
|
+
const nestedRenderState = (state: RenderState): RenderState => ({
|
|
974
|
+
params: state.params,
|
|
975
|
+
valueMappings: state.valueMappings,
|
|
976
|
+
ctes: [],
|
|
977
|
+
cteNames: new Set(state.cteNames),
|
|
978
|
+
cteSources: new Map(state.cteSources)
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
const assertMatchingSetProjections = (
|
|
982
|
+
left: readonly Projection[],
|
|
983
|
+
right: readonly Projection[]
|
|
984
|
+
): void => {
|
|
985
|
+
const leftKeys = left.map((projection) => JSON.stringify(projection.path))
|
|
986
|
+
const rightKeys = right.map((projection) => JSON.stringify(projection.path))
|
|
987
|
+
if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
|
|
988
|
+
throw new Error("set operator operands must have matching result rows")
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const assertNoGroupedMutationClauses = (
|
|
993
|
+
ast: Pick<QueryAst.Ast, "groupBy" | "having">,
|
|
994
|
+
statement: string
|
|
995
|
+
): void => {
|
|
996
|
+
if (ast.groupBy.length > 0) {
|
|
997
|
+
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
998
|
+
}
|
|
999
|
+
if (ast.having.length > 0) {
|
|
1000
|
+
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const assertNoInsertQueryClauses = (
|
|
1005
|
+
ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
|
|
1006
|
+
): void => {
|
|
1007
|
+
if (ast.where.length > 0) {
|
|
1008
|
+
throw new Error("where(...) is not supported for insert statements")
|
|
1009
|
+
}
|
|
1010
|
+
if (ast.joins.length > 0) {
|
|
1011
|
+
throw new Error("join(...) is not supported for insert statements")
|
|
1012
|
+
}
|
|
1013
|
+
if (ast.orderBy.length > 0) {
|
|
1014
|
+
throw new Error("orderBy(...) is not supported for insert statements")
|
|
1015
|
+
}
|
|
1016
|
+
if (ast.limit) {
|
|
1017
|
+
throw new Error("limit(...) is not supported for insert statements")
|
|
1018
|
+
}
|
|
1019
|
+
if (ast.offset) {
|
|
1020
|
+
throw new Error("offset(...) is not supported for insert statements")
|
|
1021
|
+
}
|
|
1022
|
+
if (ast.lock) {
|
|
1023
|
+
throw new Error("lock(...) is not supported for insert statements")
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const assertNoStatementQueryClauses = (
|
|
1028
|
+
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1029
|
+
statement: string,
|
|
1030
|
+
options: { readonly allowSelection?: boolean } = {}
|
|
1031
|
+
): void => {
|
|
1032
|
+
if (ast.distinct) {
|
|
1033
|
+
throw new Error(`distinct(...) is not supported for ${statement} statements`)
|
|
1034
|
+
}
|
|
1035
|
+
if (ast.where.length > 0) {
|
|
1036
|
+
throw new Error(`where(...) is not supported for ${statement} statements`)
|
|
1037
|
+
}
|
|
1038
|
+
if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
|
|
1039
|
+
throw new Error(`from(...) is not supported for ${statement} statements`)
|
|
1040
|
+
}
|
|
1041
|
+
if (ast.joins.length > 0) {
|
|
1042
|
+
throw new Error(`join(...) is not supported for ${statement} statements`)
|
|
1043
|
+
}
|
|
1044
|
+
if (ast.groupBy.length > 0) {
|
|
1045
|
+
throw new Error(`groupBy(...) is not supported for ${statement} statements`)
|
|
1046
|
+
}
|
|
1047
|
+
if (ast.having.length > 0) {
|
|
1048
|
+
throw new Error(`having(...) is not supported for ${statement} statements`)
|
|
1049
|
+
}
|
|
1050
|
+
if (ast.orderBy.length > 0) {
|
|
1051
|
+
throw new Error(`orderBy(...) is not supported for ${statement} statements`)
|
|
1052
|
+
}
|
|
1053
|
+
if (ast.limit) {
|
|
1054
|
+
throw new Error(`limit(...) is not supported for ${statement} statements`)
|
|
1055
|
+
}
|
|
1056
|
+
if (ast.offset) {
|
|
1057
|
+
throw new Error(`offset(...) is not supported for ${statement} statements`)
|
|
1058
|
+
}
|
|
1059
|
+
if (ast.lock) {
|
|
1060
|
+
throw new Error(`lock(...) is not supported for ${statement} statements`)
|
|
1061
|
+
}
|
|
1062
|
+
if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
|
|
1063
|
+
throw new Error(`returning(...) is not supported for ${statement} statements`)
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
819
1067
|
export const renderQueryAst = (
|
|
820
1068
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
821
1069
|
state: RenderState,
|
|
822
|
-
dialect: SqlDialect
|
|
1070
|
+
dialect: SqlDialect,
|
|
1071
|
+
options: { readonly emitCtes?: boolean } = {}
|
|
823
1072
|
): RenderedQueryAst => {
|
|
824
1073
|
let sql = ""
|
|
825
1074
|
let projections: readonly Projection[] = []
|
|
@@ -829,15 +1078,19 @@ export const renderQueryAst = (
|
|
|
829
1078
|
validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
|
|
830
1079
|
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
831
1080
|
projections = rendered.projections
|
|
1081
|
+
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
832
1082
|
const clauses = [
|
|
833
1083
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
834
|
-
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})
|
|
835
|
-
: `select${ast.distinct ? " distinct" : ""}
|
|
1084
|
+
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
|
|
1085
|
+
: `select${ast.distinct ? " distinct" : ""}${selectList}`
|
|
836
1086
|
]
|
|
837
1087
|
if (ast.from) {
|
|
838
1088
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
839
1089
|
}
|
|
840
1090
|
for (const join of ast.joins) {
|
|
1091
|
+
if (dialect.name === "mysql" && join.kind === "full") {
|
|
1092
|
+
throw new Error("Unsupported mysql full join")
|
|
1093
|
+
}
|
|
841
1094
|
const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
|
|
842
1095
|
clauses.push(
|
|
843
1096
|
join.kind === "cross"
|
|
@@ -864,8 +1117,11 @@ export const renderQueryAst = (
|
|
|
864
1117
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
865
1118
|
}
|
|
866
1119
|
if (ast.lock) {
|
|
1120
|
+
if (ast.lock.nowait && ast.lock.skipLocked) {
|
|
1121
|
+
throw new Error("lock(...) cannot specify both nowait and skipLocked")
|
|
1122
|
+
}
|
|
867
1123
|
clauses.push(
|
|
868
|
-
`${ast.lock.mode
|
|
1124
|
+
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
869
1125
|
)
|
|
870
1126
|
}
|
|
871
1127
|
sql = clauses.join(" ")
|
|
@@ -873,6 +1129,7 @@ export const renderQueryAst = (
|
|
|
873
1129
|
}
|
|
874
1130
|
case "set": {
|
|
875
1131
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1132
|
+
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
876
1133
|
const base = renderQueryAst(
|
|
877
1134
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
878
1135
|
Record<string, unknown>,
|
|
@@ -883,6 +1140,7 @@ export const renderQueryAst = (
|
|
|
883
1140
|
dialect
|
|
884
1141
|
)
|
|
885
1142
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1143
|
+
assertMatchingSetProjections(projections, base.projections)
|
|
886
1144
|
sql = [
|
|
887
1145
|
`(${base.sql})`,
|
|
888
1146
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -895,6 +1153,7 @@ export const renderQueryAst = (
|
|
|
895
1153
|
state,
|
|
896
1154
|
dialect
|
|
897
1155
|
)
|
|
1156
|
+
assertMatchingSetProjections(projections, rendered.projections)
|
|
898
1157
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
899
1158
|
})
|
|
900
1159
|
].join(" ")
|
|
@@ -902,19 +1161,26 @@ export const renderQueryAst = (
|
|
|
902
1161
|
}
|
|
903
1162
|
case "insert": {
|
|
904
1163
|
const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
|
|
1164
|
+
if (insertAst.distinct) {
|
|
1165
|
+
throw new Error("distinct(...) is not supported for insert statements")
|
|
1166
|
+
}
|
|
1167
|
+
assertNoGroupedMutationClauses(insertAst, "insert")
|
|
1168
|
+
assertNoInsertQueryClauses(insertAst)
|
|
905
1169
|
const targetSource = insertAst.into!
|
|
906
1170
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1171
|
+
const insertSource = expectInsertSourceKind(insertAst.insertSource)
|
|
1172
|
+
const conflict = expectConflictClause(insertAst.conflict)
|
|
907
1173
|
sql = `insert into ${target}`
|
|
908
|
-
if (
|
|
909
|
-
const columns =
|
|
910
|
-
const rows =
|
|
1174
|
+
if (insertSource?.kind === "values") {
|
|
1175
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
1176
|
+
const rows = insertSource.rows.map((row) =>
|
|
911
1177
|
`(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
912
1178
|
).join(", ")
|
|
913
1179
|
sql += ` (${columns}) values ${rows}`
|
|
914
|
-
} else if (
|
|
915
|
-
const columns =
|
|
1180
|
+
} else if (insertSource?.kind === "query") {
|
|
1181
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
916
1182
|
const renderedQuery = renderQueryAst(
|
|
917
|
-
Query.getAst(
|
|
1183
|
+
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
918
1184
|
Record<string, unknown>,
|
|
919
1185
|
any,
|
|
920
1186
|
QueryAst.QueryStatement
|
|
@@ -923,26 +1189,25 @@ export const renderQueryAst = (
|
|
|
923
1189
|
dialect
|
|
924
1190
|
)
|
|
925
1191
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
926
|
-
} else if (
|
|
927
|
-
const
|
|
928
|
-
const columns = unnestSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
1192
|
+
} else if (insertSource?.kind === "unnest") {
|
|
1193
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
929
1194
|
if (dialect.name === "postgres") {
|
|
930
1195
|
const table = targetSource.source as Table.AnyTable
|
|
931
1196
|
const fields = table[Table.TypeId].fields
|
|
932
|
-
const rendered =
|
|
1197
|
+
const rendered = insertSource.values.map((entry) =>
|
|
933
1198
|
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
934
1199
|
).join(", ")
|
|
935
1200
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
936
1201
|
} else {
|
|
937
|
-
const
|
|
1202
|
+
const table = targetSource.source as Table.AnyTable
|
|
1203
|
+
const fields = table[Table.TypeId].fields
|
|
1204
|
+
const encodedValues = insertSource.values.map((entry) => ({
|
|
1205
|
+
columnName: entry.columnName,
|
|
1206
|
+
values: encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect)
|
|
1207
|
+
}))
|
|
1208
|
+
const rowCount = encodedValues[0]?.values.length ?? 0
|
|
938
1209
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
939
|
-
`(${
|
|
940
|
-
dialect.renderLiteral(
|
|
941
|
-
entry.values[index],
|
|
942
|
-
state,
|
|
943
|
-
(targetSource.source as Table.AnyTable)[Table.TypeId].fields[entry.columnName]![Expression.TypeId]
|
|
944
|
-
)
|
|
945
|
-
).join(", ")})`
|
|
1210
|
+
`(${encodedValues.map((entry) => dialect.renderLiteral(entry.values[index], state)).join(", ")})`
|
|
946
1211
|
).join(", ")
|
|
947
1212
|
sql += ` (${columns}) values ${rows}`
|
|
948
1213
|
}
|
|
@@ -952,30 +1217,39 @@ export const renderQueryAst = (
|
|
|
952
1217
|
if ((insertAst.values ?? []).length > 0) {
|
|
953
1218
|
sql += ` (${columns}) values (${values})`
|
|
954
1219
|
} else {
|
|
955
|
-
sql += "
|
|
1220
|
+
sql += " () values ()"
|
|
956
1221
|
}
|
|
957
1222
|
}
|
|
958
|
-
if (
|
|
959
|
-
|
|
1223
|
+
if (conflict) {
|
|
1224
|
+
if (conflict.where) {
|
|
1225
|
+
throw new Error("Unsupported mysql conflict action predicates")
|
|
1226
|
+
}
|
|
1227
|
+
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
960
1228
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
961
1229
|
).join(", ")
|
|
962
1230
|
if (dialect.name === "postgres") {
|
|
963
|
-
const targetSql =
|
|
964
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
965
|
-
:
|
|
966
|
-
? ` on conflict (${
|
|
1231
|
+
const targetSql = conflict.target?.kind === "constraint"
|
|
1232
|
+
? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
|
|
1233
|
+
: conflict.target?.kind === "columns"
|
|
1234
|
+
? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
|
|
967
1235
|
: " on conflict"
|
|
968
1236
|
sql += targetSql
|
|
969
|
-
sql +=
|
|
1237
|
+
sql += conflict.action === "doNothing"
|
|
970
1238
|
? " do nothing"
|
|
971
|
-
: ` do update set ${updateValues}${
|
|
972
|
-
} else if (
|
|
1239
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
|
|
1240
|
+
} else if (conflict.action === "doNothing") {
|
|
973
1241
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
974
1242
|
} else {
|
|
975
1243
|
sql += ` on duplicate key update ${updateValues}`
|
|
976
1244
|
}
|
|
977
1245
|
}
|
|
978
|
-
const
|
|
1246
|
+
const hasReturning = Object.keys(insertAst.select as Record<string, unknown>).length > 0
|
|
1247
|
+
const returning = hasReturning
|
|
1248
|
+
? renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
|
|
1249
|
+
: { sql: "", projections: [] }
|
|
1250
|
+
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1251
|
+
throw new Error("Unsupported mysql returning")
|
|
1252
|
+
}
|
|
979
1253
|
projections = returning.projections
|
|
980
1254
|
if (returning.sql.length > 0) {
|
|
981
1255
|
sql += ` returning ${returning.sql}`
|
|
@@ -984,19 +1258,33 @@ export const renderQueryAst = (
|
|
|
984
1258
|
}
|
|
985
1259
|
case "update": {
|
|
986
1260
|
const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
|
|
1261
|
+
if (updateAst.distinct) {
|
|
1262
|
+
throw new Error("distinct(...) is not supported for update statements")
|
|
1263
|
+
}
|
|
1264
|
+
assertNoGroupedMutationClauses(updateAst, "update")
|
|
1265
|
+
if (updateAst.offset) {
|
|
1266
|
+
throw new Error("offset(...) is not supported for update statements")
|
|
1267
|
+
}
|
|
987
1268
|
const targetSource = updateAst.target!
|
|
988
1269
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
989
1270
|
const targets = updateAst.targets ?? [targetSource]
|
|
990
1271
|
const fromSources = updateAst.fromSources ?? []
|
|
1272
|
+
if ((updateAst.set ?? []).length === 0) {
|
|
1273
|
+
throw new Error("update statements require at least one assignment")
|
|
1274
|
+
}
|
|
991
1275
|
const assignments = updateAst.set!.map((entry) =>
|
|
992
1276
|
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
993
1277
|
if (dialect.name === "mysql") {
|
|
994
1278
|
const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
|
|
995
1279
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
996
1280
|
const joinSources = updateAst.joins.map((join) =>
|
|
997
|
-
join.kind === "
|
|
998
|
-
?
|
|
999
|
-
|
|
1281
|
+
join.kind === "full"
|
|
1282
|
+
? (() => {
|
|
1283
|
+
throw new Error("Unsupported mysql full join")
|
|
1284
|
+
})()
|
|
1285
|
+
: join.kind === "cross"
|
|
1286
|
+
? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
|
|
1287
|
+
: `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
|
|
1000
1288
|
).join(" ")
|
|
1001
1289
|
const targetList = [
|
|
1002
1290
|
...targets.map((entry) =>
|
|
@@ -1028,7 +1316,13 @@ export const renderQueryAst = (
|
|
|
1028
1316
|
if (dialect.name === "mysql" && updateAst.limit) {
|
|
1029
1317
|
sql += ` limit ${renderMysqlMutationLimit(updateAst.limit, state, dialect)}`
|
|
1030
1318
|
}
|
|
1031
|
-
const
|
|
1319
|
+
const hasReturning = Object.keys(updateAst.select as Record<string, unknown>).length > 0
|
|
1320
|
+
const returning = hasReturning
|
|
1321
|
+
? renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
|
|
1322
|
+
: { sql: "", projections: [] }
|
|
1323
|
+
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1324
|
+
throw new Error("Unsupported mysql returning")
|
|
1325
|
+
}
|
|
1032
1326
|
projections = returning.projections
|
|
1033
1327
|
if (returning.sql.length > 0) {
|
|
1034
1328
|
sql += ` returning ${returning.sql}`
|
|
@@ -1037,6 +1331,13 @@ export const renderQueryAst = (
|
|
|
1037
1331
|
}
|
|
1038
1332
|
case "delete": {
|
|
1039
1333
|
const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
|
|
1334
|
+
if (deleteAst.distinct) {
|
|
1335
|
+
throw new Error("distinct(...) is not supported for delete statements")
|
|
1336
|
+
}
|
|
1337
|
+
assertNoGroupedMutationClauses(deleteAst, "delete")
|
|
1338
|
+
if (deleteAst.offset) {
|
|
1339
|
+
throw new Error("offset(...) is not supported for delete statements")
|
|
1340
|
+
}
|
|
1040
1341
|
const targetSource = deleteAst.target!
|
|
1041
1342
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1042
1343
|
const targets = deleteAst.targets ?? [targetSource]
|
|
@@ -1048,9 +1349,13 @@ export const renderQueryAst = (
|
|
|
1048
1349
|
renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
|
|
1049
1350
|
).join(", ")
|
|
1050
1351
|
const joinSources = deleteAst.joins.map((join) =>
|
|
1051
|
-
join.kind === "
|
|
1052
|
-
?
|
|
1053
|
-
|
|
1352
|
+
join.kind === "full"
|
|
1353
|
+
? (() => {
|
|
1354
|
+
throw new Error("Unsupported mysql full join")
|
|
1355
|
+
})()
|
|
1356
|
+
: join.kind === "cross"
|
|
1357
|
+
? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
|
|
1358
|
+
: `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
|
|
1054
1359
|
).join(" ")
|
|
1055
1360
|
sql = hasJoinedSources
|
|
1056
1361
|
? `delete${modifiers} ${targetList} from ${fromSources}${joinSources.length > 0 ? ` ${joinSources}` : ""}`
|
|
@@ -1074,7 +1379,13 @@ export const renderQueryAst = (
|
|
|
1074
1379
|
if (dialect.name === "mysql" && deleteAst.limit) {
|
|
1075
1380
|
sql += ` limit ${renderMysqlMutationLimit(deleteAst.limit, state, dialect)}`
|
|
1076
1381
|
}
|
|
1077
|
-
const
|
|
1382
|
+
const hasReturning = Object.keys(deleteAst.select as Record<string, unknown>).length > 0
|
|
1383
|
+
const returning = hasReturning
|
|
1384
|
+
? renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
|
|
1385
|
+
: { sql: "", projections: [] }
|
|
1386
|
+
if (dialect.name === "mysql" && returning.sql.length > 0) {
|
|
1387
|
+
throw new Error("Unsupported mysql returning")
|
|
1388
|
+
}
|
|
1078
1389
|
projections = returning.projections
|
|
1079
1390
|
if (returning.sql.length > 0) {
|
|
1080
1391
|
sql += ` returning ${returning.sql}`
|
|
@@ -1083,12 +1394,17 @@ export const renderQueryAst = (
|
|
|
1083
1394
|
}
|
|
1084
1395
|
case "truncate": {
|
|
1085
1396
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1397
|
+
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1398
|
+
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1086
1399
|
const targetSource = truncateAst.target!
|
|
1400
|
+
if (dialect.name === "mysql" && (truncate.restartIdentity || truncate.cascade)) {
|
|
1401
|
+
throw new Error("Unsupported mysql truncate options")
|
|
1402
|
+
}
|
|
1087
1403
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1088
|
-
if (
|
|
1404
|
+
if (truncate.restartIdentity) {
|
|
1089
1405
|
sql += " restart identity"
|
|
1090
1406
|
}
|
|
1091
|
-
if (
|
|
1407
|
+
if (truncate.cascade) {
|
|
1092
1408
|
sql += " cascade"
|
|
1093
1409
|
}
|
|
1094
1410
|
break
|
|
@@ -1101,6 +1417,9 @@ export const renderQueryAst = (
|
|
|
1101
1417
|
const targetSource = mergeAst.target!
|
|
1102
1418
|
const usingSource = mergeAst.using!
|
|
1103
1419
|
const merge = mergeAst.merge!
|
|
1420
|
+
if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
|
|
1421
|
+
throw new Error("returning(...) is not supported for merge statements")
|
|
1422
|
+
}
|
|
1104
1423
|
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)}`
|
|
1105
1424
|
if (merge.whenMatched) {
|
|
1106
1425
|
sql += " when matched"
|
|
@@ -1130,25 +1449,30 @@ export const renderQueryAst = (
|
|
|
1130
1449
|
case "savepoint":
|
|
1131
1450
|
case "rollbackTo":
|
|
1132
1451
|
case "releaseSavepoint": {
|
|
1452
|
+
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1133
1453
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1134
1454
|
break
|
|
1135
1455
|
}
|
|
1136
1456
|
case "createTable": {
|
|
1137
1457
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1138
|
-
|
|
1458
|
+
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1459
|
+
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1460
|
+
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1139
1461
|
break
|
|
1140
1462
|
}
|
|
1141
1463
|
case "dropTable": {
|
|
1142
1464
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1143
|
-
|
|
1144
|
-
|
|
1465
|
+
assertNoStatementQueryClauses(dropTableAst, "dropTable")
|
|
1466
|
+
const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
|
|
1467
|
+
sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
|
|
1145
1468
|
break
|
|
1146
1469
|
}
|
|
1147
1470
|
case "createIndex": {
|
|
1148
1471
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1472
|
+
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1149
1473
|
sql = renderCreateIndexSql(
|
|
1150
1474
|
createIndexAst.target!,
|
|
1151
|
-
createIndexAst.ddl
|
|
1475
|
+
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
1152
1476
|
state,
|
|
1153
1477
|
dialect
|
|
1154
1478
|
)
|
|
@@ -1156,17 +1480,20 @@ export const renderQueryAst = (
|
|
|
1156
1480
|
}
|
|
1157
1481
|
case "dropIndex": {
|
|
1158
1482
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1483
|
+
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1159
1484
|
sql = renderDropIndexSql(
|
|
1160
1485
|
dropIndexAst.target!,
|
|
1161
|
-
dropIndexAst.ddl
|
|
1486
|
+
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
1162
1487
|
state,
|
|
1163
1488
|
dialect
|
|
1164
1489
|
)
|
|
1165
1490
|
break
|
|
1166
1491
|
}
|
|
1492
|
+
default:
|
|
1493
|
+
throw new Error("Unsupported query statement kind")
|
|
1167
1494
|
}
|
|
1168
1495
|
|
|
1169
|
-
if (state.ctes.length === 0) {
|
|
1496
|
+
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
1170
1497
|
return {
|
|
1171
1498
|
sql,
|
|
1172
1499
|
projections
|
|
@@ -1214,9 +1541,27 @@ const renderSourceReference = (
|
|
|
1214
1541
|
readonly plan: Query.Plan.Any
|
|
1215
1542
|
readonly recursive?: boolean
|
|
1216
1543
|
}
|
|
1544
|
+
const registeredCteSource = state.cteSources.get(cte.name)
|
|
1545
|
+
if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
|
|
1546
|
+
throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
|
|
1547
|
+
}
|
|
1217
1548
|
if (!state.cteNames.has(cte.name)) {
|
|
1218
1549
|
state.cteNames.add(cte.name)
|
|
1219
|
-
|
|
1550
|
+
state.cteSources.set(cte.name, cte.plan)
|
|
1551
|
+
const statement = Query.getQueryState(cte.plan).statement
|
|
1552
|
+
if (statement !== "select" && statement !== "set") {
|
|
1553
|
+
const cteAst = Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>
|
|
1554
|
+
if (Object.keys((cteAst.select ?? {}) as Record<string, unknown>).length > 0) {
|
|
1555
|
+
throw new Error("Unsupported mysql returning")
|
|
1556
|
+
}
|
|
1557
|
+
throw new Error("Unsupported mysql data-modifying cte")
|
|
1558
|
+
}
|
|
1559
|
+
const rendered = renderQueryAst(
|
|
1560
|
+
Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1561
|
+
state,
|
|
1562
|
+
dialect,
|
|
1563
|
+
{ emitCtes: false }
|
|
1564
|
+
)
|
|
1220
1565
|
state.ctes.push({
|
|
1221
1566
|
name: cte.name,
|
|
1222
1567
|
sql: rendered.sql,
|
|
@@ -1233,14 +1578,14 @@ const renderSourceReference = (
|
|
|
1233
1578
|
if (!state.cteNames.has(derived.name)) {
|
|
1234
1579
|
// derived tables are inlined, so no CTE registration is needed
|
|
1235
1580
|
}
|
|
1236
|
-
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1581
|
+
return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
|
|
1237
1582
|
}
|
|
1238
1583
|
if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
|
|
1239
1584
|
const lateral = source as unknown as {
|
|
1240
1585
|
readonly name: string
|
|
1241
1586
|
readonly plan: Query.Plan.Any
|
|
1242
1587
|
}
|
|
1243
|
-
return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
|
|
1588
|
+
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)}`
|
|
1244
1589
|
}
|
|
1245
1590
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
1246
1591
|
const values = source as unknown as {
|
|
@@ -1275,6 +1620,22 @@ const renderSourceReference = (
|
|
|
1275
1620
|
return dialect.renderTableReference(tableName, baseTableName, schemaName)
|
|
1276
1621
|
}
|
|
1277
1622
|
|
|
1623
|
+
const renderSubqueryExpressionPlan = (
|
|
1624
|
+
plan: Query.Plan.Any,
|
|
1625
|
+
state: RenderState,
|
|
1626
|
+
dialect: SqlDialect
|
|
1627
|
+
): string => {
|
|
1628
|
+
const statement = Query.getQueryState(plan).statement
|
|
1629
|
+
if (statement !== "select" && statement !== "set") {
|
|
1630
|
+
throw new Error("subquery expressions only accept select-like query plans")
|
|
1631
|
+
}
|
|
1632
|
+
return renderQueryAst(
|
|
1633
|
+
Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1634
|
+
state,
|
|
1635
|
+
dialect
|
|
1636
|
+
).sql
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1278
1639
|
/**
|
|
1279
1640
|
* Renders a scalar expression AST into SQL text.
|
|
1280
1641
|
*
|
|
@@ -1309,10 +1670,13 @@ export const renderExpression = (
|
|
|
1309
1670
|
: ">="
|
|
1310
1671
|
switch (ast.kind) {
|
|
1311
1672
|
case "column":
|
|
1312
|
-
return ast.tableName.length === 0
|
|
1673
|
+
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1313
1674
|
? dialect.quoteIdentifier(ast.columnName)
|
|
1314
1675
|
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1315
1676
|
case "literal":
|
|
1677
|
+
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1678
|
+
throw new Error("Expected a finite numeric value")
|
|
1679
|
+
}
|
|
1316
1680
|
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1317
1681
|
case "excluded":
|
|
1318
1682
|
return dialect.name === "mysql"
|
|
@@ -1375,7 +1739,7 @@ export const renderExpression = (
|
|
|
1375
1739
|
return `(${left} @> ${right})`
|
|
1376
1740
|
}
|
|
1377
1741
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1378
|
-
return `json_contains(${
|
|
1742
|
+
return `json_contains(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
1379
1743
|
}
|
|
1380
1744
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1381
1745
|
case "containedBy":
|
|
@@ -1389,7 +1753,7 @@ export const renderExpression = (
|
|
|
1389
1753
|
return `(${left} <@ ${right})`
|
|
1390
1754
|
}
|
|
1391
1755
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1392
|
-
return `json_contains(${
|
|
1756
|
+
return `json_contains(${renderJsonInputExpression(ast.right, state, dialect)}, ${renderJsonInputExpression(ast.left, state, dialect)})`
|
|
1393
1757
|
}
|
|
1394
1758
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1395
1759
|
case "overlaps":
|
|
@@ -1403,7 +1767,7 @@ export const renderExpression = (
|
|
|
1403
1767
|
return `(${left} && ${right})`
|
|
1404
1768
|
}
|
|
1405
1769
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1406
|
-
return `json_overlaps(${
|
|
1770
|
+
return `json_overlaps(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
1407
1771
|
}
|
|
1408
1772
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1409
1773
|
case "isNull":
|
|
@@ -1423,14 +1787,26 @@ export const renderExpression = (
|
|
|
1423
1787
|
case "min":
|
|
1424
1788
|
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1425
1789
|
case "and":
|
|
1790
|
+
if (ast.values.length === 0) {
|
|
1791
|
+
throw new Error("and(...) requires at least one predicate")
|
|
1792
|
+
}
|
|
1426
1793
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1427
1794
|
case "or":
|
|
1795
|
+
if (ast.values.length === 0) {
|
|
1796
|
+
throw new Error("or(...) requires at least one predicate")
|
|
1797
|
+
}
|
|
1428
1798
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1429
1799
|
case "coalesce":
|
|
1430
1800
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1431
1801
|
case "in":
|
|
1802
|
+
if (ast.values.length < 2) {
|
|
1803
|
+
throw new Error("in(...) requires at least one candidate value")
|
|
1804
|
+
}
|
|
1432
1805
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1433
1806
|
case "notIn":
|
|
1807
|
+
if (ast.values.length < 2) {
|
|
1808
|
+
throw new Error("notIn(...) requires at least one candidate value")
|
|
1809
|
+
}
|
|
1434
1810
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1435
1811
|
case "between":
|
|
1436
1812
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1441,35 +1817,15 @@ export const renderExpression = (
|
|
|
1441
1817
|
`when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
|
|
1442
1818
|
).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
|
|
1443
1819
|
case "exists":
|
|
1444
|
-
return `exists (${
|
|
1445
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1446
|
-
state,
|
|
1447
|
-
dialect
|
|
1448
|
-
).sql})`
|
|
1820
|
+
return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1449
1821
|
case "scalarSubquery":
|
|
1450
|
-
return `(${
|
|
1451
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1452
|
-
state,
|
|
1453
|
-
dialect
|
|
1454
|
-
).sql})`
|
|
1822
|
+
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1455
1823
|
case "inSubquery":
|
|
1456
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${
|
|
1457
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1458
|
-
state,
|
|
1459
|
-
dialect
|
|
1460
|
-
).sql}))`
|
|
1824
|
+
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1461
1825
|
case "comparisonAny":
|
|
1462
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${
|
|
1463
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1464
|
-
state,
|
|
1465
|
-
dialect
|
|
1466
|
-
).sql}))`
|
|
1826
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1467
1827
|
case "comparisonAll":
|
|
1468
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${
|
|
1469
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1470
|
-
state,
|
|
1471
|
-
dialect
|
|
1472
|
-
).sql}))`
|
|
1828
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1473
1829
|
case "window": {
|
|
1474
1830
|
if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
|
|
1475
1831
|
break
|