effect-qb 0.16.0 → 0.19.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 (128) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +8065 -0
  3. package/dist/mysql.js +4036 -2418
  4. package/dist/postgres/metadata.js +2536 -625
  5. package/dist/postgres.js +8248 -7857
  6. package/dist/sqlite.js +8854 -0
  7. package/dist/standard.js +8019 -0
  8. package/package.json +15 -3
  9. package/src/casing.ts +71 -0
  10. package/src/index.ts +2 -0
  11. package/src/internal/casing.ts +89 -0
  12. package/src/internal/column-state.ts +11 -6
  13. package/src/internal/column.ts +44 -7
  14. package/src/internal/datatypes/define.ts +2 -1
  15. package/src/internal/datatypes/enrich.ts +23 -0
  16. package/src/internal/datatypes/lookup.ts +14 -7
  17. package/src/internal/derived-table.ts +7 -13
  18. package/src/internal/dialect-renderers/mysql.ts +2046 -0
  19. package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
  20. package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
  21. package/src/internal/dialect.ts +37 -0
  22. package/src/internal/dsl-mutation-runtime.ts +29 -10
  23. package/src/internal/dsl-plan-runtime.ts +41 -24
  24. package/src/internal/dsl-query-runtime.ts +11 -31
  25. package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
  26. package/src/internal/executor.ts +57 -15
  27. package/src/internal/expression-ast.ts +3 -2
  28. package/src/internal/grouping-key.ts +216 -9
  29. package/src/internal/implication-runtime.ts +3 -2
  30. package/src/internal/json/types.ts +155 -40
  31. package/src/internal/predicate/context.ts +14 -1
  32. package/src/internal/predicate/key.ts +19 -2
  33. package/src/internal/predicate/runtime.ts +30 -3
  34. package/src/internal/query.d.ts +38 -11
  35. package/src/internal/query.ts +315 -54
  36. package/src/internal/renderer.ts +51 -6
  37. package/src/internal/runtime/driver-value-mapping.ts +58 -0
  38. package/src/internal/runtime/normalize.ts +74 -43
  39. package/src/internal/runtime/schema.ts +5 -3
  40. package/src/internal/runtime/value.ts +153 -30
  41. package/src/internal/scalar.ts +6 -1
  42. package/src/internal/schema-derivation.d.ts +12 -61
  43. package/src/internal/schema-derivation.ts +90 -38
  44. package/src/internal/schema-expression.ts +2 -2
  45. package/src/internal/sql-expression-renderer.ts +19 -0
  46. package/src/internal/standard-dsl.ts +6885 -0
  47. package/src/internal/table-options.ts +229 -62
  48. package/src/internal/table.d.ts +33 -32
  49. package/src/internal/table.ts +469 -160
  50. package/src/mysql/column-extension.ts +3 -0
  51. package/src/mysql/column.ts +27 -12
  52. package/src/mysql/datatypes/index.ts +24 -2
  53. package/src/mysql/errors/catalog.ts +5 -5
  54. package/src/mysql/errors/normalize.ts +2 -2
  55. package/src/mysql/executor.ts +7 -5
  56. package/src/mysql/internal/dialect.ts +9 -4
  57. package/src/mysql/internal/dsl.ts +906 -324
  58. package/src/mysql/internal/renderer.ts +7 -2
  59. package/src/mysql/json.ts +37 -0
  60. package/src/mysql/query-extension.ts +16 -0
  61. package/src/mysql/query.ts +9 -2
  62. package/src/mysql/renderer.ts +31 -4
  63. package/src/mysql.ts +4 -12
  64. package/src/postgres/column-extension.ts +28 -0
  65. package/src/postgres/column.ts +9 -13
  66. package/src/postgres/datatypes/index.d.ts +2 -1
  67. package/src/postgres/datatypes/index.ts +3 -2
  68. package/src/postgres/errors/normalize.ts +2 -2
  69. package/src/postgres/executor.ts +55 -10
  70. package/src/postgres/function/core.ts +20 -4
  71. package/src/postgres/function/index.ts +1 -17
  72. package/src/postgres/internal/dialect.ts +9 -4
  73. package/src/postgres/internal/dsl.ts +850 -359
  74. package/src/postgres/internal/renderer.ts +7 -2
  75. package/src/postgres/internal/schema-ddl.ts +22 -9
  76. package/src/postgres/internal/schema-model.ts +244 -10
  77. package/src/postgres/json.ts +100 -24
  78. package/src/postgres/jsonb.ts +38 -0
  79. package/src/postgres/query-extension.ts +2 -0
  80. package/src/postgres/query.ts +9 -2
  81. package/src/postgres/renderer.ts +31 -4
  82. package/src/postgres/schema-management.ts +108 -16
  83. package/src/postgres/schema.ts +98 -15
  84. package/src/postgres/table.ts +203 -398
  85. package/src/postgres/type.ts +8 -7
  86. package/src/postgres.ts +9 -11
  87. package/src/sqlite/column-extension.ts +3 -0
  88. package/src/sqlite/column.ts +127 -0
  89. package/src/sqlite/datatypes/index.ts +80 -0
  90. package/src/sqlite/datatypes/spec.ts +98 -0
  91. package/src/sqlite/errors/catalog.ts +103 -0
  92. package/src/sqlite/errors/fields.ts +19 -0
  93. package/src/sqlite/errors/index.ts +19 -0
  94. package/src/sqlite/errors/normalize.ts +229 -0
  95. package/src/sqlite/errors/requirements.ts +71 -0
  96. package/src/sqlite/errors/types.ts +29 -0
  97. package/src/sqlite/executor.ts +229 -0
  98. package/src/sqlite/function/aggregate.ts +2 -0
  99. package/src/sqlite/function/core.ts +2 -0
  100. package/src/sqlite/function/index.ts +19 -0
  101. package/src/sqlite/function/string.ts +2 -0
  102. package/src/sqlite/function/temporal.ts +100 -0
  103. package/src/sqlite/function/window.ts +2 -0
  104. package/src/sqlite/internal/dialect.ts +42 -0
  105. package/src/sqlite/internal/dsl.ts +6979 -0
  106. package/src/sqlite/internal/renderer.ts +51 -0
  107. package/src/sqlite/json.ts +39 -0
  108. package/src/sqlite/query-extension.ts +2 -0
  109. package/src/sqlite/query.ts +196 -0
  110. package/src/sqlite/renderer.ts +51 -0
  111. package/src/sqlite.ts +14 -0
  112. package/src/standard/column.ts +163 -0
  113. package/src/standard/datatypes/index.ts +83 -0
  114. package/src/standard/datatypes/spec.ts +98 -0
  115. package/src/standard/dialect.ts +40 -0
  116. package/src/standard/function/aggregate.ts +2 -0
  117. package/src/standard/function/core.ts +2 -0
  118. package/src/standard/function/index.ts +18 -0
  119. package/src/standard/function/string.ts +2 -0
  120. package/src/standard/function/temporal.ts +78 -0
  121. package/src/standard/function/window.ts +2 -0
  122. package/src/standard/internal/renderer.ts +45 -0
  123. package/src/standard/query.ts +152 -0
  124. package/src/standard/renderer.ts +21 -0
  125. package/src/standard/table.ts +147 -0
  126. package/src/standard.ts +18 -0
  127. package/src/internal/aggregation-validation.ts +0 -57
  128. package/src/mysql/table.ts +0 -157
@@ -1,38 +1,49 @@
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"
1
+ import * as Schema from "effect/Schema"
2
+
3
+ import * as Query from "../query.js"
4
+ import * as Expression from "../scalar.js"
5
+ import * as Table from "../table.js"
6
+ import * as QueryAst from "../query-ast.js"
7
+ import { renderDbTypeName, type RenderState, type RenderValueContext, type SqlDialect } from "../dialect.js"
8
+ import * as ExpressionAst from "../expression-ast.js"
9
+ import * as JsonPath from "../json/path.js"
10
+ import { renderSelectLockMode } from "../dsl-plan-runtime.js"
11
+ import { expectConflictClause } from "../dsl-mutation-runtime.js"
12
+ import { expectDdlClauseKind, expectTruncateClause, normalizeStatementFlag, normalizeStatementIdentifier, renderTransactionIsolationLevel } from "../dsl-transaction-ddl-runtime.js"
8
13
  import {
9
14
  renderJsonSelectSql,
10
15
  renderSelectSql,
11
16
  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
+ } from "../runtime/driver-value-mapping.js"
18
+ import { normalizeDbValue } from "../runtime/normalize.js"
19
+ import { flattenSelection, type Projection } from "../projections.js"
20
+ import { groupingKeyOfExpression } from "../grouping-key.js"
21
+ import * as SchemaExpression from "../schema-expression.js"
22
+ import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
23
+ import * as Casing from "../casing.js"
17
24
 
