effect-qb 0.17.0 → 0.19.0

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