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