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,43 +1,47 @@
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 { expectConflictClause, expectInsertSourceKind } from "../../internal/dsl-mutation-runtime.js"
11
- import { expectDdlClauseKind } 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 { expectConflictClause } from "../dsl-mutation-runtime.js"
11
+ import { expectDdlClauseKind, normalizeStatementFlag, normalizeStatementIdentifier } from "../dsl-transaction-ddl-runtime.js"
12
12
  import {
13
13
  renderJsonSelectSql,
14
14
  renderSelectSql,
15
15
  toDriverValue
16
- } from "../../internal/runtime/driver-value-mapping.js"
17
- import { normalizeDbValue } from "../../internal/runtime/normalize.js"
18
- import { flattenSelection, type Projection } from "../../internal/projections.js"
19
- import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
20
- import * as SchemaExpression from "../../internal/schema-expression.js"
21
- import { renderReferentialAction, type DdlExpressionLike } from "../../internal/table-options.js"
16
+ } from "../runtime/driver-value-mapping.js"
17
+ import { normalizeDbValue } from "../runtime/normalize.js"
18
+ import { flattenSelection, type Projection } from "../projections.js"
19
+ import * as SchemaExpression from "../schema-expression.js"
20
+ import { renderReferentialAction, validateOptions, type DdlExpressionLike, type TableOptionSpec } from "../table-options.js"
21
+ import * as Casing from "../casing.js"
22
22
 
23
23
  const renderDbType = (
24
24
  dialect: SqlDialect,
25
25
  dbType: Expression.DbType.Any
26
26
  ): string => {
27
- if (dialect.name === "sqlite" && dbType.dialect === "sqlite" && dbType.kind === "uuid") {
27
+ if (dialect.name === "sqlite" && dbType.kind === "uuid") {
28
28
  return "text"
29
29
  }
30
- return dbType.kind
30
+ return renderDbTypeName(dbType.kind)
31
31
  }
32
32
 
33
+ const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
34
+ "element" in dbType
35
+
33
36
  const renderCastType = (
34
37
  dialect: SqlDialect,
35
- dbType: Expression.DbType.Any
38
+ dbType: unknown
36
39
  ): string => {
40
+ const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
37
41
  if (dialect.name !== "sqlite") {
38
- return dbType.kind
42
+ return renderDbTypeName(kind)
39
43
  }
40
- switch (dbType.kind) {
44
+ switch (kind) {
41
45
  case "text":
42
46
  return "text"
43
47
  case "uuid":
@@ -56,7 +60,7 @@ const renderCastType = (
56
60
  case "json":
57
61
  return "json"
58
62
  default:
59
- return dbType.kind
63
+ return renderDbTypeName(kind)
60
64
  }
61
65
  }
62
66
 
@@ -128,22 +132,164 @@ const renderSqliteMutationLimit = (
128
132
  return renderExpression(expression, state, dialect)
129
133
  }
130
134
 
135
+ const casingForTable = (
136
+ table: Table.AnyTable,
137
+ state: RenderState
138
+ ): Casing.Options | undefined =>
139
+ Casing.merge(state.casing, table[Table.TypeId].casing)
140
+
141
+ const casedColumnName = (
142
+ columnName: string,
143
+ state: RenderState,
144
+ tableName?: string
145
+ ): string => {
146
+ if (tableName !== undefined) {
147
+ const mapped = state.sourceNames?.get(tableName)?.columns.get(columnName)
148
+ if (mapped !== undefined) {
149
+ return mapped
150
+ }
151
+ }
152
+ return Casing.applyCategory(state.casing, "columns", columnName)
153
+ }
154
+
155
+ const casedTableReferenceName = (
156
+ tableName: string,
157
+ state: RenderState
158
+ ): string =>
159
+ state.sourceNames?.get(tableName)?.tableName ?? Casing.applyCategory(state.casing, "tables", tableName)
160
+
161
+ const quoteColumn = (
162
+ columnName: string,
163
+ state: RenderState,
164
+ dialect: SqlDialect,
165
+ tableName?: string
166
+ ): string => dialect.quoteIdentifier(casedColumnName(columnName, state, tableName))
167
+
168
+ const stateWithTableCasing = (
169
+ state: RenderState,
170
+ source: unknown
171
+ ): RenderState =>
172
+ typeof source === "object" && source !== null && Table.TypeId in source
173
+ ? { ...state, casing: casingForTable(source as Table.AnyTable, state) }
174
+ : state
175
+
176
+ const referenceCasing = (
177
+ reference: { readonly casing?: Casing.Options },
178
+ state: RenderState
179
+ ): Casing.Options | undefined =>
180
+ Casing.merge(state.casing, reference.casing)
181
+
182
+ const renderReferenceTable = (
183
+ reference: {
184
+ readonly tableName: string
185
+ readonly schemaName?: string
186
+ readonly casing?: Casing.Options
187
+ },
188
+ state: RenderState,
189
+ dialect: SqlDialect
190
+ ): string => {
191
+ const casing = referenceCasing(reference, state)
192
+ const tableName = Casing.applyCategory(casing, "tables", reference.tableName)
193
+ const schemaName = reference.schemaName === undefined
194
+ ? undefined
195
+ : Casing.applyCategory(casing, "schemas", reference.schemaName)
196
+ return dialect.renderTableReference(tableName, tableName, schemaName)
197
+ }
198
+
199
+ const quoteReferenceColumn = (
200
+ columnName: string,
201
+ reference: { readonly casing?: Casing.Options },
202
+ state: RenderState,
203
+ dialect: SqlDialect
204
+ ): string =>
205
+ dialect.quoteIdentifier(Casing.applyCategory(referenceCasing(reference, state), "columns", columnName))
206
+
207
+ const registerSourceReference = (
208
+ source: unknown,
209
+ tableName: string,
210
+ state: RenderState
211
+ ): void => {
212
+ if (typeof source !== "object" || source === null) {
213
+ return
214
+ }
215
+ if (Table.TypeId in source) {
216
+ const table = source as Table.AnyTable
217
+ const tableState = table[Table.TypeId]
218
+ const casing = casingForTable(table, state)
219
+ const renderedTableName = tableState.kind === "alias"
220
+ ? tableName
221
+ : Casing.applyCategory(casing, "tables", tableState.baseName)
222
+ const columns = new Map(
223
+ Object.keys(tableState.fields).map((columnName) => [
224
+ columnName,
225
+ Casing.applyCategory(casing, "columns", columnName)
226
+ ] as const)
227
+ )
228
+ state.sourceNames?.set(tableName, {
229
+ tableName: renderedTableName,
230
+ columns
231
+ })
232
+ return
233
+ }
234
+ if ("columns" in source && typeof source.columns === "object" && source.columns !== null) {
235
+ state.sourceNames?.set(tableName, {
236
+ tableName,
237
+ columns: new Map(Object.keys(source.columns).map((columnName) => [columnName, columnName] as const))
238
+ })
239
+ }
240
+ }
241
+
242
+ const registerQuerySources = (
243
+ ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
244
+ state: RenderState
245
+ ): void => {
246
+ if (ast.from !== undefined) {
247
+ registerSourceReference(ast.from.source, ast.from.tableName, state)
248
+ }
249
+ for (const source of ast.fromSources ?? []) {
250
+ registerSourceReference(source.source, source.tableName, state)
251
+ }
252
+ for (const join of ast.joins) {
253
+ registerSourceReference(join.source, join.tableName, state)
254
+ }
255
+ if (ast.into !== undefined) {
256
+ registerSourceReference(ast.into.source, ast.into.tableName, state)
257
+ }
258
+ if (ast.target !== undefined) {
259
+ registerSourceReference(ast.target.source, ast.target.tableName, state)
260
+ }
261
+ for (const target of ast.targets ?? []) {
262
+ registerSourceReference(target.source, target.tableName, state)
263
+ }
264
+ if (ast.using !== undefined) {
265
+ registerSourceReference(ast.using.source, ast.using.tableName, state)
266
+ }
267
+ }
268
+
131
269
  const renderColumnDefinition = (
132
270
  dialect: SqlDialect,
133
271
  state: RenderState,
134
272
  columnName: string,
135
- column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
273
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
274
+ tableName?: string,
275
+ casing?: Casing.Options
136
276
  ): string => {
277
+ const expressionState = { ...state, casing, rowLocalColumns: true }
278
+ if (isArrayDbType(column.metadata.dbType)) {
279
+ throw new Error("Unsupported sqlite array column options")
280
+ }
137
281
  const clauses = [
138
- dialect.quoteIdentifier(columnName),
139
- column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
282
+ quoteColumn(columnName, state, dialect, tableName),
283
+ column.metadata.ddlType === undefined
284
+ ? renderDbType(dialect, column.metadata.dbType)
285
+ : renderDbTypeName(column.metadata.ddlType)
140
286
  ]
141
287
  if (column.metadata.identity) {
142
- clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
288
+ throw new Error("Unsupported sqlite identity column options")
143
289
  } else if (column.metadata.generatedValue) {
144
- clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
290
+ clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, expressionState, dialect)}) stored`)
145
291
  } else if (column.metadata.defaultValue) {
146
- clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
292
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
147
293
  }
148
294
  if (!column.metadata.nullable) {
149
295
  clauses.push("not null")
@@ -155,34 +301,50 @@ const renderCreateTableSql = (
155
301
  targetSource: QueryAst.FromClause,
156
302
  state: RenderState,
157
303
  dialect: SqlDialect,
158
- ifNotExists: boolean
304
+ ifNotExists: unknown
159
305
  ): string => {
306
+ const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
160
307
  const table = targetSource.source as Table.AnyTable
308
+ const tableCasing = casingForTable(table, state)
161
309
  const fields = table[Table.TypeId].fields
162
310
  const definitions = Object.entries(fields).map(([columnName, column]) =>
163
- renderColumnDefinition(dialect, state, columnName, column)
311
+ renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
164
312
  )
165
- for (const option of table[Table.OptionsSymbol]) {
313
+ const options = table[Table.OptionsSymbol] as unknown
314
+ const tableOptions = (Array.isArray(options) ? options : [options]) as readonly TableOptionSpec[]
315
+ validateOptions(table[Table.TypeId].name, fields, tableOptions)
316
+ for (const option of tableOptions) {
317
+ if (typeof option !== "object" || option === null || !("kind" in option)) {
318
+ continue
319
+ }
166
320
  switch (option.kind) {
167
321
  case "primaryKey":
168
- 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" : ""}` : ""}`)
322
+ if (option.deferrable || option.initiallyDeferred) {
323
+ throw new Error("Unsupported sqlite primary key constraint options")
324
+ }
325
+ 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" : ""}` : ""}`)
169
326
  break
170
327
  case "unique":
171
328
  if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
172
329
  throw new Error("Unsupported sqlite unique constraint options")
173
330
  }
174
- 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" : ""}` : ""}`)
331
+ 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" : ""}` : ""}`)
175
332
  break
176
333
  case "foreignKey": {
177
- const reference = option.references()
334
+ const reference = typeof option.references === "function"
335
+ ? option.references()
336
+ : option.references
178
337
  definitions.push(
179
- `${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" : ""}` : ""}`
180
339
  )
