effect-qb 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mysql.js +1957 -595
- package/dist/postgres/metadata.js +2507 -182
- package/dist/postgres.js +9587 -8201
- package/dist/sqlite.js +8360 -0
- package/package.json +7 -2
- package/src/internal/column-state.ts +7 -0
- package/src/internal/column.ts +22 -0
- package/src/internal/derived-table.ts +29 -3
- package/src/internal/dialect.ts +14 -1
- package/src/internal/dsl-mutation-runtime.ts +173 -4
- package/src/internal/dsl-plan-runtime.ts +165 -20
- package/src/internal/dsl-query-runtime.ts +60 -6
- package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
- package/src/internal/executor.ts +62 -13
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +141 -1
- package/src/internal/implication-runtime.ts +2 -1
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/analysis.ts +103 -1
- package/src/internal/predicate/atom.ts +7 -0
- package/src/internal/predicate/context.ts +170 -17
- package/src/internal/predicate/key.ts +64 -2
- package/src/internal/predicate/normalize.ts +115 -34
- package/src/internal/predicate/runtime.ts +144 -13
- package/src/internal/query.ts +563 -103
- package/src/internal/renderer.ts +39 -2
- package/src/internal/runtime/driver-value-mapping.ts +244 -0
- package/src/internal/runtime/normalize.ts +62 -38
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- package/src/internal/scalar.ts +11 -0
- package/src/internal/table-options.ts +108 -1
- package/src/internal/table.ts +87 -29
- package/src/mysql/column.ts +19 -2
- package/src/mysql/datatypes/index.ts +21 -0
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +20 -5
- package/src/mysql/internal/dialect.ts +12 -6
- package/src/mysql/internal/dsl.ts +995 -263
- package/src/mysql/internal/renderer.ts +13 -3
- package/src/mysql/internal/sql-expression-renderer.ts +530 -128
- package/src/mysql/query.ts +9 -2
- package/src/mysql/renderer.ts +7 -2
- package/src/mysql/table.ts +38 -12
- package/src/postgres/cast.ts +22 -7
- package/src/postgres/column.ts +5 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +68 -10
- package/src/postgres/function/core.ts +19 -1
- package/src/postgres/internal/dialect.ts +12 -6
- package/src/postgres/internal/dsl.ts +958 -288
- package/src/postgres/internal/renderer.ts +13 -3
- package/src/postgres/internal/schema-ddl.ts +2 -1
- package/src/postgres/internal/schema-model.ts +6 -3
- package/src/postgres/internal/sql-expression-renderer.ts +477 -96
- package/src/postgres/json.ts +57 -17
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +7 -2
- package/src/postgres/schema-management.ts +91 -4
- package/src/postgres/schema.ts +1 -1
- package/src/postgres/table.ts +189 -53
- package/src/postgres/type.ts +4 -0
- package/src/sqlite/column.ts +128 -0
- package/src/sqlite/datatypes/index.ts +79 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +227 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +37 -0
- package/src/sqlite/internal/dsl.ts +6926 -0
- package/src/sqlite/internal/renderer.ts +47 -0
- package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
- package/src/sqlite/json.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +24 -0
- package/src/sqlite/table.ts +183 -0
- package/src/sqlite.ts +22 -0
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema"
|
|
2
|
+
|
|
1
3
|
import * as Query from "../../internal/query.js"
|
|
2
4
|
import * as Expression from "../../internal/scalar.js"
|
|
3
5
|
import * as Table from "../../internal/table.js"
|
|
4
6
|
import * as QueryAst from "../../internal/query-ast.js"
|
|
5
|
-
import type { RenderState, SqlDialect } from "../../internal/dialect.js"
|
|
7
|
+
import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
|
|
6
8
|
import * as ExpressionAst from "../../internal/expression-ast.js"
|
|
7
9
|
import * as JsonPath from "../../internal/json/path.js"
|
|
10
|
+
import { 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"
|
|
13
|
+
import {
|
|
14
|
+
renderJsonSelectSql,
|
|
15
|
+
renderSelectSql,
|
|
16
|
+
toDriverValue
|
|
17
|
+
} from "../../internal/runtime/driver-value-mapping.js"
|
|
18
|
+
import { normalizeDbValue } from "../../internal/runtime/normalize.js"
|
|
8
19
|
import { flattenSelection, type Projection } from "../../internal/projections.js"
|
|
9
20
|
import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
|
|
10
21
|
import * as SchemaExpression from "../../internal/schema-expression.js"
|
|
11
|
-
import type
|
|
22
|
+
import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
|
|
12
23
|
|
|
13
24
|
const renderDbType = (
|
|
14
25
|
dialect: SqlDialect,
|
|
@@ -46,14 +57,59 @@ const renderCastType = (
|
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
const 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
|
+
|
|
49
100
|
const renderDdlExpression = (
|
|
50
101
|
expression: DdlExpressionLike,
|
|
51
102
|
state: RenderState,
|
|
52
103
|
dialect: SqlDialect
|
|
53
|
-
): string =>
|
|
54
|
-
SchemaExpression.isSchemaExpression(expression)
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
): string => {
|
|
105
|
+
if (SchemaExpression.isSchemaExpression(expression)) {
|
|
106
|
+
return SchemaExpression.render(expression)
|
|
107
|
+
}
|
|
108
|
+
return renderExpression(expression, state, {
|
|
109
|
+
...dialect,
|
|
110
|
+
renderLiteral: renderMysqlDdlLiteral
|
|
111
|
+
})
|
|
112
|
+
}
|
|
57
113
|
|
|
58
114
|
const renderMysqlMutationLimit = (
|
|
59
115
|
expression: Expression.Any,
|
|
@@ -109,22 +165,27 @@ const renderCreateTableSql = (
|
|
|
109
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" : ""}` : ""}`)
|
|
110
166
|
break
|
|
111
167
|
case "unique":
|
|
168
|
+
if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
|
|
169
|
+
throw new Error("Unsupported mysql unique constraint options")
|
|
170
|
+
}
|
|
112
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" : ""}` : ""}`)
|
|
113
172
|
break
|
|
114
173
|
case "foreignKey": {
|
|
115
174
|
const reference = option.references()
|
|
116
175
|
definitions.push(
|
|
117
|
-
`${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" : ""}` : ""}`
|
|
118
177
|
)
|
|
119
178
|
break
|
|
120
179
|
}
|
|
121
180
|
case "check":
|
|
122
181
|
definitions.push(
|
|
123
|
-
`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" : ""}`
|
|
124
183
|
)
|
|
125
184
|
break
|
|
126
185
|
case "index":
|
|
127
186
|
break
|
|
187
|
+
default:
|
|
188
|
+
throw new Error("Unsupported table option kind")
|
|
128
189
|
}
|
|
129
190
|
}
|
|
130
191
|
return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
|
|
@@ -136,6 +197,9 @@ const renderCreateIndexSql = (
|
|
|
136
197
|
state: RenderState,
|
|
137
198
|
dialect: SqlDialect
|
|
138
199
|
): string => {
|
|
200
|
+
if (ddl.ifNotExists) {
|
|
201
|
+
throw new Error("Unsupported mysql create index options")
|
|
202
|
+
}
|
|
139
203
|
const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
|
|
140
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(", ")})`
|
|
141
205
|
}
|
|
@@ -145,16 +209,28 @@ const renderDropIndexSql = (
|
|
|
145
209
|
ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
|
|
146
210
|
state: RenderState,
|
|
147
211
|
dialect: SqlDialect
|
|
148
|
-
): string =>
|
|
149
|
-
|
|
212
|
+
): string => {
|
|
213
|
+
if (ddl.ifExists) {
|
|
214
|
+
throw new Error("Unsupported mysql drop index options")
|
|
215
|
+
}
|
|
216
|
+
return dialect.name === "postgres"
|
|
150
217
|
? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
|
|
151
218
|
: `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
219
|
+
}
|
|
152
220
|
|
|
153
221
|
const isExpression = (value: unknown): value is Expression.Any =>
|
|
154
222
|
value !== null && typeof value === "object" && Expression.TypeId in value
|
|
155
223
|
|
|
156
|
-
const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
|
|
157
|
-
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
|
+
}
|
|
158
234
|
|
|
159
235
|
const isJsonExpression = (value: unknown): value is Expression.Any =>
|
|
160
236
|
isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
|
|
@@ -216,19 +292,19 @@ const extractJsonValue = (node: Record<string, unknown>): unknown =>
|
|
|
216
292
|
node.newValue ?? node.insert ?? node.right
|
|
217
293
|
|
|
218
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)}`
|
|
219
299
|
if (typeof segment === "string") {
|
|
220
|
-
return
|
|
221
|
-
? `.${segment}`
|
|
222
|
-
: `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
300
|
+
return renderKey(segment)
|
|
223
301
|
}
|
|
224
302
|
if (typeof segment === "number") {
|
|
225
303
|
return `[${segment}]`
|
|
226
304
|
}
|
|
227
305
|
switch (segment.kind) {
|
|
228
306
|
case "key":
|
|
229
|
-
return
|
|
230
|
-
? `.${segment.key}`
|
|
231
|
-
: `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
307
|
+
return renderKey(segment.key)
|
|
232
308
|
case "index":
|
|
233
309
|
return `[${segment.index}]`
|
|
234
310
|
case "wildcard":
|
|
@@ -242,10 +318,32 @@ const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number):
|
|
|
242
318
|
}
|
|
243
319
|
}
|
|
244
320
|
|
|
245
|
-
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 => {
|
|
246
344
|
let path = "$"
|
|
247
345
|
for (const segment of segments) {
|
|
248
|
-
path +=
|
|
346
|
+
path += renderSegment(segment)
|
|
249
347
|
}
|
|
250
348
|
return path
|
|
251
349
|
}
|
|
@@ -254,7 +352,30 @@ const renderMySqlJsonPath = (
|
|
|
254
352
|
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
255
353
|
state: RenderState,
|
|
256
354
|
dialect: SqlDialect
|
|
257
|
-
): 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
|
+
}
|
|
258
379
|
|
|
259
380
|
const renderPostgresJsonPathArray = (
|
|
260
381
|
segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
|
|
@@ -313,6 +434,76 @@ const renderPostgresJsonValue = (
|
|
|
313
434
|
: `cast(${rendered} as jsonb)`
|
|
314
435
|
}
|
|
315
436
|
|
|
437
|
+
const expressionDriverContext = (
|
|
438
|
+
expression: Expression.Any,
|
|
439
|
+
state: RenderState,
|
|
440
|
+
dialect: SqlDialect
|
|
441
|
+
) => ({
|
|
442
|
+
dialect: dialect.name,
|
|
443
|
+
valueMappings: state.valueMappings,
|
|
444
|
+
dbType: expression[Expression.TypeId].dbType,
|
|
445
|
+
runtimeSchema: expression[Expression.TypeId].runtimeSchema,
|
|
446
|
+
driverValueMapping: expression[Expression.TypeId].driverValueMapping
|
|
447
|
+
})
|
|
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
|
+
|
|
463
|
+
const renderJsonInputExpression = (
|
|
464
|
+
expression: Expression.Any,
|
|
465
|
+
state: RenderState,
|
|
466
|
+
dialect: SqlDialect
|
|
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(
|
|
475
|
+
renderExpression(expression, state, dialect),
|
|
476
|
+
expressionDriverContext(expression, state, dialect)
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const encodeArrayValues = (
|
|
481
|
+
values: readonly unknown[],
|
|
482
|
+
column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
|
|
483
|
+
state: RenderState,
|
|
484
|
+
dialect: SqlDialect
|
|
485
|
+
): readonly unknown[] =>
|
|
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, {
|
|
499
|
+
dialect: dialect.name,
|
|
500
|
+
valueMappings: state.valueMappings,
|
|
501
|
+
dbType: column.metadata.dbType,
|
|
502
|
+
runtimeSchema: column.schema,
|
|
503
|
+
driverValueMapping: column.metadata.driverValueMapping
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
316
507
|
const renderPostgresJsonKind = (
|
|
317
508
|
value: Expression.Any
|
|
318
509
|
): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
|
|
@@ -323,7 +514,10 @@ const renderJsonOpaquePath = (
|
|
|
323
514
|
dialect: SqlDialect
|
|
324
515
|
): string => {
|
|
325
516
|
if (isJsonPathValue(value)) {
|
|
326
|
-
|
|
517
|
+
const renderSegment = dialect.name === "mysql"
|
|
518
|
+
? renderMySqlJsonPathSegment
|
|
519
|
+
: renderJsonPathSegment
|
|
520
|
+
return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
|
|
327
521
|
}
|
|
328
522
|
if (typeof value === "string") {
|
|
329
523
|
return dialect.renderLiteral(value, state)
|
|
@@ -450,8 +644,9 @@ const renderJsonExpression = (
|
|
|
450
644
|
}
|
|
451
645
|
if (dialect.name === "mysql") {
|
|
452
646
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
647
|
+
const modeSql = dialect.renderLiteral(mode, state)
|
|
453
648
|
const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
|
|
454
|
-
return `json_contains_path(${baseSql}, ${
|
|
649
|
+
return `json_contains_path(${baseSql}, ${modeSql}, ${paths})`
|
|
455
650
|
}
|
|
456
651
|
return undefined
|
|
457
652
|
}
|
|
@@ -464,7 +659,7 @@ const renderJsonExpression = (
|
|
|
464
659
|
return `(${renderPostgresJsonValue(ast.left, state, dialect)} || ${renderPostgresJsonValue(ast.right, state, dialect)})`
|
|
465
660
|
}
|
|
466
661
|
if (dialect.name === "mysql") {
|
|
467
|
-
return `json_merge_preserve(${
|
|
662
|
+
return `json_merge_preserve(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
468
663
|
}
|
|
469
664
|
return undefined
|
|
470
665
|
}
|
|
@@ -474,7 +669,7 @@ const renderJsonExpression = (
|
|
|
474
669
|
: []
|
|
475
670
|
const renderedEntries = entries.flatMap((entry) => [
|
|
476
671
|
dialect.renderLiteral(entry.key, state),
|
|
477
|
-
|
|
672
|
+
renderJsonInputExpression(entry.value, state, dialect)
|
|
478
673
|
])
|
|
479
674
|
if (dialect.name === "postgres") {
|
|
480
675
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
|
|
@@ -488,7 +683,7 @@ const renderJsonExpression = (
|
|
|
488
683
|
const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
|
|
489
684
|
? (ast as { readonly values: readonly Expression.Any[] }).values
|
|
490
685
|
: []
|
|
491
|
-
const renderedValues = values.map((value) =>
|
|
686
|
+
const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
|
|
492
687
|
if (dialect.name === "postgres") {
|
|
493
688
|
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
494
689
|
}
|
|
@@ -502,10 +697,10 @@ const renderJsonExpression = (
|
|
|
502
697
|
return undefined
|
|
503
698
|
}
|
|
504
699
|
if (dialect.name === "postgres") {
|
|
505
|
-
return `to_json(${
|
|
700
|
+
return `to_json(${renderJsonInputExpression(base, state, dialect)})`
|
|
506
701
|
}
|
|
507
702
|
if (dialect.name === "mysql") {
|
|
508
|
-
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
703
|
+
return renderMySqlStructuredJsonLiteral(base, state) ?? `cast(${renderExpression(base, state, dialect)} as json)`
|
|
509
704
|
}
|
|
510
705
|
return undefined
|
|
511
706
|
case "jsonToJsonb":
|
|
@@ -513,10 +708,10 @@ const renderJsonExpression = (
|
|
|
513
708
|
return undefined
|
|
514
709
|
}
|
|
515
710
|
if (dialect.name === "postgres") {
|
|
516
|
-
return `to_jsonb(${
|
|
711
|
+
return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
|
|
517
712
|
}
|
|
518
713
|
if (dialect.name === "mysql") {
|
|
519
|
-
return `cast(${renderExpression(base, state, dialect)} as json)`
|
|
714
|
+
return renderMySqlStructuredJsonLiteral(base, state) ?? `cast(${renderExpression(base, state, dialect)} as json)`
|
|
520
715
|
}
|
|
521
716
|
return undefined
|
|
522
717
|
case "jsonTypeOf":
|
|
@@ -586,9 +781,7 @@ const renderJsonExpression = (
|
|
|
586
781
|
return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
|
|
587
782
|
}
|
|
588
783
|
if (dialect.name === "mysql") {
|
|
589
|
-
return `json_remove(${renderExpression(base, state, dialect)}, ${segments
|
|
590
|
-
renderMySqlJsonPath([segment], state, dialect)
|
|
591
|
-
).join(", ")})`
|
|
784
|
+
return `json_remove(${renderExpression(base, state, dialect)}, ${renderMySqlJsonPath(segments, state, dialect)})`
|
|
592
785
|
}
|
|
593
786
|
return undefined
|
|
594
787
|
}
|
|
@@ -612,8 +805,16 @@ const renderJsonExpression = (
|
|
|
612
805
|
return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
|
|
613
806
|
}
|
|
614
807
|
if (dialect.name === "mysql") {
|
|
615
|
-
const
|
|
616
|
-
|
|
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})`
|
|
617
818
|
}
|
|
618
819
|
return undefined
|
|
619
820
|
}
|
|
@@ -712,16 +913,7 @@ const renderMysqlMutationLock = (
|
|
|
712
913
|
if (!lock) {
|
|
713
914
|
return ""
|
|
714
915
|
}
|
|
715
|
-
|
|
716
|
-
case "lowPriority":
|
|
717
|
-
return " low_priority"
|
|
718
|
-
case "ignore":
|
|
719
|
-
return " ignore"
|
|
720
|
-
case "quick":
|
|
721
|
-
return statement === "delete" ? " quick" : ""
|
|
722
|
-
default:
|
|
723
|
-
return ""
|
|
724
|
-
}
|
|
916
|
+
return renderMysqlMutationLockMode(lock.mode, statement)
|
|
725
917
|
}
|
|
726
918
|
|
|
727
919
|
const renderTransactionClause = (
|
|
@@ -731,8 +923,9 @@ const renderTransactionClause = (
|
|
|
731
923
|
switch (clause.kind) {
|
|
732
924
|
case "transaction": {
|
|
733
925
|
const modes: string[] = []
|
|
734
|
-
|
|
735
|
-
|
|
926
|
+
const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
|
|
927
|
+
if (isolationLevel) {
|
|
928
|
+
modes.push(isolationLevel)
|
|
736
929
|
}
|
|
737
930
|
if (clause.readOnly === true) {
|
|
738
931
|
modes.push("read only")
|
|
@@ -752,7 +945,7 @@ const renderTransactionClause = (
|
|
|
752
945
|
case "releaseSavepoint":
|
|
753
946
|
return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
|
|
754
947
|
}
|
|
755
|
-
|
|
948
|
+
throw new Error("Unsupported transaction statement kind")
|
|
756
949
|
}
|
|
757
950
|
|
|
758
951
|
const renderSelectionList = (
|
|
@@ -765,19 +958,117 @@ const renderSelectionList = (
|
|
|
765
958
|
validateAggregationSelection(selection as SelectionValue, [])
|
|
766
959
|
}
|
|
767
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
|
+
}
|
|
768
964
|
const projections = selectionProjections(selection)
|
|
769
965
|
const sql = flattened.map(({ expression, alias }) =>
|
|
770
|
-
`${renderExpression(expression, state, dialect)} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
966
|
+
`${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
|
|
771
967
|
return {
|
|
772
968
|
sql,
|
|
773
969
|
projections
|
|
774
970
|
}
|
|
775
971
|
}
|
|
776
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
|
+
|
|
777
1067
|
export const renderQueryAst = (
|
|
778
1068
|
ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
779
1069
|
state: RenderState,
|
|
780
|
-
dialect: SqlDialect
|
|
1070
|
+
dialect: SqlDialect,
|
|
1071
|
+
options: { readonly emitCtes?: boolean } = {}
|
|
781
1072
|
): RenderedQueryAst => {
|
|
782
1073
|
let sql = ""
|
|
783
1074
|
let projections: readonly Projection[] = []
|
|
@@ -787,15 +1078,19 @@ export const renderQueryAst = (
|
|
|
787
1078
|
validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
|
|
788
1079
|
const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
|
|
789
1080
|
projections = rendered.projections
|
|
1081
|
+
const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
|
|
790
1082
|
const clauses = [
|
|
791
1083
|
ast.distinctOn && ast.distinctOn.length > 0
|
|
792
|
-
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})
|
|
793
|
-
: `select${ast.distinct ? " distinct" : ""}
|
|
1084
|
+
? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
|
|
1085
|
+
: `select${ast.distinct ? " distinct" : ""}${selectList}`
|
|
794
1086
|
]
|
|
795
1087
|
if (ast.from) {
|
|
796
1088
|
clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
|
|
797
1089
|
}
|
|
798
1090
|
for (const join of ast.joins) {
|
|
1091
|
+
if (dialect.name === "mysql" && join.kind === "full") {
|
|
1092
|
+
throw new Error("Unsupported mysql full join")
|
|
1093
|
+
}
|
|
799
1094
|
const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
|
|
800
1095
|
clauses.push(
|
|
801
1096
|
join.kind === "cross"
|
|
@@ -822,8 +1117,11 @@ export const renderQueryAst = (
|
|
|
822
1117
|
clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
|
|
823
1118
|
}
|
|
824
1119
|
if (ast.lock) {
|
|
1120
|
+
if (ast.lock.nowait && ast.lock.skipLocked) {
|
|
1121
|
+
throw new Error("lock(...) cannot specify both nowait and skipLocked")
|
|
1122
|
+
}
|
|
825
1123
|
clauses.push(
|
|
826
|
-
`${ast.lock.mode
|
|
1124
|
+
`${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
|
|
827
1125
|
)
|
|
828
1126
|
}
|
|
829
1127
|
sql = clauses.join(" ")
|
|
@@ -831,6 +1129,7 @@ export const renderQueryAst = (
|
|
|
831
1129
|
}
|
|
832
1130
|
case "set": {
|
|
833
1131
|
const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
|
|
1132
|
+
assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
|
|
834
1133
|
const base = renderQueryAst(
|
|
835
1134
|
Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
|
|
836
1135
|
Record<string, unknown>,
|
|
@@ -841,6 +1140,7 @@ export const renderQueryAst = (
|
|
|
841
1140
|
dialect
|
|
842
1141
|
)
|
|
843
1142
|
projections = selectionProjections(setAst.select as Record<string, unknown>)
|
|
1143
|
+
assertMatchingSetProjections(projections, base.projections)
|
|
844
1144
|
sql = [
|
|
845
1145
|
`(${base.sql})`,
|
|
846
1146
|
...(setAst.setOperations ?? []).map((entry) => {
|
|
@@ -853,6 +1153,7 @@ export const renderQueryAst = (
|
|
|
853
1153
|
state,
|
|
854
1154
|
dialect
|
|
855
1155
|
)
|
|
1156
|
+
assertMatchingSetProjections(projections, rendered.projections)
|
|
856
1157
|
return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
|
|
857
1158
|
})
|
|
858
1159
|
].join(" ")
|
|
@@ -860,19 +1161,26 @@ export const renderQueryAst = (
|
|
|
860
1161
|
}
|
|
861
1162
|
case "insert": {
|
|
862
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)
|
|
863
1169
|
const targetSource = insertAst.into!
|
|
864
1170
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
1171
|
+
const insertSource = expectInsertSourceKind(insertAst.insertSource)
|
|
1172
|
+
const conflict = expectConflictClause(insertAst.conflict)
|
|
865
1173
|
sql = `insert into ${target}`
|
|
866
|
-
if (
|
|
867
|
-
const columns =
|
|
868
|
-
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) =>
|
|
869
1177
|
`(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
|
|
870
1178
|
).join(", ")
|
|
871
1179
|
sql += ` (${columns}) values ${rows}`
|
|
872
|
-
} else if (
|
|
873
|
-
const columns =
|
|
1180
|
+
} else if (insertSource?.kind === "query") {
|
|
1181
|
+
const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
|
|
874
1182
|
const renderedQuery = renderQueryAst(
|
|
875
|
-
Query.getAst(
|
|
1183
|
+
Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
|
|
876
1184
|
Record<string, unknown>,
|
|
877
1185
|
any,
|
|
878
1186
|
QueryAst.QueryStatement
|
|
@@ -881,22 +1189,25 @@ export const renderQueryAst = (
|
|
|
881
1189
|
dialect
|
|
882
1190
|
)
|
|
883
1191
|
sql += ` (${columns}) ${renderedQuery.sql}`
|
|
884
|
-
} else if (
|
|
885
|
-
const
|
|
886
|
-
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(", ")
|
|
887
1194
|
if (dialect.name === "postgres") {
|
|
888
1195
|
const table = targetSource.source as Table.AnyTable
|
|
889
1196
|
const fields = table[Table.TypeId].fields
|
|
890
|
-
const rendered =
|
|
891
|
-
`cast(${dialect.renderLiteral(entry.values, state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
1197
|
+
const rendered = insertSource.values.map((entry) =>
|
|
1198
|
+
`cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
|
|
892
1199
|
).join(", ")
|
|
893
1200
|
sql += ` (${columns}) select * from unnest(${rendered})`
|
|
894
1201
|
} else {
|
|
895
|
-
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
|
|
896
1209
|
const rows = Array.from({ length: rowCount }, (_, index) =>
|
|
897
|
-
`(${
|
|
898
|
-
dialect.renderLiteral(entry.values[index], state)
|
|
899
|
-
).join(", ")})`
|
|
1210
|
+
`(${encodedValues.map((entry) => dialect.renderLiteral(entry.values[index], state)).join(", ")})`
|
|
900
1211
|
).join(", ")
|
|
901
1212
|
sql += ` (${columns}) values ${rows}`
|
|
902
1213
|
}
|
|
@@ -906,30 +1217,39 @@ export const renderQueryAst = (
|
|
|
906
1217
|
if ((insertAst.values ?? []).length > 0) {
|
|
907
1218
|
sql += ` (${columns}) values (${values})`
|
|
908
1219
|
} else {
|
|
909
|
-
sql += "
|
|
1220
|
+
sql += " () values ()"
|
|
910
1221
|
}
|
|
911
1222
|
}
|
|
912
|
-
if (
|
|
913
|
-
|
|
1223
|
+
if (conflict) {
|
|
1224
|
+
if (conflict.where) {
|
|
1225
|
+
throw new Error("Unsupported mysql conflict action predicates")
|
|
1226
|
+
}
|
|
1227
|
+
const updateValues = (conflict.values ?? []).map((entry) =>
|
|
914
1228
|
`${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
|
|
915
1229
|
).join(", ")
|
|
916
1230
|
if (dialect.name === "postgres") {
|
|
917
|
-
const targetSql =
|
|
918
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
919
|
-
:
|
|
920
|
-
? ` 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)}` : ""}`
|
|
921
1235
|
: " on conflict"
|
|
922
1236
|
sql += targetSql
|
|
923
|
-
sql +=
|
|
1237
|
+
sql += conflict.action === "doNothing"
|
|
924
1238
|
? " do nothing"
|
|
925
|
-
: ` do update set ${updateValues}${
|
|
926
|
-
} else if (
|
|
1239
|
+
: ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
|
|
1240
|
+
} else if (conflict.action === "doNothing") {
|
|
927
1241
|
sql = sql.replace(/^insert/, "insert ignore")
|
|
928
1242
|
} else {
|
|
929
1243
|
sql += ` on duplicate key update ${updateValues}`
|
|
930
1244
|
}
|
|
931
1245
|
}
|
|
932
|
-
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
|
+
}
|
|
933
1253
|
projections = returning.projections
|
|
934
1254
|
if (returning.sql.length > 0) {
|
|
935
1255
|
sql += ` returning ${returning.sql}`
|
|
@@ -938,19 +1258,33 @@ export const renderQueryAst = (
|
|
|
938
1258
|
}
|
|
939
1259
|
case "update": {
|
|
940
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
|
+
}
|
|
941
1268
|
const targetSource = updateAst.target!
|
|
942
1269
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
943
1270
|
const targets = updateAst.targets ?? [targetSource]
|
|
944
1271
|
const fromSources = updateAst.fromSources ?? []
|
|
1272
|
+
if ((updateAst.set ?? []).length === 0) {
|
|
1273
|
+
throw new Error("update statements require at least one assignment")
|
|
1274
|
+
}
|
|
945
1275
|
const assignments = updateAst.set!.map((entry) =>
|
|
946
1276
|
renderMutationAssignment(entry, state, dialect)).join(", ")
|
|
947
1277
|
if (dialect.name === "mysql") {
|
|
948
1278
|
const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
|
|
949
1279
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
950
1280
|
const joinSources = updateAst.joins.map((join) =>
|
|
951
|
-
join.kind === "
|
|
952
|
-
?
|
|
953
|
-
|
|
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)}`
|
|
954
1288
|
).join(" ")
|
|
955
1289
|
const targetList = [
|
|
956
1290
|
...targets.map((entry) =>
|
|
@@ -982,7 +1316,13 @@ export const renderQueryAst = (
|
|
|
982
1316
|
if (dialect.name === "mysql" && updateAst.limit) {
|
|
983
1317
|
sql += ` limit ${renderMysqlMutationLimit(updateAst.limit, state, dialect)}`
|
|
984
1318
|
}
|
|
985
|
-
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
|
+
}
|
|
986
1326
|
projections = returning.projections
|
|
987
1327
|
if (returning.sql.length > 0) {
|
|
988
1328
|
sql += ` returning ${returning.sql}`
|
|
@@ -991,6 +1331,13 @@ export const renderQueryAst = (
|
|
|
991
1331
|
}
|
|
992
1332
|
case "delete": {
|
|
993
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
|
+
}
|
|
994
1341
|
const targetSource = deleteAst.target!
|
|
995
1342
|
const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
|
|
996
1343
|
const targets = deleteAst.targets ?? [targetSource]
|
|
@@ -1002,9 +1349,13 @@ export const renderQueryAst = (
|
|
|
1002
1349
|
renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
|
|
1003
1350
|
).join(", ")
|
|
1004
1351
|
const joinSources = deleteAst.joins.map((join) =>
|
|
1005
|
-
join.kind === "
|
|
1006
|
-
?
|
|
1007
|
-
|
|
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)}`
|
|
1008
1359
|
).join(" ")
|
|
1009
1360
|
sql = hasJoinedSources
|
|
1010
1361
|
? `delete${modifiers} ${targetList} from ${fromSources}${joinSources.length > 0 ? ` ${joinSources}` : ""}`
|
|
@@ -1028,7 +1379,13 @@ export const renderQueryAst = (
|
|
|
1028
1379
|
if (dialect.name === "mysql" && deleteAst.limit) {
|
|
1029
1380
|
sql += ` limit ${renderMysqlMutationLimit(deleteAst.limit, state, dialect)}`
|
|
1030
1381
|
}
|
|
1031
|
-
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
|
+
}
|
|
1032
1389
|
projections = returning.projections
|
|
1033
1390
|
if (returning.sql.length > 0) {
|
|
1034
1391
|
sql += ` returning ${returning.sql}`
|
|
@@ -1037,12 +1394,17 @@ export const renderQueryAst = (
|
|
|
1037
1394
|
}
|
|
1038
1395
|
case "truncate": {
|
|
1039
1396
|
const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
|
|
1397
|
+
assertNoStatementQueryClauses(truncateAst, "truncate")
|
|
1398
|
+
const truncate = expectTruncateClause(truncateAst.truncate)
|
|
1040
1399
|
const targetSource = truncateAst.target!
|
|
1400
|
+
if (dialect.name === "mysql" && (truncate.restartIdentity || truncate.cascade)) {
|
|
1401
|
+
throw new Error("Unsupported mysql truncate options")
|
|
1402
|
+
}
|
|
1041
1403
|
sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
|
|
1042
|
-
if (
|
|
1404
|
+
if (truncate.restartIdentity) {
|
|
1043
1405
|
sql += " restart identity"
|
|
1044
1406
|
}
|
|
1045
|
-
if (
|
|
1407
|
+
if (truncate.cascade) {
|
|
1046
1408
|
sql += " cascade"
|
|
1047
1409
|
}
|
|
1048
1410
|
break
|
|
@@ -1055,6 +1417,9 @@ export const renderQueryAst = (
|
|
|
1055
1417
|
const targetSource = mergeAst.target!
|
|
1056
1418
|
const usingSource = mergeAst.using!
|
|
1057
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
|
+
}
|
|
1058
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)}`
|
|
1059
1424
|
if (merge.whenMatched) {
|
|
1060
1425
|
sql += " when matched"
|
|
@@ -1084,25 +1449,30 @@ export const renderQueryAst = (
|
|
|
1084
1449
|
case "savepoint":
|
|
1085
1450
|
case "rollbackTo":
|
|
1086
1451
|
case "releaseSavepoint": {
|
|
1452
|
+
assertNoStatementQueryClauses(ast, ast.kind)
|
|
1087
1453
|
sql = renderTransactionClause(ast.transaction!, dialect)
|
|
1088
1454
|
break
|
|
1089
1455
|
}
|
|
1090
1456
|
case "createTable": {
|
|
1091
1457
|
const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
|
|
1092
|
-
|
|
1458
|
+
assertNoStatementQueryClauses(createTableAst, "createTable")
|
|
1459
|
+
const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
|
|
1460
|
+
sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
|
|
1093
1461
|
break
|
|
1094
1462
|
}
|
|
1095
1463
|
case "dropTable": {
|
|
1096
1464
|
const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
|
|
1097
|
-
|
|
1098
|
-
|
|
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)}`
|
|
1099
1468
|
break
|
|
1100
1469
|
}
|
|
1101
1470
|
case "createIndex": {
|
|
1102
1471
|
const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
|
|
1472
|
+
assertNoStatementQueryClauses(createIndexAst, "createIndex")
|
|
1103
1473
|
sql = renderCreateIndexSql(
|
|
1104
1474
|
createIndexAst.target!,
|
|
1105
|
-
createIndexAst.ddl
|
|
1475
|
+
expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
|
|
1106
1476
|
state,
|
|
1107
1477
|
dialect
|
|
1108
1478
|
)
|
|
@@ -1110,17 +1480,20 @@ export const renderQueryAst = (
|
|
|
1110
1480
|
}
|
|
1111
1481
|
case "dropIndex": {
|
|
1112
1482
|
const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
|
|
1483
|
+
assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
|
|
1113
1484
|
sql = renderDropIndexSql(
|
|
1114
1485
|
dropIndexAst.target!,
|
|
1115
|
-
dropIndexAst.ddl
|
|
1486
|
+
expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
|
|
1116
1487
|
state,
|
|
1117
1488
|
dialect
|
|
1118
1489
|
)
|
|
1119
1490
|
break
|
|
1120
1491
|
}
|
|
1492
|
+
default:
|
|
1493
|
+
throw new Error("Unsupported query statement kind")
|
|
1121
1494
|
}
|
|
1122
1495
|
|
|
1123
|
-
if (state.ctes.length === 0) {
|
|
1496
|
+
if (state.ctes.length === 0 || options.emitCtes === false) {
|
|
1124
1497
|
return {
|
|
1125
1498
|
sql,
|
|
1126
1499
|
projections
|
|
@@ -1168,9 +1541,27 @@ const renderSourceReference = (
|
|
|
1168
1541
|
readonly plan: Query.Plan.Any
|
|
1169
1542
|
readonly recursive?: boolean
|
|
1170
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
|
+
}
|
|
1171
1548
|
if (!state.cteNames.has(cte.name)) {
|
|
1172
1549
|
state.cteNames.add(cte.name)
|
|
1173
|
-
|
|
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
|
+
)
|
|
1174
1565
|
state.ctes.push({
|
|
1175
1566
|
name: cte.name,
|
|
1176
1567
|
sql: rendered.sql,
|
|
@@ -1187,14 +1578,14 @@ const renderSourceReference = (
|
|
|
1187
1578
|
if (!state.cteNames.has(derived.name)) {
|
|
1188
1579
|
// derived tables are inlined, so no CTE registration is needed
|
|
1189
1580
|
}
|
|
1190
|
-
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)}`
|
|
1191
1582
|
}
|
|
1192
1583
|
if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
|
|
1193
1584
|
const lateral = source as unknown as {
|
|
1194
1585
|
readonly name: string
|
|
1195
1586
|
readonly plan: Query.Plan.Any
|
|
1196
1587
|
}
|
|
1197
|
-
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)}`
|
|
1198
1589
|
}
|
|
1199
1590
|
if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
|
|
1200
1591
|
const values = source as unknown as {
|
|
@@ -1229,6 +1620,22 @@ const renderSourceReference = (
|
|
|
1229
1620
|
return dialect.renderTableReference(tableName, baseTableName, schemaName)
|
|
1230
1621
|
}
|
|
1231
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
|
+
|
|
1232
1639
|
/**
|
|
1233
1640
|
* Renders a scalar expression AST into SQL text.
|
|
1234
1641
|
*
|
|
@@ -1263,11 +1670,14 @@ export const renderExpression = (
|
|
|
1263
1670
|
: ">="
|
|
1264
1671
|
switch (ast.kind) {
|
|
1265
1672
|
case "column":
|
|
1266
|
-
return ast.tableName.length === 0
|
|
1673
|
+
return state.rowLocalColumns || ast.tableName.length === 0
|
|
1267
1674
|
? dialect.quoteIdentifier(ast.columnName)
|
|
1268
1675
|
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1269
1676
|
case "literal":
|
|
1270
|
-
|
|
1677
|
+
if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
|
|
1678
|
+
throw new Error("Expected a finite numeric value")
|
|
1679
|
+
}
|
|
1680
|
+
return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
|
|
1271
1681
|
case "excluded":
|
|
1272
1682
|
return dialect.name === "mysql"
|
|
1273
1683
|
? `values(${dialect.quoteIdentifier(ast.columnName)})`
|
|
@@ -1329,7 +1739,7 @@ export const renderExpression = (
|
|
|
1329
1739
|
return `(${left} @> ${right})`
|
|
1330
1740
|
}
|
|
1331
1741
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1332
|
-
return `json_contains(${
|
|
1742
|
+
return `json_contains(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
1333
1743
|
}
|
|
1334
1744
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1335
1745
|
case "containedBy":
|
|
@@ -1343,7 +1753,7 @@ export const renderExpression = (
|
|
|
1343
1753
|
return `(${left} <@ ${right})`
|
|
1344
1754
|
}
|
|
1345
1755
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1346
|
-
return `json_contains(${
|
|
1756
|
+
return `json_contains(${renderJsonInputExpression(ast.right, state, dialect)}, ${renderJsonInputExpression(ast.left, state, dialect)})`
|
|
1347
1757
|
}
|
|
1348
1758
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1349
1759
|
case "overlaps":
|
|
@@ -1357,7 +1767,7 @@ export const renderExpression = (
|
|
|
1357
1767
|
return `(${left} && ${right})`
|
|
1358
1768
|
}
|
|
1359
1769
|
if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
|
|
1360
|
-
return `json_overlaps(${
|
|
1770
|
+
return `json_overlaps(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
|
|
1361
1771
|
}
|
|
1362
1772
|
throw new Error("Unsupported container operator for SQL rendering")
|
|
1363
1773
|
case "isNull":
|
|
@@ -1377,14 +1787,26 @@ export const renderExpression = (
|
|
|
1377
1787
|
case "min":
|
|
1378
1788
|
return `min(${renderExpression(ast.value, state, dialect)})`
|
|
1379
1789
|
case "and":
|
|
1790
|
+
if (ast.values.length === 0) {
|
|
1791
|
+
throw new Error("and(...) requires at least one predicate")
|
|
1792
|
+
}
|
|
1380
1793
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
|
|
1381
1794
|
case "or":
|
|
1795
|
+
if (ast.values.length === 0) {
|
|
1796
|
+
throw new Error("or(...) requires at least one predicate")
|
|
1797
|
+
}
|
|
1382
1798
|
return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
|
|
1383
1799
|
case "coalesce":
|
|
1384
1800
|
return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
|
|
1385
1801
|
case "in":
|
|
1802
|
+
if (ast.values.length < 2) {
|
|
1803
|
+
throw new Error("in(...) requires at least one candidate value")
|
|
1804
|
+
}
|
|
1386
1805
|
return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1387
1806
|
case "notIn":
|
|
1807
|
+
if (ast.values.length < 2) {
|
|
1808
|
+
throw new Error("notIn(...) requires at least one candidate value")
|
|
1809
|
+
}
|
|
1388
1810
|
return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
|
|
1389
1811
|
case "between":
|
|
1390
1812
|
return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
|
|
@@ -1395,35 +1817,15 @@ export const renderExpression = (
|
|
|
1395
1817
|
`when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
|
|
1396
1818
|
).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
|
|
1397
1819
|
case "exists":
|
|
1398
|
-
return `exists (${
|
|
1399
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1400
|
-
state,
|
|
1401
|
-
dialect
|
|
1402
|
-
).sql})`
|
|
1820
|
+
return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1403
1821
|
case "scalarSubquery":
|
|
1404
|
-
return `(${
|
|
1405
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1406
|
-
state,
|
|
1407
|
-
dialect
|
|
1408
|
-
).sql})`
|
|
1822
|
+
return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
|
|
1409
1823
|
case "inSubquery":
|
|
1410
|
-
return `(${renderExpression(ast.left, state, dialect)} in (${
|
|
1411
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1412
|
-
state,
|
|
1413
|
-
dialect
|
|
1414
|
-
).sql}))`
|
|
1824
|
+
return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1415
1825
|
case "comparisonAny":
|
|
1416
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${
|
|
1417
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1418
|
-
state,
|
|
1419
|
-
dialect
|
|
1420
|
-
).sql}))`
|
|
1826
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1421
1827
|
case "comparisonAll":
|
|
1422
|
-
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${
|
|
1423
|
-
Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
|
|
1424
|
-
state,
|
|
1425
|
-
dialect
|
|
1426
|
-
).sql}))`
|
|
1828
|
+
return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
|
|
1427
1829
|
case "window": {
|
|
1428
1830
|
if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
|
|
1429
1831
|
break
|