effect-qb 4.0.0-beta.66 → 4.0.0-beta.92

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 (126) hide show
  1. package/README.md +5 -2
  2. package/dist/index.js +9373 -0
  3. package/dist/mysql.js +3917 -2572
  4. package/dist/postgres/metadata.js +2491 -1373
  5. package/dist/postgres.js +5450 -5155
  6. package/dist/sqlite.js +6891 -5528
  7. package/dist/standard.js +9327 -0
  8. package/package.json +9 -2
  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/coercion/rules.ts +13 -1
  13. package/src/internal/column-state.ts +21 -15
  14. package/src/internal/column.ts +44 -7
  15. package/src/internal/datatypes/define.ts +7 -1
  16. package/src/internal/datatypes/enrich.ts +23 -0
  17. package/src/internal/datatypes/lookup.ts +81 -25
  18. package/src/internal/datatypes/matrix.ts +903 -0
  19. package/src/internal/datatypes/shape.ts +2 -0
  20. package/src/internal/derived-table.ts +4 -36
  21. package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/mysql.ts} +552 -361
  22. package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +657 -400
  23. package/src/{sqlite/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +505 -347
  24. package/src/internal/dialect.ts +35 -0
  25. package/src/internal/dsl-mutation-runtime.ts +12 -162
  26. package/src/internal/dsl-plan-runtime.ts +10 -138
  27. package/src/internal/dsl-query-runtime.ts +5 -79
  28. package/src/internal/dsl-transaction-ddl-runtime.ts +41 -65
  29. package/src/internal/executor.ts +14 -16
  30. package/src/internal/grouping-key.ts +87 -20
  31. package/src/internal/implication-runtime.ts +1 -1
  32. package/src/internal/json/path-access.ts +351 -0
  33. package/src/internal/predicate/runtime.ts +3 -0
  34. package/src/internal/query.d.ts +38 -11
  35. package/src/internal/query.ts +64 -25
  36. package/src/internal/renderer.ts +26 -14
  37. package/src/internal/runtime/normalize.ts +12 -5
  38. package/src/internal/scalar.ts +7 -1
  39. package/src/internal/schema-derivation.d.ts +7 -7
  40. package/src/internal/schema-derivation.ts +9 -9
  41. package/src/internal/schema-expression.ts +2 -2
  42. package/src/internal/sql-expression-renderer.ts +19 -0
  43. package/src/internal/standard-dsl.ts +6975 -0
  44. package/src/internal/table-options.ts +126 -66
  45. package/src/internal/table.ts +652 -219
  46. package/src/mysql/column-extension.ts +3 -0
  47. package/src/mysql/column.ts +8 -9
  48. package/src/mysql/datatypes/index.ts +4 -2
  49. package/src/mysql/datatypes/spec.ts +6 -176
  50. package/src/mysql/executor.ts +7 -7
  51. package/src/mysql/internal/dialect.ts +9 -4
  52. package/src/mysql/internal/dsl.ts +309 -154
  53. package/src/mysql/internal/renderer.ts +6 -2
  54. package/src/mysql/json.ts +7 -2
  55. package/src/mysql/query-extension.ts +16 -0
  56. package/src/mysql/renderer.ts +37 -3
  57. package/src/mysql/type.ts +60 -0
  58. package/src/mysql.ts +7 -13
  59. package/src/postgres/check.ts +1 -0
  60. package/src/postgres/column-extension.ts +28 -0
  61. package/src/postgres/column.ts +2 -8
  62. package/src/postgres/datatypes/index.d.ts +2 -1
  63. package/src/postgres/datatypes/index.ts +4 -2
  64. package/src/postgres/datatypes/spec.ts +6 -260
  65. package/src/postgres/executor.ts +7 -7
  66. package/src/postgres/foreign-key.ts +24 -0
  67. package/src/postgres/function/core.ts +1 -3
  68. package/src/postgres/function/index.ts +1 -17
  69. package/src/postgres/index.ts +1 -0
  70. package/src/postgres/internal/dialect.ts +9 -4
  71. package/src/postgres/internal/dsl.ts +303 -163
  72. package/src/postgres/internal/renderer.ts +6 -2
  73. package/src/postgres/internal/schema-ddl.ts +22 -10
  74. package/src/postgres/internal/schema-model.ts +238 -7
  75. package/src/postgres/json-extension.ts +7 -0
  76. package/src/postgres/json.ts +762 -173
  77. package/src/postgres/jsonb.ts +37 -0
  78. package/src/postgres/primary-key.ts +24 -0
  79. package/src/postgres/query-extension.ts +2 -0
  80. package/src/postgres/renderer.ts +37 -3
  81. package/src/postgres/schema-management.ts +12 -11
  82. package/src/postgres/schema.ts +106 -15
  83. package/src/postgres/table.ts +205 -530
  84. package/src/postgres/type.ts +93 -10
  85. package/src/postgres/unique.ts +32 -0
  86. package/src/postgres.ts +20 -16
  87. package/src/sqlite/column-extension.ts +3 -0
  88. package/src/sqlite/column.ts +8 -9
  89. package/src/sqlite/datatypes/index.ts +4 -2
  90. package/src/sqlite/datatypes/spec.ts +6 -94
  91. package/src/sqlite/executor.ts +7 -7
  92. package/src/sqlite/internal/dialect.ts +9 -4
  93. package/src/sqlite/internal/dsl.ts +298 -154
  94. package/src/sqlite/internal/renderer.ts +6 -2
  95. package/src/sqlite/json.ts +8 -2
  96. package/src/sqlite/query-extension.ts +2 -0
  97. package/src/sqlite/renderer.ts +37 -3
  98. package/src/sqlite/type.ts +40 -0
  99. package/src/sqlite.ts +7 -13
  100. package/src/standard/cast.ts +113 -0
  101. package/src/standard/check.ts +17 -0
  102. package/src/standard/column.ts +163 -0
  103. package/src/standard/datatypes/index.ts +83 -0
  104. package/src/standard/datatypes/spec.ts +12 -0
  105. package/src/standard/dialect.ts +40 -0
  106. package/src/standard/foreign-key.ts +37 -0
  107. package/src/standard/function/aggregate.ts +2 -0
  108. package/src/standard/function/core.ts +2 -0
  109. package/src/standard/function/index.ts +18 -0
  110. package/src/standard/function/string.ts +2 -0
  111. package/src/standard/function/temporal.ts +78 -0
  112. package/src/standard/function/window.ts +2 -0
  113. package/src/standard/index.ts +17 -0
  114. package/src/standard/internal/renderer.ts +45 -0
  115. package/src/standard/json.ts +883 -0
  116. package/src/standard/primary-key.ts +17 -0
  117. package/src/standard/query.ts +152 -0
  118. package/src/standard/renderer.ts +49 -0
  119. package/src/standard/table.ts +151 -0
  120. package/src/standard/unique.ts +17 -0
  121. package/src/standard.ts +32 -0
  122. package/src/internal/aggregation-validation.ts +0 -57
  123. package/src/internal/table.d.ts +0 -180
  124. package/src/mysql/table.ts +0 -186
  125. package/src/postgres/cast.ts +0 -45
  126. package/src/sqlite/table.ts +0 -175
@@ -1,44 +1,51 @@
1
1
  import * as Schema from "effect/Schema"
2
2
 
3
- import * as Query from "../../internal/query.js"
4
- import * as Expression from "../../internal/scalar.js"
5
- import * as Table from "../../internal/table.js"
6
- import * as QueryAst from "../../internal/query-ast.js"
7
- import type { RenderState, RenderValueContext, SqlDialect } from "../../internal/dialect.js"
8
- import * as ExpressionAst from "../../internal/expression-ast.js"
9
- import * as JsonPath from "../../internal/json/path.js"
10
- import { renderSelectLockMode } from "../../internal/dsl-plan-runtime.js"
11
- import { expectConflictClause, expectInsertSourceKind } from "../../internal/dsl-mutation-runtime.js"
12
- import { expectDdlClauseKind, expectTruncateClause, renderTransactionIsolationLevel } from "../../internal/dsl-transaction-ddl-runtime.js"
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 { renderPortableDatatypeCastType, renderPortableDatatypeDdlType } from "../datatypes/matrix.js"
9
+ import * as ExpressionAst from "../expression-ast.js"
10
+ import * as JsonPath from "../json/path.js"
11
+ import { renderSelectLockMode } from "../dsl-plan-runtime.js"
12
+ import { expectConflictClause } from "../dsl-mutation-runtime.js"
13
+ import { expectDdlClauseKind, expectTruncateClause, normalizeStatementFlag, normalizeStatementIdentifier, renderTransactionIsolationLevel } from "../dsl-transaction-ddl-runtime.js"
13
14
  import {
14
15
  renderJsonSelectSql,
15
16
  renderSelectSql,
16
17
  toDriverValue
17
- } from "../../internal/runtime/driver-value-mapping.js"
18
- import { normalizeDbValue } from "../../internal/runtime/normalize.js"
19
- import { flattenSelection, type Projection } from "../../internal/projections.js"
20
- import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
21
- import * as SchemaExpression from "../../internal/schema-expression.js"
22
- import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
18
+ } from "../runtime/driver-value-mapping.js"
19
+ import { normalizeDbValue } from "../runtime/normalize.js"
20
+ import { flattenSelection, type Projection } from "../projections.js"
21
+ import { groupingKeyOfExpression } from "../grouping-key.js"
22
+ import * as SchemaExpression from "../schema-expression.js"
23
+ import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
24
+ import * as Casing from "../casing.js"
23
25
 