181
340
  break
182
341
  }
183
342
  case "check":
343
+ if (option.noInherit) {
344
+ throw new Error("Unsupported sqlite check constraint options")
345
+ }
184
346
  definitions.push(
185
- `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" : ""}`
186
348
  )
187
349
  break
188
350
  case "index":
@@ -191,7 +353,7 @@ const renderCreateTableSql = (
191
353
  throw new Error("Unsupported table option kind")
192
354
  }
193
355
  }
194
- 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(", ")})`
195
357
  }
196
358
 
197
359
  const renderCreateIndexSql = (
@@ -200,8 +362,13 @@ const renderCreateIndexSql = (
200
362
  state: RenderState,
201
363
  dialect: SqlDialect
202
364
  ): string => {
203
- const maybeIfNotExists = (dialect.name === "postgres" || dialect.name === "sqlite") && 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(", ")})`
365
+ const unique = normalizeStatementFlag(ddl.unique)
366
+ const ifNotExists = normalizeStatementFlag(ddl.ifNotExists)
367
+ const name = normalizeStatementIdentifier("createIndex", "option 'name'", ddl.name)
368
+ const maybeIfNotExists = (dialect.name === "postgres" || dialect.name === "sqlite") && ifNotExists ? " if not exists" : ""
369
+ const table = targetSource.source as Table.AnyTable
370
+ const tableCasing = casingForTable(table, state)
371
+ 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
372
  }
206
373
 
207
374
  const renderDropIndexSql = (
@@ -209,10 +376,15 @@ const renderDropIndexSql = (
209
376
  ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
210
377
  state: RenderState,
211
378
  dialect: SqlDialect
212
- ): string =>
213
- dialect.name === "postgres" || dialect.name === "sqlite"
214
- ? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
215
- : `drop index ${dialect.quoteIdentifier(ddl.name)} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
379
+ ): string => {
380
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
381
+ const name = normalizeStatementIdentifier("dropIndex", "option 'name'", ddl.name)
382
+ const table = targetSource.source as Table.AnyTable
383
+ const tableCasing = casingForTable(table, state)
384
+ return dialect.name === "postgres" || dialect.name === "sqlite"
385
+ ? `drop index${ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))}`
386
+ : `drop index ${dialect.quoteIdentifier(Casing.applyCategory(tableCasing, "indexes", name))} on ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
387
+ }
216
388
 
217
389
  const isExpression = (value: unknown): value is Expression.Any =>
218
390
  value !== null && typeof value === "object" && Expression.TypeId in value
@@ -223,6 +395,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
223
395
  const isJsonExpression = (value: unknown): value is Expression.Any =>
224
396
  isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
225
397
 
