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.
Files changed (88) hide show
  1. package/dist/mysql.js +1957 -595
  2. package/dist/postgres/metadata.js +2507 -182
  3. package/dist/postgres.js +9587 -8201
  4. package/dist/sqlite.js +8360 -0
  5. package/package.json +7 -2
  6. package/src/internal/column-state.ts +7 -0
  7. package/src/internal/column.ts +22 -0
  8. package/src/internal/derived-table.ts +29 -3
  9. package/src/internal/dialect.ts +14 -1
  10. package/src/internal/dsl-mutation-runtime.ts +173 -4
  11. package/src/internal/dsl-plan-runtime.ts +165 -20
  12. package/src/internal/dsl-query-runtime.ts +60 -6
  13. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  14. package/src/internal/executor.ts +62 -13
  15. package/src/internal/expression-ast.ts +3 -2
  16. package/src/internal/grouping-key.ts +141 -1
  17. package/src/internal/implication-runtime.ts +2 -1
  18. package/src/internal/json/types.ts +155 -40
  19. package/src/internal/predicate/analysis.ts +103 -1
  20. package/src/internal/predicate/atom.ts +7 -0
  21. package/src/internal/predicate/context.ts +170 -17
  22. package/src/internal/predicate/key.ts +64 -2
  23. package/src/internal/predicate/normalize.ts +115 -34
  24. package/src/internal/predicate/runtime.ts +144 -13
  25. package/src/internal/query.ts +563 -103
  26. package/src/internal/renderer.ts +39 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +244 -0
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +5 -3
  30. package/src/internal/runtime/value.ts +153 -30
  31. package/src/internal/scalar.ts +11 -0
  32. package/src/internal/table-options.ts +108 -1
  33. package/src/internal/table.ts +87 -29
  34. package/src/mysql/column.ts +19 -2
  35. package/src/mysql/datatypes/index.ts +21 -0
  36. package/src/mysql/errors/catalog.ts +5 -5
  37. package/src/mysql/errors/normalize.ts +2 -2
  38. package/src/mysql/executor.ts +20 -5
  39. package/src/mysql/internal/dialect.ts +12 -6
  40. package/src/mysql/internal/dsl.ts +995 -263
  41. package/src/mysql/internal/renderer.ts +13 -3
  42. package/src/mysql/internal/sql-expression-renderer.ts +530 -128
  43. package/src/mysql/query.ts +9 -2
  44. package/src/mysql/renderer.ts +7 -2
  45. package/src/mysql/table.ts +38 -12
  46. package/src/postgres/cast.ts +22 -7
  47. package/src/postgres/column.ts +5 -2
  48. package/src/postgres/errors/normalize.ts +2 -2
  49. package/src/postgres/executor.ts +68 -10
  50. package/src/postgres/function/core.ts +19 -1
  51. package/src/postgres/internal/dialect.ts +12 -6
  52. package/src/postgres/internal/dsl.ts +958 -288
  53. package/src/postgres/internal/renderer.ts +13 -3
  54. package/src/postgres/internal/schema-ddl.ts +2 -1
  55. package/src/postgres/internal/schema-model.ts +6 -3
  56. package/src/postgres/internal/sql-expression-renderer.ts +477 -96
  57. package/src/postgres/json.ts +57 -17
  58. package/src/postgres/query.ts +9 -2
  59. package/src/postgres/renderer.ts +7 -2
  60. package/src/postgres/schema-management.ts +91 -4
  61. package/src/postgres/schema.ts +1 -1
  62. package/src/postgres/table.ts +189 -53
  63. package/src/postgres/type.ts +4 -0
  64. package/src/sqlite/column.ts +128 -0
  65. package/src/sqlite/datatypes/index.ts +79 -0
  66. package/src/sqlite/datatypes/spec.ts +98 -0
  67. package/src/sqlite/errors/catalog.ts +103 -0
  68. package/src/sqlite/errors/fields.ts +19 -0
  69. package/src/sqlite/errors/index.ts +19 -0
  70. package/src/sqlite/errors/normalize.ts +229 -0
  71. package/src/sqlite/errors/requirements.ts +71 -0
  72. package/src/sqlite/errors/types.ts +29 -0
  73. package/src/sqlite/executor.ts +227 -0
  74. package/src/sqlite/function/aggregate.ts +2 -0
  75. package/src/sqlite/function/core.ts +2 -0
  76. package/src/sqlite/function/index.ts +19 -0
  77. package/src/sqlite/function/string.ts +2 -0
  78. package/src/sqlite/function/temporal.ts +100 -0
  79. package/src/sqlite/function/window.ts +2 -0
  80. package/src/sqlite/internal/dialect.ts +37 -0
  81. package/src/sqlite/internal/dsl.ts +6926 -0
  82. package/src/sqlite/internal/renderer.ts +47 -0
  83. package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
  84. package/src/sqlite/json.ts +2 -0
  85. package/src/sqlite/query.ts +196 -0
  86. package/src/sqlite/renderer.ts +24 -0
  87. package/src/sqlite/table.ts +183 -0
  88. package/src/sqlite.ts +22 -0
