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