398
+ const expectValueExpression = (
399
+ _functionName: string,
400
+ value: unknown
401
+ ): Expression.Any => value as Expression.Any
402
+
403
+ const expectBinaryExpressions = (
404
+ _functionName: string,
405
+ left: unknown,
406
+ right: unknown
407
+ ): readonly [Expression.Any, Expression.Any] => [left as Expression.Any, right as Expression.Any]
408
+
409
+ const renderBinaryExpression = (
410
+ functionName: string,
411
+ operator: string,
412
+ left: unknown,
413
+ right: unknown,
414
+ state: RenderState,
415
+ dialect: SqlDialect
416
+ ): string => {
417
+ const [leftExpression, rightExpression] = expectBinaryExpressions(functionName, left, right)
418
+ return `(${renderExpression(leftExpression, state, dialect)} ${operator} ${renderExpression(rightExpression, state, dialect)})`
419
+ }
420
+
226
421
  const unsupportedJsonFeature = (
227
422
  dialect: SqlDialect,
228
423
  feature: string
@@ -246,13 +441,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
246
441
  const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
247
442
  value !== null && typeof value === "object" && JsonPath.TypeId in value
248
443
 
444
+ const isOptionalJsonPathNumber = (value: unknown): boolean =>
445
+ value === undefined || (typeof value === "number" && Number.isFinite(value))
446
+
447
+ const isJsonPathSegment = (segment: unknown): boolean => {
448
+ if (typeof segment === "string") {
449
+ return true
450
+ }
451
+ if (typeof segment === "number") {
452
+ return Number.isFinite(segment)
453
+ }
454
+ if (segment === null || typeof segment !== "object" || !("kind" in segment)) {
455
+ return false
456
+ }
457
+ switch ((segment as { readonly kind?: unknown }).kind) {
458
+ case "key":
459
+ return typeof (segment as { readonly key?: unknown }).key === "string"
460
+ case "index": {
461
+ const index = (segment as { readonly index?: unknown }).index
462
+ return typeof index === "number" && Number.isFinite(index)
463
+ }
464
+ case "wildcard":
465
+ case "descend":
466
+ return true
467
+ case "slice":
468
+ return isOptionalJsonPathNumber((segment as { readonly start?: unknown }).start) &&
469
+ isOptionalJsonPathNumber((segment as { readonly end?: unknown }).end)
470
+ default:
471
+ return false
472
+ }
473
+ }
474
+
475
+ const validateJsonPathSegments = (segments: unknown): ReadonlyArray<JsonPath.AnySegment> => {
476
+ if (!Array.isArray(segments)) {
477
+ throw new Error("JSON path expressions require a segment array")
478
+ }
479
+ if (segments.some((segment) => !isJsonPathSegment(segment))) {
480
+ throw new Error("JSON path segments require string, number, or path segment objects")
481
+ }
482
+ return segments as ReadonlyArray<JsonPath.AnySegment>
483
+ }
484
+
249
485
  const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
250
486
  const path = node.path ?? node.segments ?? node.keys
251
487
  if (isJsonPathValue(path)) {
252
- return path.segments
488
+ return validateJsonPathSegments(path.segments)
253
489
  }
254
490
  if (Array.isArray(path)) {
255
- return path as readonly JsonPath.AnySegment[]
491
+ return validateJsonPathSegments(path)
492
+ }
493
+ if (node.segments !== undefined) {
494
+ return validateJsonPathSegments(node.segments)
256
495
  }
257
496
  if ("key" in node) {
258
497
  return [JsonPath.key(String(node.key))]
@@ -271,11 +510,23 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
271
510
  return []
272
511
  }
273
512
  if ("right" in node && isJsonPathValue(node.right)) {
274
- return node.right.segments
513
+ return validateJsonPathSegments(node.right.segments)
275
514
  }
276
515
  return []
277
516
  }
278
517
 
518
+ const extractJsonKeys = (
519
+ node: Record<string, unknown>,
520
+ segments: ReadonlyArray<JsonPath.AnySegment>
521
+ ): readonly unknown[] =>
522
+ Array.isArray(node.keys)
523
+ ? node.keys
524
+ : segments.map((segment) =>
525
+ typeof segment === "object" && segment !== null && segment.kind === "key"
526
+ ? segment.key
527
+ : segment
528
+ )
529
+
279
530
  const extractJsonValue = (node: Record<string, unknown>): unknown =>
280
531
  node.newValue ?? node.insert ?? node.right
281
532
 
@@ -501,45 +752,57 @@ const renderJsonOpaquePath = (
501
752
  return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
502
753
  }
503
754
  if (typeof value === "string") {
755
+ if (value.trim().length === 0) {
756
+ throw new Error("SQL/JSON path input must be a non-empty string")
757
+ }
504
758
  return dialect.renderLiteral(value, state)
505
759
  }
506
760
  if (isExpression(value)) {
761
+ const ast = (value as Expression.Any & {
762
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
763
+ })[ExpressionAst.TypeId]
764
+ if (ast.kind === "literal" && typeof ast.value === "string" && ast.value.trim().length === 0) {
765
+ throw new Error("SQL/JSON path input must be a non-empty string")
766
+ }
507
767
  return renderExpression(value, state, dialect)
508
768
  }
509
769
  throw new Error("Unsupported SQL/JSON path input")
510
770
  }
511
771
 