24
26
  const renderDbType = (
25
27
  dialect: SqlDialect,
26
28
  dbType: Expression.DbType.Any
27
29
  ): string => {
28
- if (dialect.name === "mysql" && dbType.dialect === "mysql" && dbType.kind === "uuid") {
29
- return "char(36)"
30
- }
31
- return dbType.kind
30
+ return renderDbTypeName(renderPortableDatatypeDdlType(dialect.name, dbType.kind) ?? dbType.kind)
32
31
  }
33
32
 
33
+ const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
34
+ "element" in dbType
35
+
34
36
  const renderCastType = (
35
37
  dialect: SqlDialect,
36
- dbType: Expression.DbType.Any
38
+ dbType: unknown
37
39
  ): string => {
40
+ const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
41
+ const portableType = renderPortableDatatypeCastType(dialect.name, kind)
42
+ if (portableType !== undefined) {
43
+ return renderDbTypeName(portableType)
44
+ }
38
45
  if (dialect.name !== "mysql") {
39
- return dbType.kind
46
+ return renderDbTypeName(kind)
40
47
  }
41
- switch (dbType.kind) {
48
+ switch (kind) {
42
49
  case "text":
43
50
  return "char"
44
51
  case "uuid":
@@ -53,7 +60,159 @@ const renderCastType = (
53
60
  case "json":
54
61
  return "json"
55
62
  default:
56
- return dbType.kind
63
+ return renderDbTypeName(kind)
64
+ }
65
+ }
66
+
67
+ const casingForTable = (
68
+ table: Table.AnyTable,
69
+ state: RenderState
70
+ ): Casing.Options | undefined =>
71
+ Casing.merge(state.casing, table[Table.TypeId].casing)
72
+
73
+ const casedTableName = (
74
+ table: Table.AnyTable,
75
+ state: RenderState
76
+ ): string => {
77
+ const tableState = table[Table.TypeId]
78
+ return Casing.applyCategory(casingForTable(table, state), "tables", tableState.baseName)
79
+ }
80
+
81
+ const casedSchemaName = (
82
+ table: Table.AnyTable,
83
+ state: RenderState
84
+ ): string | undefined => {
85
+ const schemaName = table[Table.TypeId].schemaName
86
+ return schemaName === undefined
87
+ ? undefined
88
+ : Casing.applyCategory(casingForTable(table, state), "schemas", schemaName)
89
+ }
90
+
91
+ const casedColumnName = (
92
+ columnName: string,
93
+ state: RenderState,
94
+ tableName?: string
95
+ ): string => {
96
+ if (tableName !== undefined) {
97
+ const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
98
+ if (mapped !== undefined) {
99
+ return mapped
100
+ }
101
+ }
102
+ return Casing.applyCategory(state.casing, "columns", columnName)
103
+ }
104
+
105
+ const casedTableReferenceName = (
106
+ tableName: string,
107
+ state: RenderState
108
+ ): string =>
109
+ state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
110
+
111
+ const quoteColumn = (
112
+ columnName: string,
113
+ state: RenderState,
114
+ dialect: SqlDialect,
115
+ tableName?: string
116
+ ): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
117
+
118
+ const stateWithTableCasing = (
119
+ state: RenderState,
120
+ source: unknown
121
+ ): RenderState =>
122
+ typeof source === "object" && source !== null && Table.TypeId in source
123
+ ? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
124
+ : state
125
+
126
+ const referenceCasing = (
127
+ reference: { readonly casing?: Casing.Options },
128
+ state: RenderState
129
+ ): Casing.Options | undefined =>
130
+ Casing.merge(state.casing, reference.casing)
131
+
132
+ const renderReferenceTable = (
133
+ reference: {
134
+ readonly tableName: string
135
+ readonly schemaName?: string
136
+ readonly casing?: Casing.Options
137
+ },
138
+ state: RenderState,
139
+ dialect: SqlDialect
140
+ ): string => {
141
+ const casing = referenceCasing(reference, state)
142
+ const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
143
+ const schemaName = reference.schemaName === undefined
144
+ ? undefined
145
+ : Casing.applyCategory(casing, "schemas", reference.schemaName)
146
+ return dialect.renderTableReference(tableName, tableName, schemaName)
147
+ }
148
+
149
+ const quoteReferenceColumn = (
150
+ columnName: string,
151
+ reference: { readonly casing?: Casing.Options },
152
+ state: RenderState,
153
+ dialect: SqlDialect
154
+ ): string =>
155
+ dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
156
+
157
+ const registerSourceReference = (
158
+ source: unknown,
159
+ tableName: string,
160
+ state: RenderState
161
+ ): void => {
162
+ if (typeof source !== "object" || source === null) {
163
+ return
164
+ }
165
+ if (Table.TypeId in source) {
166
+ const table = source as Table.AnyTable
167
+ const tableState = table[Table.TypeId]
168
+ const casing = casingForTable(table, state)
169
+ const renderedTableName = tableState.kind === "alias"
170
+ ? tableName
171
+ : Casing.applyCategory(casing, "tables", tableState.baseName)
172
+ const columns = new Map(
173
+ Object.keys(tableState.fields).map((columnName) => [
174
+ columnName,
175
+ Casing.applyCategory(casing, "columns", columnName)
176
+ ] as const)
177
+ )
178
+ state.sourceNames?.set(tableName, {
179
+ tableName: renderedTableName,
180
+ columns
181
+ })
182
+ return
183
+ }
184
+ if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
185
+ state.sourceNames?.set(tableName, {
186
+ tableName,
187
+ columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
188
+ })
189
+ }
190
+ }
191
+
192
+ const registerQuerySources = (
193
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
194
+ state: RenderState
195
+ ): void => {
196
+ if (ast.from !== undefined) {
197
+ registerSourceReference(ast.from.source, ast.from.tableName, state)
198
+ }
199
+ for (const source of ast.fromSources ?? []) {
200
+ registerSourceReference(source.source, source.tableName, state)
201
+ }
202
+ for (const join of ast.joins) {
203
+ registerSourceReference(join.source, join.tableName, state)
204
+ }
205
+ if (ast.into !== undefined) {
206
+ registerSourceReference(ast.into.source, ast.into.tableName, state)
207
+ }
208
+ if (ast.target !== undefined) {
209
+ registerSourceReference(ast.target.source, ast.target.tableName, state)
210
+ }
211
+ for (const target of ast.targets ?? []) {
212
+ registerSourceReference(target.source, target.tableName, state)
213
+ }
214
+ if (ast.using !== undefined) {
215
+ registerSourceReference(ast.using.source, ast.using.tableName, state)
57
216
  }
58
217
  }
59
218
 
@@ -115,18 +274,29 @@ const renderColumnDefinition = (
115
274
  dialect: SqlDialect,
116
275
  state: RenderState,
117
276
  columnName: string,
118
- column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
277
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
278
+ tableName?: string,
279
+ casing?: Casing.Options
119
280
  ): string => {
281
+ const expressionState = { ...state, casing, rowLocalColumns: true }
282
+ if (dialect.name !== "postgres" && isArrayDbType(column.metadata.dbType)) {
283
+ throw new Error(`Unsupported ${dialect.name} array column options`)
284
+ }
120
285
  const clauses = [
121
- dialect.quoteIdentifier(columnName),
122
- column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
286
+ quoteColumn(columnName, state, dialect, tableName),
287
+ column.metadata.ddlType === undefined
288
+ ? renderDbType(dialect, column.metadata.dbType)
289
+ : renderDbTypeName(column.metadata.ddlType)
123
290
  ]
124
291
  if (column.metadata.identity) {
292
+ if (dialect.name !== "postgres") {
293
+ throw new Error(`Unsupported ${dialect.name} identity column options`)
294
+ }
125
295
  clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
126
296
  } else if (column.metadata.generatedValue) {
127
- clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
297
+ clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
128
298
  } else if (column.metadata.defaultValue) {
129
- clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
299
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
130
300
  }
131
301
  if (!column.metadata.nullable) {
132
302
  clauses.push("not null")
@@ -138,31 +308,56 @@ const renderCreateTableSql = (
138
308
  targetSource: QueryAst.FromClause,
139
309
  state: RenderState,
140
310
  dialect: SqlDialect,
141
- ifNotExists: boolean
311
+ ifNotExists: unknown
142
312
  ): string => {
313
+ const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
314
+ if (dialect.name !== "postgres" && normalizedIfNotExists) {
315
+ throw new Error(`Unsupported ${dialect.name} create table options`)
316
+ }
143
317
  const table = targetSource.source as Table.AnyTable
318
+ const tableCasing = casingForTable(table, state)
144
319
  const fields = table[Table.TypeId].fields
145
320
  const definitions = Object.entries(fields).map(([columnName, column]) =>
146
- renderColumnDefinition(dialect, state, columnName, column)
321
+ renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
147
322
  )
148
- for (const option of table[Table.OptionsSymbol]) {
323
+ const options = table[Table.OptionsSymbol] as unknown
324
+ const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
325
+ validateOptions(table[Table.TypeId].name, fields, tableOptions)
326
+ for (const option of tableOptions) {
327
+ if (typeof option !== "object" || option === null || !("kind" in option)) {
328
+ continue
329
+ }
149
330
  switch (option.kind) {
150
331
  case "primaryKey":
151
- 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" : ""}` : ""}`)
332
+ if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
333
+ throw new Error(`Unsupported ${dialect.name} primary key constraint options`)
334
+ }
335
+ 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" : ""}` : ""}`)
152
336
  break
153
337
  case "unique":
154
- 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" : ""}` : ""}`)
338
+ if (dialect.name !== "postgres" && (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred)) {
339
+ throw new Error(`Unsupported ${dialect.name} unique constraint options`)
340
+ }
341
+ 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" : ""}` : ""}`)
155
342
  break
156
343
  case "foreignKey": {
157
- const reference = option.references()
344
+ if (dialect.name !== "postgres" && (option.deferrable || option.initiallyDeferred)) {
345
+ throw new Error(`Unsupported ${dialect.name} foreign key constraint options`)
346
+ }
347
+ const reference = typeof option.references === "function"
348
+ ? option.references()
349
+ : option.references
158
350
  definitions.push(
159
- `${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.onDelete !== undefined ? ` on delete ${renderReferentialAction(option.onDelete)}` : ""}${option.onUpdate !== undefined ? ` on update ${renderReferentialAction(option.onUpdate)}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
351
+ `${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" : ""}` : ""}`
160
352
  )
161
353
  break
162
354
  }
163
355
  case "check":
356
+ if (dialect.name !== "postgres" && option.noInherit) {
357
+ throw new Error(`Unsupported ${dialect.name} check constraint options`)
358
+ }
164
359
  definitions.push(
165
- `constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
360
+ `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
166
361
  )
167
362
  break
168
363
  case "index":
@@ -171,7 +366,7 @@ const renderCreateTableSql = (
171
366
  throw new Error("Unsupported table option kind")
172
367
  }
173
368
  }
174
- return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
369
+ return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
175
370
  }
176
371
 
177
372
  const renderCreateIndexSql = (
@@ -180,8 +375,16 @@ const renderCreateIndexSql = (
180
375
  state: RenderState,
181
376
  dialect: SqlDialect
182
377
  ): string => {
183
- const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
184
- 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(", ")})`
378
+ const unique = normalizeStatementFlag(ddl.unique)
379
+ const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
380
+ const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
381
+ if (dialect.name !== "postgres" && ifNotExists) {
382
+ throw new Error(`Unsupported ${dialect.name} create index options`)
383
+ }
384
+ const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
385
+ const table = targetSource.source as Table.AnyTable
386
+ const tableCasing = casingForTable(table, state)
387
+ 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(", ")})`
185
388
  }
186
389
 
187
390
  const renderDropIndexSql = (
@@ -190,18 +393,29 @@ const renderDropIndexSql = (
190
393
  state: RenderState,
191
394
  dialect: SqlDialect
192
395
  ): string => {
396
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
397
+ const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
398
+ if (dialect.name !== "postgres" && ifExists) {
399
+ throw new Error(`Unsupported ${dialect.name} drop index options`)
400
+ }
193
401
  if (dialect.name === "postgres") {
194
- const schemaName = typeof targetSource.source === "object" &&
402
+ const table = typeof targetSource.source === "object" &&
195
403
  targetSource.source !== null &&
196
404
  Table.TypeId in targetSource.source
197
- ? (targetSource.source as Table.AnyTable)[Table.TypeId].schemaName
405
+ ? targetSource.source as Table.AnyTable
198
406
  : undefined
407
+ const schemaName = table?.[Table.TypeId].schemaName
408
+ const tableCasing = table === undefined ? state.casing : casingForTable(table, state)
409
+ const renderedSchemaName = table === undefined ? schemaName : casedSchemaName(table, state)
410
+ const renderedIndexName = Casing.applyCategory(tableCasing, "indexes", name)
199
411
  const indexName = schemaName === undefined || schemaName === "public"
200
- ? dialect.quoteIdentifier(ddl.name)
201
- : `${dialect.quoteIdentifier(schemaName)}.${dialect.quoteIdentifier(ddl.name)}`
202
- return `drop index${ddl.ifExists ? " if exists" : ""} ${indexName}`
412
+ ? dialect.quoteIdentifier(renderedIndexName)
413
+ : `${dialect.quoteIdentifier(renderedSchemaName ?? schemaName)}.${dialect.quoteIdentifier(renderedIndexName)}`
414
+ return `drop index${ifExists ? " if exists" : ""} ${indexName}`
203
415
  }
204
- return `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
416
+ const table = targetSource.source as Table.AnyTable
417
+ const tableCasing = casingForTable(table, state)
418
+ return `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
205
419
  }
206
420
 
207
421
  const isExpression = (value: unknown): value is Expression.Any =>
@@ -221,6 +435,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
221
435
  const isJsonExpression = (value: unknown): value is Expression.Any =>
222
436
  isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
223
437
 
438
+ const expectValueExpression = (
439
+ _functionName: string,
440
+ value: unknown
441
+ ): Expression.Any => value as Expression.Any
442
+
443
+ const expectBinaryExpressions = (
444
+ _functionName: string,
445
+ left: unknown,
446
+ right: unknown
447
+ ): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
448
+
449
+ const renderBinaryExpression = (
450
+ functionName: string,
451
+ operator: string,
452
+ left: unknown,
453
+ right: unknown,
454
+ state: RenderState,
455
+ dialect: SqlDialect
456
+ ): string => {
457
+ const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
458
+ return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
459
+ }
460
+
224
461
  const postgresRangeSubtypeByKind: Readonly<Record<string, string>> = {
225
462
  int4range: "int4",
226
463
  int8range: "int8",
@@ -280,13 +517,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
280
517
  const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
281
518
  value !== null && typeof value === "object" && JsonPath.TypeId in value
282
519
 
520
+ const isOptionalJsonPathNumber = (value: unknown): boolean =>
521
+ value === undefined || (typeof value === "number" && Number.isFinite(value))
522
+
523
+ const isJsonPathSegment = (segment: unknown): boolean => {
524
+ if (typeof segment === "string") {
525
+ return true
526
+ }
527
+ if (typeof segment === "number") {
528
+ return Number.isFinite(segment)
529
+ }
530
+ if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
531
+ return false
532
+ }
533
+ switch ((segment as { readonly kind?: unknown }).kind) {
534
+ case "key":
535
+ return typeof (segment as { readonly key?: unknown }).key === "string"
536
+ case "index": {
537
+ const index = (segment as { readonly index?: unknown }).index
538
+ return typeof index === "number" && Number.isFinite(index)
539
+ }
540
+ case "wildcard":
541
+ case "descend":
542
+ return true
543
+ case "slice":
544
+ return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
545
+ isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
546
+ default:
547
+ return false
548
+ }
549
+ }
550
+
551
+ const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
552
+ if (!Array.isArray(segments)) {
553
+ throw new Error("JSON path expressions require a segment array")
554
+ }
555
+ if (segments.some((segment) => !isJsonPathSegment(segment))) {
556
+ throw new Error("JSON path segments require string, number, or path segment objects")
557
+ }
558
+ return segments as ReadonlyArray<JsonPath.AnySegment>
559
+ }
560
+
283
561
  const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
284
562
  const path = node.path ?? node.segments ?? node.keys
285
563
  if (isJsonPathValue(path)) {
286
- return path.segments
564
+ return validateJsonPathSegments(path.segments)
287
565
  }
288
566
  if (Array.isArray(path)) {
289
- return path as readonly JsonPath.AnySegment[]
567
+ return validateJsonPathSegments(path)
568
+ }
569
+ if (node.segments !== undefined) {
570
+ return validateJsonPathSegments(node.segments)
290
571
  }
291
572
  if ("key" in node) {
292
573
  return [JsonPath.key(String(node.key))]
@@ -305,11 +586,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
305
586
  return []
306
587
  }
307
588
  if ("right" in node && isJsonPathValue(node.right)) {
308
- return node.right.segments
589
+ return validateJsonPathSegments(node.right.segments)
309
590
  }
310
591
  return []
311
592
  }
312
593
 
594
+ const extractJsonKeys = (
595
+ node: Record<string, unknown>,
596
+ segments: ReadonlyArray<JsonPath.AnySegment>
597
+ ): readonly unknown[] =>
598
+ Array.isArray(node.keys)
599
+ ? node.keys
600
+ : segments.map((segment) =>
601
+ typeof segment === "object" && segment !== null && segment.kind === "key"
602
+ ? segment.key
603
+ : segment
604
+ )
605
+
313
606
  const extractJsonValue = (node: Record<string, unknown>): unknown =>
314
607
  node.newValue ?? node.insert ?? node.right
315
608
 
@@ -479,52 +772,64 @@ const renderJsonOpaquePath = (
479
772
  return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments), state)
480
773
  }
481
774
  if (typeof value === "string") {
775
+ if (value.trim().length === 0) {
776
+ throw new Error("SQL/JSON path input must be a non-empty string")
777
+ }
482
778
  return dialect.renderLiteral(value, state)
483
779
  }
484
780
  if (isExpression(value)) {
781
+ const ast = (value as Expression.Any & {
782
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
783
+ })[ExpressionAst.TypeId]
784
+ if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
785
+ throw new Error("SQL/JSON path input must be a non-empty string")
786
+ }
485
787
  return renderExpression(value, state, dialect)
486
788
  }
487
789
  throw new Error("Unsupported SQL/JSON path input")
488
790
  }
489
791
 
792
+ const renderFunctionName = (name: unknown): string => {
793
+ return name as string
794
+ }
795
+
796
+ const renderExtractField = (field: Expression.Any): string => {
797
+ const ast = (field as Expression.Any & {
798
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
799
+ })[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
800
+ return ast.value
801
+ }
802
+
490
803
  const renderFunctionCall = (
491
- name: string,
492
- args: readonly Expression.Any[],
804
+ name: unknown,
805
+ args: unknown,
493
806
  state: RenderState,
494
807
  dialect: SqlDialect
495
808
  ): string => {
496
- if (name === "array") {
497
- return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
498
- }
499
- if (name === "extract" && args.length === 2) {
500
- const field = args[0]
501
- const source = args[1]
502
- if (field === undefined) {
503
- throw new Error("Unsupported SQL extract expression")
504
- }
505
- if (source === undefined) {
506
- throw new Error("Unsupported SQL extract expression")
507
- }
508
- const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
509
- ? field[Expression.TypeId].runtime
510
- : undefined
511
- const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
512
- return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
513
- }
514
- const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
515
- if (args.length === 0) {
516
- switch (name) {
809
+ const functionName = renderFunctionName(name)
810
+ const functionArgs = args as readonly Expression.Any[]
811
+ if (functionName === "array") {
812
+ return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
813
+ }
814
+ if (functionName === "extract") {
815
+ const field = functionArgs[0]!
816
+ const source = functionArgs[1]!
817
+ return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
818
+ }
819
+ const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
820
+ if (functionArgs.length === 0) {
821
+ switch (functionName) {
517
822
  case "current_date":
518
823
  case "current_time":
519
824
  case "current_timestamp":
520
825
  case "localtime":
521
826
  case "localtimestamp":
522
- return name
827
+ return functionName
523
828
  default:
524
- return `${name}()`
829
+ return `${functionName}()`
525
830
  }
526
831
  }
527
- return `${name}(${renderedArgs})`
832
+ return `${functionName}(${renderedArgs})`
528
833
  }
529
834
 
530
835
  const renderJsonExpression = (
@@ -588,22 +893,26 @@ const renderJsonExpression = (
588
893
  const baseSql = dialect.name === "postgres"
589
894
  ? renderPostgresJsonValue(base, state, dialect)
590
895
  : renderExpression(base, state, dialect)
591
- const keys = segments
896
+ const keys = extractJsonKeys(ast, segments)
592
897
  if (keys.length === 0) {
593
898
  return undefined
594
899
  }
900
+ if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
901
+ throw new Error("json key predicates require string keys")
902
+ }
903
+ const keyNames = keys as readonly string[]
595
904
  if (dialect.name === "postgres") {
596
905
  if (kind === "jsonHasAnyKeys") {
597
- return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
906
+ return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
598
907
  }
599
908
  if (kind === "jsonHasAllKeys") {
600
- return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
909
+ return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
601
910
  }
602
- return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
911
+ return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
603
912
  }
604
913
  if (dialect.name === "mysql") {
605
914
  const mode = kind === "jsonHasAllKeys" ? "all" : "one"
606
- const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
915
+ const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
607
916
  return `json_contains_path(${baseSql}, ${dialect.renderLiteral(mode, state)}, ${paths})`
608
917
  }
609
918
  return undefined
@@ -622,9 +931,7 @@ const renderJsonExpression = (
622
931
  return undefined
623
932
  }
624
933
  case "jsonBuildObject": {
625
- const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
626
- ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
627
- : []
934
+ const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
628
935
  const renderedEntries = entries.flatMap((entry) => [
629
936
  dialect.renderLiteral(entry.key, state),
630
937
  renderJsonInputExpression(entry.value, state, dialect)
@@ -638,9 +945,7 @@ const renderJsonExpression = (
638
945
  return undefined
639
946
  }
640
947
  case "jsonBuildArray": {
641
- const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
642
- ? (ast as { readonly values: readonly Expression.Any[] }).values
643
- : []
948
+ const values = (ast as { readonly values: readonly Expression.Any[] }).values
644
949
  const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
645
950
  if (dialect.name === "postgres") {
646
951
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
@@ -818,11 +1123,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
818
1123
  const renderMutationAssignment = (
819
1124
  entry: QueryAst.AssignmentClause,
820
1125
  state: RenderState,
821
- dialect: SqlDialect
1126
+ dialect: SqlDialect,
1127
+ targetTableName?: string
822
1128
  ): string => {
823
1129
  const column = entry.tableName && dialect.name === "mysql"
824
- ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
825
- : dialect.quoteIdentifier(entry.columnName)
1130
+ ? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
1131
+ : quoteColumn(entry.columnName, state, dialect, targetTableName)
826
1132
  return `${column} = ${renderExpression(entry.value, state, dialect)}`
827
1133
  }
828
1134
 
@@ -858,15 +1164,6 @@ const renderDeleteTargets = (
858
1164
  dialect: SqlDialect
859
1165
  ): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
860
1166
 
861
- const assertMergeActionKind = (
862
- kind: unknown,
863
- allowed: readonly string[]
864
- ): void => {
865
- if (typeof kind !== "string" || !allowed.includes(kind)) {
866
- throw new Error("Unsupported merge action kind")
867
- }
868
- }
869
-
870
1167
  const renderMysqlMutationLock = (
871
1168
  lock: QueryAst.LockClause | undefined,
872
1169
  statement: "update" | "delete"
@@ -897,7 +1194,7 @@ const renderTransactionClause = (
897
1194
  if (isolationLevel) {
898
1195
  modes.push(isolationLevel)
899
1196
  }
900
- if (clause.readOnly === true) {
1197
+ if (normalizeStatementFlag(clause.readOnly)) {
901
1198
  modes.push("read only")
902
1199
  }
903
1200
  return modes.length > 0
@@ -909,28 +1206,21 @@ const renderTransactionClause = (
909
1206
  case "rollback":
910
1207
  return "rollback"
911
1208
  case "savepoint":
912
- return `savepoint ${dialect.quoteIdentifier(clause.name)}`
1209
+ return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
913
1210
  case "rollbackTo":
914
- return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
1211
+ return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
915
1212
  case "releaseSavepoint":
916
- return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
1213
+ return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
917
1214
  }
918
- throw new Error("Unsupported transaction statement kind")
1215
+ return "start transaction"
919
1216
  }
920
1217
 
921
1218
  const renderSelectionList = (
922
1219
  selection: Record<string, unknown>,
923
1220
  state: RenderState,
924
- dialect: SqlDialect,
925
- validateAggregation: boolean
1221
+ dialect: SqlDialect
926
1222
  ): RenderedQueryAst => {
927
- if (validateAggregation) {
928
- validateAggregationSelection(selection as SelectionValue, [])
929
- }
930
1223
  const flattened = flattenSelection(selection)
931
- if (dialect.name === "mysql" && flattened.length === 0) {
932
- throw new Error("mysql select statements require at least one selected expression")
933
- }
934
1224
  const projections = selectionProjections(selection)
935
1225
  const sql = flattened.map(({ expression, alias }) =>
936
1226
  `${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
@@ -943,94 +1233,40 @@ const renderSelectionList = (
943
1233
  const nestedRenderState = (state: RenderState): RenderState => ({
944
1234
  params: state.params,
945
1235
  valueMappings: state.valueMappings,
1236
+ casing: state.casing,
946
1237
  ctes: [],
947
1238
  cteNames: new Set(state.cteNames),
948
- cteSources: new Map(state.cteSources)
1239
+ cteSources: new Map(state.cteSources),
1240
+ sourceNames: new Map(state.sourceNames)
949
1241
  })
950
1242
 
951
- const assertMatchingSetProjections = (
952
- left: readonly Projection[],
953
- right: readonly Projection[]
954
- ): void => {
955
- const leftKeys = left.map((projection) => JSON.stringify(projection.path))
956
- const rightKeys = right.map((projection) => JSON.stringify(projection.path))
957
- if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
958
- throw new Error("set operator operands must have matching result rows")
959
- }
960
- }
961
-
962
- const assertNoGroupedMutationClauses = (
963
- ast: Pick<QueryAst.Ast, "groupBy" | "having">,
964
- statement: string
1243
+ const assertSupportedMutationReturning = (
1244
+ dialect: SqlDialect,
1245
+ selection: Record<string, unknown>
965
1246
  ): void => {
966
- if (ast.groupBy.length > 0) {
967
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
968
- }
969
- if (ast.having.length > 0) {
970
- throw new Error(`having(...) is not supported for ${statement} statements`)
1247
+ if (dialect.name === "standard" && Object.keys(selection).length > 0) {
1248
+ throw new Error("Unsupported standard returning")
971
1249
  }
972
1250
  }
973
1251
 
974
- const assertNoInsertQueryClauses = (
975
- ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
1252
+ const validateDistinctOnOrdering = (
1253
+ distinctOn: readonly Expression.Any[] | undefined,
1254
+ orderBy: readonly QueryAst.OrderByClause[]
976
1255
  ): void => {
977
- if (ast.where.length > 0) {
978
- throw new Error("where(...) is not supported for insert statements")
979
- }
980
- if (ast.joins.length > 0) {
981
- throw new Error("join(...) is not supported for insert statements")
982
- }
983
- if (ast.orderBy.length > 0) {
984
- throw new Error("orderBy(...) is not supported for insert statements")
985
- }
986
- if (ast.limit) {
987
- throw new Error("limit(...) is not supported for insert statements")
988
- }
989
- if (ast.offset) {
990
- throw new Error("offset(...) is not supported for insert statements")
991
- }
992
- if (ast.lock) {
993
- throw new Error("lock(...) is not supported for insert statements")
994
- }
995
- }
996
-
997
- const assertNoStatementQueryClauses = (
998
- ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
999
- statement: string,
1000
- options: { readonly allowSelection?: boolean } = {}
1001
- ): void => {
1002
- if (ast.distinct) {
1003
- throw new Error(`distinct(...) is not supported for ${statement} statements`)
1004
- }
1005
- if (ast.where.length > 0) {
1006
- throw new Error(`where(...) is not supported for ${statement} statements`)
1007
- }
1008
- if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
1009
- throw new Error(`from(...) is not supported for ${statement} statements`)
1010
- }
1011
- if (ast.joins.length > 0) {
1012
- throw new Error(`join(...) is not supported for ${statement} statements`)
1013
- }
1014
- if (ast.groupBy.length > 0) {
1015
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
1016
- }
1017
- if (ast.having.length > 0) {
1018
- throw new Error(`having(...) is not supported for ${statement} statements`)
1019
- }
1020
- if (ast.orderBy.length > 0) {
1021
- throw new Error(`orderBy(...) is not supported for ${statement} statements`)
1022
- }
1023
- if (ast.limit) {
1024
- throw new Error(`limit(...) is not supported for ${statement} statements`)
1025
- }
1026
- if (ast.offset) {
1027
- throw new Error(`offset(...) is not supported for ${statement} statements`)
1028
- }
1029
- if (ast.lock) {
1030
- throw new Error(`lock(...) is not supported for ${statement} statements`)
1031
- }
1032
- if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
1033
- throw new Error(`returning(...) is not supported for ${statement} statements`)
1256
+ if (distinctOn === undefined || distinctOn.length === 0 || orderBy.length === 0) {
1257
+ return
1258
+ }
1259
+ const remainingDistinctKeys = new Set(distinctOn.map(groupingKeyOfExpression))
1260
+ for (const order of orderBy) {
1261
+ const key = groupingKeyOfExpression(order.value)
1262
+ if (remainingDistinctKeys.has(key)) {
1263
+ remainingDistinctKeys.delete(key)
1264
+ continue
1265
+ }
1266
+ if (remainingDistinctKeys.size > 0) {
1267
+ throw new Error("distinctOn(...) expressions must match the leftmost orderBy(...) expressions")
1268
+ }
1269
+ return
1034
1270
  }
1035
1271
  }
1036
1272
 
@@ -1040,13 +1276,14 @@ export const renderQueryAst = (
1040
1276
  dialect: SqlDialect,
1041
1277
  options: { readonly emitCtes?: boolean } = {}
1042
1278
  ): RenderedQueryAst => {
1279
+ registerQuerySources(ast, state)
1043
1280
  let sql = ""
1044
1281
  let projections: readonly Projection[] = []
1045
1282
 
1046
1283
  switch (ast.kind) {
1047
1284
  case "select": {
1048
- validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
1049
- const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1285
+ validateDistinctOnOrdering(ast.distinctOn, ast.orderBy)
1286
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
1050
1287
  projections = rendered.projections
1051
1288
  const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
1052
1289
  const clauses = [
@@ -1058,6 +1295,9 @@ export const renderQueryAst = (
1058
1295
  clauses.push(`from ${renderSourceReference(ast.from.source, ast.from.tableName, ast.from.baseTableName, state, dialect)}`)
1059
1296
  }
1060
1297
  for (const join of ast.joins) {
1298
+ if (dialect.name === "standard" && join.kind === "full") {
1299
+ throw new Error("Unsupported standard full join")
1300
+ }
1061
1301
  const source = renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)
1062
1302
  clauses.push(
1063
1303
  join.kind === "cross"
@@ -1084,8 +1324,8 @@ export const renderQueryAst = (
1084
1324
  clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
1085
1325
  }
1086
1326
  if (ast.lock) {
1087
- if (ast.lock.nowait && ast.lock.skipLocked) {
1088
- throw new Error("lock(...) cannot specify both nowait and skipLocked")
1327
+ if (dialect.name === "standard") {
1328
+ throw new Error("Unsupported standard row locking")
1089
1329
  }
1090
1330
  clauses.push(
1091
1331
  `${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
@@ -1096,7 +1336,6 @@ export const renderQueryAst = (
1096
1336
  }
1097
1337
  case "set": {
1098
1338
  const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
1099
- assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
1100
1339
  const base = renderQueryAst(
1101
1340
  Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
1102
1341
  Record<string, unknown>,
@@ -1107,7 +1346,6 @@ export const renderQueryAst = (
1107
1346
  dialect
1108
1347
  )
1109
1348
  projections = selectionProjections(setAst.select as Record<string, unknown>)
1110
- assertMatchingSetProjections(projections, base.projections)
1111
1349
  sql = [
1112
1350
  `(${base.sql})`,
1113
1351
  ...(setAst.setOperations ?? []).map((entry) => {
@@ -1120,7 +1358,9 @@ export const renderQueryAst = (
1120
1358
  state,
1121
1359
  dialect
1122
1360
  )
1123
- assertMatchingSetProjections(projections, rendered.projections)
1361
+ if (dialect.name === "standard" && entry.all && entry.kind !== "union") {
1362
+ throw new Error("Unsupported standard set operator all variant")
1363
+ }
1124
1364
  return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
1125
1365
  })
1126
1366
  ].join(" ")
@@ -1128,24 +1368,20 @@ export const renderQueryAst = (
1128
1368
  }
1129
1369
  case "insert": {
1130
1370
  const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
1131
- if (insertAst.distinct) {
1132
- throw new Error("distinct(...) is not supported for insert statements")
1133
- }
1134
- assertNoGroupedMutationClauses(insertAst, "insert")
1135
- assertNoInsertQueryClauses(insertAst)
1136
1371
  const targetSource = insertAst.into!
1137
1372
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1138
- const insertSource = expectInsertSourceKind(insertAst.insertSource)
1373
+ const targetCasingState = stateWithTableCasing(state, targetSource.source)
1374
+ const insertSource = insertAst.insertSource
1139
1375
  const conflict = expectConflictClause(insertAst.conflict)
1140
1376
  sql = `insert into ${target}`
1141
1377
  if (insertSource?.kind === "values") {
1142
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1378
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1143
1379
  const rows = insertSource.rows.map((row) =>
1144
- `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1380
+ `(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
1145
1381
  ).join(", ")
1146
1382
  sql += ` (${columns}) values ${rows}`
1147
1383
  } else if (insertSource?.kind === "query") {
1148
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1384
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1149
1385
  const renderedQuery = renderQueryAst(
1150
1386
  Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1151
1387
  Record<string, unknown>,
@@ -1157,7 +1393,7 @@ export const renderQueryAst = (
1157
1393
  )
1158
1394
  sql += ` (${columns}) ${renderedQuery.sql}`
1159
1395
  } else if (insertSource?.kind === "unnest") {
1160
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1396
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1161
1397
  if (dialect.name === "postgres") {
1162
1398
  const table = targetSource.source as Table.AnyTable
1163
1399
  const fields = table[Table.TypeId].fields
@@ -1179,38 +1415,41 @@ export const renderQueryAst = (
1179
1415
  sql += ` (${columns}) values ${rows}`
1180
1416
  }
1181
1417
  } else {
1182
- const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
1183
- const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
1184
- if ((insertAst.values ?? []).length > 0) {
1418
+ const insertValues = insertAst.values ?? []
1419
+ const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
1420
+ const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
1421
+ if (insertValues.length > 0) {
1185
1422
  sql += ` (${columns}) values (${values})`
1186
1423
  } else {
1187
1424
  sql += " default values"
1188
1425
  }
1189
1426
  }
1190
1427
  if (conflict) {
1191
- if (conflict.action === "doNothing" && conflict.where) {
1192
- throw new Error("conflict action predicates require update assignments")
1428
+ if (dialect.name === "standard") {
1429
+ throw new Error("Unsupported standard insert conflict")
1193
1430
  }
1431
+ const conflictValueState = { ...targetCasingState, allowExcluded: true }
1194
1432
  const updateValues = (conflict.values ?? []).map((entry) =>
1195
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1433
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
1196
1434
  ).join(", ")
1197
1435
  if (dialect.name === "postgres") {
1198
1436
  const targetSql = conflict.target?.kind === "constraint"
1199
- ? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
1437
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
1200
1438
  : conflict.target?.kind === "columns"
1201
- ? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
1439
+ ? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
1202
1440
  : " on conflict"
1203
1441
  sql += targetSql
1204
1442
  sql += conflict.action === "doNothing"
1205
1443
  ? " do nothing"
1206
- : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
1444
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
1207
1445
  } else if (conflict.action === "doNothing") {
1208
1446
  sql = sql.replace(/^insert/, "insert ignore")
1209
1447
  } else {
1210
1448
  sql += ` on duplicate key update ${updateValues}`
1211
1449
  }
1212
1450
  }
1213
- const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1451
+ assertSupportedMutationReturning(dialect, insertAst.select as Record<string, unknown>)
1452
+ const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
1214
1453
  projections = returning.projections
1215
1454
  if (returning.sql.length > 0) {
1216
1455
  sql += ` returning ${returning.sql}`
@@ -1219,31 +1458,15 @@ export const renderQueryAst = (
1219
1458
  }
1220
1459
  case "update": {
1221
1460
  const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
1222
- if (updateAst.distinct) {
1223
- throw new Error("distinct(...) is not supported for update statements")
1224
- }
1225
- assertNoGroupedMutationClauses(updateAst, "update")
1226
- if (updateAst.orderBy.length > 0) {
1227
- throw new Error("orderBy(...) is not supported for update statements")
1228
- }
1229
- if (updateAst.limit) {
1230
- throw new Error("limit(...) is not supported for update statements")
1231
- }
1232
- if (updateAst.offset) {
1233
- throw new Error("offset(...) is not supported for update statements")
1234
- }
1235
- if (updateAst.lock) {
1236
- throw new Error("lock(...) is not supported for update statements")
1237
- }
1238
1461
  const targetSource = updateAst.target!
1239
1462
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1240
1463
  const targets = updateAst.targets ?? [targetSource]
1241
1464
  const fromSources = updateAst.fromSources ?? []
1242
- if ((updateAst.set ?? []).length === 0) {
1243
- throw new Error("update statements require at least one assignment")
1465
+ if (dialect.name === "standard" && (targets.length > 1 || fromSources.length > 0 || updateAst.joins.length > 0)) {
1466
+ throw new Error("Unsupported standard joined mutation")
1244
1467
  }
1245
1468
  const assignments = updateAst.set!.map((entry) =>
1246
- renderMutationAssignment(entry, state, dialect)).join(", ")
1469
+ renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
1247
1470
  if (dialect.name === "mysql") {
1248
1471
  const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
1249
1472
  const extraSources = renderFromSources(fromSources, state, dialect)
@@ -1282,7 +1505,8 @@ export const renderQueryAst = (
1282
1505
  if (dialect.name === "mysql" && updateAst.limit) {
1283
1506
  sql += ` limit ${renderExpression(updateAst.limit, state, dialect)}`
1284
1507
  }
1285
- const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1508
+ assertSupportedMutationReturning(dialect, updateAst.select as Record<string, unknown>)
1509
+ const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
1286
1510
  projections = returning.projections
1287
1511
  if (returning.sql.length > 0) {
1288
1512
  sql += ` returning ${returning.sql}`
@@ -1291,25 +1515,12 @@ export const renderQueryAst = (
1291
1515
  }
1292
1516
  case "delete": {
1293
1517
  const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
1294
- if (deleteAst.distinct) {
1295
- throw new Error("distinct(...) is not supported for delete statements")
1296
- }
1297
- assertNoGroupedMutationClauses(deleteAst, "delete")
1298
- if (deleteAst.orderBy.length > 0 && dialect.name === "postgres") {
1299
- throw new Error("orderBy(...) is not supported for delete statements")
1300
- }
1301
- if (deleteAst.limit && dialect.name === "postgres") {
1302
- throw new Error("limit(...) is not supported for delete statements")
1303
- }
1304
- if (deleteAst.offset) {
1305
- throw new Error("offset(...) is not supported for delete statements")
1306
- }
1307
- if (deleteAst.lock) {
1308
- throw new Error("lock(...) is not supported for delete statements")
1309
- }
1310
1518
  const targetSource = deleteAst.target!
1311
1519
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1312
1520
  const targets = deleteAst.targets ?? [targetSource]
1521
+ if (dialect.name === "standard" && (targets.length > 1 || deleteAst.joins.length > 0)) {
1522
+ throw new Error("Unsupported standard joined mutation")
1523
+ }
1313
1524
  if (dialect.name === "mysql") {
1314
1525
  const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
1315
1526
  const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
@@ -1344,7 +1555,8 @@ export const renderQueryAst = (
1344
1555
  if (dialect.name === "mysql" && deleteAst.limit) {
1345
1556
  sql += ` limit ${renderExpression(deleteAst.limit, state, dialect)}`
1346
1557
  }
1347
- const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1558
+ assertSupportedMutationReturning(dialect, deleteAst.select as Record<string, unknown>)
1559
+ const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
1348
1560
  projections = returning.projections
1349
1561
  if (returning.sql.length > 0) {
1350
1562
  sql += ` returning ${returning.sql}`
@@ -1353,14 +1565,18 @@ export const renderQueryAst = (
1353
1565
  }
1354
1566
  case "truncate": {
1355
1567
  const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1356
- assertNoStatementQueryClauses(truncateAst, "truncate")
1568
+ if (dialect.name === "standard") {
1569
+ throw new Error("Unsupported standard truncate statement")
1570
+ }
1357
1571
  const truncate = expectTruncateClause(truncateAst.truncate)
1358
1572
  const targetSource = truncateAst.target!
1573
+ const restartIdentity = truncate.restartIdentity
1574
+ const cascade = truncate.cascade
1359
1575
  sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
1360
- if (truncate.restartIdentity) {
1576
+ if (restartIdentity) {
1361
1577
  sql += " restart identity"
1362
1578
  }
1363
- if (truncate.cascade) {
1579
+ if (cascade) {
1364
1580
  sql += " cascade"
1365
1581
  }
1366
1582
  break
@@ -1373,43 +1589,28 @@ export const renderQueryAst = (
1373
1589
  const targetSource = mergeAst.target!
1374
1590
  const usingSource = mergeAst.using!
1375
1591
  const merge = mergeAst.merge!
1376
- if (merge.kind !== "merge") {
1377
- throw new Error("Unsupported merge statement kind")
1378
- }
1379
- if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
1380
- throw new Error("returning(...) is not supported for merge statements")
1381
- }
1382
- if (!merge.whenMatched && !merge.whenNotMatched) {
1383
- throw new Error("merge statements require at least one action")
1384
- }
1385
1592
  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)}`
1386
1593
  if (merge.whenMatched) {
1387
- assertMergeActionKind(merge.whenMatched.kind, ["update", "delete"])
1594
+ const matchedKind = merge.whenMatched.kind === "delete" ? "delete" : "update"
1388
1595
  sql += " when matched"
1389
1596
  if (merge.whenMatched.predicate) {
1390
1597
  sql += ` and ${renderExpression(merge.whenMatched.predicate, state, dialect)}`
1391
1598
  }
1392
- if (merge.whenMatched.kind === "delete") {
1599
+ if (matchedKind === "delete") {
1393
1600
  sql += " then delete"
1394
1601
  } else {
1395
- if (merge.whenMatched.values.length === 0) {
1396
- throw new Error("merge update actions require at least one assignment")
1397
- }
1398
- sql += ` then update set ${merge.whenMatched.values.map((entry) =>
1399
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1602
+ const matchedUpdate = merge.whenMatched as Extract<QueryAst.MergeMatchedClause, { readonly kind: "update" }>
1603
+ sql += ` then update set ${matchedUpdate.values.map((entry) =>
1604
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, state, dialect)}`
1400
1605
  ).join(", ")}`
1401
1606
  }
1402
1607
  }
1403
1608
  if (merge.whenNotMatched) {
1404
- assertMergeActionKind(merge.whenNotMatched.kind, ["insert"])
1405
1609
  sql += " when not matched"
1406
1610
  if (merge.whenNotMatched.predicate) {
1407
1611
  sql += ` and ${renderExpression(merge.whenNotMatched.predicate, state, dialect)}`
1408
1612
  }
1409
- if (merge.whenNotMatched.values.length === 0) {
1410
- throw new Error("merge insert actions require at least one value")
1411
- }
1412
- 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(", ")})`
1613
+ 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(", ")})`
1413
1614
  }
1414
1615
  break
1415
1616
  }
@@ -1419,27 +1620,27 @@ export const renderQueryAst = (
1419
1620
  case "savepoint":
1420
1621
  case "rollbackTo":
1421
1622
  case "releaseSavepoint": {
1422
- assertNoStatementQueryClauses(ast, ast.kind)
1423
1623
  sql = renderTransactionClause(ast.transaction!, dialect)
1424
1624
  break
1425
1625
  }
1426
1626
  case "createTable": {
1427
1627
  const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1428
- assertNoStatementQueryClauses(createTableAst, "createTable")
1429
1628
  const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1430
1629
  sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1431
1630
  break
1432
1631
  }
1433
1632
  case "dropTable": {
1434
1633
  const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1435
- assertNoStatementQueryClauses(dropTableAst, "dropTable")
1436
1634
  const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1437
- sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1635
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
1636
+ if (dialect.name !== "postgres" && ifExists) {
1637
+ throw new Error(`Unsupported ${dialect.name} drop table options`)
1638
+ }
1639
+ sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1438
1640
  break
1439
1641
  }
1440
1642
  case "createIndex": {
1441
1643
  const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1442
- assertNoStatementQueryClauses(createIndexAst, "createIndex")
1443
1644
  sql = renderCreateIndexSql(
1444
1645
  createIndexAst.target!,
1445
1646
  expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
@@ -1450,7 +1651,6 @@ export const renderQueryAst = (
1450
1651
  }
1451
1652
  case "dropIndex": {
1452
1653
  const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1453
- assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
1454
1654
  sql = renderDropIndexSql(
1455
1655
  dropIndexAst.target!,
1456
1656
  expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
@@ -1459,8 +1659,12 @@ export const renderQueryAst = (
1459
1659
  )
1460
1660
  break
1461
1661
  }
1462
- default:
1463
- throw new Error("Unsupported query statement kind")
1662
+ default: {
1663
+ if (ast.transaction !== undefined) {
1664
+ sql = renderTransactionClause(ast.transaction, dialect)
1665
+ }
1666
+ break
1667
+ }
1464
1668
  }
1465
1669
 
1466
1670
  if (state.ctes.length === 0 || options.emitCtes === false) {
@@ -1547,6 +1751,9 @@ const renderSourceReference = (
1547
1751
  readonly name: string
1548
1752
  readonly plan: Query.Plan.Any
1549
1753
  }
1754
+ if (dialect.name === "standard") {
1755
+ throw new Error("Unsupported standard lateral source")
1756
+ }
1550
1757
  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)}`
1551
1758
  }
1552
1759
  if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
@@ -1573,13 +1780,26 @@ const renderSourceReference = (
1573
1780
  if (dialect.name !== "postgres") {
1574
1781
  throw new Error("Unsupported table function source for SQL rendering")
1575
1782
  }
1783
+ const functionName = renderFunctionName(tableFunction.functionName)
1576
1784
  const columnNames = Object.keys(tableFunction.columns)
1577
- return `${tableFunction.functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1785
+ return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1578
1786
  }
1579
1787
  const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1580
- ? (source as Table.AnyTable)[Table.TypeId].schemaName
1788
+ ? casedSchemaName(source as Table.AnyTable, state)
1581
1789
  : undefined
1582
- return dialect.renderTableReference(tableName, baseTableName, schemaName)
1790
+ if (typeof source === "object" && source !== null && Table.TypeId in source) {
1791
+ const table = source as Table.AnyTable
1792
+ const renderedBaseName = casedTableName(table, state)
1793
+ const renderedTableName = table[Table.TypeId].kind === "alias"
1794
+ ? tableName
1795
+ : renderedBaseName
1796
+ return dialect.renderTableReference(renderedTableName, renderedBaseName, schemaName)
1797
+ }
1798
+ return dialect.renderTableReference(
1799
+ Casing.applyCategory(state.casing, "tables", tableName),
1800
+ Casing.applyCategory(state.casing, "tables", baseTableName),
1801
+ schemaName
1802
+ )
1583
1803
  }
1584
1804
 
1585
1805
  const renderSubqueryExpressionPlan = (
@@ -1618,162 +1838,185 @@ export const renderExpression = (
1618
1838
  return jsonSql
1619
1839
  }
1620
1840
  const ast = rawAst as ExpressionAst.Any
1621
- const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1622
- operator === "eq"
1623
- ? "="
1624
- : operator === "neq"
1625
- ? "<>"
1626
- : operator === "lt"
1627
- ? "<"
1628
- : operator === "lte"
1629
- ? "<="
1630
- : operator === "gt"
1631
- ? ">"
1632
- : ">="
1633
- switch (ast.kind) {
1841
+ const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1842
+ ({
1843
+ eq: "=",
1844
+ neq: "<>",
1845
+ lt: "<",
1846
+ lte: "<=",
1847
+ gt: ">",
1848
+ gte: ">="
1849
+ } as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
1850
+ const renderCollation = (collation: unknown): string => {
1851
+ return (collation as readonly string[]).map((segment) => dialect.quoteIdentifier(segment)).join(".")
1852
+ }
1853
+ switch (ast.kind) {
1634
1854
  case "column":
1635
1855
  return state.rowLocalColumns || ast.tableName.length === 0
1636
- ? dialect.quoteIdentifier(ast.columnName)
1637
- : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1856
+ ? quoteColumn(ast.columnName, state, dialect, ast.tableName)
1857
+ : `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
1638
1858
  case "literal":
1639
1859
  if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1640
1860
  throw new Error("Expected a finite numeric value")
1641
1861
  }
1642
1862
  return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1643
1863
  case "excluded":
1864
+ if (state.allowExcluded !== true) {
1865
+ throw new Error("excluded(...) is only supported inside insert conflict handlers")
1866
+ }
1644
1867
  return dialect.name === "mysql"
1645
- ? `values(${dialect.quoteIdentifier(ast.columnName)})`
1646
- : `excluded.${dialect.quoteIdentifier(ast.columnName)}`
1868
+ ? `values(${quoteColumn(ast.columnName, state, dialect)})`
1869
+ : `excluded.${quoteColumn(ast.columnName, state, dialect)}`
1647
1870
  case "cast":
1648
- return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
1871
+ return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
1649
1872
  case "collate":
1650
- return `(${renderExpression(ast.value, state, dialect)} collate ${ast.collation.map((segment) => dialect.quoteIdentifier(segment)).join(".")})`
1873
+ return `(${renderExpression(expectValueExpression("collate", ast.value), state, dialect)} collate ${renderCollation(ast.collation)})`
1651
1874
  case "function":
1652
- return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1875
+ return renderFunctionCall(ast.name, ast.args, state, dialect)
1653
1876
  case "eq":
1654
- return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1877
+ return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
1655
1878
  case "neq":
1656
- return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1879
+ return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
1657
1880
  case "lt":
1658
- return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1881
+ return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
1659
1882
  case "lte":
1660
- return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1883
+ return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
1661
1884
  case "gt":
1662
- return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1885
+ return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
1663
1886
  case "gte":
1664
- return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1887
+ return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
1665
1888
  case "like":
1666
- return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1667
- case "ilike":
1889
+ return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
1890
+ case "ilike": {
1891
+ const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
1668
1892
  return dialect.name === "postgres"
1669
- ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1670
- : `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
1671
- case "regexMatch":
1893
+ ? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
1894
+ : `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
1895
+ }
1896
+ case "regexMatch": {
1897
+ const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
1898
+ if (dialect.name === "standard") {
1899
+ throw new Error("Unsupported standard regular-expression predicates")
1900
+ }
1672
1901
  return dialect.name === "postgres"
1673
- ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1674
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1675
- case "regexIMatch":
1902
+ ? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
1903
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1904
+ }
1905
+ case "regexIMatch": {
1906
+ const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
1907
+ if (dialect.name === "standard") {
1908
+ throw new Error("Unsupported standard regular-expression predicates")
1909
+ }
1676
1910
  return dialect.name === "postgres"
1677
- ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1678
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1679
- case "regexNotMatch":
1911
+ ? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
1912
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1913
+ }
1914
+ case "regexNotMatch": {
1915
+ const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
1916
+ if (dialect.name === "standard") {
1917
+ throw new Error("Unsupported standard regular-expression predicates")
1918
+ }
1680
1919
  return dialect.name === "postgres"
1681
- ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1682
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1683
- case "regexNotIMatch":
1920
+ ? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
1921
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1922
+ }
1923
+ case "regexNotIMatch": {
1924
+ const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
1925
+ if (dialect.name === "standard") {
1926
+ throw new Error("Unsupported standard regular-expression predicates")
1927
+ }
1684
1928
  return dialect.name === "postgres"
1685
- ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1686
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1687
- case "isDistinctFrom":
1929
+ ? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
1930
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1931
+ }
1932
+ case "isDistinctFrom": {
1933
+ const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
1688
1934
  return dialect.name === "mysql"
1689
- ? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
1690
- : `(${renderExpression(ast.left, state, dialect)} is distinct from ${renderExpression(ast.right, state, dialect)})`
1691
- case "isNotDistinctFrom":
1935
+ ? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
1936
+ : `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
1937
+ }
1938
+ case "isNotDistinctFrom": {
1939
+ const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
1692
1940
  return dialect.name === "mysql"
1693
- ? `(${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)})`
1694
- : `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1695
- case "contains":
1941
+ ? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
1942
+ : `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
1943
+ }
1944
+ case "contains": {
1945
+ const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
1696
1946
  if (dialect.name === "postgres") {
1697
- assertCompatiblePostgresRangeOperands(ast.left, ast.right)
1698
- const left = isJsonExpression(ast.left)
1699
- ? renderPostgresJsonValue(ast.left, state, dialect)
1700
- : renderExpression(ast.left, state, dialect)
1701
- const right = isJsonExpression(ast.right)
1702
- ? renderPostgresJsonValue(ast.right, state, dialect)
1703
- : renderExpression(ast.right, state, dialect)
1947
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1948
+ const left = isJsonExpression(leftExpression)
1949
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1950
+ : renderExpression(leftExpression, state, dialect)
1951
+ const right = isJsonExpression(rightExpression)
1952
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1953
+ : renderExpression(rightExpression, state, dialect)
1704
1954
  return `(${left} @> ${right})`
1705
1955
  }
1706
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1707
- return `json_contains(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1956
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1957
+ return `json_contains(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
1708
1958
  }
1709
1959
  throw new Error("Unsupported container operator for SQL rendering")
1710
- case "containedBy":
1960
+ }
1961
+ case "containedBy": {
1962
+ const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
1711
1963
  if (dialect.name === "postgres") {
1712
- assertCompatiblePostgresRangeOperands(ast.left, ast.right)
1713
- const left = isJsonExpression(ast.left)
1714
- ? renderPostgresJsonValue(ast.left, state, dialect)
1715
- : renderExpression(ast.left, state, dialect)
1716
- const right = isJsonExpression(ast.right)
1717
- ? renderPostgresJsonValue(ast.right, state, dialect)
1718
- : renderExpression(ast.right, state, dialect)
1964
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1965
+ const left = isJsonExpression(leftExpression)
1966
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1967
+ : renderExpression(leftExpression, state, dialect)
1968
+ const right = isJsonExpression(rightExpression)
1969
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1970
+ : renderExpression(rightExpression, state, dialect)
1719
1971
  return `(${left} <@ ${right})`
1720
1972
  }
1721
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1722
- return `json_contains(${renderExpression(ast.right, state, dialect)}, ${renderExpression(ast.left, state, dialect)})`
1973
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1974
+ return `json_contains(${renderExpression(rightExpression, state, dialect)}, ${renderExpression(leftExpression, state, dialect)})`
1723
1975
  }
1724
1976
  throw new Error("Unsupported container operator for SQL rendering")
1725
- case "overlaps":
1977
+ }
1978
+ case "overlaps": {
1979
+ const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
1726
1980
  if (dialect.name === "postgres") {
1727
- assertCompatiblePostgresRangeOperands(ast.left, ast.right)
1728
- const left = isJsonExpression(ast.left)
1729
- ? renderPostgresJsonValue(ast.left, state, dialect)
1730
- : renderExpression(ast.left, state, dialect)
1731
- const right = isJsonExpression(ast.right)
1732
- ? renderPostgresJsonValue(ast.right, state, dialect)
1733
- : renderExpression(ast.right, state, dialect)
1981
+ assertCompatiblePostgresRangeOperands(leftExpression, rightExpression)
1982
+ const left = isJsonExpression(leftExpression)
1983
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1984
+ : renderExpression(leftExpression, state, dialect)
1985
+ const right = isJsonExpression(rightExpression)
1986
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1987
+ : renderExpression(rightExpression, state, dialect)
1734
1988
  return `(${left} && ${right})`
1735
1989
  }
1736
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1737
- return `json_overlaps(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1990
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1991
+ return `json_overlaps(${renderExpression(leftExpression, state, dialect)}, ${renderExpression(rightExpression, state, dialect)})`
1738
1992
  }
1739
1993
  throw new Error("Unsupported container operator for SQL rendering")
1994
+ }
1740
1995
  case "isNull":
1741
- return `(${renderExpression(ast.value, state, dialect)} is null)`
1996
+ return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
1742
1997
  case "isNotNull":
1743
- return `(${renderExpression(ast.value, state, dialect)} is not null)`
1998
+ return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
1744
1999
  case "not":
1745
- return `(not ${renderExpression(ast.value, state, dialect)})`
2000
+ return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
1746
2001
  case "upper":
1747
- return `upper(${renderExpression(ast.value, state, dialect)})`
2002
+ return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
1748
2003
  case "lower":
1749
- return `lower(${renderExpression(ast.value, state, dialect)})`
2004
+ return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
1750
2005
  case "count":
1751
- return `count(${renderExpression(ast.value, state, dialect)})`
2006
+ return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
1752
2007
  case "max":
1753
- return `max(${renderExpression(ast.value, state, dialect)})`
2008
+ return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
1754
2009
  case "min":
1755
- return `min(${renderExpression(ast.value, state, dialect)})`
2010
+ return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
1756
2011
  case "and":
1757
- if (ast.values.length === 0) {
1758
- throw new Error("and(...) requires at least one predicate")
1759
- }
1760
2012
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1761
2013
  case "or":
1762
- if (ast.values.length === 0) {
1763
- throw new Error("or(...) requires at least one predicate")
1764
- }
1765
2014
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
1766
2015
  case "coalesce":
1767
2016
  return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
1768
2017
  case "in":
1769
- if (ast.values.length < 2) {
1770
- throw new Error("in(...) requires at least one candidate value")
1771
- }
1772
2018
  return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1773
2019
  case "notIn":
1774
- if (ast.values.length < 2) {
1775
- throw new Error("notIn(...) requires at least one candidate value")
1776
- }
1777
2020
  return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1778
2021
  case "between":
1779
2022
  return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
@@ -1788,21 +2031,35 @@ export const renderExpression = (
1788
2031
  case "scalarSubquery":
1789
2032
  return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1790
2033
  case "inSubquery":
1791
- return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1792
- case "comparisonAny":
1793
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1794
- case "comparisonAll":
1795
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1796
- case "window": {
1797
- if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1798
- break
2034
+ return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2035
+ case "comparisonAny": {
2036
+ const left = expectValueExpression("compareAny", ast.left)
2037
+ const operator = renderComparisonOperator(ast.operator)
2038
+ if (dialect.name === "standard") {
2039
+ throw new Error("Unsupported standard quantified comparison")
2040
+ }
2041
+ return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2042
+ }
2043
+ case "comparisonAll": {
2044
+ const left = expectValueExpression("compareAll", ast.left)
2045
+ const operator = renderComparisonOperator(ast.operator)
2046
+ if (dialect.name === "standard") {
2047
+ throw new Error("Unsupported standard quantified comparison")
1799
2048
  }
2049
+ return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2050
+ }
2051
+ case "window": {
2052
+ const partitionBy = ast.partitionBy as readonly Expression.Any[]
2053
+ const orderBy = ast.orderBy as readonly {
2054
+ readonly value: Expression.Any
2055
+ readonly direction: string
2056
+ }[]
1800
2057
  const clauses: string[] = []
1801
- if (ast.partitionBy.length > 0) {
1802
- clauses.push(`partition by ${ast.partitionBy.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}`)
2058
+ if (partitionBy.length > 0) {
2059
+ clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
1803
2060
  }
1804
- if (ast.orderBy.length > 0) {
1805
- clauses.push(`order by ${ast.orderBy.map((entry) =>
2061
+ if (orderBy.length > 0) {
2062
+ clauses.push(`order by ${orderBy.map((entry) =>
1806
2063
  `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1807
2064
  ).join(", ")}`)
1808
2065
  }
@@ -1815,7 +2072,7 @@ export const renderExpression = (
1815
2072
  case "denseRank":
1816
2073
  return `dense_rank() over (${specification})`
1817
2074
  case "over":
1818
- return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
2075
+ return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
1819
2076
  }
1820
2077
  break
1821
2078
  }