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