772
+ const renderFunctionName = (name: unknown): string => {
773
+ return name as string
774
+ }
775
+
776
+ const renderExtractField = (field: Expression.Any): string => {
777
+ const ast = (field as Expression.Any & {
778
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
779
+ })[ExpressionAst.TypeId] as ExpressionAst.LiteralNode<string>
780
+ return ast.value
781
+ }
782
+
512
783
  const renderFunctionCall = (
513
- name: string,
514
- args: readonly Expression.Any[],
784
+ name: unknown,
785
+ args: unknown,
515
786
  state: RenderState,
516
787
  dialect: SqlDialect
517
788
  ): string => {
518
- if (name === "array") {
519
- return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
520
- }
521
- if (name === "extract" && args.length === 2) {
522
- const field = args[0]
523
- const source = args[1]
524
- if (field === undefined) {
525
- throw new Error("Unsupported SQL extract expression")
526
- }
527
- if (source === undefined) {
528
- throw new Error("Unsupported SQL extract expression")
529
- }
530
- const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
531
- ? field[Expression.TypeId].runtime
532
- : undefined
533
- const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
534
- return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
535
- }
536
- const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
537
- if (args.length === 0) {
538
- switch (name) {
789
+ const functionName = renderFunctionName(name)
790
+ const functionArgs = args as readonly Expression.Any[]
791
+ if (functionName === "array") {
792
+ return `ARRAY[${functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
793
+ }
794
+ if (functionName === "extract") {
795
+ const field = functionArgs[0]!
796
+ const source = functionArgs[1]!
797
+ return `extract(${renderExtractField(field)} from ${renderExpression(source, state, dialect)})`
798
+ }
799
+ const renderedArgs = functionArgs.map((arg) => renderExpression(arg, state, dialect)).join(", ")
800
+ if (functionArgs.length === 0) {
801
+ switch (functionName) {
539
802
  case "current_date":
540
803
  case "current_time":
541
804
  case "current_timestamp":
542
- return name
805
+ return functionName
543
806
  case "localtime":
544
807
  return "time('now', 'localtime')"
545
808
  case "localtimestamp":
@@ -547,10 +810,10 @@ const renderFunctionCall = (
547
810
  case "now":
548
811
  return "current_timestamp"
549
812
  default:
550
- return `${name}()`
813
+ return `${functionName}()`
551
814
  }
552
815
  }
553
- return `${name}(${renderedArgs})`
816
+ return `${functionName}(${renderedArgs})`
554
817
  }
555
818
 
556
819
  const renderJsonExpression = (
@@ -614,22 +877,26 @@ const renderJsonExpression = (
614
877
  const baseSql = dialect.name === "postgres"
615
878
  ? renderPostgresJsonValue(base, state, dialect)
616
879
  : renderExpression(base, state, dialect)
617
- const keys = segments
880
+ const keys = extractJsonKeys(ast, segments)
618
881
  if (keys.length === 0) {
619
882
  return undefined
620
883
  }
884
+ if (keys.some((key) => typeof key !== "string" || key.length === 0)) {
885
+ throw new Error("json key predicates require string keys")
886
+ }
887
+ const keyNames = keys as readonly string[]
621
888
  if (dialect.name === "postgres") {
622
889
  if (kind === "jsonHasAnyKeys") {
623
- return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
890
+ return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
624
891
  }
625
892
  if (kind === "jsonHasAllKeys") {
626
- return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
893
+ return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
627
894
  }
628
- return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
895
+ return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
629
896
  }
630
897
  if (dialect.name === "sqlite") {
631
898
  const renderBase = () => renderExpression(base, state, dialect)
632
- const checks = keys.map((segment) => `json_type(${renderBase()}, ${renderSqliteJsonPath([segment], state, dialect)}) is not null`)
899
+ const checks = keyNames.map((segment) => `json_type(${renderBase()}, ${renderSqliteJsonPath([segment], state, dialect)}) is not null`)
633
900
  return `(${checks.join(kind === "jsonHasAllKeys" ? " and " : " or ")})`
634
901
  }
635
902
  return undefined
@@ -648,9 +915,7 @@ const renderJsonExpression = (
648
915
  return undefined
649
916
  }
650
917
  case "jsonBuildObject": {
651
- const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
652
- ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
653
- : []
918
+ const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
654
919
  const renderedEntries = entries.flatMap((entry) => [
655
920
  dialect.renderLiteral(entry.key, state),
656
921
  renderJsonInputExpression(entry.value, state, dialect)
@@ -664,9 +929,7 @@ const renderJsonExpression = (
664
929
  return undefined
665
930
  }
666
931
  case "jsonBuildArray": {
667
- const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
668
- ? (ast as { readonly values: readonly Expression.Any[] }).values
669
- : []
932
+ const values = (ast as { readonly values: readonly Expression.Any[] }).values
670
933
  const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
671
934
  if (dialect.name === "postgres") {
672
935
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
@@ -850,11 +1113,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
850
1113
  const renderMutationAssignment = (
851
1114
  entry: QueryAst.AssignmentClause,
852
1115
  state: RenderState,
853
- dialect: SqlDialect
1116
+ dialect: SqlDialect,
1117
+ targetTableName?: string
854
1118
  ): string => {
855
1119
  const column = entry.tableName && dialect.name === "sqlite"
856
- ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
857
- : dialect.quoteIdentifier(entry.columnName)
1120
+ ? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
1121
+ : quoteColumn(entry.columnName, state, dialect, targetTableName)
858
1122
  return `${column} = ${renderExpression(entry.value, state, dialect)}`
859
1123
  }
860
1124
 
@@ -896,6 +1160,9 @@ const renderTransactionClause = (
896
1160
  ): string => {
897
1161
  switch (clause.kind) {
898
1162
  case "transaction": {
1163
+ if (clause.readOnly !== undefined) {
1164
+ normalizeStatementFlag(clause.readOnly)
1165
+ }
899
1166
  if (clause.isolationLevel !== undefined || clause.readOnly !== undefined) {
900
1167
  throw new Error("Unsupported sqlite transaction options")
901
1168
  }
@@ -906,24 +1173,20 @@ const renderTransactionClause = (
906
1173
  case "rollback":
907
1174
  return "rollback"
908
1175
  case "savepoint":
909
- return `savepoint ${dialect.quoteIdentifier(clause.name)}`
1176
+ return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
910
1177
  case "rollbackTo":
911
- return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
1178
+ return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
912
1179
  case "releaseSavepoint":
913
- return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
1180
+ return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
914
1181
  }
915
- throw new Error("Unsupported transaction statement kind")
1182
+ return "begin"
916
1183
  }
917
1184
 
918
1185
  const renderSelectionList = (
919
1186
  selection: Record<string, unknown>,
920
1187
  state: RenderState,
921
- dialect: SqlDialect,
922
- validateAggregation: boolean
1188
+ dialect: SqlDialect
923
1189
  ): RenderedQueryAst => {
924
- if (validateAggregation) {
925
- validateAggregationSelection(selection as SelectionValue, [])
926
- }
927
1190
  const flattened = flattenSelection(selection)
928
1191
  const projections = selectionProjections(selection)
929
1192
  const sql = flattened.map(({ expression, alias }) =>
@@ -937,131 +1200,26 @@ const renderSelectionList = (
937
1200
  const nestedRenderState = (state: RenderState): RenderState => ({
938
1201
  params: state.params,
939
1202
  valueMappings: state.valueMappings,
1203
+ casing: state.casing,
940
1204
  ctes: [],
941
1205
  cteNames: new Set(state.cteNames),
942
- cteSources: new Map(state.cteSources)
1206
+ cteSources: new Map(state.cteSources),
1207
+ sourceNames: new Map(state.sourceNames)
943
1208
  })
944
1209
 
945
- const assertMatchingSetProjections = (
946
- left: readonly Projection[],
947
- right: readonly Projection[]
948
- ): void => {
949
- const leftKeys = left.map((projection) => JSON.stringify(projection.path))
950
- const rightKeys = right.map((projection) => JSON.stringify(projection.path))
951
- if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index])) {
952
- throw new Error("set operator operands must have matching result rows")
953
- }
954
- }
955
-
956
- const assertNoGroupedMutationClauses = (
957
- ast: Pick<QueryAst.Ast, "groupBy" | "having">,
958
- statement: string
959
- ): void => {
960
- if (ast.groupBy.length > 0) {
961
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
962
- }
963
- if (ast.having.length > 0) {
964
- throw new Error(`having(...) is not supported for ${statement} statements`)
965
- }
966
- }
967
-
968
- const assertNoSqliteMutationModifiers = (
969
- ast: Pick<QueryAst.Ast, "orderBy" | "limit" | "offset" | "lock">,
970
- statement: string
971
- ): void => {
972
- if (ast.orderBy.length > 0) {
973
- throw new Error(`orderBy(...) is not supported for ${statement} statements`)
974
- }
975
- if (ast.limit) {
976
- throw new Error(`limit(...) is not supported for ${statement} statements`)
977
- }
978
- if (ast.offset) {
979
- throw new Error(`offset(...) is not supported for ${statement} statements`)
980
- }
981
- if (ast.lock) {
982
- throw new Error(`lock(...) is not supported for ${statement} statements`)
983
- }
984
- }
985
-
986
- const assertNoInsertQueryClauses = (
987
- ast: Pick<QueryAst.Ast, "where" | "joins" | "orderBy" | "limit" | "offset" | "lock">
988
- ): void => {
989
- if (ast.where.length > 0) {
990
- throw new Error("where(...) is not supported for insert statements")
991
- }
992
- if (ast.joins.length > 0) {
993
- throw new Error("join(...) is not supported for insert statements")
994
- }
995
- if (ast.orderBy.length > 0) {
996
- throw new Error("orderBy(...) is not supported for insert statements")
997
- }
998
- if (ast.limit) {
999
- throw new Error("limit(...) is not supported for insert statements")
1000
- }
1001
- if (ast.offset) {
1002
- throw new Error("offset(...) is not supported for insert statements")
1003
- }
1004
- if (ast.lock) {
1005
- throw new Error("lock(...) is not supported for insert statements")
1006
- }
1007
- }
1008
-
1009
- const assertNoStatementQueryClauses = (
1010
- ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1011
- statement: string,
1012
- options: { readonly allowSelection?: boolean } = {}
1013
- ): void => {
1014
- if (ast.distinct) {
1015
- throw new Error(`distinct(...) is not supported for ${statement} statements`)
1016
- }
1017
- if (ast.where.length > 0) {
1018
- throw new Error(`where(...) is not supported for ${statement} statements`)
1019
- }
1020
- if ((ast.fromSources?.length ?? 0) > 0 || ast.from) {
1021
- throw new Error(`from(...) is not supported for ${statement} statements`)
1022
- }
1023
- if (ast.joins.length > 0) {
1024
- throw new Error(`join(...) is not supported for ${statement} statements`)
1025
- }
1026
- if (ast.groupBy.length > 0) {
1027
- throw new Error(`groupBy(...) is not supported for ${statement} statements`)
1028
- }
1029
- if (ast.having.length > 0) {
1030
- throw new Error(`having(...) is not supported for ${statement} statements`)
1031
- }
1032
- if (ast.orderBy.length > 0) {
1033
- throw new Error(`orderBy(...) is not supported for ${statement} statements`)
1034
- }
1035
- if (ast.limit) {
1036
- throw new Error(`limit(...) is not supported for ${statement} statements`)
1037
- }
1038
- if (ast.offset) {
1039
- throw new Error(`offset(...) is not supported for ${statement} statements`)
1040
- }
1041
- if (ast.lock) {
1042
- throw new Error(`lock(...) is not supported for ${statement} statements`)
1043
- }
1044
- if (options.allowSelection !== true && Object.keys(ast.select).length > 0) {
1045
- throw new Error(`returning(...) is not supported for ${statement} statements`)
1046
- }
1047
- }
1048
-
1049
1210
  export const renderQueryAst = (
1050
1211
  ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1051
1212
  state: RenderState,
1052
1213
  dialect: SqlDialect,
1053
1214
  options: { readonly emitCtes?: boolean } = {}
1054
1215
  ): RenderedQueryAst => {
1216
+ registerQuerySources(ast, state)
1055
1217
  let sql = ""
1056
1218
  let projections: readonly Projection[] = []
1057
1219
 
1058
1220
  switch (ast.kind) {
1059
1221
  case "select": {
1060
- validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
1061
- const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1062
- if (rendered.projections.length === 0) {
1063
- throw new Error("sqlite select statements require at least one selected expression")
1064
- }
1222
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
1065
1223
  projections = rendered.projections
1066
1224
  const clauses = [
1067
1225
  ast.distinctOn && ast.distinctOn.length > 0
@@ -1105,7 +1263,6 @@ export const renderQueryAst = (
1105
1263
  }
1106
1264
  case "set": {
1107
1265
  const setAst = ast as QueryAst.Ast<Record<string, unknown>, any, "set">
1108
- assertNoStatementQueryClauses(setAst, "set", { allowSelection: true })
1109
1266
  const base = renderQueryAst(
1110
1267
  Query.getAst(setAst.setBase as Query.Plan.Any) as QueryAst.Ast<
1111
1268
  Record<string, unknown>,
@@ -1116,7 +1273,6 @@ export const renderQueryAst = (
1116
1273
  dialect
1117
1274
  )
1118
1275
  projections = selectionProjections(setAst.select as Record<string, unknown>)
1119
- assertMatchingSetProjections(projections, base.projections)
1120
1276
  sql = [
1121
1277
  base.sql,
1122
1278
  ...(setAst.setOperations ?? []).map((entry) => {
@@ -1132,7 +1288,6 @@ export const renderQueryAst = (
1132
1288
  state,
1133
1289
  dialect
1134
1290
  )
1135
- assertMatchingSetProjections(projections, rendered.projections)
1136
1291
  return `${entry.kind}${entry.all ? " all" : ""} ${rendered.sql}`
1137
1292
  })
1138
1293
  ].join(" ")
@@ -1140,24 +1295,20 @@ export const renderQueryAst = (
1140
1295
  }
1141
1296
  case "insert": {
1142
1297
  const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
1143
- if (insertAst.distinct) {
1144
- throw new Error("distinct(...) is not supported for insert statements")
1145
- }
1146
- assertNoGroupedMutationClauses(insertAst, "insert")
1147
- assertNoInsertQueryClauses(insertAst)
1148
1298
  const targetSource = insertAst.into!
1149
1299
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1150
- const insertSource = expectInsertSourceKind(insertAst.insertSource)
1300
+ const targetCasingState = stateWithTableCasing(state, targetSource.source)
1301
+ const insertSource = insertAst.insertSource
1151
1302
  const conflict = expectConflictClause(insertAst.conflict)
1152
1303
  sql = `insert into ${target}`
1153
1304
  if (insertSource?.kind === "values") {
1154
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1305
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1155
1306
  const rows = insertSource.rows.map((row) =>
1156
- `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1307
+ `(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
1157
1308
  ).join(", ")
1158
1309
  sql += ` (${columns}) values ${rows}`
1159
1310
  } else if (insertSource?.kind === "query") {
1160
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1311
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1161
1312
  const renderedQuery = renderQueryAst(
1162
1313
  Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1163
1314
  Record<string, unknown>,
@@ -1169,7 +1320,7 @@ export const renderQueryAst = (
1169
1320
  )
1170
1321
  sql += ` (${columns}) ${renderedQuery.sql}`
1171
1322
  } else if (insertSource?.kind === "unnest") {
1172
- const columns = insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1323
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1173
1324
  if (dialect.name === "postgres") {
1174
1325
  const table = targetSource.source as Table.AnyTable
1175
1326
  const fields = table[Table.TypeId].fields
@@ -1191,41 +1342,37 @@ export const renderQueryAst = (
1191
1342
  sql += ` (${columns}) values ${rows}`
1192
1343
  }
1193
1344
  } else {
1194
- const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
1195
- const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
1196
- if ((insertAst.values ?? []).length > 0) {
1345
+ const insertValues = insertAst.values ?? []
1346
+ const columns = insertValues.map((entry) => quoteColumn(entry.columnName, state, dialect, targetSource.tableName)).join(", ")
1347
+ const values = insertValues.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")
1348
+ if (insertValues.length > 0) {
1197
1349
  sql += ` (${columns}) values (${values})`
1198
1350
  } else {
1199
1351
  sql += " default values"
1200
1352
  }
1201
1353
  }
1202
1354
  if (conflict) {
1203
- if (conflict.action === "doNothing" && conflict.where) {
1204
- throw new Error("conflict action predicates require update assignments")
1205
- }
1355
+ const conflictValueState = { ...targetCasingState, allowExcluded: true }
1206
1356
  const updateValues = (conflict.values ?? []).map((entry) =>
1207
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1357
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
1208
1358
  ).join(", ")
1209
1359
  if (dialect.name === "postgres" || dialect.name === "sqlite") {
1210
- if (dialect.name === "sqlite" && conflict.target?.kind === "constraint") {
1211
- throw new Error("Unsupported sqlite named conflict constraint")
1212
- }
1213
1360
  const targetSql = conflict.target?.kind === "constraint"
1214
- ? ` on conflict on constraint ${dialect.quoteIdentifier(conflict.target.name)}`
1361
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
1215
1362
  : conflict.target?.kind === "columns"
1216
- ? ` on conflict (${conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, state, dialect)}` : ""}`
1363
+ ? ` on conflict (${conflict.target.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")})${conflict.target.where ? ` where ${renderExpression(conflict.target.where, targetCasingState, dialect)}` : ""}`
1217
1364
  : " on conflict"
1218
1365
  sql += targetSql
1219
1366
  sql += conflict.action === "doNothing"
1220
1367
  ? " do nothing"
1221
- : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, state, dialect)}` : ""}`
1368
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
1222
1369
  } else if (conflict.action === "doNothing") {
1223
1370
  sql = sql.replace(/^insert/, "insert ignore")
1224
1371
  } else {
1225
1372
  sql += ` on duplicate key update ${updateValues}`
1226
1373
  }
1227
1374
  }