18
25
  const renderDbType = (
19
26
  dialect: SqlDialect,
20
27
  dbType: Expression.DbType.Any
21
28
  ): string => {
22
- if (dialect.name === "mysql" && dbType.dialect === "mysql" && dbType.kind === "uuid") {
23
- return "char(36)"
29
+ if (dialect.name === "postgres" && dbType.kind === "blob") {
30
+ return "bytea"
24
31
  }
25
- return dbType.kind
32
+ return renderDbTypeName(dbType.kind)
26
33
  }
27
34
 
35
+ const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
36
+ "element" in dbType
37
+
28
38
  const renderCastType = (
29
39
  dialect: SqlDialect,
30
- dbType: Expression.DbType.Any
40
+ dbType: unknown
31
41
  ): string => {
42
+ const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
32
43
  if (dialect.name !== "mysql") {
33
- return dbType.kind
44
+ return renderDbTypeName(kind)
34
45
  }
35
- switch (dbType.kind) {
46
+ switch (kind) {
36
47
  case "text":
37
48
  return "char"
38
49
  case "uuid":
@@ -47,35 +58,243 @@ const renderCastType = (
47
58
  case "json":
48
59
  return "json"
49
60
  default:
50
- return dbType.kind
61
+ return renderDbTypeName(kind)
51
62
  }
52
63
  }
53
64
 
65
+ const casingForTable = (
66
+ table: Table.AnyTable,
67
+ state: RenderState
68
+ ): Casing.Options | undefined =>
69
+ Casing.merge(state.casing, table[Table.TypeId].casing)
70
+
71
+ const casedTableName = (
72
+ table: Table.AnyTable,
73
+ state: RenderState
74
+ ): string => {
75
+ const tableState = table[Table.TypeId]
76
+ return Casing.applyCategory(casingForTable(table, state), "tables", tableState.baseName)
77
+ }
78
+
79
+ const casedSchemaName = (
80
+ table: Table.AnyTable,
81
+ state: RenderState
82
+ ): string | undefined => {
83
+ const schemaName = table[Table.TypeId].schemaName
84
+ return schemaName === undefined
85
+ ? undefined
86
+ : Casing.applyCategory(casingForTable(table, state), "schemas", schemaName)
87
+ }
88
+
89
+ const casedColumnName = (
90
+ columnName: string,
91
+ state: RenderState,
92
+ tableName?: string
93
+ ): string => {
94
+ if (tableName !== undefined) {
95
+ const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
96
+ if (mapped !== undefined) {
97
+ return mapped
98
+ }
99
+ }
100
+ return Casing.applyCategory(state.casing, "columns", columnName)
101
+ }
102
+
103
+ const casedTableReferenceName = (
104
+ tableName: string,
105
+ state: RenderState
106
+ ): string =>
107
+ state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
108
+
109
+ const quoteColumn = (
110
+ columnName: string,
111
+ state: RenderState,
112
+ dialect: SqlDialect,
113
+ tableName?: string
114
+ ): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
115
+
116
+ const stateWithTableCasing = (
117
+ state: RenderState,
118
+ source: unknown
119
+ ): RenderState =>
120
+ typeof source === "object" && source !== null && Table.TypeId in source
121
+ ? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
122
+ : state
123
+
124
+ const referenceCasing = (
125
+ reference: { readonly casing?: Casing.Options },
126
+ state: RenderState
127
+ ): Casing.Options | undefined =>
128
+ Casing.merge(state.casing, reference.casing)
129
+
130
+ const renderReferenceTable = (
131
+ reference: {
132
+ readonly tableName: string
133
+ readonly schemaName?: string
134
+ readonly casing?: Casing.Options
135
+ },
136
+ state: RenderState,
137
+ dialect: SqlDialect
138
+ ): string => {
139
+ const casing = referenceCasing(reference, state)
140
+ const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
141
+ const schemaName = reference.schemaName === undefined
142
+ ? undefined
143
+ : Casing.applyCategory(casing, "schemas", reference.schemaName)
144
+ return dialect.renderTableReference(tableName, tableName, schemaName)
145
+ }
146
+
147
+ const quoteReferenceColumn = (
148
+ columnName: string,
149
+ reference: { readonly casing?: Casing.Options },
150
+ state: RenderState,
151
+ dialect: SqlDialect
152
+ ): string =>
153
+ dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
154
+
155
+ const registerSourceReference = (
156
+ source: unknown,
157
+ tableName: string,
158
+ state: RenderState
159
+ ): void => {
160
+ if (typeof source !== "object" || source === null) {
161
+ return
162
+ }
163
+ if (Table.TypeId in source) {
164
+ const table = source as Table.AnyTable
165
+ const tableState = table[Table.TypeId]
166
+ const casing = casingForTable(table, state)
167
+ const renderedTableName = tableState.kind === "alias"
168
+ ? tableName
169
+ : Casing.applyCategory(casing, "tables", tableState.baseName)
170
+ const columns = new Map(
171
+ Object.keys(tableState.fields).map((columnName) => [
172
+ columnName,
173
+ Casing.applyCategory(casing, "columns", columnName)
174
+ ] as const)
175
+ )
176
+ state.sourceNames?.set(tableName, {
177
+ tableName: renderedTableName,
178
+ columns
179
+ })
180
+ return
181
+ }
182
+ if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
183
+ state.sourceNames?.set(tableName, {
184
+ tableName,
185
+ columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
186
+ })
187
+ }
188
+ }
189
+
190
+ const registerQuerySources = (
191
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
192
+ state: RenderState
193
+ ): void => {
194
+ if (ast.from !== undefined) {
195
+ registerSourceReference(ast.from.source, ast.from.tableName, state)
196
+ }
197
+ for (const source of ast.fromSources ?? []) {
198
+ registerSourceReference(source.source, source.tableName, state)
199
+ }
200
+ for (const join of ast.joins) {
201
+ registerSourceReference(join.source, join.tableName, state)
202
+ }
203
+ if (ast.into !== undefined) {
204
+ registerSourceReference(ast.into.source, ast.into.tableName, state)
205
+ }
206
+ if (ast.target !== undefined) {
207
+ registerSourceReference(ast.target.source, ast.target.tableName, state)
208
+ }
209
+ for (const target of ast.targets ?? []) {
210
+ registerSourceReference(target.source, target.tableName, state)
211
+ }
212
+ if (ast.using !== undefined) {
213
+ registerSourceReference(ast.using.source, ast.using.tableName, state)
214
+ }
215
+ }
216
+
217
+ const renderPostgresDdlString = (value: string): string =>
218
+ `'${value.replaceAll("'", "''")}'`
219
+
220
+ const renderPostgresDdlBytes = (value: Uint8Array): string =>
221
+ `decode('${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}', 'hex')`
222
+
223
+ const renderPostgresDdlLiteral = (
224
+ value: unknown,
225
+ state: RenderState,
226
+ context: RenderValueContext = {}
227
+ ): string => {
228
+ const driverValue = toDriverValue(value, {
229
+ dialect: "postgres",
230
+ valueMappings: state.valueMappings,
231
+ ...context
232
+ })
233
+ if (driverValue === null) {
234
+ return "null"
235
+ }
236
+ switch (typeof driverValue) {
237
+ case "boolean":
238
+ return driverValue ? "true" : "false"
239
+ case "number":
240
+ if (!Number.isFinite(driverValue)) {
241
+ throw new Error("Expected a finite numeric value")
242
+ }
243
+ return String(driverValue)
244
+ case "bigint":
245
+ return driverValue.toString()
246
+ case "string":
247
+ return renderPostgresDdlString(driverValue)
248
+ case "object":
249
+ if (driverValue instanceof Uint8Array) {
250
+ return renderPostgresDdlBytes(driverValue)
251
+ }
252
+ break
253
+ }
254
+ throw new Error("Unsupported postgres DDL literal value")
255
+ }
256
+
54
257
  const renderDdlExpression = (
55
258
  expression: DdlExpressionLike,
56
259
  state: RenderState,
57
260
  dialect: SqlDialect
58
- ): string =>
59
- SchemaExpression.isSchemaExpression(expression)
60
- ? SchemaExpression.render(expression)
61
- : renderExpression(expression, state, dialect)
261
+ ): string => {
262
+ if (SchemaExpression.isSchemaExpression(expression)) {
263
+ return SchemaExpression.render(expression)
264
+ }
265
+ return renderExpression(expression, state, {
266
+ ...dialect,
267
+ renderLiteral: renderPostgresDdlLiteral
268
+ })
269
+ }
62
270
 
63
271
  const renderColumnDefinition = (
64
272
  dialect: SqlDialect,
65
273
  state: RenderState,
66
274
  columnName: string,
67
- column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
275
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
276
+ tableName?: string,
277
+ casing?: Casing.Options
68
278
  ): string => {
279
+ const expressionState = { ...state, casing, rowLocalColumns: true }
280
+ if (dialect.name !== "postgres" && isArrayDbType(column.metadata.dbType)) {
281
+ throw new Error(`Unsupported ${dialect.name} array column options`)
282
+ }
69
283
  const clauses = [
70
- dialect.quoteIdentifier(columnName),
71
- column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
284
+ quoteColumn(columnName, state, dialect, tableName),
285
+ column.metadata.ddlType === undefined
286
+ ? renderDbType(dialect, column.metadata.dbType)
287
+ : renderDbTypeName(column.metadata.ddlType)
72
288
  ]
73
289
  if (column.metadata.identity) {
290
+ if (dialect.name !== "postgres") {
291
+ throw new Error(`Unsupported ${dialect.name} identity column options`)
292
+ }
74
293
  clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
75
294
  } else if (column.metadata.generatedValue) {
76
- clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
295
+ clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
77
296
  } else if (column.metadata.defaultValue) {
78
- clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
297
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
79
298
  }
80
299
  if (!column.metadata.nullable) {
81
300
  clauses.push("not null")
@@ -87,38 +306,65 @@ const renderCreateTableSql = (
87
306
  targetSource: QueryAst.FromClause,
88
307
  state: RenderState,
89
308
  dialect: SqlDialect,
90
- ifNotExists: boolean
309
+ ifNotExists: unknown
91
310
  ): string => {
311
+ const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
312
+ if (dialect.name !== "postgres" && normalizedIfNotExists) {
313
+ throw new Error(`Unsupported ${dialect.name} create table options`)
314
+ }
92
315
  const table = targetSource.source as Table.AnyTable
316
+ const tableCasing = casingForTable(table, state)
93
317
  const fields = table[Table.TypeId].fields
94
318
  const definitions = Object.entries(fields).map(([columnName, column]) =>
95
- renderColumnDefinition(dialect, state, columnName, column)
319
+ renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
96
320
  )
97
- for (const option of table[Table.OptionsSymbol]) {
321
+ const options = table[Table.OptionsSymbol] as unknown
322
+ const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
323
+ validateOptions(table[Table.TypeId].name, fields, tableOptions)
324
+ for (const option of tableOptions) {
325
+ if (typeof option !== "object" || option === null || !("kind" in option)) {
326
+ continue
327
+ }
98
328
  switch (option.kind) {
99
329
  case "primaryKey":
100
- 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" : ""}` : ""}`)
330
+ if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
331
+ throw new Error(`Unsupported ${dialect.name} primary key constraint options`)
332
+ }
333
+ definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}primary key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
101
334
  break
102
335
  case "unique":
103
- 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" : ""}` : ""}`)
336
+ if (dialect.name !== "postgres" && (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred)) {
337
+ throw new Error(`Unsupported ${dialect.name} unique constraint options`)
338
+ }
339
+ definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
104
340
  break
105
341
  case "foreignKey": {
106
- const reference = option.references()
342
+ if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
343
+ throw new Error(`Unsupported ${dialect.name} foreign key constraint options`)
344
+ }
345
+ const reference = typeof option.references === "function"
346
+ ? option.references()
347
+ : option.references
107
348
  definitions.push(
108
- `${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.onDelete ? ` on delete ${option.onDelete.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" : ""}` : ""}`
349
+ `${option.name ? `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} ` : ""}foreign key (${option.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")}) references ${renderReferenceTable(reference, state, dialect)} (${reference.columns.map((column) => quoteReferenceColumn(column, reference, state, dialect)).join(", ")})${option.onDelete !== undefined ? ` on delete ${renderReferentialAction(option.onDelete)}` : ""}${option.onUpdate !== undefined ? ` on update ${renderReferentialAction(option.onUpdate)}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
109
350
  )
110
351
  break
111
352
  }
112
353
  case "check":
354
+ if (dialect.name !== "postgres" && option.noInherit) {
355
+ throw new Error(`Unsupported ${dialect.name} check constraint options`)
356
+ }
113
357
  definitions.push(
114
- `constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, dialect)})${option.noInherit ? " no inherit" : ""}`
358
+ `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
115
359
  )
116
360
  break
117
361
  case "index":
118
362
  break
363
+ default:
364
+ throw new Error("Unsupported table option kind")
119
365
  }
120
366
  }
121
- return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
367
+ return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
122
368
  }
123
369
 
124
370
  const renderCreateIndexSql = (
@@ -127,8 +373,16 @@ const renderCreateIndexSql = (
127
373
  state: RenderState,
128
374
  dialect: SqlDialect
129
375
  ): string => {
130
- const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
131
- 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(", ")})`
376
+ const unique = normalizeStatementFlag(ddl.unique)
377
+ const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
378
+ const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
379
+ if (dialect.name !== "postgres" && ifNotExists) {
380
+ throw new Error(`Unsupported ${dialect.name} create index options`)
381
+ }
382
+ const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
383
+ const table = targetSource.source as Table.AnyTable
384
+ const tableCasing = casingForTable(table, state)
385
+ return `create${unique ? " unique" : ""} index${maybeIfNotExists} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${ddl.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})`
132
386
  }
133
387
 
134
388
  const renderDropIndexSql = (
@@ -136,20 +390,108 @@ const renderDropIndexSql = (
136
390
  ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
137
391
  state: RenderState,
138
392
  dialect: SqlDialect
139
- ): string =>
140
- dialect.name === "postgres"
141
- ? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
142
- : `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
393
+ ): string => {
394
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
395
+ const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
396
+ if (dialect.name !== "postgres" && ifExists) {
397
+ throw new Error(`Unsupported ${dialect.name} drop index options`)
398
+ }
399
+ if (dialect.name === "postgres") {
400
+ const table = typeof targetSource.source === "object" &&
401
+ targetSource.source !== null &&
402
+ Table.TypeId in targetSource.source
403
+ ? targetSource.source as Table.AnyTable
404
+ : undefined
405
+ const schemaName = table?.[Table.TypeId].schemaName
406
+ const tableCasing = table === undefined ? state.casing : casingForTable(table, state)
407
+ const renderedSchemaName = table === undefined ? schemaName : casedSchemaName(table, state)
408
+ const renderedIndexName = Casing.applyCategory(tableCasing, "indexes", name)
409
+ const indexName = schemaName === undefined || schemaName === "public"
410
+ ? dialect.quoteIdentifier(renderedIndexName)
411
+ : `${dialect.quoteIdentifier(renderedSchemaName ?? schemaName)}.${dialect.quoteIdentifier(renderedIndexName)}`
412
+ return `drop index${ifExists ? " if exists" : ""} ${indexName}`
413
+ }
414
+ const table = targetSource.source as Table.AnyTable
415
+ const tableCasing = casingForTable(table, state)
416
+ return `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
417
+ }
143
418
 
144
419
  const isExpression = (value: unknown): value is Expression.Any =>
145
420
  value !== null && typeof value === "object" && Expression.TypeId in value
146
421
 
147
- const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
148
- dbType.kind === "jsonb" || dbType.kind === "json" || ("variant" in dbType && dbType.variant === "json")
422
+ const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
423
+ if (dbType.kind === "jsonb" || dbType.kind === "json") {
424
+ return true
425
+ }
426
+ if (!("variant" in dbType)) {
427
+ return false
428
+ }
429
+ const variant = dbType.variant as string
430
+ return variant === "json" || variant === "jsonb"
431
+ }
149
432
 
150
433
  const isJsonExpression = (value: unknown): value is Expression.Any =>
151
434
  isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
152
435
 
436
+ const expectValueExpression = (
437
+ _functionName: string,
438
+ value: unknown
439
+ ): Expression.Any => value as Expression.Any
440
+
441
+ const expectBinaryExpressions = (
442
+ _functionName: string,
443
+ left: unknown,
444
+ right: unknown
445
+ ): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
446
+
447
+ const renderBinaryExpression = (
448
+ functionName: string,
449
+ operator: string,
450
+ left: unknown,
451
+ right: unknown,
452
+ state: RenderState,
453
+ dialect: SqlDialect
454
+ ): string => {
455
+ const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
456
+ return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
457
+ }
458
+
459
+ const postgresRangeSubtypeByKind: Readonly<Record<string, string>> = {
460
+ int4range: "int4",
461
+ int8range: "int8",
462
+ numrange: "numeric",
463
+ tsrange: "timestamp",
464
+ tstzrange: "timestamptz",
465
+ daterange: "date",
466
+ int4multirange: "int4",
467
+ int8multirange: "int8",
468
+ nummultirange: "numeric",
469
+ tsmultirange: "timestamp",
470
+ tstzmultirange: "timestamptz",
471
+ datemultirange: "date"
472
+ }
473
+
474
+ const postgresRangeSubtypeKey = (dbType: Expression.DbType.Any): string | undefined => {
475
+ if ("base" in dbType) {
476
+ return postgresRangeSubtypeKey(dbType.base)
477
+ }
478
+ if ("subtype" in dbType) {
479
+ return postgresRangeSubtypeKey(dbType.subtype) ?? dbType.subtype.kind
480
+ }
481
+ return postgresRangeSubtypeByKind[dbType.kind]
482
+ }
483
+
484
+ const assertCompatiblePostgresRangeOperands = (
485
+ left: Expression.Any,
486
+ right: Expression.Any
487
+ ): void => {
488
+ const leftKey = postgresRangeSubtypeKey(left[Expression.TypeId].dbType)
489
+ const rightKey = postgresRangeSubtypeKey(right[Expression.TypeId].dbType)
490
+ if (leftKey !== undefined && rightKey !== undefined && leftKey !== rightKey) {
491
+ throw new Error("Incompatible postgres range operands")
492
+ }
493
+ }
494
+
153
495
  const unsupportedJsonFeature = (
154
496
  dialect: SqlDialect,
155
497
  feature: string
@@ -173,13 +515,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
173
515
  const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
174
516
  value !== null && typeof value === "object" && JsonPath.TypeId in value
175
517
 
518
+ const isOptionalJsonPathNumber = (value: unknown): boolean =>
519
+ value === undefined || (typeof value === "number" && Number.isFinite(value))
520
+
521
+ const isJsonPathSegment = (segment: unknown): boolean => {
522
+ if (typeof segment === "string") {
523
+ return true
524
+ }
525
+ if (typeof segment === "number") {
526
+ return Number.isFinite(segment)
527
+ }
528
+ if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
529
+ return false
530
+ }
531
+ switch ((segment as { readonly kind?: unknown }).kind) {
532
+ case "key":
533
+ return typeof (segment as { readonly key?: unknown }).key === "string"
534
+ case "index": {
535
+ const index = (segment as { readonly index?: unknown }).index
536
+ return typeof index === "number" && Number.isFinite(index)
537
+ }
538
+ case "wildcard":
539
+ case "descend":
540
+ return true
541
+ case "slice":
542
+ return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
543
+ isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
544
+ default:
545
+ return false
546
+ }
547
+ }
548
+
549
+ const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
550
+ if (!Array.isArray(segments)) {
551
+ throw new Error("JSON path expressions require a segment array")
552
+ }
553
+ if (segments.some((segment) => !isJsonPathSegment(segment))) {
554
+ throw new Error("JSON path segments require string, number, or path segment objects")
555
+ }
556
+ return segments as ReadonlyArray<JsonPath.AnySegment>
557
+ }
558
+
176
559
  const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
177
560
  const path = node.path ?? node.segments ?? node.keys
178
561
  if (isJsonPathValue(path)) {
179
- return path.segments
562
+ return validateJsonPathSegments(path.segments)
180
563
  }
181
564
  if (Array.isArray(path)) {
182
- return path as readonly JsonPath.AnySegment[]
565
+ return validateJsonPathSegments(path)
566
+ }
567
+ if (node.segments !== undefined) {
568
+ return validateJsonPathSegments(node.segments)
183
569
  }
184
570
  if ("key" in node) {
185
571
  return [JsonPath.key(String(node.key))]
@@ -198,28 +584,40 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
198
584
  return []
199
585
  }
200
586
  if ("right" in node && isJsonPathValue(node.right)) {
201
- return node.right.segments
587
+ return validateJsonPathSegments(node.right.segments)
202
588
  }
203
589
  return []
204
590
  }
205
591
 
592
+ const extractJsonKeys = (
593
+ node: Record<string, unknown>,
594
+ segments: ReadonlyArray<JsonPath.AnySegment>
595
+ ): readonly unknown[] =>
596
+ Array.isArray(node.keys)
597
+ ? node.keys
598
+ : segments.map((segment) =>
599
+ typeof segment === "object" && segment !== null && segment.kind === "key"
600
+ ? segment.key
601
+ : segment
602
+ )
603
+
206
604
  const extractJsonValue = (node: Record<string, unknown>): unknown =>
207
605
  node.newValue ?? node.insert ?? node.right
208
606
 
209
607
  const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
608
+ const renderKey = (value: string): string =>
609
+ /^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
610
+ ? `.${value}`
611
+ : `.${JSON.stringify(value)}`
210
612
  if (typeof segment === "string") {
211
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)
212
- ? `.${segment}`
213
- : `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
613
+ return renderKey(segment)
214
614
  }
215
615
  if (typeof segment === "number") {
216
616
  return `[${segment}]`
217
617
  }
218
618
  switch (segment.kind) {
219
619
  case "key":
220
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(segment.key)
221
- ? `.${segment.key}`
222
- : `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
620
+ return renderKey(segment.key)
223
621
  case "index":
224
622
  return `[${segment.index}]`
225
623
  case "wildcard":
@@ -284,7 +682,7 @@ const renderPostgresJsonAccessStep = (
284
682
  case "key":
285
683
  return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.key, state)}`
286
684
  case "index":
287
- return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(String(segment.index), state)}`
685
+ return `${textMode ? "->>" : "->"} ${dialect.renderLiteral(segment.index, state)}`
288
686
  default:
