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,48 @@
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 { renderMysqlMutationLockMode, 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 { renderMysqlMutationLockMode, 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 * as SchemaExpression from "../schema-expression.js"
21
+ import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
22
+ import * as Casing from "../casing.js"
23
23
 
24
24
  const renderDbType = (
25
25
  dialect: SqlDialect,
26
26
  dbType: Expression.DbType.Any
27
27
  ): string => {
28
- if (dialect.name === "mysql" && dbType.dialect === "mysql" && dbType.kind === "uuid") {
28
+ if (dialect.name === "mysql" && dbType.kind === "uuid") {
29
29
  return "char(36)"
30
30
  }
31
- return dbType.kind
31
+ return renderDbTypeName(dbType.kind)
32
32
  }
33
33
 
34
+ const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
35
+ "element" in dbType
36
+
34
37
  const renderCastType = (
35
38
  dialect: SqlDialect,
36
- dbType: Expression.DbType.Any
39
+ dbType: unknown
37
40
  ): string => {
41
+ const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
38
42
  if (dialect.name !== "mysql") {
39
- return dbType.kind
43
+ return renderDbTypeName(kind)
40
44
  }
41
- switch (dbType.kind) {
45
+ switch (kind) {
42
46
  case "text":
43
47
  return "char"
44
48
  case "uuid":
@@ -53,7 +57,7 @@ const renderCastType = (
53
57
  case "json":
54
58
  return "json"
55
59
  default:
56
- return dbType.kind
60
+ return renderDbTypeName(kind)
57
61
  }
58
62
  }
59
63
 
@@ -125,22 +129,164 @@ const renderMysqlMutationLimit = (
125
129
  return renderExpression(expression, state, dialect)
126
130
  }
127
131
 
132
+ const casingForTable = (
133
+ table: Table.AnyTable,
134
+ state: RenderState
135
+ ): Casing.Options | undefined =>
136
+ Casing.merge(state.casing, table[Table.TypeId].casing)
137
+
138
+ const casedColumnName = (
139
+ columnName: string,
140
+ state: RenderState,
141
+ tableName?: string
142
+ ): string => {
143
+ if (tableName !== undefined) {
144
+ const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
145
+ if (mapped !== undefined) {
146
+ return mapped
147
+ }
148
+ }
149
+ return Casing.applyCategory(state.casing, "columns", columnName)
150
+ }
151
+
152
+ const casedTableReferenceName = (
153
+ tableName: string,
154
+ state: RenderState
155
+ ): string =>
156
+ state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
157
+
158
+ const quoteColumn = (
159
+ columnName: string,
160
+ state: RenderState,
161
+ dialect: SqlDialect,
162
+ tableName?: string
163
+ ): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
164
+
165
+ const stateWithTableCasing = (
166
+ state: RenderState,
167
+ source: unknown
168
+ ): RenderState =>
169
+ typeof source === "object" && source !== null && Table.TypeId in source
170
+ ? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
171
+ : state
172
+
173
+ const referenceCasing = (
174
+ reference: { readonly casing?: Casing.Options },
175
+ state: RenderState
176
+ ): Casing.Options | undefined =>
177
+ Casing.merge(state.casing, reference.casing)
178
+
179
+ const renderReferenceTable = (
180
+ reference: {
181
+ readonly tableName: string
182
+ readonly schemaName?: string
183
+ readonly casing?: Casing.Options
184
+ },
185
+ state: RenderState,
186
+ dialect: SqlDialect
187
+ ): string => {
188
+ const casing = referenceCasing(reference, state)
189
+ const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
190
+ const schemaName = reference.schemaName === undefined
191
+ ? undefined
192
+ : Casing.applyCategory(casing, "schemas", reference.schemaName)
193
+ return dialect.renderTableReference(tableName, tableName, schemaName)
194
+ }
195
+
196
+ const quoteReferenceColumn = (
197
+ columnName: string,
198
+ reference: { readonly casing?: Casing.Options },
199
+ state: RenderState,
200
+ dialect: SqlDialect
201
+ ): string =>
202
+ dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
203
+
204
+ const registerSourceReference = (
205
+ source: unknown,
206
+ tableName: string,
207
+ state: RenderState
208
+ ): void => {
209
+ if (typeof source !== "object" || source === null) {
210
+ return
211
+ }
212
+ if (Table.TypeId in source) {
213
+ const table = source as Table.AnyTable
214
+ const tableState = table[Table.TypeId]
215
+ const casing = casingForTable(table, state)
216
+ const renderedTableName = tableState.kind === "alias"
217
+ ? tableName
218
+ : Casing.applyCategory(casing, "tables", tableState.baseName)
219
+ const columns = new Map(
220
+ Object.keys(tableState.fields).map((columnName) => [
221
+ columnName,
222
+ Casing.applyCategory(casing, "columns", columnName)
223
+ ] as const)
224
+ )
225
+ state.sourceNames?.set(tableName, {
226
+ tableName: renderedTableName,
227
+ columns
228
+ })
229
+ return
230
+ }
231
+ if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
232
+ state.sourceNames?.set(tableName, {
233
+ tableName,
234
+ columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
235
+ })
236
+ }
237
+ }
238
+
239
+ const registerQuerySources = (
240
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
241
+ state: RenderState
242
+ ): void => {
243
+ if (ast.from !== undefined) {
244
+ registerSourceReference(ast.from.source, ast.from.tableName, state)
245
+ }
246
+ for (const source of ast.fromSources ?? []) {
247
+ registerSourceReference(source.source, source.tableName, state)
248
+ }
249
+ for (const join of ast.joins) {
250
+ registerSourceReference(join.source, join.tableName, state)
251
+ }
252
+ if (ast.into !== undefined) {
253
+ registerSourceReference(ast.into.source, ast.into.tableName, state)
254
+ }
255
+ if (ast.target !== undefined) {
256
+ registerSourceReference(ast.target.source, ast.target.tableName, state)
257
+ }
258
+ for (const target of ast.targets ?? []) {
259
+ registerSourceReference(target.source, target.tableName, state)
260
+ }
261
+ if (ast.using !== undefined) {
262
+ registerSourceReference(ast.using.source, ast.using.tableName, state)
263
+ }
264
+ }
265
+
128
266
  const renderColumnDefinition = (
129
267
  dialect: SqlDialect,
130
268
  state: RenderState,
131
269
  columnName: string,
132
- column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
270
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
271
+ tableName?: string,
272
+ casing?: Casing.Options
133
273
  ): string => {
274
+ const expressionState = { ...state, casing, rowLocalColumns: true }
275
+ if (isArrayDbType(column.metadata.dbType)) {
276
+ throw new Error("Unsupported mysql array column options")
277
+ }
134
278
  const clauses = [
135
- dialect.quoteIdentifier(columnName),
136
- column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
279
+ quoteColumn(columnName, state, dialect, tableName),
280
+ column.metadata.ddlType === undefined
281
+ ? renderDbType(dialect, column.metadata.dbType)
282
+ : renderDbTypeName(column.metadata.ddlType)
137
283
  ]
138
284
  if (column.metadata.identity) {
139
- clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
285
+ throw new Error("Unsupported mysql identity column options")
140
286
  } else if (column.metadata.generatedValue) {
141
- clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
287
+ clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
142
288
  } else if (column.metadata.defaultValue) {
143
- clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
289
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
144
290
  }
145
291
  if (!column.metadata.nullable) {
146
292
  clauses.push("not null")
@@ -152,34 +298,53 @@ const renderCreateTableSql = (
152
298
  targetSource: QueryAst.FromClause,
153
299
  state: RenderState,
154
300
  dialect: SqlDialect,
155
- ifNotExists: boolean
301
+ ifNotExists: unknown
156
302
  ): string => {
303
+ const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
157
304
  const table = targetSource.source as Table.AnyTable
305
+ const tableCasing = casingForTable(table, state)
158
306
  const fields = table[Table.TypeId].fields
159
307
  const definitions = Object.entries(fields).map(([columnName, column]) =>
160
- renderColumnDefinition(dialect, state, columnName, column)
308
+ renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
161
309
  )
162
- for (const option of table[Table.OptionsSymbol]) {
310
+ const options = table[Table.OptionsSymbol] as unknown
311
+ const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
312
+ validateOptions(table[Table.TypeId].name, fields, tableOptions)
313
+ for (const option of tableOptions) {
314
+ if (typeof option !== "object" || option === null || !("kind" in option)) {
315
+ continue
316
+ }
163
317
  switch (option.kind) {
164
318
  case "primaryKey":
165
- 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" : ""}` : ""}`)
319
+ if (option.deferrable || option.initiallyDeferred) {
320
+ throw new Error("Unsupported mysql primary key constraint options")
321
+ }
322
+ 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" : ""}` : ""}`)
166
323
  break
167
324
  case "unique":
168
325
  if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
169
326
  throw new Error("Unsupported mysql unique constraint options")
170
327
  }
171
- 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" : ""}` : ""}`)
328
+ 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" : ""}` : ""}`)
172
329
  break
173
330
  case "foreignKey": {
174
- const reference = option.references()
331
+ if (option.deferrable || option.initiallyDeferred) {
332
+ throw new Error("Unsupported mysql foreign key constraint options")
333
+ }
334
+ const reference = typeof option.references === "function"
335
+ ? option.references()
336
+ : option.references
175
337
  definitions.push(
176
- `${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" : ""}` : ""}`
338
+ `${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" : ""}` : ""}`
177
339
  )
178
340
  break
179
341
  }
180
342
  case "check":
343
+ if (option.noInherit) {
344
+ throw new Error("Unsupported mysql check constraint options")
345
+ }
181
346
  definitions.push(
182
- `constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, { ...state, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
347
+ `constraint ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "constraints", option.name))} check (${renderDdlExpression(option.predicate, { ...state, casing: tableCasing, rowLocalColumns: true }, dialect)})${option.noInherit ? " no inherit" : ""}`
183
348
  )
184
349
  break
185
350
  case "index":
@@ -188,7 +353,7 @@ const renderCreateTableSql = (
188
353
  throw new Error("Unsupported table option kind")
189
354
  }
190
355
  }
191
- return `create table${ifNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
356
+ return `create table${normalizedIfNotExists ? " if not exists" : ""} ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${definitions.join(", ")})`
192
357
  }
193
358
 
194
359
  const renderCreateIndexSql = (
@@ -197,11 +362,16 @@ const renderCreateIndexSql = (
197
362
  state: RenderState,
198
363
  dialect: SqlDialect
199
364
  ): string => {
200
- if (ddl.ifNotExists) {
365
+ const unique = normalizeStatementFlag(ddl.unique)
366
+ const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
367
+ const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
368
+ if (ifNotExists) {
201
369
  throw new Error("Unsupported mysql create index options")
202
370
  }
203
- const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
204
- return `create${ddl.unique ? " unique" : ""} index${maybeIfNotExists} ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)} (${ddl.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`
371
+ const maybeIfNotExists = dialect.name === "postgres" && ifNotExists ? " if not exists" : ""
372
+ const table = targetSource.source as Table.AnyTable
373
+ const tableCasing = casingForTable(table, state)
374
+ 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(", ")})`
205
375
  }
206
376
 
207
377
  const renderDropIndexSql = (
@@ -210,12 +380,16 @@ const renderDropIndexSql = (
210
380
  state: RenderState,
211
381
  dialect: SqlDialect
212
382
  ): string => {
213
- if (ddl.ifExists) {
383
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
384
+ const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
385
+ if (ifExists) {
214
386
  throw new Error("Unsupported mysql drop index options")
215
387
  }
388
+ const table = targetSource.source as Table.AnyTable
389
+ const tableCasing = casingForTable(table, state)
216
390
  return dialect.name === "postgres"
217
- ? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
218
- : `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
391
+ ? `drop index${ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))}`
392
+ : `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
219
393
  }
220
394
 
221
395
  const isExpression = (value: unknown): value is Expression.Any =>
@@ -235,6 +409,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean => {
235
409
  const isJsonExpression = (value: unknown): value is Expression.Any =>
236
410
  isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
237
411
 
412
+ const expectValueExpression = (
413
+ _functionName: string,
414
+ value: unknown
415
+ ): Expression.Any => value as Expression.Any
416
+
417
+ const expectBinaryExpressions = (
418
+ _functionName: string,
419
+ left: unknown,
420
+ right: unknown
421
+ ): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
422
+
423
+ const renderBinaryExpression = (
424
+ functionName: string,
425
+ operator: string,
426
+ left: unknown,
427
+ right: unknown,
428
+ state: RenderState,
429
+ dialect: SqlDialect
430
+ ): string => {
431
+ const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
432
+ return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
433
+ }
434
+
238
435
  const unsupportedJsonFeature = (
239
436
  dialect: SqlDialect,
240
437
  feature: string
@@ -258,13 +455,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
258
455
  const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
259
456
  value !== null && typeof value === "object" && JsonPath.TypeId in value
260
457
 
458
+ const isOptionalJsonPathNumber = (value: unknown): boolean =>
459
+ value === undefined || (typeof value === "number" && Number.isFinite(value))
460
+
461
+ const isJsonPathSegment = (segment: unknown): boolean => {
462
+ if (typeof segment === "string") {
463
+ return true
464
+ }
465
+ if (typeof segment === "number") {
466
+ return Number.isFinite(segment)
467
+ }
468
+ if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
469
+ return false
470
+ }
471
+ switch ((segment as { readonly kind?: unknown }).kind) {
472
+ case "key":
473
+ return typeof (segment as { readonly key?: unknown }).key === "string"
474
+ case "index": {
475
+ const index = (segment as { readonly index?: unknown }).index
476
+ return typeof index === "number" && Number.isFinite(index)
477
+ }
478
+ case "wildcard":
479
+ case "descend":
480
+ return true
481
+ case "slice":
482
+ return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
483
+ isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
484
+ default:
485
+ return false
486
+ }
487
+ }
488
+
489
+ const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
490
+ if (!Array.isArray(segments)) {
491
+ throw new Error("JSON path expressions require a segment array")
492
+ }
493
+ if (segments.some((segment) => !isJsonPathSegment(segment))) {
494
+ throw new Error("JSON path segments require string, number, or path segment objects")
495
+ }
496
+ return segments as ReadonlyArray<JsonPath.AnySegment>
497
+ }
498
+
261
499
  const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
262
500
  const path = node.path ?? node.segments ?? node.keys
263
501
  if (isJsonPathValue(path)) {
264
- return path.segments
502
+ return validateJsonPathSegments(path.segments)
265
503
  }
266
504
  if (Array.isArray(path)) {
267
- return path as readonly JsonPath.AnySegment[]
505
+ return validateJsonPathSegments(path)
506
+ }
507
+ if (node.segments !== undefined) {
508
+ return validateJsonPathSegments(node.segments)
268
509
  }
269
510
  if ("key" in node) {
270
511
  return [JsonPath.key(String(node.key))]
@@ -283,11 +524,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
283
524
  return []
284
525
  }
285
526
  if ("right" in node && isJsonPathValue(node.right)) {
286
- return node.right.segments
527
+ return validateJsonPathSegments(node.right.segments)
287
528
  }
288
529
  return []
289
530
  }
290
531
 
532
+ const extractJsonKeys = (
533
+ node: Record<string, unknown>,
534
+ segments: ReadonlyArray<JsonPath.AnySegment>
535
+ ): readonly unknown[] =>
536
+ Array.isArray(node.keys)
537
+ ? node.keys
538
+ : segments.map((segment) =>
539
+ typeof segment === "object" && segment !== null && segment.kind === "key"
540
+ ? segment.key
541
+ : segment
542
+ )
543
+
291
544
  const extractJsonValue = (node: Record<string, unknown>): unknown =>
292
545
  node.newValue ?? node.insert ?? node.right
293
546
 
@@ -357,6 +610,19 @@ const renderMySqlJsonPath = (
357
610
  const isJsonArrayIndexSegment = (segment: JsonPath.AnySegment | string | number | undefined): boolean =>
358
611
  typeof segment === "number" || (typeof segment === "object" && segment !== null && segment.kind === "index")
359
612
 
613
+ const isExactJsonPathSegment = (segment: JsonPath.AnySegment | string | number): boolean =>
614
+ typeof segment === "string" ||
615
+ typeof segment === "number" ||
616
+ (typeof segment === "object" && segment !== null && (segment.kind === "key" || segment.kind === "index"))
617
+
618
+ const assertMySqlJsonMutationPath = (
619
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>
620
+ ): void => {
621
+ if (!segments.every(isExactJsonPathSegment)) {
622
+ throw new Error("MySQL JSON mutation paths require key/index segments")
623
+ }
624
+ }
625
+
360
626
  const renderMySqlJsonInsertPath = (
361
627
  segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
362
628
  insertAfter: boolean,
@@ -520,52 +786,64 @@ const renderJsonOpaquePath = (
520
786
  return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
521
787
  }
522
788
  if (typeof value === "string") {
789
+ if (value.trim().length === 0) {
790
+ throw new Error("SQL/JSON path input must be a non-empty string")
791
+ }
523
792
  return dialect.renderLiteral(value, state)
524
793
  }
525
794
  if (isExpression(value)) {
795
+ const ast = (value as Expression.Any & {
796
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
797
+ })[ExpressionAst.TypeId]
798
+ if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
799
+ throw new Error("SQL/JSON path input must be a non-empty string")
800
+ }
526
801
  return renderExpression(value, state, dialect)
527
802
  }
528
803
  throw new Error("Unsupported SQL/JSON path input")
529
804
  }
530
805
 
806
+ const renderFunctionName = (name: unknown): string => {
807
+ return name as string
808
+ }
809
+
810
+ const renderExtractField = (field: Expression.Any): string => {
811
+ const ast = (field as Expression.Any & {
812
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
813
+ })[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
814
+ return ast.value
815
+ }
816
+
531
817
  const renderFunctionCall = (
532
- name: string,
533
- args: readonly Expression.Any[],
818
+ name: unknown,
819
+ args: unknown,
534
820
  state: RenderState,
535
821
  dialect: SqlDialect
536
822
  ): string => {
537
- if (name === "array") {
538
- return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
539
- }
540
- if (name === "extract" && args.length === 2) {
541
- const field = args[0]
542
- const source = args[1]
543
- if (field === undefined) {
544
- throw new Error("Unsupported SQL extract expression")
545
- }
546
- if (source === undefined) {
547
- throw new Error("Unsupported SQL extract expression")
548
- }
549
- const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
550
- ? field[Expression.TypeId].runtime
551
- : undefined
552
- const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
553
- return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
554
- }
555
- const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
556
- if (args.length === 0) {
557
- switch (name) {
823
+ const functionName = renderFunctionName(name)
824
+ const functionArgs = args as readonly Expression.Any[]
825
+ if (functionName === "array") {
826
+ return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
827
+ }
828
+ if (functionName === "extract") {
829
+ const field = functionArgs[0]!
830
+ const source = functionArgs[1]!
831
+ return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
832
+ }
833
+ const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
834
+ if (functionArgs.length === 0) {
835
+ switch (functionName) {
558
836
  case "current_date":
559
837
  case "current_time":
560
838
  case "current_timestamp":
561
839
  case "localtime":
562
840
  case "localtimestamp":
563
- return name
841
+ return functionName
564
842
  default:
565
- return `${name}()`
843
+ return `${functionName}()`
566
844
  }
567
845
  }
568
- return `${name}(${renderedArgs})`
846
+ return `${functionName}(${renderedArgs})`
569
847
  }
570
848
 
571
849
  const renderJsonExpression = (
@@ -629,23 +907,27 @@ const renderJsonExpression = (
629
907
  const baseSql = dialect.name === "postgres"
630
908
  ? renderPostgresJsonValue(base, state, dialect)
631
909
  : renderExpression(base, state, dialect)
632
- const keys = segments
910
+ const keys = extractJsonKeys(ast, segments)
633
911
  if (keys.length === 0) {
634
912
  return undefined
635
913
  }
914
+ if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
915
+ throw new Error("json key predicates require string keys")
916
+ }
917
+ const keyNames = keys as readonly string[]
636
918
  if (dialect.name === "postgres") {
637
919
  if (kind === "jsonHasAnyKeys") {
638
- return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
920
+ return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
639
921
  }
640
922
  if (kind === "jsonHasAllKeys") {
641
- return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
923
+ return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
642
924
  }
643
- return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
925
+ return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
644
926
  }
645
927
  if (dialect.name === "mysql") {
646
928
  const mode = kind === "jsonHasAllKeys" ? "all" : "one"
647
929
  const modeSql = dialect.renderLiteral(mode, state)
648
- const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
930
+ const paths = keyNames.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
649
931
  return `json_contains_path(${baseSql}, ${modeSql}, ${paths})`
650
932
  }
651
933
  return undefined
@@ -664,9 +946,7 @@ const renderJsonExpression = (
664
946
  return undefined
665
947
  }
666
948
  case "jsonBuildObject": {
667
- const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
668
- ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
669
- : []
949
+ const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
670
950
  const renderedEntries = entries.flatMap((entry) => [
671
951
  dialect.renderLiteral(entry.key, state),
672
952
  renderJsonInputExpression(entry.value, state, dialect)
@@ -680,9 +960,7 @@ const renderJsonExpression = (
680
960
  return undefined
681
961
  }
682
962
  case "jsonBuildArray": {
683
- const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
684
- ? (ast as { readonly values: readonly Expression.Any[] }).values
685
- : []
963
+ const values = (ast as { readonly values: readonly Expression.Any[] }).values
686
964
  const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
687
965
  if (dialect.name === "postgres") {
688
966
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
@@ -781,6 +1059,7 @@ const renderJsonExpression = (
781
1059
  return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
782
1060
  }
783
1061
  if (dialect.name === "mysql") {
1062
+ assertMySqlJsonMutationPath(segments)
784
1063
  return `json_remove(${renderExpression(base, state, dialect)}, ${renderMySqlJsonPath(segments, state, dialect)})`
785
1064
  }
786
1065
  return undefined
@@ -805,6 +1084,7 @@ const renderJsonExpression = (
805
1084
  return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
806
1085
  }
807
1086
  if (dialect.name === "mysql") {
1087
+ assertMySqlJsonMutationPath(segments)
808
1088
  const renderedBase = renderExpression(base, state, dialect)
809
1089
  if (kind === "jsonInsert" && isJsonArrayIndexSegment(segments[segments.length - 1])) {
810
1090
  const renderedPath = renderMySqlJsonInsertPath(segments, insertAfter, state, dialect)
@@ -866,11 +1146,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
866
1146
  const renderMutationAssignment = (
867
1147
  entry: QueryAst.AssignmentClause,
868
1148
  state: RenderState,
869
- dialect: SqlDialect
1149
+ dialect: SqlDialect,
1150
+ targetTableName?: string
870
1151
  ): string => {
871
1152
  const column = entry.tableName && dialect.name === "mysql"
872
- ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
873
- : dialect.quoteIdentifier(entry.columnName)
1153
+ ? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
1154
+ : quoteColumn(entry.columnName, state, dialect, targetTableName)
874
1155
  return `${column} = ${renderExpression(entry.value, state, dialect)}`
875
1156
  }
876
1157
 
@@ -903,8 +1184,9 @@ const renderJoinPredicatesForMutation = (
903
1184
 
904
1185
  const renderDeleteTargets = (
905
1186
  targets: readonly QueryAst.FromClause[],
1187
+ state: RenderState,
906
1188
  dialect: SqlDialect
907
- ): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
1189
+ ): string => targets.map((target) => dialect.quoteIdentifier(casedTableReferenceName(target.tableName, state))).join(", ")
908
1190
 
909
1191
  const renderMysqlMutationLock = (
910
1192
  lock: QueryAst.LockClause | undefined,
@@ -927,7 +1209,7 @@ const renderTransactionClause = (
927
1209
  if (isolationLevel) {
928
1210
  modes.push(isolationLevel)
929
1211
  }
930
- if (clause.readOnly === true) {
1212
+ if (normalizeStatementFlag(clause.readOnly)) {
931
1213
  modes.push("read only")
932
1214
  }
933
1215
  return modes.length > 0
@@ -939,28 +1221,21 @@ const renderTransactionClause = (
939
1221
  case "rollback":
940
1222
  return "rollback"
941
1223
  case "savepoint":
942
- return `savepoint ${dialect.quoteIdentifier(clause.name)}`
1224
+ return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
943
1225
  case "rollbackTo":
944
- return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
1226
+ return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
945
1227
  case "releaseSavepoint":
946
- return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
1228
+ return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
947
1229
  }
948
- throw new Error("Unsupported transaction statement kind")
1230
+ return "start transaction"
949
1231
  }
950
1232
 
951
1233
  const renderSelectionList = (
952
1234
  selection: Record<string, unknown>,
953
1235
  state: RenderState,
954
- dialect: SqlDialect,
955
- validateAggregation: boolean
1236
+ dialect: SqlDialect
956
1237
  ): RenderedQueryAst => {
957
- if (validateAggregation) {
958
- validateAggregationSelection(selection as SelectionValue, [])
959
- }
960
1238
  const flattened = flattenSelection(selection)
961
- if (dialect.name === "mysql" && flattened.length === 0) {
962
- throw new Error("mysql select statements require at least one selected expression")
963
- }
964
1239
  const projections = selectionProjections(selection)
965
1240
  const sql = flattened.map(({ expression, alias }) =>
966
1241
  `${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
@@ -973,110 +1248,26 @@ const renderSelectionList = (
973
1248
  const nestedRenderState = (state: RenderState): RenderState => ({
974
1249
  params: state.params,
975
1250
  valueMappings: state.valueMappings,
1251
+ casing: state.casing,
976
1252
  ctes: [],
977
1253
  cteNames: new Set(state.cteNames),
978
- cteSources: new Map(state.cteSources)
1254
+ cteSources: new Map(state.cteSources),
1255
+ sourceNames: new Map(state.sourceNames)
979
1256
  })
980
1257
 
981
- const assertMatchingSetProjections = (
982
- left: readonly Projection[],
983
- right: readonly Projection[]
984
- ): void => {
985
- const leftKeys = left.map((projection) => JSON.stringify(projection.path))
986
- const rightKeys = right.map((projection) => JSON.stringify(projection.path))
987
- if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
988
- throw new Error("set operator operands must have matching result rows")
989
- }
990
- }
991
-
992
- const assertNoGroupedMutationClauses = (
993
- ast: Pick<QueryAst.Ast, "groupBy" | "having">,
994
- statement: string
995
- ): void => {
996
- if (ast.groupBy.length > 0) {
997
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
998
- }
999
- if (ast.having.length > 0) {
1000
- throw new Error(`having(...) is not supported for ${statement} statements`)
1001
- }
1002
- }
1003
-
1004
- const assertNoInsertQueryClauses = (
1005
- ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
1006
- ): void => {
1007
- if (ast.where.length > 0) {
1008
- throw new Error("where(...) is not supported for insert statements")
1009
- }
1010
- if (ast.joins.length > 0) {
1011
- throw new Error("join(...) is not supported for insert statements")
1012
- }
1013
- if (ast.orderBy.length > 0) {
1014
- throw new Error("orderBy(...) is not supported for insert statements")
1015
- }
1016
- if (ast.limit) {
1017
- throw new Error("limit(...) is not supported for insert statements")
1018
- }
1019
- if (ast.offset) {
1020
- throw new Error("offset(...) is not supported for insert statements")
1021
- }
1022
- if (ast.lock) {
1023
- throw new Error("lock(...) is not supported for insert statements")
1024
- }
1025
- }
1026
-
1027
- const assertNoStatementQueryClauses = (
1028
- ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1029
- statement: string,
1030
- options: { readonly allowSelection?: boolean } = {}
1031
- ): void => {
1032
- if (ast.distinct) {
1033
- throw new Error(`distinct(...) is not supported for ${statement} statements`)
1034
- }
1035
- if (ast.where.length > 0) {
1036
- throw new Error(`where(...) is not supported for ${statement} statements`)
1037
- }
1038
- if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
1039
- throw new Error(`from(...) is not supported for ${statement} statements`)
1040
- }
1041
- if (ast.joins.length > 0) {
1042
- throw new Error(`join(...) is not supported for ${statement} statements`)
1043
- }
1044
- if (ast.groupBy.length > 0) {
1045
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
1046
- }
1047
- if (ast.having.length > 0) {
1048
- throw new Error(`having(...) is not supported for ${statement} statements`)
1049
- }
1050
- if (ast.orderBy.length > 0) {
1051
- throw new Error(`orderBy(...) is not supported for ${statement} statements`)
1052
- }
1053
- if (ast.limit) {
1054
- throw new Error(`limit(...) is not supported for ${statement} statements`)
1055
- }
1056
- if (ast.offset) {
1057
- throw new Error(`offset(...) is not supported for ${statement} statements`)
1058
- }
1059
- if (ast.lock) {
1060
- throw new Error(`lock(...) is not supported for ${statement} statements`)
1061
- }
1062
- if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
1063
- throw new Error(`returning(...) is not supported for ${statement} statements`)
1064
- }
1065
- }
1066
-
1067
1258
  export const renderQueryAst = (
1068
1259
  ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1069
1260
  state: RenderState,
1070
1261
  dialect: SqlDialect,
1071
1262
  options: { readonly emitCtes?: boolean } = {}
1072
1263
  ): RenderedQueryAst => {
1264
+ registerQuerySources(ast, state)
1073
1265
  let sql = ""
1074
1266
  let projections: readonly Projection[] = []
1075
1267
 
1076
1268
  switch (ast.kind) {
1077
1269
  case "select": {
1078
- validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
1079
- const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1270
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
1080
1271
  projections = rendered.projections
1081
1272
  const selectList = rendered.sql.length > 0 ? ` ${rendered.sql}` : ""
1082
1273
  const clauses = [
@@ -1117,9 +1308,6 @@ export const renderQueryAst = (
1117
1308
  clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
1118
1309
  }
1119
1310
  if (ast.lock) {
1120
- if (ast.lock.nowait && ast.lock.skipLocked) {
1121
- throw new Error("lock(...) cannot specify both nowait and skipLocked")
1122
- }
1123
1311
  clauses.push(
1124
1312
  `${renderSelectLockMode(ast.lock.mode)}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
1125
1313
  )
@@ -1129,7 +1317,6 @@ export const renderQueryAst = (
1129
1317
  }
1130
1318
  case "set": {
1131
1319
  const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
1132
- assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
1133
1320
  const base = renderQueryAst(
1134
1321
  Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
1135
1322
  Record<string, unknown>,
@@ -1140,7 +1327,6 @@ export const renderQueryAst = (
1140
1327
  dialect
1141
1328
  )
1142
1329
  projections = selectionProjections(setAst.select as Record<string, unknown>)
1143
- assertMatchingSetProjections(projections, base.projections)
1144
1330
  sql = [
1145
1331
  `(${base.sql})`,
1146
1332
  ...(setAst.setOperations ?? []).map((entry) => {
@@ -1153,7 +1339,6 @@ export const renderQueryAst = (
1153
1339
  state,
1154
1340
  dialect
1155
1341
  )
1156
- assertMatchingSetProjections(projections, rendered.projections)
1157
1342
  return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
1158
1343
  })
1159
1344
  ].join(" ")
@@ -1161,24 +1346,20 @@ export const renderQueryAst = (
1161
1346
  }
1162
1347
  case "insert": {
1163
1348
  const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
1164
- if (insertAst.distinct) {
1165
- throw new Error("distinct(...) is not supported for insert statements")
1166
- }
1167
- assertNoGroupedMutationClauses(insertAst, "insert")
1168
- assertNoInsertQueryClauses(insertAst)
1169
1349
  const targetSource = insertAst.into!
1170
1350
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1171
- const insertSource = expectInsertSourceKind(insertAst.insertSource)
1351
+ const targetCasingState = stateWithTableCasing(state, targetSource.source)
1352
+ const insertSource = insertAst.insertSource
1172
1353
  const conflict = expectConflictClause(insertAst.conflict)
1173
1354
  sql = `insert into ${target}`
1174
1355
  if (insertSource?.kind === "values") {
1175
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1356
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1176
1357
  const rows = insertSource.rows.map((row) =>
1177
- `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1358
+ `(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
1178
1359
  ).join(", ")
1179
1360
  sql += ` (${columns}) values ${rows}`
1180
1361
  } else if (insertSource?.kind === "query") {
1181
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1362
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1182
1363
  const renderedQuery = renderQueryAst(
1183
1364
  Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1184
1365
  Record<string, unknown>,
@@ -1190,7 +1371,7 @@ export const renderQueryAst = (
1190
1371
  )
1191
1372
  sql += ` (${columns}) ${renderedQuery.sql}`
1192
1373
  } else if (insertSource?.kind === "unnest") {
1193
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1374
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1194
1375
  if (dialect.name === "postgres") {
1195
1376
  const table = targetSource.source as Table.AnyTable
1196
1377
  const fields = table[Table.TypeId].fields
@@ -1212,31 +1393,30 @@ export const renderQueryAst = (
1212
1393
  sql += ` (${columns}) values ${rows}`
1213
1394
  }
1214
1395
  } else {
1215
- const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
1216
- const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
1217
- if ((insertAst.values ?? []).length > 0) {
1396
+ const insertValues = insertAst.values ?? []
1397
+ const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
1398
+ const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
1399
+ if (insertValues.length > 0) {
1218
1400
  sql += ` (${columns}) values (${values})`
1219
1401
  } else {
1220
1402
  sql += " () values ()"
1221
1403
  }
1222
1404
  }
1223
1405
  if (conflict) {
1224
- if (conflict.where) {
1225
- throw new Error("Unsupported mysql conflict action predicates")
1226
- }
1406
+ const conflictValueState = { ...targetCasingState, allowExcluded: true }
1227
1407
  const updateValues = (conflict.values ?? []).map((entry) =>
1228
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1408
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
1229
1409
  ).join(", ")
1230
1410
  if (dialect.name === "postgres") {
1231
1411
  const targetSql = conflict.target?.kind === "constraint"
1232
- ? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
1412
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
1233
1413
  : conflict.target?.kind === "columns"
1234
- ? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
1414
+ ? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
1235
1415
  : " on conflict"
1236
1416
  sql += targetSql
1237
1417
  sql += conflict.action === "doNothing"
1238
1418
  ? " do nothing"
1239
- : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
1419
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
1240
1420
  } else if (conflict.action === "doNothing") {
1241
1421
  sql = sql.replace(/^insert/, "insert ignore")
1242
1422
  } else {
@@ -1245,7 +1425,7 @@ export const renderQueryAst = (
1245
1425
  }
1246
1426
  const hasReturning = Object.keys(insertAst.select as Record<string, unknown>).length > 0
1247
1427
  const returning = hasReturning
1248
- ? renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1428
+ ? renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
1249
1429
  : { sql: "", projections: [] }
1250
1430
  if (dialect.name === "mysql" && returning.sql.length > 0) {
1251
1431
  throw new Error("Unsupported mysql returning")
@@ -1258,22 +1438,12 @@ export const renderQueryAst = (
1258
1438
  }
1259
1439
  case "update": {
1260
1440
  const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
1261
- if (updateAst.distinct) {
1262
- throw new Error("distinct(...) is not supported for update statements")
1263
- }
1264
- assertNoGroupedMutationClauses(updateAst, "update")
1265
- if (updateAst.offset) {
1266
- throw new Error("offset(...) is not supported for update statements")
1267
- }
1268
1441
  const targetSource = updateAst.target!
1269
1442
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1270
1443
  const targets = updateAst.targets ?? [targetSource]
1271
1444
  const fromSources = updateAst.fromSources ?? []
1272
- if ((updateAst.set ?? []).length === 0) {
1273
- throw new Error("update statements require at least one assignment")
1274
- }
1275
1445
  const assignments = updateAst.set!.map((entry) =>
1276
- renderMutationAssignment(entry, state, dialect)).join(", ")
1446
+ renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
1277
1447
  if (dialect.name === "mysql") {
1278
1448
  const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
1279
1449
  const extraSources = renderFromSources(fromSources, state, dialect)
@@ -1318,7 +1488,7 @@ export const renderQueryAst = (
1318
1488
  }
1319
1489
  const hasReturning = Object.keys(updateAst.select as Record<string, unknown>).length > 0
1320
1490
  const returning = hasReturning
1321
- ? renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1491
+ ? renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
1322
1492
  : { sql: "", projections: [] }
1323
1493
  if (dialect.name === "mysql" && returning.sql.length > 0) {
1324
1494
  throw new Error("Unsupported mysql returning")
@@ -1331,20 +1501,13 @@ export const renderQueryAst = (
1331
1501
  }
1332
1502
  case "delete": {
1333
1503
  const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
1334
- if (deleteAst.distinct) {
1335
- throw new Error("distinct(...) is not supported for delete statements")
1336
- }
1337
- assertNoGroupedMutationClauses(deleteAst, "delete")
1338
- if (deleteAst.offset) {
1339
- throw new Error("offset(...) is not supported for delete statements")
1340
- }
1341
1504
  const targetSource = deleteAst.target!
1342
1505
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1343
1506
  const targets = deleteAst.targets ?? [targetSource]
1344
1507
  if (dialect.name === "mysql") {
1345
1508
  const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
1346
1509
  const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
1347
- const targetList = renderDeleteTargets(targets, dialect)
1510
+ const targetList = renderDeleteTargets(targets, state, dialect)
1348
1511
  const fromSources = targets.map((entry) =>
1349
1512
  renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
1350
1513
  ).join(", ")
@@ -1381,7 +1544,7 @@ export const renderQueryAst = (
1381
1544
  }
1382
1545
  const hasReturning = Object.keys(deleteAst.select as Record<string, unknown>).length > 0
1383
1546
  const returning = hasReturning
1384
- ? renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1547
+ ? renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
1385
1548
  : { sql: "", projections: [] }
1386
1549
  if (dialect.name === "mysql" && returning.sql.length > 0) {
1387
1550
  throw new Error("Unsupported mysql returning")
@@ -1394,17 +1557,18 @@ export const renderQueryAst = (
1394
1557
  }
1395
1558
  case "truncate": {
1396
1559
  const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1397
- assertNoStatementQueryClauses(truncateAst, "truncate")
1398
1560
  const truncate = expectTruncateClause(truncateAst.truncate)
1399
1561
  const targetSource = truncateAst.target!
1400
- if (dialect.name === "mysql" && (truncate.restartIdentity || truncate.cascade)) {
1562
+ const restartIdentity = truncate.restartIdentity
1563
+ const cascade = truncate.cascade
1564
+ if (dialect.name === "mysql" && (restartIdentity || cascade)) {
1401
1565
  throw new Error("Unsupported mysql truncate options")
1402
1566
  }
1403
1567
  sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
1404
- if (truncate.restartIdentity) {
1568
+ if (restartIdentity) {
1405
1569
  sql += " restart identity"
1406
1570
  }
1407
- if (truncate.cascade) {
1571
+ if (cascade) {
1408
1572
  sql += " cascade"
1409
1573
  }
1410
1574
  break
@@ -1417,9 +1581,6 @@ export const renderQueryAst = (
1417
1581
  const targetSource = mergeAst.target!
1418
1582
  const usingSource = mergeAst.using!
1419
1583
  const merge = mergeAst.merge!
1420
- if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
1421
- throw new Error("returning(...) is not supported for merge statements")
1422
- }
1423
1584
  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)}`
1424
1585
  if (merge.whenMatched) {
1425
1586
  sql += " when matched"
@@ -1449,27 +1610,24 @@ export const renderQueryAst = (
1449
1610
  case "savepoint":
1450
1611
  case "rollbackTo":
1451
1612
  case "releaseSavepoint": {
1452
- assertNoStatementQueryClauses(ast, ast.kind)
1453
1613
  sql = renderTransactionClause(ast.transaction!, dialect)
1454
1614
  break
1455
1615
  }
1456
1616
  case "createTable": {
1457
1617
  const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1458
- assertNoStatementQueryClauses(createTableAst, "createTable")
1459
1618
  const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1460
1619
  sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1461
1620
  break
1462
1621
  }
1463
1622
  case "dropTable": {
1464
1623
  const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1465
- assertNoStatementQueryClauses(dropTableAst, "dropTable")
1466
1624
  const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1467
- sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1625
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
1626
+ sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1468
1627
  break
1469
1628
  }
1470
1629
  case "createIndex": {
1471
1630
  const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1472
- assertNoStatementQueryClauses(createIndexAst, "createIndex")
1473
1631
  sql = renderCreateIndexSql(
1474
1632
  createIndexAst.target!,
1475
1633
  expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
@@ -1480,7 +1638,6 @@ export const renderQueryAst = (
1480
1638
  }
1481
1639
  case "dropIndex": {
1482
1640
  const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1483
- assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
1484
1641
  sql = renderDropIndexSql(
1485
1642
  dropIndexAst.target!,
1486
1643
  expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
@@ -1489,8 +1646,12 @@ export const renderQueryAst = (
1489
1646
  )
1490
1647
  break
1491
1648
  }
1492
- default:
1493
- throw new Error("Unsupported query statement kind")
1649
+ default: {
1650
+ if (ast.transaction !== undefined) {
1651
+ sql = renderTransactionClause(ast.transaction, dialect)
1652
+ }
1653
+ break
1654
+ }
1494
1655
  }
1495
1656
 
1496
1657
  if (state.ctes.length === 0 || options.emitCtes === false) {
@@ -1611,13 +1772,31 @@ const renderSourceReference = (
1611
1772
  if (dialect.name !== "postgres") {
1612
1773
  throw new Error("Unsupported table function source for SQL rendering")
1613
1774
  }
1775
+ const functionName = renderFunctionName(tableFunction.functionName)
1614
1776
  const columnNames = Object.keys(tableFunction.columns)
1615
- return `${tableFunction.functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1777
+ return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1616
1778
  }
1617
1779
  const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1618
1780
  ? (source as Table.AnyTable)[Table.TypeId].schemaName
1619
1781
  : undefined
1620
- return dialect.renderTableReference(tableName, baseTableName, schemaName)
1782
+ if (typeof source === "object" && source !== null && Table.TypeId in source) {
1783
+ const table = source as Table.AnyTable
1784
+ const tableState = table[Table.TypeId]
1785
+ const casing = casingForTable(table, state)
1786
+ const renderedTableName = tableState.kind === "alias"
1787
+ ? tableName
1788
+ : Casing.applyCategory(casing, "tables", baseTableName)
1789
+ const renderedBaseName = Casing.applyCategory(casing, "tables", baseTableName)
1790
+ const renderedSchemaName = schemaName === undefined
1791
+ ? undefined
1792
+ : Casing.applyCategory(casing, "schemas", schemaName)
1793
+ return dialect.renderTableReference(renderedTableName, renderedBaseName, renderedSchemaName)
1794
+ }
1795
+ return dialect.renderTableReference(
1796
+ Casing.applyCategory(state.casing, "tables", tableName),
1797
+ Casing.applyCategory(state.casing, "tables", baseTableName),
1798
+ schemaName === undefined ? undefined : Casing.applyCategory(state.casing, "schemas", schemaName)
1799
+ )
1621
1800
  }
1622
1801
 
1623
1802
  const renderSubqueryExpressionPlan = (
@@ -1656,157 +1835,165 @@ export const renderExpression = (
1656
1835
  return jsonSql
1657
1836
  }
1658
1837
  const ast = rawAst as ExpressionAst.Any
1659
- const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1660
- operator === "eq"
1661
- ? "="
1662
- : operator === "neq"
1663
- ? "<>"
1664
- : operator === "lt"
1665
- ? "<"
1666
- : operator === "lte"
1667
- ? "<="
1668
- : operator === "gt"
1669
- ? ">"
1670
- : ">="
1671
- switch (ast.kind) {
1838
+ const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1839
+ ({
1840
+ eq: "=",
1841
+ neq: "<>",
1842
+ lt: "<",
1843
+ lte: "<=",
1844
+ gt: ">",
1845
+ gte: ">="
1846
+ } as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
1847
+ switch (ast.kind) {
1672
1848
  case "column":
1673
1849
  return state.rowLocalColumns || ast.tableName.length === 0
1674
- ? dialect.quoteIdentifier(ast.columnName)
1675
- : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1850
+ ? quoteColumn(ast.columnName, state, dialect, ast.tableName)
1851
+ : `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
1676
1852
  case "literal":
1677
1853
  if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1678
1854
  throw new Error("Expected a finite numeric value")
1679
1855
  }
1680
1856
  return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1681
1857
  case "excluded":
1858
+ if (state.allowExcluded !== true) {
1859
+ throw new Error("excluded(...) is only supported inside insert conflict handlers")
1860
+ }
1682
1861
  return dialect.name === "mysql"
1683
- ? `values(${dialect.quoteIdentifier(ast.columnName)})`
1684
- : `excluded.${dialect.quoteIdentifier(ast.columnName)}`
1862
+ ? `values(${quoteColumn(ast.columnName, state, dialect)})`
1863
+ : `excluded.${quoteColumn(ast.columnName, state, dialect)}`
1685
1864
  case "cast":
1686
- return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
1865
+ return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
1687
1866
  case "function":
1688
- return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1867
+ return renderFunctionCall(ast.name, ast.args, state, dialect)
1689
1868
  case "eq":
1690
- return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1869
+ return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
1691
1870
  case "neq":
1692
- return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1871
+ return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
1693
1872
  case "lt":
1694
- return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1873
+ return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
1695
1874
  case "lte":
1696
- return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1875
+ return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
1697
1876
  case "gt":
1698
- return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1877
+ return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
1699
1878
  case "gte":
1700
- return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1879
+ return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
1701
1880
  case "like":
1702
- return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1703
- case "ilike":
1881
+ return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
1882
+ case "ilike": {
1883
+ const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
1704
1884
  return dialect.name === "postgres"
1705
- ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1706
- : `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
1707
- case "regexMatch":
1885
+ ? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
1886
+ : `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
1887
+ }
1888
+ case "regexMatch": {
1889
+ const [left, right] = expectBinaryExpressions("regexMatch", ast.left, ast.right)
1708
1890
  return dialect.name === "postgres"
1709
- ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1710
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1711
- case "regexIMatch":
1891
+ ? `(${renderExpression(left, state, dialect)} ~ ${renderExpression(right, state, dialect)})`
1892
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1893
+ }
1894
+ case "regexIMatch": {
1895
+ const [left, right] = expectBinaryExpressions("regexIMatch", ast.left, ast.right)
1712
1896
  return dialect.name === "postgres"
1713
- ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1714
- : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1715
- case "regexNotMatch":
1897
+ ? `(${renderExpression(left, state, dialect)} ~* ${renderExpression(right, state, dialect)})`
1898
+ : `(${renderExpression(left, state, dialect)} regexp ${renderExpression(right, state, dialect)})`
1899
+ }
1900
+ case "regexNotMatch": {
1901
+ const [left, right] = expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
1716
1902
  return dialect.name === "postgres"
1717
- ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1718
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1719
- case "regexNotIMatch":
1903
+ ? `(${renderExpression(left, state, dialect)} !~ ${renderExpression(right, state, dialect)})`
1904
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1905
+ }
1906
+ case "regexNotIMatch": {
1907
+ const [left, right] = expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
1720
1908
  return dialect.name === "postgres"
1721
- ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1722
- : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1723
- case "isDistinctFrom":
1909
+ ? `(${renderExpression(left, state, dialect)} !~* ${renderExpression(right, state, dialect)})`
1910
+ : `(${renderExpression(left, state, dialect)} not regexp ${renderExpression(right, state, dialect)})`
1911
+ }
1912
+ case "isDistinctFrom": {
1913
+ const [left, right] = expectBinaryExpressions("isDistinctFrom", ast.left, ast.right)
1724
1914
  return dialect.name === "mysql"
1725
- ? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
1726
- : `(${renderExpression(ast.left, state, dialect)} is distinct from ${renderExpression(ast.right, state, dialect)})`
1727
- case "isNotDistinctFrom":
1915
+ ? `(not (${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)}))`
1916
+ : `(${renderExpression(left, state, dialect)} is distinct from ${renderExpression(right, state, dialect)})`
1917
+ }
1918
+ case "isNotDistinctFrom": {
1919
+ const [left, right] = expectBinaryExpressions("isNotDistinctFrom", ast.left, ast.right)
1728
1920
  return dialect.name === "mysql"
1729
- ? `(${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)})`
1730
- : `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1731
- case "contains":
1921
+ ? `(${renderExpression(left, state, dialect)} <=> ${renderExpression(right, state, dialect)})`
1922
+ : `(${renderExpression(left, state, dialect)} is not distinct from ${renderExpression(right, state, dialect)})`
1923
+ }
1924
+ case "contains": {
1925
+ const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
1732
1926
  if (dialect.name === "postgres") {
1733
- const left = isJsonExpression(ast.left)
1734
- ? renderPostgresJsonValue(ast.left, state, dialect)
1735
- : renderExpression(ast.left, state, dialect)
1736
- const right = isJsonExpression(ast.right)
1737
- ? renderPostgresJsonValue(ast.right, state, dialect)
1738
- : renderExpression(ast.right, state, dialect)
1927
+ const left = isJsonExpression(leftExpression)
1928
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1929
+ : renderExpression(leftExpression, state, dialect)
1930
+ const right = isJsonExpression(rightExpression)
1931
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1932
+ : renderExpression(rightExpression, state, dialect)
1739
1933
  return `(${left} @> ${right})`
1740
1934
  }
1741
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1742
- return `json_contains(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
1935
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1936
+ return `json_contains(${renderJsonInputExpression(leftExpression, state, dialect)}, ${renderJsonInputExpression(rightExpression, state, dialect)})`
1743
1937
  }
1744
1938
  throw new Error("Unsupported container operator for SQL rendering")
1745
- case "containedBy":
1939
+ }
1940
+ case "containedBy": {
1941
+ const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
1746
1942
  if (dialect.name === "postgres") {
1747
- const left = isJsonExpression(ast.left)
1748
- ? renderPostgresJsonValue(ast.left, state, dialect)
1749
- : renderExpression(ast.left, state, dialect)
1750
- const right = isJsonExpression(ast.right)
1751
- ? renderPostgresJsonValue(ast.right, state, dialect)
1752
- : renderExpression(ast.right, state, dialect)
1943
+ const left = isJsonExpression(leftExpression)
1944
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1945
+ : renderExpression(leftExpression, state, dialect)
1946
+ const right = isJsonExpression(rightExpression)
1947
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1948
+ : renderExpression(rightExpression, state, dialect)
1753
1949
  return `(${left} <@ ${right})`
1754
1950
  }
1755
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1756
- return `json_contains(${renderJsonInputExpression(ast.right, state, dialect)}, ${renderJsonInputExpression(ast.left, state, dialect)})`
1951
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1952
+ return `json_contains(${renderJsonInputExpression(rightExpression, state, dialect)}, ${renderJsonInputExpression(leftExpression, state, dialect)})`
1757
1953
  }
1758
1954
  throw new Error("Unsupported container operator for SQL rendering")
1759
- case "overlaps":
1955
+ }
1956
+ case "overlaps": {
1957
+ const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
1760
1958
  if (dialect.name === "postgres") {
1761
- const left = isJsonExpression(ast.left)
1762
- ? renderPostgresJsonValue(ast.left, state, dialect)
1763
- : renderExpression(ast.left, state, dialect)
1764
- const right = isJsonExpression(ast.right)
1765
- ? renderPostgresJsonValue(ast.right, state, dialect)
1766
- : renderExpression(ast.right, state, dialect)
1959
+ const left = isJsonExpression(leftExpression)
1960
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1961
+ : renderExpression(leftExpression, state, dialect)
1962
+ const right = isJsonExpression(rightExpression)
1963
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1964
+ : renderExpression(rightExpression, state, dialect)
1767
1965
  return `(${left} && ${right})`
1768
1966
  }
1769
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1770
- return `json_overlaps(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
1967
+ if (dialect.name === "mysql" && isJsonExpression(leftExpression) && isJsonExpression(rightExpression)) {
1968
+ return `json_overlaps(${renderJsonInputExpression(leftExpression, state, dialect)}, ${renderJsonInputExpression(rightExpression, state, dialect)})`
1771
1969
  }
1772
1970
  throw new Error("Unsupported container operator for SQL rendering")
1971
+ }
1773
1972
  case "isNull":
1774
- return `(${renderExpression(ast.value, state, dialect)} is null)`
1973
+ return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
1775
1974
  case "isNotNull":
1776
- return `(${renderExpression(ast.value, state, dialect)} is not null)`
1975
+ return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
1777
1976
  case "not":
1778
- return `(not ${renderExpression(ast.value, state, dialect)})`
1977
+ return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
1779
1978
  case "upper":
1780
- return `upper(${renderExpression(ast.value, state, dialect)})`
1979
+ return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
1781
1980
  case "lower":
1782
- return `lower(${renderExpression(ast.value, state, dialect)})`
1981
+ return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
1783
1982
  case "count":
1784
- return `count(${renderExpression(ast.value, state, dialect)})`
1983
+ return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
1785
1984
  case "max":
1786
- return `max(${renderExpression(ast.value, state, dialect)})`
1985
+ return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
1787
1986
  case "min":
1788
- return `min(${renderExpression(ast.value, state, dialect)})`
1987
+ return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
1789
1988
  case "and":
1790
- if (ast.values.length === 0) {
1791
- throw new Error("and(...) requires at least one predicate")
1792
- }
1793
1989
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1794
1990
  case "or":
1795
- if (ast.values.length === 0) {
1796
- throw new Error("or(...) requires at least one predicate")
1797
- }
1798
1991
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
1799
1992
  case "coalesce":
1800
1993
  return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
1801
1994
  case "in":
1802
- if (ast.values.length < 2) {
1803
- throw new Error("in(...) requires at least one candidate value")
1804
- }
1805
1995
  return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1806
1996
  case "notIn":
1807
- if (ast.values.length < 2) {
1808
- throw new Error("notIn(...) requires at least one candidate value")
1809
- }
1810
1997
  return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1811
1998
  case "between":
1812
1999
  return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
@@ -1821,21 +2008,23 @@ export const renderExpression = (
1821
2008
  case "scalarSubquery":
1822
2009
  return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1823
2010
  case "inSubquery":
1824
- return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2011
+ return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1825
2012
  case "comparisonAny":
1826
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2013
+ return `(${renderExpression(expectValueExpression("compareAny", ast.left), state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1827
2014
  case "comparisonAll":
1828
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
2015
+ return `(${renderExpression(expectValueExpression("compareAll", ast.left), state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1829
2016
  case "window": {
1830
- if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1831
- break
1832
- }
2017
+ const partitionBy = ast.partitionBy as readonly Expression.Any[]
2018
+ const orderBy = ast.orderBy as readonly {
2019
+ readonly value: Expression.Any
2020
+ readonly direction: string
2021
+ }[]
1833
2022
  const clauses: string[] = []
1834
- if (ast.partitionBy.length > 0) {
1835
- clauses.push(`partition by ${ast.partitionBy.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}`)
2023
+ if (partitionBy.length > 0) {
2024
+ clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
1836
2025
  }
1837
- if (ast.orderBy.length > 0) {
1838
- clauses.push(`order by ${ast.orderBy.map((entry) =>
2026
+ if (orderBy.length > 0) {
2027
+ clauses.push(`order by ${orderBy.map((entry) =>
1839
2028
  `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1840
2029
  ).join(", ")}`)
1841
2030
  }
@@ -1848,7 +2037,7 @@ export const renderExpression = (
1848
2037
  case "denseRank":
1849
2038
  return `dense_rank() over (${specification})`
1850
2039
  case "over":
1851
- return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
2040
+ return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
1852
2041
  }
1853
2042
  break
1854
2043
  }