1228
- const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1375
+ const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
1229
1376
  projections = returning.projections
1230
1377
  if (returning.sql.length > 0) {
1231
1378
  sql += ` returning ${returning.sql}`
@@ -1234,11 +1381,6 @@ export const renderQueryAst = (
1234
1381
  }
1235
1382
  case "update": {
1236
1383
  const updateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "update">
1237
- if (updateAst.distinct) {
1238
- throw new Error("distinct(...) is not supported for update statements")
1239
- }
1240
- assertNoGroupedMutationClauses(updateAst, "update")
1241
- assertNoSqliteMutationModifiers(updateAst, "update")
1242
1384
  const targetSource = updateAst.target!
1243
1385
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1244
1386
  const targets = updateAst.targets ?? [targetSource]
@@ -1246,11 +1388,8 @@ export const renderQueryAst = (
1246
1388
  if (targets.length > 1) {
1247
1389
  throw new Error("Unsupported sqlite multi-table update")
1248
1390
  }
1249
- if ((updateAst.set ?? []).length === 0) {
1250
- throw new Error("update statements require at least one assignment")
1251
- }
1252
1391
  const assignments = updateAst.set!.map((entry) =>
1253
- renderMutationAssignment(entry, state, dialect)).join(", ")
1392
+ renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
1254
1393
  if (dialect.name === "mysql") {
1255
1394
  const modifiers = ""
1256
1395
  const extraSources = renderFromSources(fromSources, state, dialect)
@@ -1293,7 +1432,7 @@ export const renderQueryAst = (
1293
1432
  if (dialect.name === "mysql" && updateAst.limit) {
1294
1433
  sql += ` limit ${renderSqliteMutationLimit(updateAst.limit, state, dialect)}`
1295
1434
  }
1296
- const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1435
+ const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
1297
1436
  projections = returning.projections
1298
1437
  if (returning.sql.length > 0) {
1299
1438
  sql += ` returning ${returning.sql}`
@@ -1302,11 +1441,6 @@ export const renderQueryAst = (
1302
1441
  }
1303
1442
  case "delete": {
1304
1443
  const deleteAst = ast as QueryAst.Ast<Record<string, unknown>, any, "delete">
1305
- if (deleteAst.distinct) {
1306
- throw new Error("distinct(...) is not supported for delete statements")
1307
- }
1308
- assertNoGroupedMutationClauses(deleteAst, "delete")
1309
- assertNoSqliteMutationModifiers(deleteAst, "delete")
1310
1444
  const targetSource = deleteAst.target!
1311
1445
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1312
1446
  const targets = deleteAst.targets ?? [targetSource]
@@ -1354,7 +1488,7 @@ export const renderQueryAst = (
1354
1488
  if (dialect.name === "mysql" && deleteAst.limit) {
1355
1489
  sql += ` limit ${renderSqliteMutationLimit(deleteAst.limit, state, dialect)}`
1356
1490
  }
1357
- const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1491
+ const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
1358
1492
  projections = returning.projections
1359
1493
  if (returning.sql.length > 0) {
1360
1494
  sql += ` returning ${returning.sql}`
@@ -1363,7 +1497,6 @@ export const renderQueryAst = (
1363
1497
  }
1364
1498
  case "truncate": {
1365
1499
  const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1366
- assertNoStatementQueryClauses(truncateAst, "truncate")
1367
1500
  throw new Error("Unsupported sqlite truncate statement")
1368
1501
  break
1369
1502
  }
@@ -1375,9 +1508,6 @@ export const renderQueryAst = (
1375
1508
  const targetSource = mergeAst.target!
1376
1509
  const usingSource = mergeAst.using!
1377
1510
  const merge = mergeAst.merge!
1378
- if (Object.keys(mergeAst.select as Record<string, unknown>).length > 0) {
1379
- throw new Error("returning(...) is not supported for merge statements")
1380
- }
1381
1511
  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)}`
1382
1512
  if (merge.whenMatched) {
1383
1513
  sql += " when matched"
@@ -1407,27 +1537,24 @@ export const renderQueryAst = (
1407
1537
  case "savepoint":
1408
1538
  case "rollbackTo":
1409
1539
  case "releaseSavepoint": {
1410
- assertNoStatementQueryClauses(ast, ast.kind)
1411
1540
  sql = renderTransactionClause(ast.transaction!, dialect)
1412
1541
  break
1413
1542
  }
1414
1543
  case "createTable": {
1415
1544
  const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1416
- assertNoStatementQueryClauses(createTableAst, "createTable")
1417
1545
  const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1418
1546
  sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1419
1547
  break
1420
1548
  }
1421
1549
  case "dropTable": {
1422
1550
  const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1423
- assertNoStatementQueryClauses(dropTableAst, "dropTable")
1424
1551
  const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1425
- sql = `drop table${ddl.ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1552
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
1553
+ sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1426
1554
  break
1427
1555
  }
1428
1556
  case "createIndex": {
1429
1557
  const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1430
- assertNoStatementQueryClauses(createIndexAst, "createIndex")
1431
1558
  sql = renderCreateIndexSql(
1432
1559
  createIndexAst.target!,
1433
1560
  expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
@@ -1438,7 +1565,6 @@ export const renderQueryAst = (
1438
1565
  }
1439
1566
  case "dropIndex": {
1440
1567
  const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1441
- assertNoStatementQueryClauses(dropIndexAst, "dropIndex")
1442
1568
  sql = renderDropIndexSql(
1443
1569
  dropIndexAst.target!,
1444
1570
  expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
@@ -1447,8 +1573,12 @@ export const renderQueryAst = (
1447
1573
  )
1448
1574
  break
1449
1575
  }
1450
- default:
1451
- throw new Error("Unsupported query statement kind")
1576
+ default: {
1577
+ if (ast.transaction !== undefined) {
1578
+ sql = renderTransactionClause(ast.transaction, dialect)
1579
+ }
1580
+ break
1581
+ }
1452
1582
  }
1453
1583
 
1454
1584
  if (state.ctes.length === 0 || options.emitCtes === false) {
@@ -1572,13 +1702,31 @@ const renderSourceReference = (
1572
1702
  if (dialect.name !== "postgres") {
1573
1703
  throw new Error("Unsupported table function source for SQL rendering")
1574
1704
  }
1705
+ const functionName = renderFunctionName(tableFunction.functionName)
1575
1706
  const columnNames = Object.keys(tableFunction.columns)
1576
- return `${tableFunction.functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1707
+ return `${functionName}(${tableFunction.args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}) as ${dialect.quoteIdentifier(tableFunction.name)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1577
1708
  }
1578
1709
  const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1579
1710
  ? (source as Table.AnyTable)[Table.TypeId].schemaName
1580
1711
  : undefined
1581
- return dialect.renderTableReference(tableName, baseTableName, schemaName)
1712
+ if (typeof source === "object" && source !== null && Table.TypeId in source) {
1713
+ const table = source as Table.AnyTable
1714
+ const tableState = table[Table.TypeId]
1715
+ const casing = casingForTable(table, state)
1716
+ const renderedTableName = tableState.kind === "alias"
1717
+ ? tableName
1718
+ : Casing.applyCategory(casing, "tables", baseTableName)
1719
+ const renderedBaseName = Casing.applyCategory(casing, "tables", baseTableName)
1720
+ const renderedSchemaName = schemaName === undefined
1721
+ ? undefined
1722
+ : Casing.applyCategory(casing, "schemas", schemaName)
1723
+ return dialect.renderTableReference(renderedTableName, renderedBaseName, renderedSchemaName)
1724
+ }
1725
+ return dialect.renderTableReference(
1726
+ Casing.applyCategory(state.casing, "tables", tableName),
1727
+ Casing.applyCategory(state.casing, "tables", baseTableName),
1728
+ schemaName === undefined ? undefined : Casing.applyCategory(state.casing, "schemas", schemaName)
1729
+ )
1582
1730
  }
1583
1731
 
1584
1732
  const renderSubqueryExpressionPlan = (
@@ -1617,53 +1765,56 @@ export const renderExpression = (
1617
1765
  return jsonSql
1618
1766
  }
1619
1767
  const ast = rawAst as ExpressionAst.Any
1620
- const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1621
- operator === "eq"
1622
- ? "="
1623
- : operator === "neq"
1624
- ? "<>"
1625
- : operator === "lt"
1626
- ? "<"
1627
- : operator === "lte"
1628
- ? "<="
1629
- : operator === "gt"
1630
- ? ">"
1631
- : ">="
1632
- switch (ast.kind) {
1768
+ const renderComparisonOperator = (operator: unknown): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1769
+ ({
1770
+ eq: "=",
1771
+ neq: "<>",
1772
+ lt: "<",
1773
+ lte: "<=",
1774
+ gt: ">",
1775
+ gte: ">="
1776
+ } as const)[operator as "eq" | "neq" | "lt" | "lte" | "gt" | "gte"]!
1777
+ switch (ast.kind) {
1633
1778
  case "column":
1634
1779
  return state.rowLocalColumns || ast.tableName.length === 0
1635
- ? dialect.quoteIdentifier(ast.columnName)
1636
- : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1780
+ ? quoteColumn(ast.columnName, state, dialect, ast.tableName)
1781
+ : `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
1637
1782
  case "literal":
1638
1783
  if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1639
1784
  throw new Error("Expected a finite numeric value")
1640
1785
  }
1641
1786
  return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1642
1787
  case "excluded":
1643
- return `excluded.${dialect.quoteIdentifier(ast.columnName)}`
1788
+ if (state.allowExcluded !== true) {
1789
+ throw new Error("excluded(...) is only supported inside insert conflict handlers")
1790
+ }
1791
+ return `excluded.${quoteColumn(ast.columnName, state, dialect)}`
1644
1792
  case "cast":
1645
- return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
1793
+ return `cast(${renderExpression(expectValueExpression("cast", ast.value), state, dialect)} as ${renderCastType(dialect, ast.target)})`
1646
1794
  case "function":
1647
- return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1795
+ return renderFunctionCall(ast.name, ast.args, state, dialect)
1648
1796
  case "eq":
1649
- return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1797
+ return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
1650
1798
  case "neq":
1651
- return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1799
+ return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
1652
1800
  case "lt":
1653
- return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1801
+ return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
1654
1802
  case "lte":
1655
- return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1803
+ return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
1656
1804
  case "gt":
1657
- return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1805
+ return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
1658
1806
  case "gte":
1659
- return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1807
+ return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
1660
1808
  case "like":
1661
- return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1662
- case "ilike":
1809
+ return renderBinaryExpression("like", "like", ast.left, ast.right, state, dialect)
1810
+ case "ilike": {
1811
+ const [left, right] = expectBinaryExpressions("ilike", ast.left, ast.right)
1663
1812
  return dialect.name === "postgres"
1664
- ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1665
- : `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
1813
+ ? `(${renderExpression(left, state, dialect)} ilike ${renderExpression(right, state, dialect)})`
1814
+ : `(lower(${renderExpression(left, state, dialect)}) like lower(${renderExpression(right, state, dialect)}))`
1815
+ }
1666
1816
  case "regexMatch":
1817
+ expectBinaryExpressions("regexMatch", ast.left, ast.right)
1667
1818
  if (dialect.name === "sqlite") {
1668
1819
  throw new Error("Unsupported sqlite regex operator")
1669
1820
  }
@@ -1671,6 +1822,7 @@ export const renderExpression = (
1671
1822
  ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1672
1823
  : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1673
1824
  case "regexIMatch":
1825
+ expectBinaryExpressions("regexIMatch", ast.left, ast.right)
1674
1826
  if (dialect.name === "sqlite") {
1675
1827
  throw new Error("Unsupported sqlite regex operator")
1676
1828
  }
@@ -1678,6 +1830,7 @@ export const renderExpression = (
1678
1830
  ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1679
1831
  : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1680
1832
  case "regexNotMatch":
1833
+ expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
1681
1834
  if (dialect.name === "sqlite") {
1682
1835
  throw new Error("Unsupported sqlite regex operator")
1683
1836
  }
@@ -1685,6 +1838,7 @@ export const renderExpression = (
1685
1838
  ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1686
1839
  : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1687
1840
  case "regexNotIMatch":
1841
+ expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
1688
1842
  if (dialect.name === "sqlite") {
1689
1843
  throw new Error("Unsupported sqlite regex operator")
1690
1844
  }
@@ -1692,79 +1846,73 @@ export const renderExpression = (
1692
1846
  ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1693
1847
  : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1694
1848
  case "isDistinctFrom":
1695
- return `(${renderExpression(ast.left, state, dialect)} is distinct from ${renderExpression(ast.right, state, dialect)})`
1849
+ return renderBinaryExpression("isDistinctFrom", "is distinct from", ast.left, ast.right, state, dialect)
1696
1850
  case "isNotDistinctFrom":
1697
- return `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1698
- case "contains":
1851
+ return renderBinaryExpression("isNotDistinctFrom", "is not distinct from", ast.left, ast.right, state, dialect)
1852
+ case "contains": {
1853
+ const [leftExpression, rightExpression] = expectBinaryExpressions("contains", ast.left, ast.right)
1699
1854
  if (dialect.name === "postgres") {
1700
- const left = isJsonExpression(ast.left)
1701
- ? renderPostgresJsonValue(ast.left, state, dialect)
1702
- : renderExpression(ast.left, state, dialect)
1703
- const right = isJsonExpression(ast.right)
1704
- ? renderPostgresJsonValue(ast.right, state, dialect)
1705
- : renderExpression(ast.right, state, dialect)
1855
+ const left = isJsonExpression(leftExpression)
1856
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1857
+ : renderExpression(leftExpression, state, dialect)
1858
+ const right = isJsonExpression(rightExpression)
1859
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1860
+ : renderExpression(rightExpression, state, dialect)
1706
1861
  return `(${left} @> ${right})`
1707
1862
  }
1708
1863
  throw new Error("Unsupported container operator for SQL rendering")
1709
- case "containedBy":
1864
+ }
1865
+ case "containedBy": {
1866
+ const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
1710
1867
  if (dialect.name === "postgres") {
1711
- const left = isJsonExpression(ast.left)
1712
- ? renderPostgresJsonValue(ast.left, state, dialect)
1713
- : renderExpression(ast.left, state, dialect)
1714
- const right = isJsonExpression(ast.right)
1715
- ? renderPostgresJsonValue(ast.right, state, dialect)
1716
- : renderExpression(ast.right, state, dialect)
1868
+ const left = isJsonExpression(leftExpression)
1869
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1870
+ : renderExpression(leftExpression, state, dialect)
1871
+ const right = isJsonExpression(rightExpression)
1872
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1873
+ : renderExpression(rightExpression, state, dialect)
1717
1874
  return `(${left} <@ ${right})`
1718
1875
  }
1719
1876
  throw new Error("Unsupported container operator for SQL rendering")
1720
- case "overlaps":
1877
+ }
1878
+ case "overlaps": {
1879
+ const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
1721
1880
  if (dialect.name === "postgres") {
1722
- const left = isJsonExpression(ast.left)
1723
- ? renderPostgresJsonValue(ast.left, state, dialect)
1724
- : renderExpression(ast.left, state, dialect)
1725
- const right = isJsonExpression(ast.right)
1726
- ? renderPostgresJsonValue(ast.right, state, dialect)
1727
- : renderExpression(ast.right, state, dialect)
1881
+ const left = isJsonExpression(leftExpression)
1882
+ ? renderPostgresJsonValue(leftExpression, state, dialect)
1883
+ : renderExpression(leftExpression, state, dialect)
1884
+ const right = isJsonExpression(rightExpression)
1885
+ ? renderPostgresJsonValue(rightExpression, state, dialect)
1886
+ : renderExpression(rightExpression, state, dialect)
1728
1887
  return `(${left} && ${right})`
1729
1888
  }
1730
1889
  throw new Error("Unsupported container operator for SQL rendering")
1890
+ }
1731
1891
  case "isNull":
1732
- return `(${renderExpression(ast.value, state, dialect)} is null)`
1892
+ return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
1733
1893
  case "isNotNull":
1734
- return `(${renderExpression(ast.value, state, dialect)} is not null)`
1894
+ return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
1735
1895
  case "not":
1736
- return `(not ${renderExpression(ast.value, state, dialect)})`
1896
+ return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
1737
1897
  case "upper":
1738
- return `upper(${renderExpression(ast.value, state, dialect)})`
1898
+ return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
1739
1899
  case "lower":
1740
- return `lower(${renderExpression(ast.value, state, dialect)})`
1900
+ return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
1741
1901
  case "count":
1742
- return `count(${renderExpression(ast.value, state, dialect)})`
1902
+ return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
1743
1903
  case "max":
1744
- return `max(${renderExpression(ast.value, state, dialect)})`
1904
+ return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
1745
1905
  case "min":
1746
- return `min(${renderExpression(ast.value, state, dialect)})`
1906
+ return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
1747
1907
  case "and":
1748
- if (ast.values.length === 0) {
1749
- throw new Error("and(...) requires at least one predicate")
1750
- }
1751
1908
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1752
1909
  case "or":
1753
- if (ast.values.length === 0) {
1754
- throw new Error("or(...) requires at least one predicate")
1755
- }
1756
1910
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" or ")})`
1757
1911
  case "coalesce":
1758
1912
  return `coalesce(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")})`
1759
1913
  case "in":
1760
- if (ast.values.length < 2) {
1761
- throw new Error("in(...) requires at least one candidate value")
1762
- }
1763
1914
  return `(${renderExpression(ast.values[0]!, state, dialect)} in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1764
1915
  case "notIn":
1765
- if (ast.values.length < 2) {
1766
- throw new Error("notIn(...) requires at least one candidate value")
1767
- }
1768
1916
  return `(${renderExpression(ast.values[0]!, state, dialect)} not in (${ast.values.slice(1).map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}))`
1769
1917
  case "between":
1770
1918
  return `(${renderExpression(ast.values[0]!, state, dialect)} between ${renderExpression(ast.values[1]!, state, dialect)} and ${renderExpression(ast.values[2]!, state, dialect)})`
@@ -1779,27 +1927,35 @@ export const renderExpression = (
1779
1927
  case "scalarSubquery":
1780
1928
  return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1781
1929
  case "inSubquery":
1782
- return `(${renderExpression(ast.left, state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1783
- case "comparisonAny":
1930
+ return `(${renderExpression(expectValueExpression("inSubquery", ast.left), state, dialect)} in (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1931
+ case "comparisonAny": {
1932
+ const left = expectValueExpression("compareAny", ast.left)
1933
+ const operator = renderComparisonOperator(ast.operator)
1784
1934
  if (dialect.name === "sqlite") {
1785
1935
  throw new Error("Unsupported sqlite quantified comparison")
1786
1936
  }
1787
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1788
- case "comparisonAll":
1937
+ return `(${renderExpression(left, state, dialect)} ${operator} any (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1938
+ }
1939
+ case "comparisonAll": {
1940
+ const left = expectValueExpression("compareAll", ast.left)
1941
+ const operator = renderComparisonOperator(ast.operator)
1789
1942
  if (dialect.name === "sqlite") {
1790
1943
  throw new Error("Unsupported sqlite quantified comparison")
1791
1944
  }
1792
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1945
+ return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1946
+ }
1793
1947
  case "window": {
1794
- if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1795
- break
1796
- }
1948
+ const partitionBy = ast.partitionBy as readonly Expression.Any[]
1949
+ const orderBy = ast.orderBy as readonly {
1950
+ readonly value: Expression.Any
1951
+ readonly direction: string
1952
+ }[]
1797
1953
  const clauses: string[] = []
1798
- if (ast.partitionBy.length > 0) {
1799
- clauses.push(`partition by ${ast.partitionBy.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(", ")}`)
1954
+ if (partitionBy.length > 0) {
1955
+ clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
1800
1956
  }
1801
- if (ast.orderBy.length > 0) {
1802
- clauses.push(`order by ${ast.orderBy.map((entry) =>
1957
+ if (orderBy.length > 0) {
1958
+ clauses.push(`order by ${orderBy.map((entry) =>
1803
1959
  `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1804
1960
  ).join(", ")}`)
1805
1961
  }
@@ -1812,7 +1968,7 @@ export const renderExpression = (
1812
1968
  case "denseRank":
1813
1969
  return `dense_rank() over (${specification})`
1814
1970
  case "over":
1815
- return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
1971
+ return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
1816
1972
  }
1817
1973
  break
1818
1974
  }