289
687
  throw new Error("Postgres exact JSON access requires key/index segments")
290
688
  }
@@ -338,14 +736,26 @@ const encodeArrayValues = (
338
736
  state: RenderState,
339
737
  dialect: SqlDialect
340
738
  ): readonly unknown[] =>
341
- values.map((value) =>
342
- toDriverValue(value, {
739
+ values.map((value) => {
740
+ if (value === null && column.metadata.nullable) {
741
+ return null
742
+ }
743
+ const runtimeSchemaAccepts = column.schema !== undefined &&
744
+ (Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
745
+ const normalizedValue = runtimeSchemaAccepts
746
+ ? value
747
+ : normalizeDbValue(column.metadata.dbType, value)
748
+ const encodedValue = column.schema === undefined || runtimeSchemaAccepts
749
+ ? normalizedValue
750
+ : (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
751
+ return toDriverValue(encodedValue, {
343
752
  dialect: dialect.name,
344
753
  valueMappings: state.valueMappings,
345
754
  dbType: column.metadata.dbType,
346
755
  runtimeSchema: column.schema,
347
756
  driverValueMapping: column.metadata.driverValueMapping
348
- }))
757
+ })
758
+ })
349
759
 
350
760
  const renderPostgresJsonKind = (
351
761
  value: Expression.Any
@@ -360,52 +770,64 @@ const renderJsonOpaquePath = (
360
770
  return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments), state)
361
771
  }
362
772
  if (typeof value === "string") {
773
+ if (value.trim().length === 0) {
774
+ throw new Error("SQL/JSON path input must be a non-empty string")
775
+ }
363
776
  return dialect.renderLiteral(value, state)
364
777
  }
365
778
  if (isExpression(value)) {
779
+ const ast = (value as Expression.Any & {
780
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
781
+ })[ExpressionAst.TypeId]
782
+ if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
783
+ throw new Error("SQL/JSON path input must be a non-empty string")
784
+ }
366
785
  return renderExpression(value, state, dialect)
367
786
  }