@@ -0,0 +1,1821 @@
1
+ import * as Schema from "effect/Schema"
2
+
3
+ import * as Query from "../../internal/query.js"
4
+ import * as Expression from "../../internal/scalar.js"
5
+ import * as Table from "../../internal/table.js"
6
+ import * as QueryAst from "../../internal/query-ast.js"
7
+ import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
8
+ import * as ExpressionAst from "../../internal/expression-ast.js"
9
+ import * as JsonPath from "../../internal/json/path.js"
10
+ import { expectConflictClause, expectInsertSourceKind } from "../../internal/dsl-mutation-runtime.js"
11
+ import { expectDdlClauseKind } from "../../internal/dsl-transaction-ddl-runtime.js"
12
+ import {
13
+ renderJsonSelectSql,
14
+ renderSelectSql,
15
+ toDriverValue
16
+ } from "../../internal/runtime/driver-value-mapping.js"
17
+ import { normalizeDbValue } from "../../internal/runtime/normalize.js"
18
+ import { flattenSelection, type Projection } from "../../internal/projections.js"
19
+ import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
20
+ import * as SchemaExpression from "../../internal/schema-expression.js"
21
+ import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
22
+
23
+ const renderDbType = (
24
+ dialect: SqlDialect,
25
+ dbType: Expression.DbType.Any
26
+ ): string => {
27
+ if (dialect.name === "sqlite" && dbType.dialect === "sqlite" && dbType.kind === "uuid") {
28
+ return "text"
29
+ }
30
+ return dbType.kind
31
+ }
32
+
33
+ const renderCastType = (
34
+ dialect: SqlDialect,
35
+ dbType: Expression.DbType.Any
36
+ ): string => {
37
+ if (dialect.name !== "sqlite") {
38
+ return dbType.kind
39
+ }
40
+ switch (dbType.kind) {
41
+ case "text":
42
+ return "text"
43
+ case "uuid":
44
+ return "text"
45
+ case "numeric":
46
+ return "numeric"
47
+ case "int":
48
+ return "integer"
49
+ case "time":
50
+ return "time"
51
+ case "timestamp":
52
+ return "datetime"
53
+ case "bool":
54
+ case "boolean":
55
+ return "boolean"
56
+ case "json":
57
+ return "json"
58
+ default:
59
+ return dbType.kind
60
+ }
61
+ }
62
+
63
+ const renderSqliteDdlString = (value: string): string =>
64
+ `'${value.replaceAll("'", "''")}'`
65
+
66
+ const renderSqliteDdlBytes = (value: Uint8Array): string =>
67
+ `x'${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}'`
68
+
69
+ const renderSqliteDdlLiteral = (
70
+ value: unknown,
71
+ state: RenderState,
72
+ context: RenderValueContext = {}
73
+ ): string => {
74
+ const driverValue = toDriverValue(value, {
75
+ dialect: "sqlite",
76
+ valueMappings: state.valueMappings,
77
+ ...context
78
+ })
79
+ if (driverValue === null) {
80
+ return "null"
81
+ }
82
+ switch (typeof driverValue) {
83
+ case "boolean":
84
+ return driverValue ? "1" : "0"
85
+ case "number":
86
+ if (!Number.isFinite(driverValue)) {
87
+ throw new Error("Expected a finite numeric value")
88
+ }
89
+ return String(driverValue)
90
+ case "bigint":
91
+ return driverValue.toString()
92
+ case "string":
93
+ return renderSqliteDdlString(driverValue)
94
+ case "object":
95
+ if (driverValue instanceof Uint8Array) {
96
+ return renderSqliteDdlBytes(driverValue)
97
+ }
98
+ break
99
+ }
100
+ throw new Error("Unsupported sqlite DDL literal value")
101
+ }
102
+
103
+ const renderDdlExpression = (
104
+ expression: DdlExpressionLike,
105
+ state: RenderState,
106
+ dialect: SqlDialect
107
+ ): string => {
108
+ if (SchemaExpression.isSchemaExpression(expression)) {
109
+ return SchemaExpression.render(expression)
110
+ }
111
+ return renderExpression(expression, state, {
112
+ ...dialect,
113
+ renderLiteral: renderSqliteDdlLiteral
114
+ })
115
+ }
116
+
117
+ const renderSqliteMutationLimit = (
118
+ expression: Expression.Any,
119
+ state: RenderState,
120
+ dialect: SqlDialect
121
+ ): string => {
122
+ const ast = (expression as Expression.Any & {
123
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
124
+ })[ExpressionAst.TypeId]
125
+ if (ast.kind === "literal" && typeof ast.value === "number" && Number.isInteger(ast.value) && ast.value >= 0) {
126
+ return String(ast.value)
127
+ }
128
+ return renderExpression(expression, state, dialect)
129
+ }
130
+
131
+ const renderColumnDefinition = (
132
+ dialect: SqlDialect,
133
+ state: RenderState,
134
+ columnName: string,
135
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
136
+ ): string => {
137
+ const clauses = [
138
+ dialect.quoteIdentifier(columnName),
139
+ column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
140
+ ]
141
+ if (column.metadata.identity) {
142
+ clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
143
+ } else if (column.metadata.generatedValue) {
144
+ clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
145
+ } else if (column.metadata.defaultValue) {
146
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
147
+ }
148
+ if (!column.metadata.nullable) {
149
+ clauses.push("not null")
150
+ }
151
+ return clauses.join(" ")
152
+ }
153
+
154
+ const renderCreateTableSql = (
155
+ targetSource: QueryAst.FromClause,
156
+ state: RenderState,
157
+ dialect: SqlDialect,
158
+ ifNotExists: boolean
159
+ ): string => {
160
+ const table = targetSource.source as Table.AnyTable
161
+ const fields = table[Table.TypeId].fields
162
+ const definitions = Object.entries(fields).map(([columnName, column]) =>
163
+ renderColumnDefinition(dialect, state, columnName, column)
164
+ )
165
+ for (const option of table[Table.OptionsSymbol]) {
166
+ switch (option.kind) {
167
+ case "primaryKey":
168
+ 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" : ""}` : ""}`)
169
+ break
170
+ case "unique":
171
+ if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
172
+ throw new Error("Unsupported sqlite unique constraint options")
173
+ }
174
+ 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" : ""}` : ""}`)
175
+ break
176
+ case "foreignKey": {
177
+ const reference = option.references()
178
+ definitions.push(
179
+ `${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" : ""}` : ""}`
180
+ )
181
+ break
182
+ }
183
+ case "check":
184
+ definitions.push(
185
+ `constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
186
+ )
187
+ break
188
+ case "index":
189
+ break
190
+ default:
191
+ throw new Error("Unsupported table option kind")
192
+ }
193
+ }
194
+ return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
195
+ }
196
+
197
+ const renderCreateIndexSql = (
198
+ targetSource: QueryAst.FromClause,
199
+ ddl: Extract<QueryAst.DdlClause, { readonly kind: "createIndex" }>,
200
+ state: RenderState,
201
+ dialect: SqlDialect
202
+ ): string => {
203
+ const maybeIfNotExists = (dialect.name === "postgres" || dialect.name === "sqlite") && ddl.ifNotExists ? " if not exists" : ""
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(", ")})`
205
+ }
206
+
207
+ const renderDropIndexSql = (
208
+ targetSource: QueryAst.FromClause,
209
+ ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
210
+ state: RenderState,
211
+ dialect: SqlDialect
212
+ ): string =>
213
+ dialect.name === "postgres" || dialect.name === "sqlite"
214
+ ? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
215
+ : `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
216
+
217
+ const isExpression = (value: unknown): value is Expression.Any =>
218
+ value !== null && typeof value === "object" && Expression.TypeId in value
219
+
220
+ const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
221
+ dbType.kind === "jsonb" || dbType.kind === "json" || ("variant" in dbType && dbType.variant === "json")
222
+
223
+ const isJsonExpression = (value: unknown): value is Expression.Any =>
224
+ isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
225
+
226
+ const unsupportedJsonFeature = (
227
+ dialect: SqlDialect,
228
+ feature: string
229
+ ): never => {
230
+ const error = new Error(`Unsupported JSON feature for ${dialect.name}: ${feature}`) as Error & {
231
+ readonly tag: string
232
+ readonly dialect: string
233
+ readonly feature: string
234
+ }
235
+ Object.assign(error, {
236
+ tag: `@${dialect.name}/unsupported/json-feature`,
237
+ dialect: dialect.name,
238
+ feature
239
+ })
240
+ throw error
241
+ }
242
+
243
+ const extractJsonBase = (node: Record<string, unknown>): unknown =>
244
+ node.value ?? node.base ?? node.input ?? node.left ?? node.target
245
+
246
+ const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
247
+ value !== null && typeof value === "object" && JsonPath.TypeId in value
248
+
249
+ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
250
+ const path = node.path ?? node.segments ?? node.keys
251
+ if (isJsonPathValue(path)) {
252
+ return path.segments
253
+ }
254
+ if (Array.isArray(path)) {
255
+ return path as readonly JsonPath.AnySegment[]
256
+ }
257
+ if ("key" in node) {
258
+ return [JsonPath.key(String(node.key))]
259
+ }
260
+ if ("segment" in node) {
261
+ const segment = node.segment
262
+ if (typeof segment === "string") {
263
+ return [JsonPath.key(segment)]
264
+ }
265
+ if (typeof segment === "number") {
266
+ return [JsonPath.index(segment)]
267
+ }
268
+ if (segment !== null && typeof segment === "object" && JsonPath.SegmentTypeId in segment) {
269
+ return [segment as JsonPath.AnySegment]
270
+ }
271
+ return []
272
+ }
273
+ if ("right" in node && isJsonPathValue(node.right)) {
274
+ return node.right.segments
275
+ }
276
+ return []
277
+ }
278
+
279
+ const extractJsonValue = (node: Record<string, unknown>): unknown =>
280
+ node.newValue ?? node.insert ?? node.right
281
+
282
+ const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
283
+ const renderKey = (value: string): string =>
284
+ /^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
285
+ ? `.${value}`
286
+ : `.${JSON.stringify(value)}`
287
+ if (typeof segment === "string") {
288
+ return renderKey(segment)
289
+ }
290
+ if (typeof segment === "number") {
291
+ return `[${segment}]`
292
+ }
293
+ switch (segment.kind) {
294
+ case "key":
295
+ return renderKey(segment.key)
296
+ case "index":
297
+ return `[${segment.index}]`
298
+ case "wildcard":
299
+ return "[*]"
300
+ case "slice":
301
+ return `[${segment.start ?? 0} to ${segment.end ?? "last"}]`
302
+ case "descend":
303
+ return ".**"
304
+ default:
305
+ throw new Error("Unsupported JSON path segment")
306
+ }
307
+ }
308
+
309
+ const renderSqliteJsonIndex = (index: number): string =>
310
+ index >= 0 ? String(index) : `#${index}`
311
+
312
+ const renderSqliteJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
313
+ if (typeof segment === "number") {
314
+ return `[${renderSqliteJsonIndex(segment)}]`
315
+ }
316
+ if (typeof segment === "object" && segment !== null && segment.kind === "index") {
317
+ return `[${renderSqliteJsonIndex(segment.index)}]`
318
+ }
319
+ if (typeof segment === "object" && segment !== null && segment.kind === "slice") {
320
+ throw new Error("SQLite JSON paths do not support slice segments")
321
+ }
322
+ if (typeof segment === "object" && segment !== null && segment.kind === "wildcard") {
323
+ throw new Error("SQLite JSON paths do not support wildcard segments")
324
+ }
325
+ if (typeof segment === "object" && segment !== null && segment.kind === "descend") {
326
+ throw new Error("SQLite JSON paths do not support recursive descent segments")
327
+ }
328
+ return renderJsonPathSegment(segment)
329
+ }
330
+
331
+ const renderJsonPathStringLiteral = (
332
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
333
+ renderSegment: (segment: JsonPath.AnySegment | string | number) => string = renderJsonPathSegment
334
+ ): string => {
335
+ let path = "$"
336
+ for (const segment of segments) {
337
+ path += renderSegment(segment)
338
+ }
339
+ return path
340
+ }
341
+
342
+ const renderSqliteJsonPath = (
343
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
344
+ state: RenderState,
345
+ dialect: SqlDialect
346
+ ): string => dialect.renderLiteral(renderJsonPathStringLiteral(segments, renderSqliteJsonPathSegment), state)
347
+
348
+ const isJsonArrayIndexSegment = (segment: JsonPath.AnySegment | string | number | undefined): boolean =>
349
+ typeof segment === "number" || (typeof segment === "object" && segment !== null && segment.kind === "index")
350
+
351
+ const renderSqliteJsonInsertPath = (
352
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
353
+ insertAfter: boolean,
354
+ state: RenderState,
355
+ dialect: SqlDialect
356
+ ): string => {
357
+ if (!insertAfter || segments.length === 0) {
358
+ return renderSqliteJsonPath(segments, state, dialect)
359
+ }
360
+ const last = segments[segments.length - 1]
361
+ const nextSegments = segments.slice(0, -1)
362
+ if (typeof last === "number") {
363
+ return renderSqliteJsonPath([...nextSegments, last + 1], state, dialect)
364
+ }
365
+ if (typeof last === "object" && last !== null && last.kind === "index") {
366
+ return renderSqliteJsonPath([...nextSegments, { ...last, index: last.index + 1 }], state, dialect)
367
+ }
368
+ return renderSqliteJsonPath(segments, state, dialect)
369
+ }
370
+
371
+ const renderPostgresJsonPathArray = (
372
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
373
+ state: RenderState,
374
+ dialect: SqlDialect
375
+ ): string => `array[${segments.map((segment) => {
376
+ if (typeof segment === "string") {
377
+ return dialect.renderLiteral(segment, state)
378
+ }
379
+ if (typeof segment === "number") {
380
+ return dialect.renderLiteral(String(segment), state)
381
+ }
382
+ switch (segment.kind) {
383
+ case "key":
384
+ return dialect.renderLiteral(segment.key, state)
385
+ case "index":
386
+ return dialect.renderLiteral(String(segment.index), state)
387
+ default:
388
+ throw new Error("Postgres JSON traversal requires exact key/index segments")
389
+ }
390
+ }).join(", ")}]`
391
+
392
+ const renderPostgresTextLiteral = (
393
+ value: string,
394
+ state: RenderState,
395
+ dialect: SqlDialect
396
+ ): string => `cast(${dialect.renderLiteral(value, state)} as text)`
397
+
398
+ const renderPostgresJsonAccessStep = (
399
+ segment: JsonPath.AnySegment,
400
+ textMode: boolean,
401
+ state: RenderState,
402
+ dialect: SqlDialect
403
+ ): string => {
404
+ switch (segment.kind) {
405
+ case "key":
406
+ return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.key, state)}`
407
+ case "index":
408
+ return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(String(segment.index), state)}`
409
+ default:
410
+ throw new Error("Postgres exact JSON access requires key/index segments")
411
+ }
412
+ }
413
+
414
+ const renderPostgresJsonValue = (
415
+ value: unknown,
416
+ state: RenderState,
417
+ dialect: SqlDialect
418
+ ): string => {
419
+ if (!isExpression(value)) {
420
+ throw new Error("Expected a JSON expression")
421
+ }
422
+ const rendered = renderExpression(value, state, dialect)
423
+ return value[Expression.TypeId].dbType.kind === "jsonb"
424
+ ? rendered
425
+ : `cast(${rendered} as jsonb)`
426
+ }
427
+
428
+ const expressionDriverContext = (
429
+ expression: Expression.Any,
430
+ state: RenderState,
431
+ dialect: SqlDialect
432
+ ) => ({
433
+ dialect: dialect.name,
434
+ valueMappings: state.valueMappings,
435
+ dbType: expression[Expression.TypeId].dbType,
436
+ runtimeSchema: expression[Expression.TypeId].runtimeSchema,
437
+ driverValueMapping: expression[Expression.TypeId].driverValueMapping
438
+ })
439
+
440
+ const renderJsonInputExpression = (
441
+ expression: Expression.Any,
442
+ state: RenderState,
443
+ dialect: SqlDialect
444
+ ): string => {
445
+ if (dialect.name === "sqlite" && isJsonDbType(expression[Expression.TypeId].dbType)) {
446
+ const ast = (expression as Expression.Any & {
447
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
448
+ })[ExpressionAst.TypeId]
449
+ if (ast.kind === "literal") {
450
+ state.params.push(JSON.stringify(ast.value))
451
+ return "json(?)"
452
+ }
453
+ return `json(${renderExpression(expression, state, dialect)})`
454
+ }
455
+ return renderJsonSelectSql(
456
+ renderExpression(expression, state, dialect),
457
+ expressionDriverContext(expression, state, dialect)
458
+ )
459
+ }
460
+
461
+ const encodeArrayValues = (
462
+ values: readonly unknown[],
463
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
464
+ state: RenderState,
465
+ dialect: SqlDialect
466
+ ): readonly unknown[] =>
467
+ values.map((value) => {
468
+ if (value === null && column.metadata.nullable) {
469
+ return null
470
+ }
471
+ const runtimeSchemaAccepts = column.schema !== undefined &&
472
+ (Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
473
+ const normalizedValue = runtimeSchemaAccepts
474
+ ? value
475
+ : normalizeDbValue(column.metadata.dbType, value)
476
+ const encodedValue = column.schema === undefined || runtimeSchemaAccepts
477
+ ? normalizedValue
478
+ : (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
479
+ return toDriverValue(encodedValue, {
480
+ dialect: dialect.name,
481
+ valueMappings: state.valueMappings,
482
+ dbType: column.metadata.dbType,
483
+ runtimeSchema: column.schema,
484
+ driverValueMapping: column.metadata.driverValueMapping
485
+ })
486
+ })
487
+
488
+ const renderPostgresJsonKind = (
489
+ value: Expression.Any
490
+ ): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
491
+
492
+ const renderJsonOpaquePath = (
493
+ value: unknown,
494
+ state: RenderState,
495
+ dialect: SqlDialect
496
+ ): string => {
497
+ if (isJsonPathValue(value)) {
498
+ const renderSegment = dialect.name === "sqlite"
499
+ ? renderSqliteJsonPathSegment
500
+ : renderJsonPathSegment
501
+ return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
502
+ }
503
+ if (typeof value === "string") {
504
+ return dialect.renderLiteral(value, state)
505
+ }
506
+ if (isExpression(value)) {
507
+ return renderExpression(value, state, dialect)
508
+ }
509
+ throw new Error("Unsupported SQL/JSON path input")
510
+ }
511
+
512
+ const renderFunctionCall = (
513
+ name: string,
514
+ args: readonly Expression.Any[],
515
+ state: RenderState,
516
+ dialect: SqlDialect
517
+ ): string => {
518
+ if (name === "array") {
519
+ return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
520
+ }
521
+ if (name === "extract" && args.length === 2) {
522
+ const field = args[0]
523
+ const source = args[1]
524
+ if (field === undefined) {
525
+ throw new Error("Unsupported SQL extract expression")
526
+ }
527
+ if (source === undefined) {
528
+ throw new Error("Unsupported SQL extract expression")
529
+ }
530
+ const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
531
+ ? field[Expression.TypeId].runtime
532
+ : undefined
533
+ const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
534
+ return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
535
+ }
536
+ const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
537
+ if (args.length === 0) {
538
+ switch (name) {
539
+ case "current_date":
540
+ case "current_time":
541
+ case "current_timestamp":
542
+ return name
543
+ case "localtime":
544
+ return "time('now', 'localtime')"
545
+ case "localtimestamp":
546
+ return "datetime('now', 'localtime')"
547
+ case "now":
548
+ return "current_timestamp"
549
+ default:
550
+ return `${name}()`
551
+ }
552
+ }
553
+ return `${name}(${renderedArgs})`
554
+ }
555
+
556
+ const renderJsonExpression = (
557
+ expression: Expression.Any,
558
+ ast: Record<string, unknown>,
559
+ state: RenderState,
560
+ dialect: SqlDialect
561
+ ): string | undefined => {
562
+ const kind = typeof ast.kind === "string" ? ast.kind : undefined
563
+ if (!kind) {
564
+ return undefined
565
+ }
566
+
567
+ const base = extractJsonBase(ast)
568
+ const segments = extractJsonPathSegments(ast)
569
+ const exact = segments.every((segment) => segment.kind === "key" || segment.kind === "index")
570
+ const postgresExpressionKind = dialect.name === "postgres" && isJsonExpression(expression)
571
+ ? renderPostgresJsonKind(expression)
572
+ : undefined
573
+ const postgresBaseKind = dialect.name === "postgres" && isJsonExpression(base)
574
+ ? renderPostgresJsonKind(base)
575
+ : undefined
576
+
577
+ switch (kind) {
578
+ case "jsonGet":
579
+ case "jsonPath":
580
+ case "jsonAccess":
581
+ case "jsonTraverse":
582
+ case "jsonGetText":
583
+ case "jsonPathText":
584
+ case "jsonAccessText":
585
+ case "jsonTraverseText": {
586
+ if (!isExpression(base) || segments.length === 0) {
587
+ return undefined
588
+ }
589
+ const baseSql = renderExpression(base, state, dialect)
590
+ const textMode = kind.endsWith("Text") || ast.text === true || ast.asText === true
591
+ if (dialect.name === "postgres") {
592
+ if (exact) {
593
+ return segments.length === 1
594
+ ? `(${baseSql} ${renderPostgresJsonAccessStep(segments[0]!, textMode, state, dialect)})`
595
+ : `(${baseSql} ${textMode ? "#>>" : "#>"} ${renderPostgresJsonPathArray(segments, state, dialect)})`
596
+ }
597
+ const jsonPathLiteral = dialect.renderLiteral(renderJsonPathStringLiteral(segments), state)
598
+ const queried = `jsonb_path_query_first(${renderPostgresJsonValue(base, state, dialect)}, ${jsonPathLiteral})`
599
+ return textMode ? `(${queried} #>> '{}')` : queried
600
+ }
601
+ if (dialect.name === "sqlite") {
602
+ const extracted = `json_extract(${baseSql}, ${renderSqliteJsonPath(segments, state, dialect)})`
603
+ return extracted
604
+ }
605
+ return undefined
606
+ }
607
+ case "jsonHasKey":
608
+ case "jsonKeyExists":
609
+ case "jsonHasAnyKeys":
610
+ case "jsonHasAllKeys": {
611
+ if (!isExpression(base)) {
612
+ return undefined
613
+ }
614
+ const baseSql = dialect.name === "postgres"
615
+ ? renderPostgresJsonValue(base, state, dialect)
616
+ : renderExpression(base, state, dialect)
617
+ const keys = segments
618
+ if (keys.length === 0) {
619
+ return undefined
620
+ }
621
+ if (dialect.name === "postgres") {
622
+ if (kind === "jsonHasAnyKeys") {
623
+ return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
624
+ }
625
+ if (kind === "jsonHasAllKeys") {
626
+ return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
627
+ }
628
+ return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
629
+ }
630
+ if (dialect.name === "sqlite") {
631
+ const renderBase = () => renderExpression(base, state, dialect)
632
+ const checks = keys.map((segment) => `json_type(${renderBase()}, ${renderSqliteJsonPath([segment], state, dialect)}) is not null`)
633
+ return `(${checks.join(kind === "jsonHasAllKeys" ? " and " : " or ")})`
634
+ }
635
+ return undefined
636
+ }
637
+ case "jsonConcat":
638
+ case "jsonMerge": {
639
+ if (!isExpression(ast.left) || !isExpression(ast.right)) {
640
+ return undefined
641
+ }
642
+ if (dialect.name === "postgres") {
643
+ return `(${renderPostgresJsonValue(ast.left, state, dialect)} || ${renderPostgresJsonValue(ast.right, state, dialect)})`
644
+ }
645
+ if (dialect.name === "sqlite") {
646
+ return `json_patch(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
647
+ }
648
+ return undefined
649
+ }
650
+ case "jsonBuildObject": {
651
+ const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
652
+ ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
653
+ : []
654
+ const renderedEntries = entries.flatMap((entry) => [
655
+ dialect.renderLiteral(entry.key, state),
656
+ renderJsonInputExpression(entry.value, state, dialect)
657
+ ])
658
+ if (dialect.name === "postgres") {
659
+ return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
660
+ }
661
+ if (dialect.name === "sqlite") {
662
+ return `json_object(${renderedEntries.join(", ")})`
663
+ }
664
+ return undefined
665
+ }
666
+ case "jsonBuildArray": {
667
+ const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
668
+ ? (ast as { readonly values: readonly Expression.Any[] }).values
669
+ : []
670
+ const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
671
+ if (dialect.name === "postgres") {
672
+ return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
673
+ }
674
+ if (dialect.name === "sqlite") {
675
+ return `json_array(${renderedValues})`
676
+ }
677
+ return undefined
678
+ }
679
+ case "jsonToJson":
680
+ if (!isExpression(base)) {
681
+ return undefined
682
+ }
683
+ if (dialect.name === "postgres") {
684
+ return `to_json(${renderJsonInputExpression(base, state, dialect)})`
685
+ }
686
+ if (dialect.name === "sqlite") {
687
+ return `json_quote(${renderExpression(base, state, dialect)})`
688
+ }
689
+ return undefined
690
+ case "jsonToJsonb":
691
+ if (!isExpression(base)) {
692
+ return undefined
693
+ }
694
+ if (dialect.name === "postgres") {
695
+ return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
696
+ }
697
+ if (dialect.name === "sqlite") {
698
+ return `json_quote(${renderExpression(base, state, dialect)})`
699
+ }
700
+ return undefined
701
+ case "jsonTypeOf":
702
+ if (!isExpression(base)) {
703
+ return undefined
704
+ }
705
+ if (dialect.name === "postgres") {
706
+ const baseSql = renderExpression(base, state, dialect)
707
+ return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof(${baseSql})`
708
+ }
709
+ if (dialect.name === "sqlite") {
710
+ return `json_type(${renderExpression(base, state, dialect)})`
711
+ }
712
+ return undefined
713
+ case "jsonLength":
714
+ if (!isExpression(base)) {
715
+ return undefined
716
+ }
717
+ if (dialect.name === "postgres") {
718
+ const baseSql = renderExpression(base, state, dialect)
719
+ const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
720
+ const arrayLength = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_array_length`
721
+ const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
722
+ return `(case when ${typeOf}(${baseSql}) = 'array' then ${arrayLength}(${baseSql}) when ${typeOf}(${baseSql}) = 'object' then (select count(*)::int from ${objectKeys}(${baseSql})) else null end)`
723
+ }
724
+ if (dialect.name === "sqlite") {
725
+ if (segments.length > 0) {
726
+ return `json_array_length(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)})`
727
+ }
728
+ const renderBase = () => renderExpression(base, state, dialect)
729
+ return `(case when json_type(${renderBase()}) = 'array' then json_array_length(${renderBase()}) when json_type(${renderBase()}) = 'object' then (select count(*) from json_each(${renderBase()})) else null end)`
730
+ }
731
+ return undefined
732
+ case "jsonKeys":
733
+ if (!isExpression(base)) {
734
+ return undefined
735
+ }
736
+ if (dialect.name === "postgres") {
737
+ const baseSql = renderExpression(base, state, dialect)
738
+ const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
739
+ const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
740
+ return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
741
+ }
742
+ if (dialect.name === "sqlite") {
743
+ const renderBase = () => renderExpression(base, state, dialect)
744
+ return `(case when json_type(${renderBase()}) = 'object' then (select json_group_array(key) from json_each(${renderBase()})) else null end)`
745
+ }
746
+ return undefined
747
+ case "jsonStripNulls":
748
+ if (!isExpression(base)) {
749
+ return undefined
750
+ }
751
+ if (dialect.name === "postgres") {
752
+ return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_strip_nulls(${renderExpression(base, state, dialect)})`
753
+ }
754
+ unsupportedJsonFeature(dialect, "jsonStripNulls")
755
+ return undefined
756
+ case "jsonDelete":
757
+ case "jsonDeletePath":
758
+ case "jsonRemove": {
759
+ if (!isExpression(base) || segments.length === 0) {
760
+ return undefined
761
+ }
762
+ if (dialect.name === "postgres") {
763
+ const baseSql = renderPostgresJsonValue(base, state, dialect)
764
+ if (segments.length === 1 && (segments[0]!.kind === "key" || segments[0]!.kind === "index")) {
765
+ const segment = segments[0]!
766
+ return `(${baseSql} - ${segment.kind === "key"
767
+ ? dialect.renderLiteral(segment.key, state)
768
+ : dialect.renderLiteral(String(segment.index), state)})`
769
+ }
770
+ return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
771
+ }
772
+ if (dialect.name === "sqlite") {
773
+ return `json_remove(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)})`
774
+ }
775
+ return undefined
776
+ }
777
+ case "jsonSet":
778
+ case "jsonInsert": {
779
+ if (!isExpression(base) || segments.length === 0) {
780
+ return undefined
781
+ }
782
+ const nextValue = extractJsonValue(ast)
783
+ if (!isExpression(nextValue)) {
784
+ return undefined
785
+ }
786
+ const createMissing = ast.createMissing === true
787
+ const insertAfter = ast.insertAfter === true
788
+ if (dialect.name === "postgres") {
789
+ const functionName = kind === "jsonInsert" ? "jsonb_insert" : "jsonb_set"
790
+ const extra =
791
+ kind === "jsonInsert"
792
+ ? `, ${insertAfter ? "true" : "false"}`
793
+ : `, ${createMissing ? "true" : "false"}`
794
+ return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
795
+ }
796
+ if (dialect.name === "sqlite") {
797
+ if (kind === "jsonInsert" && isJsonArrayIndexSegment(segments[segments.length - 1])) {
798
+ unsupportedJsonFeature(dialect, insertAfter ? "jsonInsertAfter" : "jsonInsertArrayIndex")
799
+ }
800
+ const functionName = kind === "jsonInsert" ? "json_insert" : createMissing ? "json_set" : "json_replace"
801
+ return `${functionName}(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)}, ${renderJsonInputExpression(nextValue, state, dialect)})`
802
+ }
803
+ return undefined
804
+ }
805
+ case "jsonPathExists": {
806
+ if (!isExpression(base)) {
807
+ return undefined
808
+ }
809
+ const path = ast.path ?? ast.query ?? ast.right
810
+ if (path === undefined) {
811
+ return undefined
812
+ }
813
+ if (dialect.name === "postgres") {
814
+ return `(${renderPostgresJsonValue(base, state, dialect)} @? ${renderJsonOpaquePath(path, state, dialect)})`
815
+ }
816
+ if (dialect.name === "sqlite") {
817
+ return `(json_type(${renderExpression(base, state, dialect)}, ${renderJsonOpaquePath(path, state, dialect)}) is not null)`
818
+ }
819
+ return undefined
820
+ }
821
+ case "jsonPathMatch": {
822
+ if (!isExpression(base)) {
823
+ return undefined
824
+ }
825
+ const path = ast.path ?? ast.query ?? ast.right
826
+ if (path === undefined) {
827
+ return undefined
828
+ }
829
+ if (dialect.name === "postgres") {
830
+ return `(${renderPostgresJsonValue(base, state, dialect)} @@ ${renderJsonOpaquePath(path, state, dialect)})`
831
+ }
832
+ unsupportedJsonFeature(dialect, "jsonPathMatch")
833
+ }
834
+ }
835
+
836
+ return undefined
837
+ }
838
+
839
+ export interface RenderedQueryAst {
840
+ readonly sql: string
841
+ readonly projections: readonly Projection[]
842
+ }
843
+
844
+ const selectionProjections = (selection: Record<string, unknown>): readonly Projection[] =>
845
+ flattenSelection(selection).map(({ path, alias }) => ({
846
+ path,
847
+ alias
848
+ }))
849
+
850
+ const renderMutationAssignment = (
851
+ entry: QueryAst.AssignmentClause,
852
+ state: RenderState,
853
+ dialect: SqlDialect
854
+ ): string => {
855
+ const column = entry.tableName && dialect.name === "sqlite"
856
+ ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
857
+ : dialect.quoteIdentifier(entry.columnName)
858
+ return `${column} = ${renderExpression(entry.value, state, dialect)}`
859
+ }
860
+
861
+ const renderJoinSourcesForMutation = (
862
+ joins: readonly QueryAst.JoinClause[],
863
+ state: RenderState,
864
+ dialect: SqlDialect
865
+ ): string => joins.map((join) =>
866
+ renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
867
+ ).join(", ")
868
+
869
+ const renderFromSources = (
870
+ sources: readonly QueryAst.FromClause[],
871
+ state: RenderState,
872
+ dialect: SqlDialect
873
+ ): string => sources.map((source) =>
874
+ renderSourceReference(source.source, source.tableName, source.baseTableName, state, dialect)
875
+ ).join(", ")
876
+
877
+ const renderJoinPredicatesForMutation = (
878
+ joins: readonly QueryAst.JoinClause[],
879
+ state: RenderState,
880
+ dialect: SqlDialect
881
+ ): readonly string[] =>
882
+ joins.flatMap((join) =>
883
+ join.kind === "cross" || !join.on
884
+ ? []
885
+ : [renderExpression(join.on, state, dialect)]
886
+ )
887
+
888
+ const renderDeleteTargets = (
889
+ targets: readonly QueryAst.FromClause[],
890
+ dialect: SqlDialect
891
+ ): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
892
+
893
+ const renderTransactionClause = (
894
+ clause: QueryAst.TransactionClause,
895
+ dialect: SqlDialect
896
+ ): string => {
897
+ switch (clause.kind) {
898
+ case "transaction": {
899
+ if (clause.isolationLevel !== undefined || clause.readOnly !== undefined) {
900
+ throw new Error("Unsupported sqlite transaction options")
901
+ }
902
+ return "begin"
903
+ }
904
+ case "commit":
905
+ return "commit"
906
+ case "rollback":
907
+ return "rollback"
908
+ case "savepoint":
909
+ return `savepoint ${dialect.quoteIdentifier(clause.name)}`
910
+ case "rollbackTo":
911
+ return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
912
+ case "releaseSavepoint":
913
+ return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
914
+ }
915
+ throw new Error("Unsupported transaction statement kind")
916
+ }
917
+
918
+ const renderSelectionList = (
919
+ selection: Record<string, unknown>,
920
+ state: RenderState,
921
+ dialect: SqlDialect,
922
+ validateAggregation: boolean
923
+ ): RenderedQueryAst => {
924
+ if (validateAggregation) {
925
+ validateAggregationSelection(selection as SelectionValue, [])
926
+ }
927
+ const flattened = flattenSelection(selection)
928
+ const projections = selectionProjections(selection)
929
+ const sql = flattened.map(({ expression, alias }) =>
930
+ `${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
931
+ return {
932
+ sql,
933
+ projections
934
+ }
935
+ }
936
+
937
+ const nestedRenderState = (state: RenderState): RenderState => ({
938
+ params: state.params,
939
+ valueMappings: state.valueMappings,
940
+ ctes: [],
941
+ cteNames: new Set(state.cteNames),
942
+ cteSources: new Map(state.cteSources)
943
+ })
944
+
945
+ const assertMatchingSetProjections = (
946
+ left: readonly Projection[],
947
+ right: readonly Projection[]
948
+ ): void => {
949
+ const leftKeys = left.map((projection) => JSON.stringify(projection.path))
950
+ const rightKeys = right.map((projection) => JSON.stringify(projection.path))
951
+ if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
952
+ throw new Error("set operator operands must have matching result rows")
953
+ }
954
+ }
955
+
956
+ const assertNoGroupedMutationClauses = (
957
+ ast: Pick<QueryAst.Ast, "groupBy" | "having">,
958
+ statement: string
959
+ ): void => {
960
+ if (ast.groupBy.length > 0) {
961
+ throw new Error(`groupBy(...) is not supported for ${statement} statements`)
962
+ }
963
+ if (ast.having.length > 0) {
964
+ throw new Error(`having(...) is not supported for ${statement} statements`)
965
+ }
966
+ }
967
+
968
+ const assertNoSqliteMutationModifiers = (
969
+ ast: Pick<QueryAst.Ast, "orderBy" | "limit" | "offset" | "lock">,
970
+ statement: string
971
+ ): void => {
972
+ if (ast.orderBy.length > 0) {
973
+ throw new Error(`orderBy(...) is not supported for ${statement} statements`)
974
+ }
975
+ if (ast.limit) {
976
+ throw new Error(`limit(...) is not supported for ${statement} statements`)
977
+ }
978
+ if (ast.offset) {
979
+ throw new Error(`offset(...) is not supported for ${statement} statements`)
980
+ }
981
+ if (ast.lock) {
982
+ throw new Error(`lock(...) is not supported for ${statement} statements`)
983
+ }
984
+ }
985
+
986
+ const assertNoInsertQueryClauses = (
987
+ ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
988
+ ): void => {
989
+ if (ast.where.length > 0) {
990
+ throw new Error("where(...) is not supported for insert statements")
991
+ }
992
+ if (ast.joins.length > 0) {
993
+ throw new Error("join(...) is not supported for insert statements")
994
+ }
995
+ if (ast.orderBy.length > 0) {
996
+ throw new Error("orderBy(...) is not supported for insert statements")
997
+ }
998
+ if (ast.limit) {
999
+ throw new Error("limit(...) is not supported for insert statements")
1000
+ }
1001
+ if (ast.offset) {
1002
+ throw new Error("offset(...) is not supported for insert statements")
1003
+ }
1004
+ if (ast.lock) {
1005
+ throw new Error("lock(...) is not supported for insert statements")
1006
+ }
1007
+ }
1008
+
1009
+ const assertNoStatementQueryClauses = (
1010
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1011
+ statement: string,
1012
+ options: { readonly allowSelection?: boolean } = {}
1013
+ ): void => {
1014
+ if (ast.distinct) {
1015
+ throw new Error(`distinct(...) is not supported for ${statement} statements`)
1016
+ }
1017
+ if (ast.where.length > 0) {
1018
+ throw new Error(`where(...) is not supported for ${statement} statements`)
1019
+ }
1020
+ if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
1021
+ throw new Error(`from(...) is not supported for ${statement} statements`)
1022
+ }
1023
+ if (ast.joins.length > 0) {
1024
+ throw new Error(`join(...) is not supported for ${statement} statements`)
1025
+ }
1026
+ if (ast.groupBy.length > 0) {
1027
+ throw new Error(`groupBy(...) is not supported for ${statement} statements`)
1028
+ }
1029
+ if (ast.having.length > 0) {
1030
+ throw new Error(`having(...) is not supported for ${statement} statements`)
1031
+ }
1032
+ if (ast.orderBy.length > 0) {
1033
+ throw new Error(`orderBy(...) is not supported for ${statement} statements`)
1034
+ }
1035
+ if (ast.limit) {
1036
+ throw new Error(`limit(...) is not supported for ${statement} statements`)
1037
+ }
1038
+ if (ast.offset) {
1039
+ throw new Error(`offset(...) is not supported for ${statement} statements`)
1040
+ }
1041
+ if (ast.lock) {
1042
+ throw new Error(`lock(...) is not supported for ${statement} statements`)
1043
+ }
1044
+ if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
1045
+ throw new Error(`returning(...) is not supported for ${statement} statements`)
1046
+ }
1047
+ }
1048
+
1049
+ export const renderQueryAst = (
1050
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1051
+ state: RenderState,
1052
+ dialect: SqlDialect,
1053
+ options: { readonly emitCtes?: boolean } = {}
1054
+ ): RenderedQueryAst => {
1055
+ let sql = ""
1056
+ let projections: readonly Projection[] = []
1057
+
1058
+ switch (ast.kind) {
1059
+ case "select": {
1060
+ validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
1061
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1062
+ if (rendered.projections.length === 0) {
1063
+ throw new Error("sqlite select statements require at least one selected expression")
1064
+ }
1065
+ projections = rendered.projections
1066
+ const clauses = [
1067
+ ast.distinctOn && ast.distinctOn.length > 0
1068
+ ? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")}) ${rendered.sql}`
1069
+ : `select${ast.distinct ? " distinct" : ""} ${rendered.sql}`
1070
+ ]
1071
+ if (ast.from) {
1072
+ clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
1073
+ }
1074
+ for (const join of ast.joins) {
1075
+ const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
1076
+ clauses.push(
1077
+ join.kind === "cross"
1078
+ ? `cross join ${source}`
1079
+ : `${join.kind} join ${source} on ${renderExpression(join.on!, state, dialect)}`
1080
+ )
1081
+ }
1082
+ if (ast.where.length > 0) {
1083
+ clauses.push(`where ${ast.where.map((entry: QueryAst.WhereClause) => renderExpression(entry.predicate, state, dialect)).join(" and ")}`)
1084
+ }
1085
+ if (ast.groupBy.length > 0) {
1086
+ clauses.push(`group by ${ast.groupBy.map((value: QueryAst.Ast["groupBy"][number]) => renderExpression(value, state, dialect)).join(", ")}`)
1087
+ }
1088
+ if (ast.having.length > 0) {
1089
+ clauses.push(`having ${ast.having.map((entry: QueryAst.HavingClause) => renderExpression(entry.predicate, state, dialect)).join(" and ")}`)
1090
+ }
1091
+ if (ast.orderBy.length > 0) {
1092
+ clauses.push(`order by ${ast.orderBy.map((entry: QueryAst.OrderByClause) => `${renderExpression(entry.value, state, dialect)} ${entry.direction}`).join(", ")}`)
1093
+ }
1094
+ if (ast.limit) {
1095
+ clauses.push(`limit ${renderExpression(ast.limit, state, dialect)}`)
1096
+ }
1097
+ if (ast.offset) {
1098
+ clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
1099
+ }
1100
+ if (ast.lock) {
1101
+ throw new Error("Unsupported sqlite row locking")
1102
+ }
1103
+ sql = clauses.join(" ")
1104
+ break
1105
+ }
1106
+ case "set": {
1107
+ const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
1108
+ assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
1109
+ const base = renderQueryAst(
1110
+ Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
1111
+ Record<string, unknown>,
1112
+ any,
1113
+ QueryAst.QueryStatement
1114
+ >,
1115
+ state,
1116
+ dialect
1117
+ )
1118
+ projections = selectionProjections(setAst.select as Record<string, unknown>)
1119
+ assertMatchingSetProjections(projections, base.projections)
1120
+ sql = [
1121
+ base.sql,
1122
+ ...(setAst.setOperations ?? []).map((entry) => {
1123
+ if (dialect.name === "sqlite" && entry.all && entry.kind !== "union") {
1124
+ throw new Error("Unsupported sqlite set operator all variant")
1125
+ }
1126
+ const rendered = renderQueryAst(
1127
+ Query.getAst(entry.query as Query.Plan.Any) as QueryAst.Ast<
1128
+ Record<string, unknown>,
1129
+ any,
1130
+ QueryAst.QueryStatement
1131
+ >,
1132
+ state,
1133
+ dialect
1134
+ )
1135
+ assertMatchingSetProjections(projections, rendered.projections)
1136
+ return `${entry.kind}${entry.all ? " all" : ""} ${rendered.sql}`
1137
+ })
1138
+ ].join(" ")
1139
+ break
1140
+ }
1141
+ case "insert": {
1142
+ const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
1143
+ if (insertAst.distinct) {
1144
+ throw new Error("distinct(...) is not supported for insert statements")
1145
+ }
1146
+ assertNoGroupedMutationClauses(insertAst, "insert")
1147
+ assertNoInsertQueryClauses(insertAst)
1148
+ const targetSource = insertAst.into!
1149
+ const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1150
+ const insertSource = expectInsertSourceKind(insertAst.insertSource)
1151
+ const conflict = expectConflictClause(insertAst.conflict)
1152
+ sql = `insert into ${target}`
1153
+ if (insertSource?.kind === "values") {
1154
+ const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1155
+ const rows = insertSource.rows.map((row) =>
1156
+ `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1157
+ ).join(", ")
1158
+ sql += ` (${columns}) values ${rows}`
1159
+ } else if (insertSource?.kind === "query") {
1160
+ const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1161
+ const renderedQuery = renderQueryAst(
1162
+ Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1163
+ Record<string, unknown>,
1164
+ any,
1165
+ QueryAst.QueryStatement
1166
+ >,
1167
+ state,
1168
+ dialect
1169
+ )
1170
+ sql += ` (${columns}) ${renderedQuery.sql}`
1171
+ } else if (insertSource?.kind === "unnest") {
1172
+ const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1173
+ if (dialect.name === "postgres") {
1174
+ const table = targetSource.source as Table.AnyTable
1175
+ const fields = table[Table.TypeId].fields
1176
+ const rendered = insertSource.values.map((entry) =>
1177
+ `cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
1178
+ ).join(", ")
1179
+ sql += ` (${columns}) select * from unnest(${rendered})`
1180
+ } else {
1181
+ const table = targetSource.source as Table.AnyTable
1182
+ const fields = table[Table.TypeId].fields
1183
+ const encodedValues = insertSource.values.map((entry) => ({
1184
+ columnName: entry.columnName,
1185
+ values: encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect)
1186
+ }))
1187
+ const rowCount = encodedValues[0]?.values.length ?? 0
1188
+ const rows = Array.from({ length: rowCount }, (_, index) =>
1189
+ `(${encodedValues.map((entry) => dialect.renderLiteral(entry.values[index], state)).join(", ")})`
1190
+ ).join(", ")
1191
+ sql += ` (${columns}) values ${rows}`
1192
+ }
1193
+ } else {
1194
+ const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
1195
+ const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
1196
+ if ((insertAst.values ?? []).length > 0) {
1197
+ sql += ` (${columns}) values (${values})`
1198
+ } else {
1199
+ sql += " default values"
1200
+ }
1201
+ }
1202
+ if (conflict) {
1203
+ if (conflict.action === "doNothing" && conflict.where) {
1204
+ throw new Error("conflict action predicates require update assignments")
1205
+ }
1206
+ const updateValues = (conflict.values ?? []).map((entry) =>
1207
+ `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1208
+ ).join(", ")
1209
+ if (dialect.name === "postgres" || dialect.name === "sqlite") {
1210
+ if (dialect.name === "sqlite" && conflict.target?.kind === "constraint") {
1211
+ throw new Error("Unsupported sqlite named conflict constraint")
1212
+ }
1213
+ const targetSql = conflict.target?.kind === "constraint"
1214
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
1215
+ : conflict.target?.kind === "columns"
1216
+ ? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
1217
+ : " on conflict"
1218
+ sql += targetSql
1219
+ sql += conflict.action === "doNothing"
1220
+ ? " do nothing"
1221
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
1222
+ } else if (conflict.action === "doNothing") {
1223
+ sql = sql.replace(/^insert/, "insert ignore")
1224
+ } else {
1225
+ sql += ` on duplicate key update ${updateValues}`
1226
+ }
1227
+ }
1228
+ const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1229
+ projections = returning.projections
1230
+ if (returning.sql.length > 0) {
1231
+ sql += ` returning ${returning.sql}`
1232
+ }
1233
+ break
1234
+ }
1235
+ case "update": {
1236
+ const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
1237
+ if (updateAst.distinct) {
1238
+ throw new Error("distinct(...) is not supported for update statements")
1239
+ }
1240
+ assertNoGroupedMutationClauses(updateAst, "update")
1241
+ assertNoSqliteMutationModifiers(updateAst, "update")
1242
+ const targetSource = updateAst.target!
1243
+ const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1244
+ const targets = updateAst.targets ?? [targetSource]
1245
+ const fromSources = updateAst.fromSources ?? []
1246
+ if (targets.length > 1) {
1247
+ throw new Error("Unsupported sqlite multi-table update")
1248
+ }
1249
+ if ((updateAst.set ?? []).length === 0) {
1250
+ throw new Error("update statements require at least one assignment")
1251
+ }
1252
+ const assignments = updateAst.set!.map((entry) =>
1253
+ renderMutationAssignment(entry, state, dialect)).join(", ")
1254
+ if (dialect.name === "mysql") {
1255
+ const modifiers = ""
1256
+ const extraSources = renderFromSources(fromSources, state, dialect)
1257
+ const joinSources = updateAst.joins.map((join) =>
1258
+ join.kind === "full"
1259
+ ? (() => {
1260
+ throw new Error("Unsupported sqlite full join")
1261
+ })()
1262
+ : join.kind === "cross"
1263
+ ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
1264
+ : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1265
+ ).join(" ")
1266
+ const targetList = [
1267
+ ...targets.map((entry) =>
1268
+ renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
1269
+ ),
1270
+ ...(extraSources.length > 0 ? [extraSources] : [])
1271
+ ].join(", ")
1272
+ sql = `update${modifiers} ${targetList}${joinSources.length > 0 ? ` ${joinSources}` : ""} set ${assignments}`
1273
+ } else {
1274
+ sql = `update ${target} set ${assignments}`
1275
+ const mutationSources = [
1276
+ ...(fromSources.length > 0 ? [renderFromSources(fromSources, state, dialect)] : []),
1277
+ ...(updateAst.joins.length > 0 ? [renderJoinSourcesForMutation(updateAst.joins, state, dialect)] : [])
1278
+ ].filter((part) => part.length > 0)
1279
+ if (mutationSources.length > 0) {
1280
+ sql += ` from ${mutationSources.join(", ")}`
1281
+ }
1282
+ }
1283
+ const whereParts = [
1284
+ ...(dialect.name === "postgres" || dialect.name === "sqlite" ? renderJoinPredicatesForMutation(updateAst.joins, state, dialect) : []),
1285
+ ...updateAst.where.map((entry: QueryAst.WhereClause) => renderExpression(entry.predicate, state, dialect))
1286
+ ]
1287
+ if (whereParts.length > 0) {
1288
+ sql += ` where ${whereParts.join(" and ")}`
1289
+ }
1290
+ if (dialect.name === "mysql" && updateAst.orderBy.length > 0) {
1291
+ sql += ` order by ${updateAst.orderBy.map((entry: QueryAst.OrderByClause) => `${renderExpression(entry.value, state, dialect)} ${entry.direction}`).join(", ")}`
1292
+ }
1293
+ if (dialect.name === "mysql" && updateAst.limit) {
1294
+ sql += ` limit ${renderSqliteMutationLimit(updateAst.limit, state, dialect)}`
1295
+ }
1296
+ const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1297
+ projections = returning.projections
1298
+ if (returning.sql.length > 0) {
1299
+ sql += ` returning ${returning.sql}`
1300
+ }
1301
+ break
1302
+ }
1303
+ case "delete": {
1304
+ const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
1305
+ if (deleteAst.distinct) {
1306
+ throw new Error("distinct(...) is not supported for delete statements")
1307
+ }
1308
+ assertNoGroupedMutationClauses(deleteAst, "delete")
1309
+ assertNoSqliteMutationModifiers(deleteAst, "delete")
1310
+ const targetSource = deleteAst.target!
1311
+ const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1312
+ const targets = deleteAst.targets ?? [targetSource]
1313
+ if (targets.length > 1) {
1314
+ throw new Error("Unsupported sqlite multi-table delete")
1315
+ }
1316
+ if (dialect.name === "mysql") {
1317
+ const modifiers = ""
1318
+ const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
1319
+ const targetList = renderDeleteTargets(targets, dialect)
1320
+ const fromSources = targets.map((entry) =>
1321
+ renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
1322
+ ).join(", ")
1323
+ const joinSources = deleteAst.joins.map((join) =>
1324
+ join.kind === "full"
1325
+ ? (() => {
1326
+ throw new Error("Unsupported sqlite full join")
1327
+ })()
1328
+ : join.kind === "cross"
1329
+ ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
1330
+ : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1331
+ ).join(" ")
1332
+ sql = hasJoinedSources
1333
+ ? `delete${modifiers} ${targetList} from ${fromSources}${joinSources.length > 0 ? ` ${joinSources}` : ""}`
1334
+ : `delete${modifiers} from ${fromSources}`
1335
+ } else {
1336
+ if (dialect.name === "sqlite" && deleteAst.joins.length > 0) {
1337
+ throw new Error("Unsupported sqlite joined delete")
1338
+ }
1339
+ sql = `delete from ${target}`
1340
+ if (deleteAst.joins.length > 0) {
1341
+ sql += ` using ${renderJoinSourcesForMutation(deleteAst.joins, state, dialect)}`
1342
+ }
1343
+ }
1344
+ const whereParts = [
1345
+ ...(dialect.name === "postgres" ? renderJoinPredicatesForMutation(deleteAst.joins, state, dialect) : []),
1346
+ ...deleteAst.where.map((entry: QueryAst.WhereClause) => renderExpression(entry.predicate, state, dialect))
1347
+ ]
1348
+ if (whereParts.length > 0) {
1349
+ sql += ` where ${whereParts.join(" and ")}`
1350
+ }
1351
+ if (dialect.name === "mysql" && deleteAst.orderBy.length > 0) {
1352
+ sql += ` order by ${deleteAst.orderBy.map((entry: QueryAst.OrderByClause) => `${renderExpression(entry.value, state, dialect)} ${entry.direction}`).join(", ")}`
1353
+ }
1354
+ if (dialect.name === "mysql" && deleteAst.limit) {
1355
+ sql += ` limit ${renderSqliteMutationLimit(deleteAst.limit, state, dialect)}`
1356
+ }
1357
+ const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1358
+ projections = returning.projections
1359
+ if (returning.sql.length > 0) {
1360
+ sql += ` returning ${returning.sql}`
1361
+ }
1362
+ break
1363
+ }
1364
+ case "truncate": {
1365
+ const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1366
+ assertNoStatementQueryClauses(truncateAst, "truncate")
1367
+ throw new Error("Unsupported sqlite truncate statement")
1368
+ break
1369
+ }
1370
+ case "merge": {
1371
+ if (dialect.name !== "postgres") {
1372
+ throw new Error(`Unsupported merge statement for ${dialect.name}`)
1373
+ }
1374
+ const mergeAst = ast as QueryAst.Ast<Record<string, unknown>, any, "merge">
1375
+ const targetSource = mergeAst.target!
1376
+ const usingSource = mergeAst.using!
1377
+ const merge = mergeAst.merge!
1378
+ if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
1379
+ throw new Error("returning(...) is not supported for merge statements")
1380
+ }
1381
+ sql = `merge into ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} using ${renderSourceReference(usingSource.source, usingSource.tableName, usingSource.baseTableName, state, dialect)} on ${renderExpression(merge.on, state, dialect)}`
1382
+ if (merge.whenMatched) {
1383
+ sql += " when matched"
1384
+ if (merge.whenMatched.predicate) {
1385
+ sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
1386
+ }
1387
+ if (merge.whenMatched.kind === "delete") {
1388
+ sql += " then delete"
1389
+ } else {
1390
+ sql += ` then update set ${merge.whenMatched.values.map((entry) =>
1391
+ `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1392
+ ).join(", ")}`
1393
+ }
1394
+ }
1395
+ if (merge.whenNotMatched) {
1396
+ sql += " when not matched"
1397
+ if (merge.whenNotMatched.predicate) {
1398
+ sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
1399
+ }
1400
+ sql += ` then insert (${merge.whenNotMatched.values.map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1401
+ }
1402
+ break
1403
+ }
1404
+ case "transaction":
1405
+ case "commit":
1406
+ case "rollback":
1407
+ case "savepoint":
1408
+ case "rollbackTo":
1409
+ case "releaseSavepoint": {
1410
+ assertNoStatementQueryClauses(ast, ast.kind)
1411
+ sql = renderTransactionClause(ast.transaction!, dialect)
1412
+ break
1413
+ }
1414
+ case "createTable": {
1415
+ const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1416
+ assertNoStatementQueryClauses(createTableAst, "createTable")
1417
+ const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1418
+ sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1419
+ break
1420
+ }
1421
+ case "dropTable": {
1422
+ const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1423
+ assertNoStatementQueryClauses(dropTableAst, "dropTable")
1424
+ const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1425
+ sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1426
+ break
1427
+ }
1428
+ case "createIndex": {
1429
+ const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1430
+ assertNoStatementQueryClauses(createIndexAst, "createIndex")
1431
+ sql = renderCreateIndexSql(
1432
+ createIndexAst.target!,
1433
+ expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
1434
+ state,
1435
+ dialect
1436
+ )
1437
+ break
1438
+ }
1439
+ case "dropIndex": {
1440
+ const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1441
+ assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
1442
+ sql = renderDropIndexSql(
1443
+ dropIndexAst.target!,
1444
+ expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
1445
+ state,
1446
+ dialect
1447
+ )
1448
+ break
1449
+ }
1450
+ default:
1451
+ throw new Error("Unsupported query statement kind")
1452
+ }
1453
+
1454
+ if (state.ctes.length === 0 || options.emitCtes === false) {
1455
+ return {
1456
+ sql,
1457
+ projections
1458
+ }
1459
+ }
1460
+ return {
1461
+ sql: `with${state.ctes.some((entry) => entry.recursive) ? " recursive" : ""} ${state.ctes.map((entry) => `${dialect.quoteIdentifier(entry.name)} as (${entry.sql})`).join(", ")} ${sql}`,
1462
+ projections
1463
+ }
1464
+ }
1465
+
1466
+ const renderSourceReference = (
1467
+ source: unknown,
1468
+ tableName: string,
1469
+ baseTableName: string,
1470
+ state: RenderState,
1471
+ dialect: SqlDialect
1472
+ ): string => {
1473
+ const renderSelectRows = (
1474
+ rows: readonly Record<string, Expression.Any>[],
1475
+ columnNames: readonly string[]
1476
+ ): string => {
1477
+ const renderedRows = rows.map((row) =>
1478
+ `select ${columnNames.map((columnName) =>
1479
+ `${renderExpression(row[columnName]!, state, dialect)} as ${dialect.quoteIdentifier(columnName)}`
1480
+ ).join(", ")}`
1481
+ )
1482
+ return `(${renderedRows.join(" union all ")}) as ${dialect.quoteIdentifier(tableName)}`
1483
+ }
1484
+
1485
+ const renderUnnestRows = (
1486
+ arrays: Readonly<Record<string, readonly Expression.Any[]>>,
1487
+ columnNames: readonly string[]
1488
+ ): string => {
1489
+ const rowCount = arrays[columnNames[0]!]!.length
1490
+ const rows = Array.from({ length: rowCount }, (_, index) =>
1491
+ Object.fromEntries(columnNames.map((columnName) => [columnName, arrays[columnName]![index]!] as const)) as Record<string, Expression.Any>
1492
+ )
1493
+ return renderSelectRows(rows, columnNames)
1494
+ }
1495
+
1496
+ if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "cte") {
1497
+ const cte = source as unknown as {
1498
+ readonly name: string
1499
+ readonly plan: Query.Plan.Any
1500
+ readonly recursive?: boolean
1501
+ }
1502
+ const registeredCteSource = state.cteSources.get(cte.name)
1503
+ if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
1504
+ throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
1505
+ }
1506
+ if (!state.cteNames.has(cte.name)) {
1507
+ state.cteNames.add(cte.name)
1508
+ state.cteSources.set(cte.name, cte.plan)
1509
+ const statement = Query.getQueryState(cte.plan).statement
1510
+ if (statement !== "select" && statement !== "set") {
1511
+ const cteAst = Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>
1512
+ if (Object.keys((cteAst.select ?? {}) as Record<string, unknown>).length > 0) {
1513
+ throw new Error("Unsupported sqlite returning")
1514
+ }
1515
+ throw new Error("Unsupported sqlite data-modifying cte")
1516
+ }
1517
+ const rendered = renderQueryAst(
1518
+ Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1519
+ state,
1520
+ dialect,
1521
+ { emitCtes: false }
1522
+ )
1523
+ state.ctes.push({
1524
+ name: cte.name,
1525
+ sql: rendered.sql,
1526
+ recursive: cte.recursive
1527
+ })
1528
+ }
1529
+ return dialect.quoteIdentifier(cte.name)
1530
+ }
1531
+ if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "derived") {
1532
+ const derived = source as unknown as {
1533
+ readonly name: string
1534
+ readonly plan: Query.Plan.Any
1535
+ }
1536
+ if (!state.cteNames.has(derived.name)) {
1537
+ // derived tables are inlined, so no CTE registration is needed
1538
+ }
1539
+ return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
1540
+ }
1541
+ if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
1542
+ const lateral = source as unknown as {
1543
+ readonly name: string
1544
+ readonly plan: Query.Plan.Any
1545
+ }
1546
+ if (dialect.name === "sqlite") {
1547
+ throw new Error("Unsupported sqlite lateral source")
1548
+ }
1549
+ 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)}`
1550
+ }
1551
+ if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
1552
+ const values = source as unknown as {
1553
+ readonly columns: Record<string, Expression.Any>
1554
+ readonly rows: readonly Record<string, Expression.Any>[]
1555
+ }
1556
+ return renderSelectRows(values.rows, Object.keys(values.columns))
1557
+ }
1558
+ if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "unnest") {
1559
+ const unnest = source as unknown as {
1560
+ readonly columns: Record<string, Expression.Any>
1561
+ readonly arrays: Readonly<Record<string, readonly Expression.Any[]>>
1562
+ }
1563
+ return renderUnnestRows(unnest.arrays, Object.keys(unnest.columns))
1564
+ }
1565
+ if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "tableFunction") {
1566
+ const tableFunction = source as unknown as {
1567
+ readonly name: string
1568
+ readonly columns: Record<string, Expression.Any>
1569
+ readonly functionName: string
1570
+ readonly args: readonly Expression.Any[]
1571
+ }
1572
+ if (dialect.name !== "postgres") {
1573
+ throw new Error("Unsupported table function source for SQL rendering")
1574
+ }
1575
+ const columnNames = Object.keys(tableFunction.columns)
1576
+ return `${tableFunction.functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1577
+ }
1578
+ const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1579
+ ? (source as Table.AnyTable)[Table.TypeId].schemaName
1580
+ : undefined
1581
+ return dialect.renderTableReference(tableName, baseTableName, schemaName)
1582
+ }
1583
+
1584
+ const renderSubqueryExpressionPlan = (
1585
+ plan: Query.Plan.Any,
1586
+ state: RenderState,
1587
+ dialect: SqlDialect
1588
+ ): string => {
1589
+ const statement = Query.getQueryState(plan).statement
1590
+ if (statement !== "select" && statement !== "set") {
1591
+ throw new Error("subquery expressions only accept select-like query plans")
1592
+ }
1593
+ return renderQueryAst(
1594
+ Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1595
+ state,
1596
+ dialect
1597
+ ).sql
1598
+ }
1599
+
1600
+ /**
1601
+ * Renders a scalar expression AST into SQL text.
1602
+ *
1603
+ * This is parameterized by a runtime dialect so the same expression walker can
1604
+ * be reused across dialect-specific renderers while still delegating quoting
1605
+ * and literal serialization to the concrete dialect implementation.
1606
+ */
1607
+ export const renderExpression = (
1608
+ expression: Expression.Any,
1609
+ state: RenderState,
1610
+ dialect: SqlDialect
1611
+ ): string => {
1612
+ const rawAst = (expression as Expression.Any & {
1613
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
1614
+ })[ExpressionAst.TypeId] as ExpressionAst.Any | Record<string, unknown>
1615
+ const jsonSql = renderJsonExpression(expression, rawAst as Record<string, unknown>, state, dialect)
1616
+ if (jsonSql !== undefined) {
1617
+ return jsonSql
1618
+ }
1619
+ const ast = rawAst as ExpressionAst.Any
1620
+ const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1621
+ operator === "eq"
1622
+ ? "="
1623
+ : operator === "neq"
1624
+ ? "<>"
1625
+ : operator === "lt"
1626
+ ? "<"
1627
+ : operator === "lte"
1628
+ ? "<="
1629
+ : operator === "gt"
1630
+ ? ">"
1631
+ : ">="
1632
+ switch (ast.kind) {
1633
+ case "column":
1634
+ return state.rowLocalColumns || ast.tableName.length === 0
1635
+ ? dialect.quoteIdentifier(ast.columnName)
1636
+ : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1637
+ case "literal":
1638
+ if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1639
+ throw new Error("Expected a finite numeric value")
1640
+ }
1641
+ return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1642
+ case "excluded":
1643
+ return `excluded.${dialect.quoteIdentifier(ast.columnName)}`
1644
+ case "cast":
1645
+ return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
1646
+ case "function":
1647
+ return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1648
+ case "eq":
1649
+ return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1650
+ case "neq":
1651
+ return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1652
+ case "lt":
1653
+ return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1654
+ case "lte":
1655
+ return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1656
+ case "gt":
1657
+ return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1658
+ case "gte":
1659
+ return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1660
+ case "like":
1661
+ return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1662
+ case "ilike":
1663
+ return dialect.name === "postgres"
1664
+ ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1665
+ : `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
1666
+ case "regexMatch":
1667
+ if (dialect.name === "sqlite") {
1668
+ throw new Error("Unsupported sqlite regex operator")
1669
+ }
1670
+ return dialect.name === "postgres"
1671
+ ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1672
+ : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1673
+ case "regexIMatch":
1674
+ if (dialect.name === "sqlite") {
1675
+ throw new Error("Unsupported sqlite regex operator")
1676
+ }
1677
+ return dialect.name === "postgres"
1678
+ ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1679
+ : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1680
+ case "regexNotMatch":
1681
+ if (dialect.name === "sqlite") {
1682
+ throw new Error("Unsupported sqlite regex operator")
1683
+ }
1684
+ return dialect.name === "postgres"
1685
+ ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1686
+ : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1687
+ case "regexNotIMatch":
1688
+ if (dialect.name === "sqlite") {
1689
+ throw new Error("Unsupported sqlite regex operator")
1690
+ }
1691
+ return dialect.name === "postgres"
1692
+ ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1693
+ : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1694
+ case "isDistinctFrom":
1695
+ return `(${renderExpression(ast.left, state, dialect)} is distinct from ${renderExpression(ast.right, state, dialect)})`
1696
+ case "isNotDistinctFrom":
1697
+ return `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1698
+ case "contains":
1699
+ if (dialect.name === "postgres") {
1700
+ const left = isJsonExpression(ast.left)
1701
+ ? renderPostgresJsonValue(ast.left, state, dialect)
1702
+ : renderExpression(ast.left, state, dialect)
1703
+ const right = isJsonExpression(ast.right)
1704
+ ? renderPostgresJsonValue(ast.right, state, dialect)
1705
+ : renderExpression(ast.right, state, dialect)
1706
+ return `(${left} @> ${right})`
1707
+ }
1708
+ throw new Error("Unsupported container operator for SQL rendering")
1709
+ case "containedBy":
1710
+ if (dialect.name === "postgres") {
1711
+ const left = isJsonExpression(ast.left)
1712
+ ? renderPostgresJsonValue(ast.left, state, dialect)
1713
+ : renderExpression(ast.left, state, dialect)
1714
+ const right = isJsonExpression(ast.right)
1715
+ ? renderPostgresJsonValue(ast.right, state, dialect)
1716
+ : renderExpression(ast.right, state, dialect)
1717
+ return `(${left} <@ ${right})`
1718
+ }
1719
+ throw new Error("Unsupported container operator for SQL rendering")
1720
+ case "overlaps":
1721
+ if (dialect.name === "postgres") {
1722
+ const left = isJsonExpression(ast.left)
1723
+ ? renderPostgresJsonValue(ast.left, state, dialect)
1724
+ : renderExpression(ast.left, state, dialect)
1725
+ const right = isJsonExpression(ast.right)
1726
+ ? renderPostgresJsonValue(ast.right, state, dialect)
1727
+ : renderExpression(ast.right, state, dialect)
1728
+ return `(${left} && ${right})`
1729
+ }
1730
+ throw new Error("Unsupported container operator for SQL rendering")
1731
+ case "isNull":
1732
+ return `(${renderExpression(ast.value, state, dialect)} is null)`
1733
+ case "isNotNull":
1734
+ return `(${renderExpression(ast.value, state, dialect)} is not null)`
1735
+ case "not":
1736
+ return `(not ${renderExpression(ast.value, state, dialect)})`
1737
+ case "upper":
1738
+ return `upper(${renderExpression(ast.value, state, dialect)})`
1739
+ case "lower":
1740
+ return `lower(${renderExpression(ast.value, state, dialect)})`
1741
+ case "count":
1742
+ return `count(${renderExpression(ast.value, state, dialect)})`
1743
+ case "max":
1744
+ return `max(${renderExpression(ast.value, state, dialect)})`
1745
+ case "min":
1746
+ return `min(${renderExpression(ast.value, state, dialect)})`
1747
+ case "and":
1748
+ if (ast.values.length === 0) {
1749
+ throw new Error("and(...) requires at least one predicate")
1750
+ }
1751
+ return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1752
+ case "or":
1753
+ if (ast.values.length === 0) {
1754
+ throw new Error("or(...) requires at least one predicate")
1755
+ }
1756
+ return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
1757
+ case "coalesce":
1758
+ return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
1759
+ case "in":
1760
+ if (ast.values.length < 2) {
1761
+ throw new Error("in(...) requires at least one candidate value")
1762
+ }
1763
+ return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1764
+ case "notIn":
1765
+ if (ast.values.length < 2) {
1766
+ throw new Error("notIn(...) requires at least one candidate value")
1767
+ }
1768
+ return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1769
+ case "between":
1770
+ return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
1771
+ case "concat":
1772
+ return dialect.renderConcat(ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)))
1773
+ case "case":
1774
+ return `case ${ast.branches.map((branch) =>
1775
+ `when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
1776
+ ).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
1777
+ case "exists":
1778
+ return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1779
+ case "scalarSubquery":
1780
+ return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1781
+ case "inSubquery":
1782
+ return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1783
+ case "comparisonAny":
1784
+ if (dialect.name === "sqlite") {
1785
+ throw new Error("Unsupported sqlite quantified comparison")
1786
+ }
1787
+ return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1788
+ case "comparisonAll":
1789
+ if (dialect.name === "sqlite") {
1790
+ throw new Error("Unsupported sqlite quantified comparison")
1791
+ }
1792
+ return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1793
+ case "window": {
1794
+ if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1795
+ break
1796
+ }
1797
+ const clauses: string[] = []
1798
+ if (ast.partitionBy.length > 0) {
1799
+ clauses.push(`partition by ${ast.partitionBy.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}`)
1800
+ }
1801
+ if (ast.orderBy.length > 0) {
1802
+ clauses.push(`order by ${ast.orderBy.map((entry) =>
1803
+ `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1804
+ ).join(", ")}`)
1805
+ }
1806
+ const specification = clauses.join(" ")
1807
+ switch (ast.function) {
1808
+ case "rowNumber":
1809
+ return `row_number() over (${specification})`
1810
+ case "rank":
1811
+ return `rank() over (${specification})`
1812
+ case "denseRank":
1813
+ return `dense_rank() over (${specification})`
1814
+ case "over":
1815
+ return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
1816
+ }
1817
+ break
1818
+ }
1819
+ }
1820
+ throw new Error("Unsupported expression for SQL rendering")
1821
+ }