368
787
  throw new Error("Unsupported SQL/JSON path input")
369
788
  }
370
789
 
790
+ const renderFunctionName = (name: unknown): string => {
791
+ return name as string
792
+ }
793
+
794
+ const renderExtractField = (field: Expression.Any): string => {
795
+ const ast = (field as Expression.Any & {
796
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
797
+ })[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
798
+ return ast.value
799
+ }
800
+
371
801
  const renderFunctionCall = (
372
- name: string,
373
- args: readonly Expression.Any[],
802
+ name: unknown,
803
+ args: unknown,
374
804
  state: RenderState,
375
805
  dialect: SqlDialect
376
806
  ): string => {
377
- if (name === "array") {
378
- return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
379
- }
380
- if (name === "extract" && args.length === 2) {
381
- const field = args[0]
382
- const source = args[1]
383
- if (field === undefined) {
384
- throw new Error("Unsupported SQL extract expression")
385
- }
386
- if (source === undefined) {
387
- throw new Error("Unsupported SQL extract expression")
388
- }
389
- const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
390
- ? field[Expression.TypeId].runtime
391
- : undefined
392
- const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
393
- return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
807
+ const functionName = renderFunctionName(name)
808
+ const functionArgs = args as readonly Expression.Any[]
809
+ if (functionName === "array") {
810
+ return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
811
+ }
812
+ if (functionName === "extract") {
813
+ const field = functionArgs[0]!
814
+ const source = functionArgs[1]!
815
+ return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
394
816
  }
395
- const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
396
- if (args.length === 0) {
397
- switch (name) {
817
+ const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
818
+ if (functionArgs.length === 0) {
819
+ switch (functionName) {
398
820
  case "current_date":
399
821
  case "current_time":
400
822
  case "current_timestamp":
401
823
  case "localtime":
402
824
  case "localtimestamp":
403
- return name
825
+ return functionName
404
826
  default:
405
- return `${name}()`
827
+ return `${functionName}()`
406
828
  }
407
829
  }
408
- return `${name}(${renderedArgs})`
830
+ return `${functionName}(${renderedArgs})`
409
831
  }
410
832
 
411
833
  const renderJsonExpression = (
@@ -469,22 +891,26 @@ const renderJsonExpression = (
469
891
  const baseSql = dialect.name === "postgres"
470
892
  ? renderPostgresJsonValue(base, state, dialect)
471
893
  : renderExpression(base, state, dialect)
472
- const keys = segments
894
+ const keys = extractJsonKeys(ast, segments)
473
895
  if (keys.length === 0) {
474
896
  return undefined
475
897
  }
898
+ if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
899
+ throw new Error("json key predicates require string keys")
900
+ }
901
+ const keyNames = keys as readonly string[]
476
902
  if (dialect.name === "postgres") {
477
903
  if (kind === "jsonHasAnyKeys") {
478
- return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
904
+ return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
479
905
  }
480
906
  if (kind === "jsonHasAllKeys") {
481
- return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
907
+ return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
482
908
  }
483
- return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
909
+ return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
484
910
  }
485
911
  if (dialect.name === "mysql") {
486
912
  const mode = kind === "jsonHasAllKeys" ? "all" : "one"
487
- const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
913
+ const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
488
914
  return `json_contains_path(${baseSql}, ${dialect.renderLiteral(mode, state)}, ${paths})`
489
915
  }
490
916
  return undefined
@@ -503,9 +929,7 @@ const renderJsonExpression = (
503
929
  return undefined
504
930
  }
505
931
  case "jsonBuildObject": {
506
- const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
507
- ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
508
- : []
932
+ const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
509
933
  const renderedEntries = entries.flatMap((entry) => [
510
934
  dialect.renderLiteral(entry.key, state),
511
935
  renderJsonInputExpression(entry.value, state, dialect)
@@ -519,9 +943,7 @@ const renderJsonExpression = (
519
943
  return undefined
520
944
  }
521
945
  case "jsonBuildArray": {
522
- const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
523
- ? (ast as { readonly values: readonly Expression.Any[] }).values
524
- : []
946
+ const values = (ast as { readonly values: readonly Expression.Any[] }).values
525
947
  const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
526
948
  if (dialect.name === "postgres") {
527
949
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
@@ -588,7 +1010,7 @@ const renderJsonExpression = (
588
1010
  const baseSql = renderExpression(base, state, dialect)
589
1011
  const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
590
1012
  const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
591
- return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
1013
+ return `(case when ${typeOf}(${baseSql}) = 'object' then to_json(array(select ${objectKeys}(${baseSql}))) else null end)`
592
1014
  }
593
1015
  if (dialect.name === "mysql") {
594
1016
  return `json_keys(${renderExpression(base, state, dialect)})`
@@ -615,7 +1037,7 @@ const renderJsonExpression = (
615
1037
  const segment = segments[0]!
616
1038
  return `(${baseSql} - ${segment.kind === "key"
617
1039
  ? dialect.renderLiteral(segment.key, state)
618
- : dialect.renderLiteral(String(segment.index), state)})`
1040
+ : dialect.renderLiteral(segment.index, state)})`
619
1041
  }
620
1042
  return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
621
1043
  }
@@ -699,11 +1121,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
699
1121
  const renderMutationAssignment = (
700
1122
  entry: QueryAst.AssignmentClause,
701
1123
  state: RenderState,
702
- dialect: SqlDialect
1124
+ dialect: SqlDialect,
1125
+ targetTableName?: string
703
1126
  ): string => {
704
1127
  const column = entry.tableName && dialect.name === "mysql"
705
- ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
706
- : dialect.quoteIdentifier(entry.columnName)
1128
+ ? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
1129
+ : quoteColumn(entry.columnName, state, dialect, targetTableName)
707
1130
  return `${column} = ${renderExpression(entry.value, state, dialect)}`
708
1131
  }
709
1132
 
@@ -765,10 +1188,11 @@ const renderTransactionClause = (
765
1188
  switch (clause.kind) {
766
1189
  case "transaction": {
767
1190
  const modes: string[] = []
768
- if (clause.isolationLevel) {
769
- modes.push(`isolation level ${clause.isolationLevel}`)
1191
+ const isolationLevel = renderTransactionIsolationLevel(clause.isolationLevel)
1192
+ if (isolationLevel) {
1193
+ modes.push(isolationLevel)
770
1194
  }
771
- if (clause.readOnly === true) {
1195
+ if (normalizeStatementFlag(clause.readOnly)) {
772
1196
  modes.push("read only")
773
1197
  }
774
1198
  return modes.length > 0
@@ -780,24 +1204,20 @@ const renderTransactionClause = (
780
1204
  case "rollback":
781
1205
  return "rollback"
782
1206
  case "savepoint":
783
- return `savepoint ${dialect.quoteIdentifier(clause.name)}`
1207
+ return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
784
1208
  case "rollbackTo":
785
- return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
1209
+ return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
786
1210
  case "releaseSavepoint":
787
- return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
1211
+ return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
788
1212
  }
789
- return ""
1213
+ return "start transaction"
790
1214
  }
791
1215
 
792
1216
  const renderSelectionList = (
793
1217
  selection: Record<string, unknown>,
794
1218
  state: RenderState,
795
- dialect: SqlDialect,
796
- validateAggregation: boolean
1219
+ dialect: SqlDialect
797
1220
  ): RenderedQueryAst => {
798
- if (validateAggregation) {
799
- validateAggregationSelection(selection as SelectionValue, [])
800
- }
801
1221
  const flattened = flattenSelection(selection)
802
1222
  const projections = selectionProjections(selection)
803
1223
  const sql = flattened.map(({ expression, alias }) =>
@@ -808,28 +1228,74 @@ const renderSelectionList = (
808
1228
  }
809
1229
  }
810
1230
 
1231
+ const nestedRenderState = (state: RenderState): RenderState => ({
1232
+ params: state.params,
1233
+ valueMappings: state.valueMappings,
1234
+ casing: state.casing,
1235
+ ctes: [],
1236
+ cteNames: new Set(state.cteNames),
1237
+ cteSources: new Map(state.cteSources),
1238
+ sourceNames: new Map(state.sourceNames)
1239
+ })
1240
+
1241
+ const assertSupportedMutationReturning = (
1242
+ dialect: SqlDialect,
1243
+ selection: Record<string, unknown>
1244
+ ): void => {
1245
+ if (dialect.name === "standard" && Object.keys(selection).length > 0) {
1246
+ throw new Error("Unsupported standard returning")
1247
+ }
1248
+ }
1249
+
1250
+ const validateDistinctOnOrdering = (
1251
+ distinctOn: readonly Expression.Any[] | undefined,
1252
+ orderBy: readonly QueryAst.OrderByClause[]
1253
+ ): void => {
1254
+ if (distinctOn === undefined || distinctOn.length === 0 || orderBy.length === 0) {
1255
+ return
1256
+ }
1257
+ const remainingDistinctKeys = new Set(distinctOn.map(groupingKeyOfExpression))
1258
+ for (const order of orderBy) {
1259
+ const key = groupingKeyOfExpression(order.value)
1260
+ if (remainingDistinctKeys.has(key)) {
1261
+ remainingDistinctKeys.delete(key)
1262
+ continue
1263
+ }
1264
+ if (remainingDistinctKeys.size > 0) {
1265
+ throw new Error("distinctOn(...) expressions must match the leftmost orderBy(...) expressions")
1266
+ }
1267
+ return
1268
+ }
1269
+ }
1270
+
811
1271
  export const renderQueryAst = (
812
1272
  ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
813
1273
  state: RenderState,
814
- dialect: SqlDialect
1274
+ dialect: SqlDialect,
1275
+ options: { readonly emitCtes?: boolean } = {}
815
1276
  ): RenderedQueryAst => {
1277
+ registerQuerySources(ast, state)
816
1278
  let sql = ""
817
1279
  let projections: readonly Projection[] = []
818
1280
 
819
1281
  switch (ast.kind) {
820
1282
  case "select": {
821
- validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
822
- const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1283
+ validateDistinctOnOrdering(ast.distinctOn, ast.orderBy)
1284
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
823
1285
  projections = rendered.projections
1286
+ const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
824
1287
  const clauses = [
825
1288
  ast.distinctOn && ast.distinctOn.length > 0
826
- ? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")}) ${rendered.sql}`
827
- : `select${ast.distinct ? " distinct" : ""} ${rendered.sql}`
1289
+ ? `select distinct on (${ast.distinctOn.map((value) => renderExpression(value, state, dialect)).join(", ")})${selectList}`
1290
+ : `select${ast.distinct ? " distinct" : ""}${selectList}`
828
1291
  ]
829
1292
  if (ast.from) {
830
1293
  clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
831
1294
  }
832
1295
  for (const join of ast.joins) {
1296
+ if (dialect.name === "standard" && join.kind === "full") {
1297
+ throw new Error("Unsupported standard full join")
1298
+ }
833
1299
  const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
834
1300
  clauses.push(
835
1301
  join.kind === "cross"
@@ -856,8 +1322,11 @@ export const renderQueryAst = (
856
1322
  clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
857
1323
  }
858
1324
  if (ast.lock) {
1325
+ if (dialect.name === "standard") {
1326
+ throw new Error("Unsupported standard row locking")
1327
+ }
859
1328
  clauses.push(
860
- `${ast.lock.mode === "update" ? "for update" : "for share"}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
1329
+ `${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
861
1330
  )
862
1331
  }
863
1332
  sql = clauses.join(" ")
@@ -887,6 +1356,9 @@ export const renderQueryAst = (
887
1356
  state,
888
1357
  dialect
889
1358
  )
1359
+ if (dialect.name === "standard" && entry.all && entry.kind !== "union") {
1360
+ throw new Error("Unsupported standard set operator all variant")
1361
+ }
890
1362
  return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
891
1363
  })
892
1364
  ].join(" ")
@@ -896,17 +1368,20 @@ export const renderQueryAst = (
896
1368
  const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
897
1369
  const targetSource = insertAst.into!
898
1370
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1371
+ const targetCasingState = stateWithTableCasing(state, targetSource.source)
1372
+ const insertSource = insertAst.insertSource
1373
+ const conflict = expectConflictClause(insertAst.conflict)
899
1374
  sql = `insert into ${target}`
900
- if (insertAst.insertSource?.kind === "values") {
901
- const columns = insertAst.insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
902
- const rows = insertAst.insertSource.rows.map((row) =>
903
- `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1375
+ if (insertSource?.kind === "values") {
1376
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1377
+ const rows = insertSource.rows.map((row) =>
1378
+ `(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
904
1379
  ).join(", ")
905
1380
  sql += ` (${columns}) values ${rows}`
906
- } else if (insertAst.insertSource?.kind === "query") {
907
- const columns = insertAst.insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1381
+ } else if (insertSource?.kind === "query") {
1382
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
908
1383
  const renderedQuery = renderQueryAst(
909
- Query.getAst(insertAst.insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1384
+ Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
910
1385
  Record<string, unknown>,
911
1386
  any,
912
1387
  QueryAst.QueryStatement
@@ -915,20 +1390,19 @@ export const renderQueryAst = (
915
1390
  dialect
916
1391
  )
917
1392
  sql += ` (${columns}) ${renderedQuery.sql}`
918
- } else if (insertAst.insertSource?.kind === "unnest") {
919
- const unnestSource = insertAst.insertSource
920
- const columns = unnestSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1393
+ } else if (insertSource?.kind === "unnest") {
1394
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
921
1395
  if (dialect.name === "postgres") {
922
1396
  const table = targetSource.source as Table.AnyTable
923
1397
  const fields = table[Table.TypeId].fields
924
- const rendered = unnestSource.values.map((entry) =>
1398
+ const rendered = insertSource.values.map((entry) =>
925
1399
  `cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
926
1400
  ).join(", ")
927
1401
  sql += ` (${columns}) select * from unnest(${rendered})`
928
1402
  } else {
929
- const rowCount = unnestSource.values[0]?.values.length ?? 0
1403
+ const rowCount = insertSource.values[0]?.values.length ?? 0
930
1404
  const rows = Array.from({ length: rowCount }, (_, index) =>
931
- `(${unnestSource.values.map((entry) =>
1405
+ `(${insertSource.values.map((entry) =>
932
1406
  dialect.renderLiteral(
933
1407
  entry.values[index],
934
1408
  state,
@@ -939,35 +1413,41 @@ export const renderQueryAst = (
939
1413
  sql += ` (${columns}) values ${rows}`
940
1414
  }
941
1415
  } else {
942
- const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
943
- const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
944
- if ((insertAst.values ?? []).length > 0) {
1416
+ const insertValues = insertAst.values ?? []
1417
+ const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
1418
+ const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
1419
+ if (insertValues.length > 0) {
945
1420
  sql += ` (${columns}) values (${values})`
946
1421
  } else {
947
1422
  sql += " default values"
948
1423
  }
949
1424
  }
950
- if (insertAst.conflict) {
951
- const updateValues = (insertAst.conflict.values ?? []).map((entry) =>
952
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1425
+ if (conflict) {
1426
+ if (dialect.name === "standard") {
1427
+ throw new Error("Unsupported standard insert conflict")
1428
+ }
1429
+ const conflictValueState = { ...targetCasingState, allowExcluded: true }
1430
+ const updateValues = (conflict.values ?? []).map((entry) =>
1431
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
953
1432
  ).join(", ")
954
1433
  if (dialect.name === "postgres") {
955
- const targetSql = insertAst.conflict.target?.kind === "constraint"
956
- ? ` on conflict on constraint ${dialect.quoteIdentifier(insertAst.conflict.target.name)}`
957
- : insertAst.conflict.target?.kind === "columns"
958
- ? ` on conflict (${insertAst.conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${insertAst.conflict.target.where ? ` where ${renderExpression(insertAst.conflict.target.where, state, dialect)}` : ""}`
1434
+ const targetSql = conflict.target?.kind === "constraint"
1435
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
1436
+ : conflict.target?.kind === "columns"
1437
+ ? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
959
1438
  : " on conflict"
960
1439
  sql += targetSql
961
- sql += insertAst.conflict.action === "doNothing"
1440
+ sql += conflict.action === "doNothing"
962
1441
  ? " do nothing"
963
- : ` do update set ${updateValues}${insertAst.conflict.where ? ` where ${renderExpression(insertAst.conflict.where, state, dialect)}` : ""}`
964
- } else if (insertAst.conflict.action === "doNothing") {
1442
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
1443
+ } else if (conflict.action === "doNothing") {
965
1444
  sql = sql.replace(/^insert/, "insert ignore")
966
1445
  } else {
967
1446
  sql += ` on duplicate key update ${updateValues}`
968
1447
  }
969
1448
  }
970
- const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1449
+ assertSupportedMutationReturning(dialect, insertAst.select as Record<string, unknown>)
1450
+ const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
971
1451
  projections = returning.projections
972
1452
  if (returning.sql.length > 0) {
973
1453
  sql += ` returning ${returning.sql}`
@@ -980,8 +1460,11 @@ export const renderQueryAst = (
980
1460
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
981
1461
  const targets = updateAst.targets ?? [targetSource]
982
1462
  const fromSources = updateAst.fromSources ?? []
1463
+ if (dialect.name === "standard" && (targets.length > 1 || fromSources.length > 0 || updateAst.joins.length > 0)) {
1464
+ throw new Error("Unsupported standard joined mutation")
1465
+ }
983
1466
  const assignments = updateAst.set!.map((entry) =>
984
- renderMutationAssignment(entry, state, dialect)).join(", ")
1467
+ renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
985
1468
  if (dialect.name === "mysql") {
986
1469
  const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
987
1470
  const extraSources = renderFromSources(fromSources, state, dialect)
@@ -1020,7 +1503,8 @@ export const renderQueryAst = (
1020
1503
  if (dialect.name === "mysql" && updateAst.limit) {
1021
1504
  sql += ` limit ${renderExpression(updateAst.limit, state, dialect)}`
1022
1505
  }
1023
- const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1506
+ assertSupportedMutationReturning(dialect, updateAst.select as Record<string, unknown>)
1507
+ const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
1024
1508
  projections = returning.projections
1025
1509
  if (returning.sql.length > 0) {
1026
1510
  sql += ` returning ${returning.sql}`
@@ -1032,6 +1516,9 @@ export const renderQueryAst = (
1032
1516
  const targetSource = deleteAst.target!
1033
1517
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1034
1518
  const targets = deleteAst.targets ?? [targetSource]
1519
+ if (dialect.name === "standard" && (targets.length > 1 || deleteAst.joins.length > 0)) {
1520
+ throw new Error("Unsupported standard joined mutation")
1521
+ }
1035
1522
  if (dialect.name === "mysql") {
1036
1523
  const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
1037
1524
  const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
@@ -1066,7 +1553,8 @@ export const renderQueryAst = (
1066
1553
  if (dialect.name === "mysql" && deleteAst.limit) {
1067
1554
  sql += ` limit ${renderExpression(deleteAst.limit, state, dialect)}`
1068
1555
  }
1069
- const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1556
+ assertSupportedMutationReturning(dialect, deleteAst.select as Record<string, unknown>)
1557
+ const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
1070
1558
  projections = returning.projections
1071
1559
  if (returning.sql.length > 0) {
1072
1560
  sql += ` returning ${returning.sql}`
@@ -1075,12 +1563,18 @@ export const renderQueryAst = (
1075
1563
  }
1076
1564
  case "truncate": {
1077
1565
  const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1566
+ if (dialect.name === "standard") {
1567
+ throw new Error("Unsupported standard truncate statement")
1568
+ }
1569
+ const truncate = expectTruncateClause(truncateAst.truncate)
1078
1570
  const targetSource = truncateAst.target!
1571
+ const restartIdentity = truncate.restartIdentity
1572
+ const cascade = truncate.cascade
1079
1573
  sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
1080
- if (truncateAst.truncate?.restartIdentity) {
1574
+ if (restartIdentity) {
1081
1575
  sql += " restart identity"
1082
1576
  }
1083
- if (truncateAst.truncate?.cascade) {
1577
+ if (cascade) {
1084
1578
  sql += " cascade"
1085
1579
  }
1086
1580
  break
@@ -1095,15 +1589,17 @@ export const renderQueryAst = (
1095
1589
  const merge = mergeAst.merge!
1096
1590
  sql = `merge into ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} using ${renderSourceReference(usingSource.source, usingSource.tableName, usingSource.baseTableName, state, dialect)} on ${renderExpression(merge.on, state, dialect)}`
1097
1591
  if (merge.whenMatched) {
1592
+ const matchedKind = merge.whenMatched.kind === "delete" ? "delete" : "update"
1098
1593
  sql += " when matched"
1099
1594
  if (merge.whenMatched.predicate) {
1100
1595
  sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
1101
1596
  }
1102
- if (merge.whenMatched.kind === "delete") {
1597
+ if (matchedKind === "delete") {
1103
1598
  sql += " then delete"
1104
1599
  } else {
1105
- sql += ` then update set ${merge.whenMatched.values.map((entry) =>
1106
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1600
+ const matchedUpdate = merge.whenMatched as Extract<QueryAst.MergeMatchedClause, { readonly kind: "update" }>
1601
+ sql += ` then update set ${matchedUpdate.values.map((entry) =>
1602
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, state, dialect)}`
1107
1603
  ).join(", ")}`
1108
1604
  }
1109
1605
  }
@@ -1112,7 +1608,7 @@ export const renderQueryAst = (
1112
1608
  if (merge.whenNotMatched.predicate) {
1113
1609
  sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
1114
1610
  }
1115
- 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(", ")})`
1611
+ sql += ` then insert (${merge.whenNotMatched.values.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")}) values (${merge.whenNotMatched.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1116
1612
  }
1117
1613
  break
1118
1614
  }
@@ -1127,12 +1623,17 @@ export const renderQueryAst = (
1127
1623
  }
1128
1624
  case "createTable": {
1129
1625
  const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1130
- sql = renderCreateTableSql(createTableAst.target!, state, dialect, createTableAst.ddl?.kind === "createTable" && createTableAst.ddl.ifNotExists)
1626
+ const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1627
+ sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1131
1628
  break
1132
1629
  }
1133
1630
  case "dropTable": {
1134
1631
  const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1135
- const ifExists = dropTableAst.ddl?.kind === "dropTable" && dropTableAst.ddl.ifExists
1632
+ const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1633
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
1634
+ if (dialect.name !== "postgres" && ifExists) {
1635
+ throw new Error(`Unsupported ${dialect.name} drop table options`)
1636
+ }
1136
1637
  sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1137
1638
  break
1138
1639
  }
@@ -1140,7 +1641,7 @@ export const renderQueryAst = (
1140
1641
  const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1141
1642
  sql = renderCreateIndexSql(
1142
1643
  createIndexAst.target!,
1143
- createIndexAst.ddl as Extract<QueryAst.DdlClause, { readonly kind: "createIndex" }>,
1644
+ expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
1144
1645
  state,
1145
1646
  dialect
1146
1647
  )
@@ -1150,15 +1651,21 @@ export const renderQueryAst = (
1150
1651
  const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1151
1652
  sql = renderDropIndexSql(
1152
1653
  dropIndexAst.target!,
1153
- dropIndexAst.ddl as Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
1654
+ expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
1154
1655
  state,
1155
1656
  dialect
1156
1657
  )
1157
1658
  break
1158
1659
  }
1660
+ default: {
1661
+ if (ast.transaction !== undefined) {
1662
+ sql = renderTransactionClause(ast.transaction, dialect)
1663
+ }
1664
+ break
1665
+ }
1159
1666
  }
1160
1667
 
1161
- if (state.ctes.length === 0) {
1668
+ if (state.ctes.length === 0 || options.emitCtes === false) {
1162
1669
  return {
1163
1670
  sql,
1164
1671
  projections
@@ -1206,9 +1713,19 @@ const renderSourceReference = (
1206
1713
  readonly plan: Query.Plan.Any
1207
1714
  readonly recursive?: boolean
1208
1715
  }
1716
+ const registeredCteSource = state.cteSources.get(cte.name)
1717
+ if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
1718
+ throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
1719
+ }
1209
1720
  if (!state.cteNames.has(cte.name)) {
1210
1721
  state.cteNames.add(cte.name)
1211
- const rendered = renderQueryAst(Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect)
1722
+ state.cteSources.set(cte.name, cte.plan)
1723
+ const rendered = renderQueryAst(
1724
+ Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1725
+ state,
1726
+ dialect,
1727
+ { emitCtes: false }
1728
+ )
1212
1729
  state.ctes.push({
1213
1730
  name: cte.name,
1214
1731
  sql: rendered.sql,
@@ -1225,14 +1742,17 @@ const renderSourceReference = (
1225
1742
  if (!state.cteNames.has(derived.name)) {
1226
1743
  // derived tables are inlined, so no CTE registration is needed
1227
1744
  }
1228
- return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
1745
+ return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
1229
1746
  }
1230
1747
  if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
1231
1748
  const lateral = source as unknown as {
1232
1749
  readonly name: string
1233
1750
  readonly plan: Query.Plan.Any
1234
1751
  }
1235
- return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
1752
+ if (dialect.name === "standard") {
1753
+ throw new Error("Unsupported standard lateral source")
1754
+ }
1755
+ return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
1236
1756
  }
1237
1757
  if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
1238
1758
  const values = source as unknown as {
@@ -1258,13 +1778,42 @@ const renderSourceReference = (
1258
1778
  if (dialect.name !== "postgres") {
1259
1779
  throw new Error("Unsupported table function source for SQL rendering")
1260
1780
  }
1781
+ const functionName = renderFunctionName(tableFunction.functionName)
1261
1782
  const columnNames = Object.keys(tableFunction.columns)
1262
- return `${tableFunction.functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1783
+ return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1263
1784
  }
1264
1785
  const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1265
- ? (source as Table.AnyTable)[Table.TypeId].schemaName
1786
+ ? casedSchemaName(source as Table.AnyTable, state)
1266
1787
  : undefined
1267
- return dialect.renderTableReference(tableName, baseTableName, schemaName)
1788
+ if (typeof source === "object" && source !== null && Table.TypeId in source) {
1789
+ const table = source as Table.AnyTable
1790
+ const renderedBaseName = casedTableName(table, state)
1791
+ const renderedTableName = table[Table.TypeId].kind === "alias"
1792
+ ? tableName
1793
+ : renderedBaseName
1794
+ return dialect.renderTableReference(renderedTableName, renderedBaseName, schemaName)
1795
+ }
1796
+ return dialect.renderTableReference(
1797
+ Casing.applyCategory(state.casing, "tables", tableName),
1798
+ Casing.applyCategory(state.casing, "tables", baseTableName),
1799
+ schemaName
1800
+ )
1801
+ }
1802
+
1803
+ const renderSubqueryExpressionPlan = (
1804
+ plan: Query.Plan.Any,
1805
+ state: RenderState,
1806
+ dialect: SqlDialect
1807
+ ): string => {
1808
+ const statement = Query.getQueryState(plan).statement
1809
+ if (statement !== "select" && statement !== "set") {
1810
+ throw new Error("subquery expressions only accept select-like query plans")
1811
+ }
1812
+ return renderQueryAst(
1813
+ Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1814
+ state,
1815
+ dialect
1816
+ ).sql
1268
1817
  }
1269
1818
 
1270
1819
  /**
@@ -1287,135 +1836,176 @@ export const renderExpression = (
1287
1836
  return jsonSql
1288
1837
  }
1289
1838
  const ast = rawAst as ExpressionAst.Any
1290
- const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1291
- operator === "eq"
1292
- ? "="
1293
- : operator === "neq"
1294
- ? "<>"
1295
- : operator === "lt"
1296
- ? "<"
1297
- : operator === "lte"
1298
- ? "<="
1299
- : operator === "gt"
1300
- ? ">"
1301
- : ">="
1302
- switch (ast.kind) {
1839
+ const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1840
+ ({
1841
+ eq: "=",
1842
+ neq: "<>",
1843
+ lt: "<",
1844
+ lte: "<=",
1845
+ gt: ">",
1846
+ gte: ">="
1847
+ } as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
1848
+ const renderCollation = (collation: unknown): string => {
1849
+ return (collation as readonly string[]).map((segment) => dialect.quoteIdentifier(segment)).join(".")
1850
+ }
1851
+ switch (ast.kind) {
1303
1852
  case "column":
1304
- return ast.tableName.length === 0
1305
- ? dialect.quoteIdentifier(ast.columnName)
1306
- : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1853
+ return state.rowLocalColumns || ast.tableName.length === 0
1854
+ ? quoteColumn(ast.columnName, state, dialect, ast.tableName)
1855
+ : `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
1307
1856
  case "literal":
1857
+ if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1858
+ throw new Error("Expected a finite numeric value")
1859
+ }
1308
1860
  return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1309
1861
  case "excluded":
1862
+ if (state.allowExcluded !== true) {
1863
+ throw new Error("excluded(...) is only supported inside insert conflict handlers")
1864
+ }
1310
1865
  return dialect.name === "mysql"
1311
- ? `values(${dialect.quoteIdentifier(ast.columnName)})`
1312
- : `excluded.${dialect.quoteIdentifier(ast.columnName)}`
1866
+ ? `values(${quoteColumn(ast.columnName, state, dialect)})`
1867
+ : `excluded.${quoteColumn(ast.columnName, state, dialect)}`
1313
1868
  case "cast":
1314
- return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
1869
+ return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
1315
1870
  case "collate":
1316
- return `(${renderExpression(ast.value, state, dialect)} collate ${ast.collation.map((segment) => dialect.quoteIdentifier(segment)).join(".")})`
1871
+ return `(${renderExpression(expectValueExpression("collate", ast.value), state, dialect)} collate ${renderCollation(ast.collation)})`
1317
1872
  case "function":
1318
- return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1873
+ return renderFunctionCall(ast.name, ast.args, state, dialect)
1319
1874
  case "eq":
1320
- return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1875
+ return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
1321
1876
  case "neq":
1322
- return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1877
+ return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
1323
1878
  case "lt":
1324
- return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1879
+ return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
1325
1880
  case "lte":
1326
- return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1881
+ return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
1327
1882
  case "gt":
1328
- return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1883
+ return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
1329
1884
  case "gte":
1330
- return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1885
+ return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
1331
1886
  case "like":
1332
- return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1333
- case "ilike":
1887
+ return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
1888
+ case "ilike": {
1889
+ const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
1334
1890
  return dialect.name === "postgres"
1335
- ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1336
- : `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
1337
- case "regexMatch":
1891
+ ? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
1892
+ : `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
1893
+ }
1894
+ case "regexMatch": {
1895
+ const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
1896
+ if (dialect.name === "standard") {
1897
+ throw new Error("Unsupported standard regular-expression predicates")
1898
+ }
1338
1899
  return dialect.name === "postgres"
1339
- ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1340
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1341
- case "regexIMatch":
1900
+ ? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
1901
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1902
+ }
1903
+ case "regexIMatch": {
1904
+ const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
1905
+ if (dialect.name === "standard") {
1906
+ throw new Error("Unsupported standard regular-expression predicates")
1907
+ }
1342
1908
  return dialect.name === "postgres"
1343
- ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1344
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1345
- case "regexNotMatch":
1909
+ ? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
1910
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1911
+ }
1912
+ case "regexNotMatch": {
1913
+ const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
1914
+ if (dialect.name === "standard") {
1915
+ throw new Error("Unsupported standard regular-expression predicates")
1916
+ }
1346
1917
  return dialect.name === "postgres"
1347
- ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1348
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1349
- case "regexNotIMatch":
1918
+ ? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
1919
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1920
+ }
1921
+ case "regexNotIMatch": {
1922
+ const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
1923
+ if (dialect.name === "standard") {
1924
+ throw new Error("Unsupported standard regular-expression predicates")
1925
+ }
1350
1926
  return dialect.name === "postgres"
1351
- ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1352
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1353
- case "isDistinctFrom":
1927
+ ? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
1928
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1929
+ }
1930
+ case "isDistinctFrom": {
1931
+ const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
1354
1932
  return dialect.name === "mysql"
1355
- ? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
1356
- : `(${renderExpression(ast.left, state, dialect)} is distinct from ${renderExpression(ast.right, state, dialect)})`
1357
- case "isNotDistinctFrom":
1933
+ ? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
1934
+ : `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
1935
+ }
1936
+ case "isNotDistinctFrom": {
1937
+ const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
1358
1938
  return dialect.name === "mysql"
1359
- ? `(${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)})`
1360
- : `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1361
- case "contains":
1939
+ ? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
1940
+ : `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
1941
+ }
1942
+ case "contains": {
1943
+ const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
1362
1944
  if (dialect.name === "postgres") {
1363
- const left = isJsonExpression(ast.left)
1364
- ? renderPostgresJsonValue(ast.left, state, dialect)
1365
- : renderExpression(ast.left, state, dialect)
1366
- const right = isJsonExpression(ast.right)
1367
- ? renderPostgresJsonValue(ast.right, state, dialect)
1368
- : renderExpression(ast.right, state, dialect)
1945
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1946
+ const left = isJsonExpression(leftExpression)
1947
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1948
+ : renderExpression(leftExpression, state, dialect)
1949
+ const right = isJsonExpression(rightExpression)
1950
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1951
+ : renderExpression(rightExpression, state, dialect)
1369
1952
  return `(${left} @> ${right})`
1370
1953
  }
1371
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1372
- return `json_contains(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1954
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1955
+ return `json_contains(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
1373
1956
  }
1374
1957
  throw new Error("Unsupported container operator for SQL rendering")
1375
- case "containedBy":
1958
+ }
1959
+ case "containedBy": {
1960
+ const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
1376
1961
  if (dialect.name === "postgres") {
1377
- const left = isJsonExpression(ast.left)
1378
- ? renderPostgresJsonValue(ast.left, state, dialect)
1379
- : renderExpression(ast.left, state, dialect)
1380
- const right = isJsonExpression(ast.right)
1381
- ? renderPostgresJsonValue(ast.right, state, dialect)
1382
- : renderExpression(ast.right, state, dialect)
1962
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1963
+ const left = isJsonExpression(leftExpression)
1964
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1965
+ : renderExpression(leftExpression, state, dialect)
1966
+ const right = isJsonExpression(rightExpression)
1967
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1968
+ : renderExpression(rightExpression, state, dialect)
1383
1969
  return `(${left} <@ ${right})`
1384
1970
  }
1385
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1386
- return `json_contains(${renderExpression(ast.right, state, dialect)}, ${renderExpression(ast.left, state, dialect)})`
1971
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1972
+ return `json_contains(${renderExpression(rightExpression, state, dialect)}, ${renderExpression(leftExpression, state, dialect)})`
1387
1973
  }
1388
1974
  throw new Error("Unsupported container operator for SQL rendering")
1389
- case "overlaps":
1975
+ }
1976
+ case "overlaps": {
1977
+ const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
1390
1978
  if (dialect.name === "postgres") {
1391
- const left = isJsonExpression(ast.left)
1392
- ? renderPostgresJsonValue(ast.left, state, dialect)
1393
- : renderExpression(ast.left, state, dialect)
1394
- const right = isJsonExpression(ast.right)
1395
- ? renderPostgresJsonValue(ast.right, state, dialect)
1396
- : renderExpression(ast.right, state, dialect)
1979
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1980
+ const left = isJsonExpression(leftExpression)
1981
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1982
+ : renderExpression(leftExpression, state, dialect)
1983
+ const right = isJsonExpression(rightExpression)
1984
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1985
+ : renderExpression(rightExpression, state, dialect)
1397
1986
  return `(${left} && ${right})`
1398
1987
  }
1399
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1400
- return `json_overlaps(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1988
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1989
+ return `json_overlaps(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
1401
1990
  }
1402
1991
  throw new Error("Unsupported container operator for SQL rendering")
1992
+ }
1403
1993
  case "isNull":
1404
- return `(${renderExpression(ast.value, state, dialect)} is null)`
1994
+ return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
1405
1995
  case "isNotNull":
1406
- return `(${renderExpression(ast.value, state, dialect)} is not null)`
1996
+ return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
1407
1997
  case "not":
1408
- return `(not ${renderExpression(ast.value, state, dialect)})`
1998
+ return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
1409
1999
  case "upper":
1410
- return `upper(${renderExpression(ast.value, state, dialect)})`
2000
+ return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
1411
2001
  case "lower":
1412
- return `lower(${renderExpression(ast.value, state, dialect)})`
2002
+ return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
1413
2003
  case "count":
1414
- return `count(${renderExpression(ast.value, state, dialect)})`
2004
+ return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
1415
2005
  case "max":
1416
- return `max(${renderExpression(ast.value, state, dialect)})`
2006
+ return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
1417
2007
  case "min":
1418
- return `min(${renderExpression(ast.value, state, dialect)})`
2008
+ return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
1419
2009
  case "and":
1420
2010
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1421
2011
  case "or":
@@ -1435,45 +2025,39 @@ export const renderExpression = (
1435
2025
  `when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
1436
2026
  ).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
1437
2027
  case "exists":
1438
- return `exists (${renderQueryAst(
1439
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1440
- state,
1441
- dialect
1442
- ).sql})`
2028
+ return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1443
2029
  case "scalarSubquery":
1444
- return `(${renderQueryAst(
1445
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1446
- state,
1447
- dialect
1448
- ).sql})`
2030
+ return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1449
2031
  case "inSubquery":
1450
- return `(${renderExpression(ast.left, state, dialect)} in (${renderQueryAst(
1451
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1452
- state,
1453
- dialect
1454
- ).sql}))`
1455
- case "comparisonAny":
1456
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderQueryAst(
1457
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1458
- state,
1459
- dialect
1460
- ).sql}))`
1461
- case "comparisonAll":
1462
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderQueryAst(
1463
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1464
- state,
1465
- dialect
1466
- ).sql}))`
1467
- case "window": {
1468
- if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1469
- break
2032
+ return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2033
+ case "comparisonAny": {
2034
+ const left = expectValueExpression("compareAny", ast.left)
2035
+ const operator = renderComparisonOperator(ast.operator)
2036
+ if (dialect.name === "standard") {
2037
+ throw new Error("Unsupported standard quantified comparison")
2038
+ }
2039
+ return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2040
+ }
2041
+ case "comparisonAll": {
2042
+ const left = expectValueExpression("compareAll", ast.left)
2043
+ const operator = renderComparisonOperator(ast.operator)
2044
+ if (dialect.name === "standard") {
2045
+ throw new Error("Unsupported standard quantified comparison")
1470
2046
  }
2047
+ return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2048
+ }
2049
+ case "window": {
2050
+ const partitionBy = ast.partitionBy as readonly Expression.Any[]
2051
+ const orderBy = ast.orderBy as readonly {
2052
+ readonly value: Expression.Any
2053
+ readonly direction: string
2054
+ }[]
1471
2055
  const clauses: string[] = []
1472
- if (ast.partitionBy.length > 0) {
1473
- clauses.push(`partition by ${ast.partitionBy.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}`)
2056
+ if (partitionBy.length > 0) {
2057
+ clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
1474
2058
  }
1475
- if (ast.orderBy.length > 0) {
1476
- clauses.push(`order by ${ast.orderBy.map((entry) =>
2059
+ if (orderBy.length > 0) {
2060
+ clauses.push(`order by ${orderBy.map((entry) =>
1477
2061
  `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1478
2062
  ).join(", ")}`)
1479
2063
  }
@@ -1486,7 +2070,7 @@ export const renderExpression = (
1486
2070
  case "denseRank":
1487
2071
  return `dense_rank() over (${specification})`
1488
2072
  case "over":
1489
- return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
2073
+ return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
1490
2074
  }
1491
2075
  break
1492
2076
  }