effect-qb 0.16.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 (128) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +8065 -0
  3. package/dist/mysql.js +4036 -2418
  4. package/dist/postgres/metadata.js +2536 -625
  5. package/dist/postgres.js +8248 -7857
  6. package/dist/sqlite.js +8854 -0
  7. package/dist/standard.js +8019 -0
  8. package/package.json +15 -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 +7 -13
  18. package/src/internal/dialect-renderers/mysql.ts +2046 -0
  19. package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
  20. package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
  21. package/src/internal/dialect.ts +37 -0
  22. package/src/internal/dsl-mutation-runtime.ts +29 -10
  23. package/src/internal/dsl-plan-runtime.ts +41 -24
  24. package/src/internal/dsl-query-runtime.ts +11 -31
  25. package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
  26. package/src/internal/executor.ts +57 -15
  27. package/src/internal/expression-ast.ts +3 -2
  28. package/src/internal/grouping-key.ts +216 -9
  29. package/src/internal/implication-runtime.ts +3 -2
  30. package/src/internal/json/types.ts +155 -40
  31. package/src/internal/predicate/context.ts +14 -1
  32. package/src/internal/predicate/key.ts +19 -2
  33. package/src/internal/predicate/runtime.ts +30 -3
  34. package/src/internal/query.d.ts +38 -11
  35. package/src/internal/query.ts +315 -54
  36. package/src/internal/renderer.ts +51 -6
  37. package/src/internal/runtime/driver-value-mapping.ts +58 -0
  38. package/src/internal/runtime/normalize.ts +74 -43
  39. package/src/internal/runtime/schema.ts +5 -3
  40. package/src/internal/runtime/value.ts +153 -30
  41. package/src/internal/scalar.ts +6 -1
  42. package/src/internal/schema-derivation.d.ts +12 -61
  43. package/src/internal/schema-derivation.ts +90 -38
  44. package/src/internal/schema-expression.ts +2 -2
  45. package/src/internal/sql-expression-renderer.ts +19 -0
  46. package/src/internal/standard-dsl.ts +6885 -0
  47. package/src/internal/table-options.ts +229 -62
  48. package/src/internal/table.d.ts +33 -32
  49. package/src/internal/table.ts +469 -160
  50. package/src/mysql/column-extension.ts +3 -0
  51. package/src/mysql/column.ts +27 -12
  52. package/src/mysql/datatypes/index.ts +24 -2
  53. package/src/mysql/errors/catalog.ts +5 -5
  54. package/src/mysql/errors/normalize.ts +2 -2
  55. package/src/mysql/executor.ts +7 -5
  56. package/src/mysql/internal/dialect.ts +9 -4
  57. package/src/mysql/internal/dsl.ts +906 -324
  58. package/src/mysql/internal/renderer.ts +7 -2
  59. package/src/mysql/json.ts +37 -0
  60. package/src/mysql/query-extension.ts +16 -0
  61. package/src/mysql/query.ts +9 -2
  62. package/src/mysql/renderer.ts +31 -4
  63. package/src/mysql.ts +4 -12
  64. package/src/postgres/column-extension.ts +28 -0
  65. package/src/postgres/column.ts +9 -13
  66. package/src/postgres/datatypes/index.d.ts +2 -1
  67. package/src/postgres/datatypes/index.ts +3 -2
  68. package/src/postgres/errors/normalize.ts +2 -2
  69. package/src/postgres/executor.ts +55 -10
  70. package/src/postgres/function/core.ts +20 -4
  71. package/src/postgres/function/index.ts +1 -17
  72. package/src/postgres/internal/dialect.ts +9 -4
  73. package/src/postgres/internal/dsl.ts +850 -359
  74. package/src/postgres/internal/renderer.ts +7 -2
  75. package/src/postgres/internal/schema-ddl.ts +22 -9
  76. package/src/postgres/internal/schema-model.ts +244 -10
  77. package/src/postgres/json.ts +100 -24
  78. package/src/postgres/jsonb.ts +38 -0
  79. package/src/postgres/query-extension.ts +2 -0
  80. package/src/postgres/query.ts +9 -2
  81. package/src/postgres/renderer.ts +31 -4
  82. package/src/postgres/schema-management.ts +108 -16
  83. package/src/postgres/schema.ts +98 -15
  84. package/src/postgres/table.ts +203 -398
  85. package/src/postgres/type.ts +8 -7
  86. package/src/postgres.ts +9 -11
  87. package/src/sqlite/column-extension.ts +3 -0
  88. package/src/sqlite/column.ts +127 -0
  89. package/src/sqlite/datatypes/index.ts +80 -0
  90. package/src/sqlite/datatypes/spec.ts +98 -0
  91. package/src/sqlite/errors/catalog.ts +103 -0
  92. package/src/sqlite/errors/fields.ts +19 -0
  93. package/src/sqlite/errors/index.ts +19 -0
  94. package/src/sqlite/errors/normalize.ts +229 -0
  95. package/src/sqlite/errors/requirements.ts +71 -0
  96. package/src/sqlite/errors/types.ts +29 -0
  97. package/src/sqlite/executor.ts +229 -0
  98. package/src/sqlite/function/aggregate.ts +2 -0
  99. package/src/sqlite/function/core.ts +2 -0
  100. package/src/sqlite/function/index.ts +19 -0
  101. package/src/sqlite/function/string.ts +2 -0
  102. package/src/sqlite/function/temporal.ts +100 -0
  103. package/src/sqlite/function/window.ts +2 -0
  104. package/src/sqlite/internal/dialect.ts +42 -0
  105. package/src/sqlite/internal/dsl.ts +6979 -0
  106. package/src/sqlite/internal/renderer.ts +51 -0
  107. package/src/sqlite/json.ts +39 -0
  108. package/src/sqlite/query-extension.ts +2 -0
  109. package/src/sqlite/query.ts +196 -0
  110. package/src/sqlite/renderer.ts +51 -0
  111. package/src/sqlite.ts +14 -0
  112. package/src/standard/column.ts +163 -0
  113. package/src/standard/datatypes/index.ts +83 -0
  114. package/src/standard/datatypes/spec.ts +98 -0
  115. package/src/standard/dialect.ts +40 -0
  116. package/src/standard/function/aggregate.ts +2 -0
  117. package/src/standard/function/core.ts +2 -0
  118. package/src/standard/function/index.ts +18 -0
  119. package/src/standard/function/string.ts +2 -0
  120. package/src/standard/function/temporal.ts +78 -0
  121. package/src/standard/function/window.ts +2 -0
  122. package/src/standard/internal/renderer.ts +45 -0
  123. package/src/standard/query.ts +152 -0
  124. package/src/standard/renderer.ts +21 -0
  125. package/src/standard/table.ts +147 -0
  126. package/src/standard.ts +18 -0
  127. package/src/internal/aggregation-validation.ts +0 -57
  128. package/src/mysql/table.ts +0 -157
@@ -1,44 +1,57 @@
1
- import * as Query from "../../internal/query.js"
2
- import * as Expression from "../../internal/scalar.js"
3
- import * as Table from "../../internal/table.js"
4
- import * as QueryAst from "../../internal/query-ast.js"
5
- import type { RenderState, SqlDialect } from "../../internal/dialect.js"
6
- import * as ExpressionAst from "../../internal/expression-ast.js"
7
- import * as JsonPath from "../../internal/json/path.js"
1
+ import * as Schema from "effect/Schema"
2
+
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"
8
12
  import {
9
13
  renderJsonSelectSql,
10
14
  renderSelectSql,
11
15
  toDriverValue
12
- } from "../../internal/runtime/driver-value-mapping.js"
13
- import { flattenSelection, type Projection } from "../../internal/projections.js"
14
- import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
15
- import * as SchemaExpression from "../../internal/schema-expression.js"
16
- import 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"
17
22
 
18
23
  const renderDbType = (
19
24
  dialect: SqlDialect,
20
25
  dbType: Expression.DbType.Any
21
26
  ): string => {
22
- if (dialect.name === "mysql" && dbType.dialect === "mysql" && dbType.kind === "uuid") {
23
- return "char(36)"
27
+ if (dialect.name === "sqlite" && dbType.kind === "uuid") {
28
+ return "text"
24
29
  }
25
- return dbType.kind
30
+ return renderDbTypeName(dbType.kind)
26
31
  }
27
32
 
33
+ const isArrayDbType = (dbType: Expression.DbType.Any): boolean =>
34
+ "element" in dbType
35
+
28
36
  const renderCastType = (
29
37
  dialect: SqlDialect,
30
- dbType: Expression.DbType.Any
38
+ dbType: unknown
31
39
  ): string => {
32
- if (dialect.name !== "mysql") {
33
- return dbType.kind
40
+ const kind = (dbType as { readonly kind?: string } | undefined)?.kind as string
41
+ if (dialect.name !== "sqlite") {
42
+ return renderDbTypeName(kind)
34
43
  }
35
- switch (dbType.kind) {
44
+ switch (kind) {
36
45
  case "text":
37
- return "char"
46
+ return "text"
38
47
  case "uuid":
39
- return "char(36)"
48
+ return "text"
40
49
  case "numeric":
41
- return "decimal"
50
+ return "numeric"
51
+ case "int":
52
+ return "integer"
53
+ case "time":
54
+ return "time"
42
55
  case "timestamp":
43
56
  return "datetime"
44
57
  case "bool":
@@ -47,20 +60,65 @@ const renderCastType = (
47
60
  case "json":
48
61
  return "json"
49
62
  default:
50
- return dbType.kind
63
+ return renderDbTypeName(kind)
64
+ }
65
+ }
66
+
67
+ const renderSqliteDdlString = (value: string): string =>
68
+ `'${value.replaceAll("'", "''")}'`
69
+
70
+ const renderSqliteDdlBytes = (value: Uint8Array): string =>
71
+ `x'${Array.from(value, (byte) => byte.toString(16).padStart(2, "0")).join("")}'`
72
+
73
+ const renderSqliteDdlLiteral = (
74
+ value: unknown,
75
+ state: RenderState,
76
+ context: RenderValueContext = {}
77
+ ): string => {
78
+ const driverValue = toDriverValue(value, {
79
+ dialect: "sqlite",
80
+ valueMappings: state.valueMappings,
81
+ ...context
82
+ })
83
+ if (driverValue === null) {
84
+ return "null"
85
+ }
86
+ switch (typeof driverValue) {
87
+ case "boolean":
88
+ return driverValue ? "1" : "0"
89
+ case "number":
90
+ if (!Number.isFinite(driverValue)) {
91
+ throw new Error("Expected a finite numeric value")
92
+ }
93
+ return String(driverValue)
94
+ case "bigint":
95
+ return driverValue.toString()
96
+ case "string":
97
+ return renderSqliteDdlString(driverValue)
98
+ case "object":
99
+ if (driverValue instanceof Uint8Array) {
100
+ return renderSqliteDdlBytes(driverValue)
101
+ }
102
+ break
51
103
  }
104
+ throw new Error("Unsupported sqlite DDL literal value")
52
105
  }
53
106
 
54
107
  const renderDdlExpression = (
55
108
  expression: DdlExpressionLike,
56
109
  state: RenderState,
57
110
  dialect: SqlDialect
58
- ): string =>
59
- SchemaExpression.isSchemaExpression(expression)
60
- ? SchemaExpression.render(expression)
61
- : renderExpression(expression, state, dialect)
111
+ ): string => {
112
+ if (SchemaExpression.isSchemaExpression(expression)) {
113
+ return SchemaExpression.render(expression)
114
+ }
115
+ return renderExpression(expression, state, {
116
+ ...dialect,
117
+ renderLiteral: renderSqliteDdlLiteral
118
+ })
119
+ }
62
120
 
63
- const renderMysqlMutationLimit = (
121
+ const renderSqliteMutationLimit = (
64
122
  expression: Expression.Any,
65
123
  state: RenderState,
66
124
  dialect: SqlDialect
@@ -74,22 +132,164 @@ const renderMysqlMutationLimit = (
74
132
  return renderExpression(expression, state, dialect)
75
133
  }
76
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
+
77
269
  const renderColumnDefinition = (
78
270
  dialect: SqlDialect,
79
271
  state: RenderState,
80
272
  columnName: string,
81
- column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
273
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
274
+ tableName?: string,
275
+ casing?: Casing.Options
82
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
+ }
83
281
  const clauses = [
84
- dialect.quoteIdentifier(columnName),
85
- 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)
86
286
  ]
87
287
  if (column.metadata.identity) {
88
- clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
288
+ throw new Error("Unsupported sqlite identity column options")
89
289
  } else if (column.metadata.generatedValue) {
90
- 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`)
91
291
  } else if (column.metadata.defaultValue) {
92
- clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
292
+ clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, expressionState, dialect)}`)
93
293
  }
94
294
  if (!column.metadata.nullable) {
95
295
  clauses.push("not null")
@@ -101,38 +301,59 @@ const renderCreateTableSql = (
101
301
  targetSource: QueryAst.FromClause,
102
302
  state: RenderState,
103
303
  dialect: SqlDialect,
104
- ifNotExists: boolean
304
+ ifNotExists: unknown
105
305
  ): string => {
306
+ const normalizedIfNotExists = normalizeStatementFlag(ifNotExists)
106
307
  const table = targetSource.source as Table.AnyTable
308
+ const tableCasing = casingForTable(table, state)
107
309
  const fields = table[Table.TypeId].fields
108
310
  const definitions = Object.entries(fields).map(([columnName, column]) =>
109
- renderColumnDefinition(dialect, state, columnName, column)
311
+ renderColumnDefinition(dialect, state, columnName, column, targetSource.tableName, tableCasing)
110
312
  )
111
- 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
+ }
112
320
  switch (option.kind) {
113
321
  case "primaryKey":
114
- 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" : ""}` : ""}`)
115
326
  break
116
327
  case "unique":
117
- definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
328
+ if (option.nullsNotDistinct || option.deferrable || option.initiallyDeferred) {
329
+ throw new Error("Unsupported sqlite unique constraint options")
330
+ }
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" : ""}` : ""}`)
118
332
  break
119
333
  case "foreignKey": {
120
- const reference = option.references()
334
+ const reference = typeof option.references === "function"
335
+ ? option.references()
336
+ : option.references
121
337
  definitions.push(
122
- `${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 ? ` on delete ${option.onDelete.replace(/[A-Z]/g, (value) => ` ${value.toLowerCase()}`).trim()}` : ""}${option.onUpdate ? ` on update ${option.onUpdate.replace(/[A-Z]/g, (value) => ` ${value.toLowerCase()}`).trim()}` : ""}${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" : ""}` : ""}`
123
339
  )
124
340
  break
125
341
  }
126
342
  case "check":
343
+ if (option.noInherit) {
344
+ throw new Error("Unsupported sqlite check constraint options")
345
+ }
127
346
  definitions.push(
128
- `constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, 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" : ""}`
129
348
  )
130
349
  break
131
350
  case "index":
132
351
  break
352
+ default:
353
+ throw new Error("Unsupported table option kind")
133
354
  }
134
355
  }
135
- 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(", ")})`
136
357
  }
137
358
 
138
359
  const renderCreateIndexSql = (
@@ -141,8 +362,13 @@ const renderCreateIndexSql = (
141
362
  state: RenderState,
142
363
  dialect: SqlDialect
143
364
  ): string => {
144
- const maybeIfNotExists = dialect.name === "postgres" && ddl.ifNotExists ? " if not exists" : ""
145
- 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(", ")})`
146
372
  }
147
373
 
148
374
  const renderDropIndexSql = (
@@ -150,10 +376,15 @@ const renderDropIndexSql = (
150
376
  ddl: Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
151
377
  state: RenderState,
152
378
  dialect: SqlDialect
153
- ): string =>
154
- dialect.name === "postgres"
155
- ? `drop index${ddl.ifExists ? " if exists" : ""} ${dialect.quoteIdentifier(ddl.name)}`
156
- : `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
+ }
157
388
 
158
389
  const isExpression = (value: unknown): value is Expression.Any =>
159
390
  value !== null && typeof value === "object" && Expression.TypeId in value
@@ -164,6 +395,29 @@ const isJsonDbType = (dbType: Expression.DbType.Any): boolean =>
164
395
  const isJsonExpression = (value: unknown): value is Expression.Any =>
165
396
  isExpression(value) && isJsonDbType(value[Expression.TypeId].dbType)
166
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
+
167
421
  const unsupportedJsonFeature = (
168
422
  dialect: SqlDialect,
169
423
  feature: string
@@ -187,13 +441,57 @@ const extractJsonBase = (node: Record<string, unknown>): unknown =>
187
441
  const isJsonPathValue = (value: unknown): value is JsonPath.Path<any> =>
188
442
  value !== null && typeof value === "object" && JsonPath.TypeId in value
189
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
+
190
485
  const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<JsonPath.AnySegment> => {
191
486
  const path = node.path ?? node.segments ?? node.keys
192
487
  if (isJsonPathValue(path)) {
193
- return path.segments
488
+ return validateJsonPathSegments(path.segments)
194
489
  }
195
490
  if (Array.isArray(path)) {
196
- return path as readonly JsonPath.AnySegment[]
491
+ return validateJsonPathSegments(path)
492
+ }
493
+ if (node.segments !== undefined) {
494
+ return validateJsonPathSegments(node.segments)
197
495
  }
198
496
  if ("key" in node) {
199
497
  return [JsonPath.key(String(node.key))]
@@ -212,28 +510,40 @@ const extractJsonPathSegments = (node: Record<string, unknown>): ReadonlyArray<J
212
510
  return []
213
511
  }
214
512
  if ("right" in node && isJsonPathValue(node.right)) {
215
- return node.right.segments
513
+ return validateJsonPathSegments(node.right.segments)
216
514
  }
217
515
  return []
218
516
  }
219
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
+
220
530
  const extractJsonValue = (node: Record<string, unknown>): unknown =>
221
531
  node.newValue ?? node.insert ?? node.right
222
532
 
223
533
  const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
534
+ const renderKey = (value: string): string =>
535
+ /^[A-Za-z_][A-Za-z0-9_]*$/.test(value)
536
+ ? `.${value}`
537
+ : `.${JSON.stringify(value)}`
224
538
  if (typeof segment === "string") {
225
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)
226
- ? `.${segment}`
227
- : `."${segment.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
539
+ return renderKey(segment)
228
540
  }
229
541
  if (typeof segment === "number") {
230
542
  return `[${segment}]`
231
543
  }
232
544
  switch (segment.kind) {
233
545
  case "key":
234
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(segment.key)
235
- ? `.${segment.key}`
236
- : `."${segment.key.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
546
+ return renderKey(segment.key)
237
547
  case "index":
238
548
  return `[${segment.index}]`
239
549
  case "wildcard":
@@ -247,19 +557,67 @@ const renderJsonPathSegment = (segment: JsonPath.AnySegment | string | number):
247
557
  }
248
558
  }
249
559
 
250
- const renderJsonPathStringLiteral = (segments: ReadonlyArray<JsonPath.AnySegment | string | number>): string => {
560
+ const renderSqliteJsonIndex = (index: number): string =>
561
+ index >= 0 ? String(index) : `#${index}`
562
+
563
+ const renderSqliteJsonPathSegment = (segment: JsonPath.AnySegment | string | number): string => {
564
+ if (typeof segment === "number") {
565
+ return `[${renderSqliteJsonIndex(segment)}]`
566
+ }
567
+ if (typeof segment === "object" && segment !== null && segment.kind === "index") {
568
+ return `[${renderSqliteJsonIndex(segment.index)}]`
569
+ }
570
+ if (typeof segment === "object" && segment !== null && segment.kind === "slice") {
571
+ throw new Error("SQLite JSON paths do not support slice segments")
572
+ }
573
+ if (typeof segment === "object" && segment !== null && segment.kind === "wildcard") {
574
+ throw new Error("SQLite JSON paths do not support wildcard segments")
575
+ }
576
+ if (typeof segment === "object" && segment !== null && segment.kind === "descend") {
577
+ throw new Error("SQLite JSON paths do not support recursive descent segments")
578
+ }
579
+ return renderJsonPathSegment(segment)
580
+ }
581
+
582
+ const renderJsonPathStringLiteral = (
583
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
584
+ renderSegment: (segment: JsonPath.AnySegment | string | number) => string = renderJsonPathSegment
585
+ ): string => {
251
586
  let path = "$"
252
587
  for (const segment of segments) {
253
- path += renderJsonPathSegment(segment)
588
+ path += renderSegment(segment)
254
589
  }
255
590
  return path
256
591
  }
257
592
 
258
- const renderMySqlJsonPath = (
593
+ const renderSqliteJsonPath = (
259
594
  segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
260
595
  state: RenderState,
261
596
  dialect: SqlDialect
262
- ): string => dialect.renderLiteral(renderJsonPathStringLiteral(segments), state)
597
+ ): string => dialect.renderLiteral(renderJsonPathStringLiteral(segments, renderSqliteJsonPathSegment), state)
598
+
599
+ const isJsonArrayIndexSegment = (segment: JsonPath.AnySegment | string | number | undefined): boolean =>
600
+ typeof segment === "number" || (typeof segment === "object" && segment !== null && segment.kind === "index")
601
+
602
+ const renderSqliteJsonInsertPath = (
603
+ segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
604
+ insertAfter: boolean,
605
+ state: RenderState,
606
+ dialect: SqlDialect
607
+ ): string => {
608
+ if (!insertAfter || segments.length === 0) {
609
+ return renderSqliteJsonPath(segments, state, dialect)
610
+ }
611
+ const last = segments[segments.length - 1]
612
+ const nextSegments = segments.slice(0, -1)
613
+ if (typeof last === "number") {
614
+ return renderSqliteJsonPath([...nextSegments, last + 1], state, dialect)
615
+ }
616
+ if (typeof last === "object" && last !== null && last.kind === "index") {
617
+ return renderSqliteJsonPath([...nextSegments, { ...last, index: last.index + 1 }], state, dialect)
618
+ }
619
+ return renderSqliteJsonPath(segments, state, dialect)
620
+ }
263
621
 
264
622
  const renderPostgresJsonPathArray = (
265
623
  segments: ReadonlyArray<JsonPath.AnySegment | string | number>,
@@ -334,11 +692,22 @@ const renderJsonInputExpression = (
334
692
  expression: Expression.Any,
335
693
  state: RenderState,
336
694
  dialect: SqlDialect
337
- ): string =>
338
- renderJsonSelectSql(
695
+ ): string => {
696
+ if (dialect.name === "sqlite" && isJsonDbType(expression[Expression.TypeId].dbType)) {
697
+ const ast = (expression as Expression.Any & {
698
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
699
+ })[ExpressionAst.TypeId]
700
+ if (ast.kind === "literal") {
701
+ state.params.push(JSON.stringify(ast.value))
702
+ return "json(?)"
703
+ }
704
+ return `json(${renderExpression(expression, state, dialect)})`
705
+ }
706
+ return renderJsonSelectSql(
339
707
  renderExpression(expression, state, dialect),
340
708
  expressionDriverContext(expression, state, dialect)
341
709
  )
710
+ }
342
711
 
343
712
  const encodeArrayValues = (
344
713
  values: readonly unknown[],
@@ -346,14 +715,26 @@ const encodeArrayValues = (
346
715
  state: RenderState,
347
716
  dialect: SqlDialect
348
717
  ): readonly unknown[] =>
349
- values.map((value) =>
350
- toDriverValue(value, {
718
+ values.map((value) => {
719
+ if (value === null && column.metadata.nullable) {
720
+ return null
721
+ }
722
+ const runtimeSchemaAccepts = column.schema !== undefined &&
723
+ (Schema.is(column.schema) as (candidate: unknown) => boolean)(value)
724
+ const normalizedValue = runtimeSchemaAccepts
725
+ ? value
726
+ : normalizeDbValue(column.metadata.dbType, value)
727
+ const encodedValue = column.schema === undefined || runtimeSchemaAccepts
728
+ ? normalizedValue
729
+ : (Schema.decodeUnknownSync as any)(column.schema)(normalizedValue)
730
+ return toDriverValue(encodedValue, {
351
731
  dialect: dialect.name,
352
732
  valueMappings: state.valueMappings,
353
733
  dbType: column.metadata.dbType,
354
734
  runtimeSchema: column.schema,
355
735
  driverValueMapping: column.metadata.driverValueMapping
356
- }))
736
+ })
737
+ })
357
738
 
358
739
  const renderPostgresJsonKind = (
359
740
  value: Expression.Any
@@ -365,55 +746,74 @@ const renderJsonOpaquePath = (
365
746
  dialect: SqlDialect
366
747
  ): string => {
367
748
  if (isJsonPathValue(value)) {
368
- return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments), state)
749
+ const renderSegment = dialect.name === "sqlite"
750
+ ? renderSqliteJsonPathSegment
751
+ : renderJsonPathSegment
752
+ return dialect.renderLiteral(renderJsonPathStringLiteral(value.segments, renderSegment), state)
369
753
  }
370
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
+ }
371
758
  return dialect.renderLiteral(value, state)
372
759
  }
373
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
+ }
374
767
  return renderExpression(value, state, dialect)
375
768
  }
376
769
  throw new Error("Unsupported SQL/JSON path input")
377
770
  }
378
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
+
379
783
  const renderFunctionCall = (
380
- name: string,
381
- args: readonly Expression.Any[],
784
+ name: unknown,
785
+ args: unknown,
382
786
  state: RenderState,
383
787
  dialect: SqlDialect
384
788
  ): string => {
385
- if (name === "array") {
386
- return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
387
- }
388
- if (name === "extract" && args.length === 2) {
389
- const field = args[0]
390
- const source = args[1]
391
- if (field === undefined) {
392
- throw new Error("Unsupported SQL extract expression")
393
- }
394
- if (source === undefined) {
395
- throw new Error("Unsupported SQL extract expression")
396
- }
397
- const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
398
- ? field[Expression.TypeId].runtime
399
- : undefined
400
- const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
401
- return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
402
- }
403
- const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
404
- if (args.length === 0) {
405
- 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) {
406
802
  case "current_date":
407
803
  case "current_time":
408
804
  case "current_timestamp":
805
+ return functionName
409
806
  case "localtime":
807
+ return "time('now', 'localtime')"
410
808
  case "localtimestamp":
411
- return name
809
+ return "datetime('now', 'localtime')"
810
+ case "now":
811
+ return "current_timestamp"
412
812
  default:
413
- return `${name}()`
813
+ return `${functionName}()`
414
814
  }
415
815
  }
416
- return `${name}(${renderedArgs})`
816
+ return `${functionName}(${renderedArgs})`
417
817
  }
418
818
 
419
819
  const renderJsonExpression = (
@@ -461,9 +861,9 @@ const renderJsonExpression = (
461
861
  const queried = `jsonb_path_query_first(${renderPostgresJsonValue(base, state, dialect)}, ${jsonPathLiteral})`
462
862
  return textMode ? `(${queried} #>> '{}')` : queried
463
863
  }
464
- if (dialect.name === "mysql") {
465
- const extracted = `json_extract(${baseSql}, ${renderMySqlJsonPath(segments, state, dialect)})`
466
- return textMode ? `json_unquote(${extracted})` : extracted
864
+ if (dialect.name === "sqlite") {
865
+ const extracted = `json_extract(${baseSql}, ${renderSqliteJsonPath(segments, state, dialect)})`
866
+ return extracted
467
867
  }
468
868
  return undefined
469
869
  }
@@ -477,23 +877,27 @@ const renderJsonExpression = (
477
877
  const baseSql = dialect.name === "postgres"
478
878
  ? renderPostgresJsonValue(base, state, dialect)
479
879
  : renderExpression(base, state, dialect)
480
- const keys = segments
880
+ const keys = extractJsonKeys(ast, segments)
481
881
  if (keys.length === 0) {
482
882
  return undefined
483
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[]
484
888
  if (dialect.name === "postgres") {
485
889
  if (kind === "jsonHasAnyKeys") {
486
- return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
890
+ return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
487
891
  }
488
892
  if (kind === "jsonHasAllKeys") {
489
- return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
893
+ return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
490
894
  }
491
- return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
895
+ return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
492
896
  }
493
- if (dialect.name === "mysql") {
494
- const mode = kind === "jsonHasAllKeys" ? "all" : "one"
495
- const paths = keys.map((segment) => renderMySqlJsonPath([segment], state, dialect)).join(", ")
496
- return `json_contains_path(${baseSql}, ${dialect.renderLiteral(mode, state)}, ${paths})`
897
+ if (dialect.name === "sqlite") {
898
+ const renderBase = () => renderExpression(base, state, dialect)
899
+ const checks = keyNames.map((segment) => `json_type(${renderBase()}, ${renderSqliteJsonPath([segment], state, dialect)}) is not null`)
900
+ return `(${checks.join(kind === "jsonHasAllKeys" ? " and " : " or ")})`
497
901
  }
498
902
  return undefined
499
903
  }
@@ -505,15 +909,13 @@ const renderJsonExpression = (
505
909
  if (dialect.name === "postgres") {
506
910
  return `(${renderPostgresJsonValue(ast.left, state, dialect)} || ${renderPostgresJsonValue(ast.right, state, dialect)})`
507
911
  }
508
- if (dialect.name === "mysql") {
509
- return `json_merge_preserve(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
912
+ if (dialect.name === "sqlite") {
913
+ return `json_patch(${renderJsonInputExpression(ast.left, state, dialect)}, ${renderJsonInputExpression(ast.right, state, dialect)})`
510
914
  }
511
915
  return undefined
512
916
  }
513
917
  case "jsonBuildObject": {
514
- const entries = Array.isArray((ast as { readonly entries?: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries)
515
- ? (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
516
- : []
918
+ const entries = (ast as { readonly entries: readonly { readonly key: string; readonly value: Expression.Any }[] }).entries
517
919
  const renderedEntries = entries.flatMap((entry) => [
518
920
  dialect.renderLiteral(entry.key, state),
519
921
  renderJsonInputExpression(entry.value, state, dialect)
@@ -521,20 +923,18 @@ const renderJsonExpression = (
521
923
  if (dialect.name === "postgres") {
522
924
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
523
925
  }
524
- if (dialect.name === "mysql") {
926
+ if (dialect.name === "sqlite") {
525
927
  return `json_object(${renderedEntries.join(", ")})`
526
928
  }
527
929
  return undefined
528
930
  }
529
931
  case "jsonBuildArray": {
530
- const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
531
- ? (ast as { readonly values: readonly Expression.Any[] }).values
532
- : []
932
+ const values = (ast as { readonly values: readonly Expression.Any[] }).values
533
933
  const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
534
934
  if (dialect.name === "postgres") {
535
935
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
536
936
  }
537
- if (dialect.name === "mysql") {
937
+ if (dialect.name === "sqlite") {
538
938
  return `json_array(${renderedValues})`
539
939
  }
540
940
  return undefined
@@ -546,8 +946,8 @@ const renderJsonExpression = (
546
946
  if (dialect.name === "postgres") {
547
947
  return `to_json(${renderJsonInputExpression(base, state, dialect)})`
548
948
  }
549
- if (dialect.name === "mysql") {
550
- return `cast(${renderExpression(base, state, dialect)} as json)`
949
+ if (dialect.name === "sqlite") {
950
+ return `json_quote(${renderExpression(base, state, dialect)})`
551
951
  }
552
952
  return undefined
553
953
  case "jsonToJsonb":
@@ -557,8 +957,8 @@ const renderJsonExpression = (
557
957
  if (dialect.name === "postgres") {
558
958
  return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
559
959
  }
560
- if (dialect.name === "mysql") {
561
- return `cast(${renderExpression(base, state, dialect)} as json)`
960
+ if (dialect.name === "sqlite") {
961
+ return `json_quote(${renderExpression(base, state, dialect)})`
562
962
  }
563
963
  return undefined
564
964
  case "jsonTypeOf":
@@ -569,7 +969,7 @@ const renderJsonExpression = (
569
969
  const baseSql = renderExpression(base, state, dialect)
570
970
  return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof(${baseSql})`
571
971
  }
572
- if (dialect.name === "mysql") {
972
+ if (dialect.name === "sqlite") {
573
973
  return `json_type(${renderExpression(base, state, dialect)})`
574
974
  }
575
975
  return undefined
@@ -584,8 +984,12 @@ const renderJsonExpression = (
584
984
  const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
585
985
  return `(case when ${typeOf}(${baseSql}) = 'array' then ${arrayLength}(${baseSql}) when ${typeOf}(${baseSql}) = 'object' then (select count(*)::int from ${objectKeys}(${baseSql})) else null end)`
586
986
  }
587
- if (dialect.name === "mysql") {
588
- return `json_length(${renderExpression(base, state, dialect)})`
987
+ if (dialect.name === "sqlite") {
988
+ if (segments.length > 0) {
989
+ return `json_array_length(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)})`
990
+ }
991
+ const renderBase = () => renderExpression(base, state, dialect)
992
+ return `(case when json_type(${renderBase()}) = 'array' then json_array_length(${renderBase()}) when json_type(${renderBase()}) = 'object' then (select count(*) from json_each(${renderBase()})) else null end)`
589
993
  }
590
994
  return undefined
591
995
  case "jsonKeys":
@@ -598,8 +1002,9 @@ const renderJsonExpression = (
598
1002
  const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
599
1003
  return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
600
1004
  }
601
- if (dialect.name === "mysql") {
602
- return `json_keys(${renderExpression(base, state, dialect)})`
1005
+ if (dialect.name === "sqlite") {
1006
+ const renderBase = () => renderExpression(base, state, dialect)
1007
+ return `(case when json_type(${renderBase()}) = 'object' then (select json_group_array(key) from json_each(${renderBase()})) else null end)`
603
1008
  }
604
1009
  return undefined
605
1010
  case "jsonStripNulls":
@@ -627,10 +1032,8 @@ const renderJsonExpression = (
627
1032
  }
628
1033
  return `(${baseSql} #- ${renderPostgresJsonPathArray(segments, state, dialect)})`
629
1034
  }
630
- if (dialect.name === "mysql") {
631
- return `json_remove(${renderExpression(base, state, dialect)}, ${segments.map((segment) =>
632
- renderMySqlJsonPath([segment], state, dialect)
633
- ).join(", ")})`
1035
+ if (dialect.name === "sqlite") {
1036
+ return `json_remove(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)})`
634
1037
  }
635
1038
  return undefined
636
1039
  }
@@ -653,9 +1056,12 @@ const renderJsonExpression = (
653
1056
  : `, ${createMissing ? "true" : "false"}`
654
1057
  return `${functionName}(${renderPostgresJsonValue(base, state, dialect)}, ${renderPostgresJsonPathArray(segments, state, dialect)}, ${renderPostgresJsonValue(nextValue, state, dialect)}${extra})`
655
1058
  }
656
- if (dialect.name === "mysql") {
657
- const functionName = kind === "jsonInsert" ? "json_insert" : "json_set"
658
- return `${functionName}(${renderExpression(base, state, dialect)}, ${renderMySqlJsonPath(segments, state, dialect)}, ${renderExpression(nextValue, state, dialect)})`
1059
+ if (dialect.name === "sqlite") {
1060
+ if (kind === "jsonInsert" && isJsonArrayIndexSegment(segments[segments.length - 1])) {
1061
+ unsupportedJsonFeature(dialect, insertAfter ? "jsonInsertAfter" : "jsonInsertArrayIndex")
1062
+ }
1063
+ const functionName = kind === "jsonInsert" ? "json_insert" : createMissing ? "json_set" : "json_replace"
1064
+ return `${functionName}(${renderExpression(base, state, dialect)}, ${renderSqliteJsonPath(segments, state, dialect)}, ${renderJsonInputExpression(nextValue, state, dialect)})`
659
1065
  }
660
1066
  return undefined
661
1067
  }
@@ -670,8 +1076,8 @@ const renderJsonExpression = (
670
1076
  if (dialect.name === "postgres") {
671
1077
  return `(${renderPostgresJsonValue(base, state, dialect)} @? ${renderJsonOpaquePath(path, state, dialect)})`
672
1078
  }
673
- if (dialect.name === "mysql") {
674
- return `json_contains_path(${renderExpression(base, state, dialect)}, ${dialect.renderLiteral("one", state)}, ${renderJsonOpaquePath(path, state, dialect)})`
1079
+ if (dialect.name === "sqlite") {
1080
+ return `(json_type(${renderExpression(base, state, dialect)}, ${renderJsonOpaquePath(path, state, dialect)}) is not null)`
675
1081
  }
676
1082
  return undefined
677
1083
  }
@@ -707,11 +1113,12 @@ const selectionProjections = (selection: Record<string, unknown>): readonly Proj
707
1113
  const renderMutationAssignment = (
708
1114
  entry: QueryAst.AssignmentClause,
709
1115
  state: RenderState,
710
- dialect: SqlDialect
1116
+ dialect: SqlDialect,
1117
+ targetTableName?: string
711
1118
  ): string => {
712
- const column = entry.tableName && dialect.name === "mysql"
713
- ? `${dialect.quoteIdentifier(entry.tableName)}.${dialect.quoteIdentifier(entry.columnName)}`
714
- : dialect.quoteIdentifier(entry.columnName)
1119
+ const column = entry.tableName && dialect.name === "sqlite"
1120
+ ? `${dialect.quoteIdentifier(casedTableReferenceName(entry.tableName, state))}.${quoteColumn(entry.columnName, state, dialect, entry.tableName)}`
1121
+ : quoteColumn(entry.columnName, state, dialect, targetTableName)
715
1122
  return `${column} = ${renderExpression(entry.value, state, dialect)}`
716
1123
  }
717
1124
 
@@ -747,65 +1154,39 @@ const renderDeleteTargets = (
747
1154
  dialect: SqlDialect
748
1155
  ): string => targets.map((target) => dialect.quoteIdentifier(target.tableName)).join(", ")
749
1156
 
750
- const renderMysqlMutationLock = (
751
- lock: QueryAst.LockClause | undefined,
752
- statement: "update" | "delete"
753
- ): string => {
754
- if (!lock) {
755
- return ""
756
- }
757
- switch (lock.mode) {
758
- case "lowPriority":
759
- return " low_priority"
760
- case "ignore":
761
- return " ignore"
762
- case "quick":
763
- return statement === "delete" ? " quick" : ""
764
- default:
765
- return ""
766
- }
767
- }
768
-
769
1157
  const renderTransactionClause = (
770
1158
  clause: QueryAst.TransactionClause,
771
1159
  dialect: SqlDialect
772
1160
  ): string => {
773
1161
  switch (clause.kind) {
774
1162
  case "transaction": {
775
- const modes: string[] = []
776
- if (clause.isolationLevel) {
777
- modes.push(`isolation level ${clause.isolationLevel}`)
1163
+ if (clause.readOnly !== undefined) {
1164
+ normalizeStatementFlag(clause.readOnly)
778
1165
  }
779
- if (clause.readOnly === true) {
780
- modes.push("read only")
1166
+ if (clause.isolationLevel !== undefined || clause.readOnly !== undefined) {
1167
+ throw new Error("Unsupported sqlite transaction options")
781
1168
  }
782
- return modes.length > 0
783
- ? `start transaction ${modes.join(", ")}`
784
- : "start transaction"
1169
+ return "begin"
785
1170
  }
786
1171
  case "commit":
787
1172
  return "commit"
788
1173
  case "rollback":
789
1174
  return "rollback"
790
1175
  case "savepoint":
791
- return `savepoint ${dialect.quoteIdentifier(clause.name)}`
1176
+ return `savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("savepoint", "name", clause.name))}`
792
1177
  case "rollbackTo":
793
- return `rollback to savepoint ${dialect.quoteIdentifier(clause.name)}`
1178
+ return `rollback to savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("rollbackTo", "name", clause.name))}`
794
1179
  case "releaseSavepoint":
795
- return `release savepoint ${dialect.quoteIdentifier(clause.name)}`
1180
+ return `release savepoint ${dialect.quoteIdentifier(normalizeStatementIdentifier("releaseSavepoint", "name", clause.name))}`
796
1181
  }
797
- return ""
1182
+ return "begin"
798
1183
  }
799
1184
 
800
1185
  const renderSelectionList = (
801
1186
  selection: Record<string, unknown>,
802
1187
  state: RenderState,
803
- dialect: SqlDialect,
804
- validateAggregation: boolean
1188
+ dialect: SqlDialect
805
1189
  ): RenderedQueryAst => {
806
- if (validateAggregation) {
807
- validateAggregationSelection(selection as SelectionValue, [])
808
- }
809
1190
  const flattened = flattenSelection(selection)
810
1191
  const projections = selectionProjections(selection)
811
1192
  const sql = flattened.map(({ expression, alias }) =>
@@ -816,18 +1197,29 @@ const renderSelectionList = (
816
1197
  }
817
1198
  }
818
1199
 
1200
+ const nestedRenderState = (state: RenderState): RenderState => ({
1201
+ params: state.params,
1202
+ valueMappings: state.valueMappings,
1203
+ casing: state.casing,
1204
+ ctes: [],
1205
+ cteNames: new Set(state.cteNames),
1206
+ cteSources: new Map(state.cteSources),
1207
+ sourceNames: new Map(state.sourceNames)
1208
+ })
1209
+
819
1210
  export const renderQueryAst = (
820
1211
  ast: QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
821
1212
  state: RenderState,
822
- dialect: SqlDialect
1213
+ dialect: SqlDialect,
1214
+ options: { readonly emitCtes?: boolean } = {}
823
1215
  ): RenderedQueryAst => {
1216
+ registerQuerySources(ast, state)
824
1217
  let sql = ""
825
1218
  let projections: readonly Projection[] = []
826
1219
 
827
1220
  switch (ast.kind) {
828
1221
  case "select": {
829
- validateAggregationSelection(ast.select as SelectionValue, ast.groupBy)
830
- const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect, false)
1222
+ const rendered = renderSelectionList(ast.select as Record<string, unknown>, state, dialect)
831
1223
  projections = rendered.projections
832
1224
  const clauses = [
833
1225
  ast.distinctOn && ast.distinctOn.length > 0
@@ -864,9 +1256,7 @@ export const renderQueryAst = (
864
1256
  clauses.push(`offset ${renderExpression(ast.offset, state, dialect)}`)
865
1257
  }
866
1258
  if (ast.lock) {
867
- clauses.push(
868
- `${ast.lock.mode === "update" ? "for update" : "for share"}${ast.lock.nowait ? " nowait" : ""}${ast.lock.skipLocked ? " skip locked" : ""}`
869
- )
1259
+ throw new Error("Unsupported sqlite row locking")
870
1260
  }
871
1261
  sql = clauses.join(" ")
872
1262
  break
@@ -884,8 +1274,11 @@ export const renderQueryAst = (
884
1274
  )
885
1275
  projections = selectionProjections(setAst.select as Record<string, unknown>)
886
1276
  sql = [
887
- `(${base.sql})`,
1277
+ base.sql,
888
1278
  ...(setAst.setOperations ?? []).map((entry) => {
1279
+ if (dialect.name === "sqlite" && entry.all && entry.kind !== "union") {
1280
+ throw new Error("Unsupported sqlite set operator all variant")
1281
+ }
889
1282
  const rendered = renderQueryAst(
890
1283
  Query.getAst(entry.query as Query.Plan.Any) as QueryAst.Ast<
891
1284
  Record<string, unknown>,
@@ -895,7 +1288,7 @@ export const renderQueryAst = (
895
1288
  state,
896
1289
  dialect
897
1290
  )
898
- return `${entry.kind}${entry.all ? " all" : ""} (${rendered.sql})`
1291
+ return `${entry.kind}${entry.all ? " all" : ""} ${rendered.sql}`
899
1292
  })
900
1293
  ].join(" ")
901
1294
  break
@@ -904,17 +1297,20 @@ export const renderQueryAst = (
904
1297
  const insertAst = ast as QueryAst.Ast<Record<string, unknown>, any, "insert">
905
1298
  const targetSource = insertAst.into!
906
1299
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1300
+ const targetCasingState = stateWithTableCasing(state, targetSource.source)
1301
+ const insertSource = insertAst.insertSource
1302
+ const conflict = expectConflictClause(insertAst.conflict)
907
1303
  sql = `insert into ${target}`
908
- if (insertAst.insertSource?.kind === "values") {
909
- const columns = insertAst.insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
910
- const rows = insertAst.insertSource.rows.map((row) =>
911
- `(${row.values.map((entry) => renderExpression(entry.value, state, dialect)).join(", ")})`
1304
+ if (insertSource?.kind === "values") {
1305
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
1306
+ const rows = insertSource.rows.map((row) =>
1307
+ `(${row.values.map((entry) => renderExpression(entry.value, targetCasingState, dialect)).join(", ")})`
912
1308
  ).join(", ")
913
1309
  sql += ` (${columns}) values ${rows}`
914
- } else if (insertAst.insertSource?.kind === "query") {
915
- const columns = insertAst.insertSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1310
+ } else if (insertSource?.kind === "query") {
1311
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
916
1312
  const renderedQuery = renderQueryAst(
917
- Query.getAst(insertAst.insertSource.query as Query.Plan.Any) as QueryAst.Ast<
1313
+ Query.getAst(insertSource.query as Query.Plan.Any) as QueryAst.Ast<
918
1314
  Record<string, unknown>,
919
1315
  any,
920
1316
  QueryAst.QueryStatement
@@ -923,59 +1319,60 @@ export const renderQueryAst = (
923
1319
  dialect
924
1320
  )
925
1321
  sql += ` (${columns}) ${renderedQuery.sql}`
926
- } else if (insertAst.insertSource?.kind === "unnest") {
927
- const unnestSource = insertAst.insertSource
928
- const columns = unnestSource.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")
1322
+ } else if (insertSource?.kind === "unnest") {
1323
+ const columns = insertSource.columns.map((column) => quoteColumn(column, state, dialect, targetSource.tableName)).join(", ")
929
1324
  if (dialect.name === "postgres") {
930
1325
  const table = targetSource.source as Table.AnyTable
931
1326
  const fields = table[Table.TypeId].fields
932
- const rendered = unnestSource.values.map((entry) =>
1327
+ const rendered = insertSource.values.map((entry) =>
933
1328
  `cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
934
1329
  ).join(", ")
935
1330
  sql += ` (${columns}) select * from unnest(${rendered})`
936
1331
  } else {
937
- const rowCount = unnestSource.values[0]?.values.length ?? 0
1332
+ const table = targetSource.source as Table.AnyTable
1333
+ const fields = table[Table.TypeId].fields
1334
+ const encodedValues = insertSource.values.map((entry) => ({
1335
+ columnName: entry.columnName,
1336
+ values: encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect)
1337
+ }))
1338
+ const rowCount = encodedValues[0]?.values.length ?? 0
938
1339
  const rows = Array.from({ length: rowCount }, (_, index) =>
939
- `(${unnestSource.values.map((entry) =>
940
- dialect.renderLiteral(
941
- entry.values[index],
942
- state,
943
- (targetSource.source as Table.AnyTable)[Table.TypeId].fields[entry.columnName]![Expression.TypeId]
944
- )
945
- ).join(", ")})`
1340
+ `(${encodedValues.map((entry) => dialect.renderLiteral(entry.values[index], state)).join(", ")})`
946
1341
  ).join(", ")
947
1342
  sql += ` (${columns}) values ${rows}`
948
1343
  }
949
1344
  } else {
950
- const columns = (insertAst.values ?? []).map((entry) => dialect.quoteIdentifier(entry.columnName)).join(", ")
951
- const values = (insertAst.values ?? []).map((entry) => renderExpression(entry.value, state, dialect)).join(", ")
952
- 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) {
953
1349
  sql += ` (${columns}) values (${values})`
954
1350
  } else {
955
1351
  sql += " default values"
956
1352
  }
957
1353
  }
958
- if (insertAst.conflict) {
959
- const updateValues = (insertAst.conflict.values ?? []).map((entry) =>
960
- `${dialect.quoteIdentifier(entry.columnName)} = ${renderExpression(entry.value, state, dialect)}`
1354
+ if (conflict) {
1355
+ const conflictValueState = { ...targetCasingState, allowExcluded: true }
1356
+ const updateValues = (conflict.values ?? []).map((entry) =>
1357
+ `${quoteColumn(entry.columnName, state, dialect, targetSource.tableName)} = ${renderExpression(entry.value, conflictValueState, dialect)}`
961
1358
  ).join(", ")
962
- if (dialect.name === "postgres") {
963
- const targetSql = insertAst.conflict.target?.kind === "constraint"
964
- ? ` on conflict on constraint ${dialect.quoteIdentifier(insertAst.conflict.target.name)}`
965
- : insertAst.conflict.target?.kind === "columns"
966
- ? ` on conflict (${insertAst.conflict.target.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${insertAst.conflict.target.where ? ` where ${renderExpression(insertAst.conflict.target.where, state, dialect)}` : ""}`
1359
+ if (dialect.name === "postgres" || dialect.name === "sqlite") {
1360
+ const targetSql = conflict.target?.kind === "constraint"
1361
+ ? ` on conflict on constraint ${dialect.quoteIdentifier(Casing.applyCategory(targetCasingState.casing, "constraints", conflict.target.name))}`
1362
+ : conflict.target?.kind === "columns"
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)}` : ""}`
967
1364
  : " on conflict"
968
1365
  sql += targetSql
969
- sql += insertAst.conflict.action === "doNothing"
1366
+ sql += conflict.action === "doNothing"
970
1367
  ? " do nothing"
971
- : ` do update set ${updateValues}${insertAst.conflict.where ? ` where ${renderExpression(insertAst.conflict.where, state, dialect)}` : ""}`
972
- } else if (insertAst.conflict.action === "doNothing") {
1368
+ : ` do update set ${updateValues}${conflict.where ? ` where ${renderExpression(conflict.where, conflictValueState, dialect)}` : ""}`
1369
+ } else if (conflict.action === "doNothing") {
973
1370
  sql = sql.replace(/^insert/, "insert ignore")
974
1371
  } else {
975
1372
  sql += ` on duplicate key update ${updateValues}`
976
1373
  }
977
1374
  }
978
- const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect, false)
1375
+ const returning = renderSelectionList(insertAst.select as Record<string, unknown>, state, dialect)
979
1376
  projections = returning.projections
980
1377
  if (returning.sql.length > 0) {
981
1378
  sql += ` returning ${returning.sql}`
@@ -988,15 +1385,22 @@ export const renderQueryAst = (
988
1385
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
989
1386
  const targets = updateAst.targets ?? [targetSource]
990
1387
  const fromSources = updateAst.fromSources ?? []
1388
+ if (targets.length > 1) {
1389
+ throw new Error("Unsupported sqlite multi-table update")
1390
+ }
991
1391
  const assignments = updateAst.set!.map((entry) =>
992
- renderMutationAssignment(entry, state, dialect)).join(", ")
1392
+ renderMutationAssignment(entry, state, dialect, targetSource.tableName)).join(", ")
993
1393
  if (dialect.name === "mysql") {
994
- const modifiers = renderMysqlMutationLock(updateAst.lock, "update")
1394
+ const modifiers = ""
995
1395
  const extraSources = renderFromSources(fromSources, state, dialect)
996
1396
  const joinSources = updateAst.joins.map((join) =>
997
- join.kind === "cross"
998
- ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
999
- : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1397
+ join.kind === "full"
1398
+ ? (() => {
1399
+ throw new Error("Unsupported sqlite full join")
1400
+ })()
1401
+ : join.kind === "cross"
1402
+ ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
1403
+ : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1000
1404
  ).join(" ")
1001
1405
  const targetList = [
1002
1406
  ...targets.map((entry) =>
@@ -1016,7 +1420,7 @@ export const renderQueryAst = (
1016
1420
  }
1017
1421
  }
1018
1422
  const whereParts = [
1019
- ...(dialect.name === "postgres" ? renderJoinPredicatesForMutation(updateAst.joins, state, dialect) : []),
1423
+ ...(dialect.name === "postgres" || dialect.name === "sqlite" ? renderJoinPredicatesForMutation(updateAst.joins, state, dialect) : []),
1020
1424
  ...updateAst.where.map((entry: QueryAst.WhereClause) => renderExpression(entry.predicate, state, dialect))
1021
1425
  ]
1022
1426
  if (whereParts.length > 0) {
@@ -1026,9 +1430,9 @@ export const renderQueryAst = (
1026
1430
  sql += ` order by ${updateAst.orderBy.map((entry: QueryAst.OrderByClause) => `${renderExpression(entry.value, state, dialect)} ${entry.direction}`).join(", ")}`
1027
1431
  }
1028
1432
  if (dialect.name === "mysql" && updateAst.limit) {
1029
- sql += ` limit ${renderMysqlMutationLimit(updateAst.limit, state, dialect)}`
1433
+ sql += ` limit ${renderSqliteMutationLimit(updateAst.limit, state, dialect)}`
1030
1434
  }
1031
- const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect, false)
1435
+ const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect)
1032
1436
  projections = returning.projections
1033
1437
  if (returning.sql.length > 0) {
1034
1438
  sql += ` returning ${returning.sql}`
@@ -1040,22 +1444,32 @@ export const renderQueryAst = (
1040
1444
  const targetSource = deleteAst.target!
1041
1445
  const target = renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)
1042
1446
  const targets = deleteAst.targets ?? [targetSource]
1447
+ if (targets.length > 1) {
1448
+ throw new Error("Unsupported sqlite multi-table delete")
1449
+ }
1043
1450
  if (dialect.name === "mysql") {
1044
- const modifiers = renderMysqlMutationLock(deleteAst.lock, "delete")
1451
+ const modifiers = ""
1045
1452
  const hasJoinedSources = deleteAst.joins.length > 0 || targets.length > 1
1046
1453
  const targetList = renderDeleteTargets(targets, dialect)
1047
1454
  const fromSources = targets.map((entry) =>
1048
1455
  renderSourceReference(entry.source, entry.tableName, entry.baseTableName, state, dialect)
1049
1456
  ).join(", ")
1050
1457
  const joinSources = deleteAst.joins.map((join) =>
1051
- join.kind === "cross"
1052
- ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
1053
- : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1458
+ join.kind === "full"
1459
+ ? (() => {
1460
+ throw new Error("Unsupported sqlite full join")
1461
+ })()
1462
+ : join.kind === "cross"
1463
+ ? `cross join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)}`
1464
+ : `${join.kind} join ${renderSourceReference(join.source, join.tableName, join.baseTableName, state, dialect)} on ${renderExpression(join.on!, state, dialect)}`
1054
1465
  ).join(" ")
1055
1466
  sql = hasJoinedSources
1056
1467
  ? `delete${modifiers} ${targetList} from ${fromSources}${joinSources.length > 0 ? ` ${joinSources}` : ""}`
1057
1468
  : `delete${modifiers} from ${fromSources}`
1058
1469
  } else {
1470
+ if (dialect.name === "sqlite" && deleteAst.joins.length > 0) {
1471
+ throw new Error("Unsupported sqlite joined delete")
1472
+ }
1059
1473
  sql = `delete from ${target}`
1060
1474
  if (deleteAst.joins.length > 0) {
1061
1475
  sql += ` using ${renderJoinSourcesForMutation(deleteAst.joins, state, dialect)}`
@@ -1072,9 +1486,9 @@ export const renderQueryAst = (
1072
1486
  sql += ` order by ${deleteAst.orderBy.map((entry: QueryAst.OrderByClause) => `${renderExpression(entry.value, state, dialect)} ${entry.direction}`).join(", ")}`
1073
1487
  }
1074
1488
  if (dialect.name === "mysql" && deleteAst.limit) {
1075
- sql += ` limit ${renderMysqlMutationLimit(deleteAst.limit, state, dialect)}`
1489
+ sql += ` limit ${renderSqliteMutationLimit(deleteAst.limit, state, dialect)}`
1076
1490
  }
1077
- const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect, false)
1491
+ const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect)
1078
1492
  projections = returning.projections
1079
1493
  if (returning.sql.length > 0) {
1080
1494
  sql += ` returning ${returning.sql}`
@@ -1083,14 +1497,7 @@ export const renderQueryAst = (
1083
1497
  }
1084
1498
  case "truncate": {
1085
1499
  const truncateAst = ast as QueryAst.Ast<Record<string, unknown>, any, "truncate">
1086
- const targetSource = truncateAst.target!
1087
- sql = `truncate table ${renderSourceReference(targetSource.source, targetSource.tableName, targetSource.baseTableName, state, dialect)}`
1088
- if (truncateAst.truncate?.restartIdentity) {
1089
- sql += " restart identity"
1090
- }
1091
- if (truncateAst.truncate?.cascade) {
1092
- sql += " cascade"
1093
- }
1500
+ throw new Error("Unsupported sqlite truncate statement")
1094
1501
  break
1095
1502
  }
1096
1503
  case "merge": {
@@ -1135,12 +1542,14 @@ export const renderQueryAst = (
1135
1542
  }
1136
1543
  case "createTable": {
1137
1544
  const createTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createTable">
1138
- sql = renderCreateTableSql(createTableAst.target!, state, dialect, createTableAst.ddl?.kind === "createTable" && createTableAst.ddl.ifNotExists)
1545
+ const ddl = expectDdlClauseKind(createTableAst.ddl, "createTable")
1546
+ sql = renderCreateTableSql(createTableAst.target!, state, dialect, ddl.ifNotExists)
1139
1547
  break
1140
1548
  }
1141
1549
  case "dropTable": {
1142
1550
  const dropTableAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropTable">
1143
- const ifExists = dropTableAst.ddl?.kind === "dropTable" && dropTableAst.ddl.ifExists
1551
+ const ddl = expectDdlClauseKind(dropTableAst.ddl, "dropTable")
1552
+ const ifExists = normalizeStatementFlag(ddl.ifExists)
1144
1553
  sql = `drop table${ifExists ? " if exists" : ""} ${renderSourceReference(dropTableAst.target!.source, dropTableAst.target!.tableName, dropTableAst.target!.baseTableName, state, dialect)}`
1145
1554
  break
1146
1555
  }
@@ -1148,7 +1557,7 @@ export const renderQueryAst = (
1148
1557
  const createIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "createIndex">
1149
1558
  sql = renderCreateIndexSql(
1150
1559
  createIndexAst.target!,
1151
- createIndexAst.ddl as Extract<QueryAst.DdlClause, { readonly kind: "createIndex" }>,
1560
+ expectDdlClauseKind(createIndexAst.ddl, "createIndex"),
1152
1561
  state,
1153
1562
  dialect
1154
1563
  )
@@ -1158,15 +1567,21 @@ export const renderQueryAst = (
1158
1567
  const dropIndexAst = ast as QueryAst.Ast<Record<string, unknown>, any, "dropIndex">
1159
1568
  sql = renderDropIndexSql(
1160
1569
  dropIndexAst.target!,
1161
- dropIndexAst.ddl as Extract<QueryAst.DdlClause, { readonly kind: "dropIndex" }>,
1570
+ expectDdlClauseKind(dropIndexAst.ddl, "dropIndex"),
1162
1571
  state,
1163
1572
  dialect
1164
1573
  )
1165
1574
  break
1166
1575
  }
1576
+ default: {
1577
+ if (ast.transaction !== undefined) {
1578
+ sql = renderTransactionClause(ast.transaction, dialect)
1579
+ }
1580
+ break
1581
+ }
1167
1582
  }
1168
1583
 
1169
- if (state.ctes.length === 0) {
1584
+ if (state.ctes.length === 0 || options.emitCtes === false) {
1170
1585
  return {
1171
1586
  sql,
1172
1587
  projections
@@ -1194,7 +1609,7 @@ const renderSourceReference = (
1194
1609
  `${renderExpression(row[columnName]!, state, dialect)} as ${dialect.quoteIdentifier(columnName)}`
1195
1610
  ).join(", ")}`
1196
1611
  )
1197
- return `(${renderedRows.join(" union all ")}) as ${dialect.quoteIdentifier(tableName)}(${columnNames.map((columnName) => dialect.quoteIdentifier(columnName)).join(", ")})`
1612
+ return `(${renderedRows.join(" union all ")}) as ${dialect.quoteIdentifier(tableName)}`
1198
1613
  }
1199
1614
 
1200
1615
  const renderUnnestRows = (
@@ -1214,9 +1629,27 @@ const renderSourceReference = (
1214
1629
  readonly plan: Query.Plan.Any
1215
1630
  readonly recursive?: boolean
1216
1631
  }
1632
+ const registeredCteSource = state.cteSources.get(cte.name)
1633
+ if (registeredCteSource !== undefined && registeredCteSource !== cte.plan) {
1634
+ throw new Error(`common table expression name is already registered with a different plan: ${cte.name}`)
1635
+ }
1217
1636
  if (!state.cteNames.has(cte.name)) {
1218
1637
  state.cteNames.add(cte.name)
1219
- const rendered = renderQueryAst(Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect)
1638
+ state.cteSources.set(cte.name, cte.plan)
1639
+ const statement = Query.getQueryState(cte.plan).statement
1640
+ if (statement !== "select" && statement !== "set") {
1641
+ const cteAst = Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>
1642
+ if (Object.keys((cteAst.select ?? {}) as Record<string, unknown>).length > 0) {
1643
+ throw new Error("Unsupported sqlite returning")
1644
+ }
1645
+ throw new Error("Unsupported sqlite data-modifying cte")
1646
+ }
1647
+ const rendered = renderQueryAst(
1648
+ Query.getAst(cte.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1649
+ state,
1650
+ dialect,
1651
+ { emitCtes: false }
1652
+ )
1220
1653
  state.ctes.push({
1221
1654
  name: cte.name,
1222
1655
  sql: rendered.sql,
@@ -1233,14 +1666,17 @@ const renderSourceReference = (
1233
1666
  if (!state.cteNames.has(derived.name)) {
1234
1667
  // derived tables are inlined, so no CTE registration is needed
1235
1668
  }
1236
- return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
1669
+ return `(${renderQueryAst(Query.getAst(derived.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(derived.name)}`
1237
1670
  }
1238
1671
  if (typeof source === "object" && source !== null && "kind" in source && (source as { readonly kind?: string }).kind === "lateral") {
1239
1672
  const lateral = source as unknown as {
1240
1673
  readonly name: string
1241
1674
  readonly plan: Query.Plan.Any
1242
1675
  }
1243
- return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, state, dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
1676
+ if (dialect.name === "sqlite") {
1677
+ throw new Error("Unsupported sqlite lateral source")
1678
+ }
1679
+ return `lateral (${renderQueryAst(Query.getAst(lateral.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>, nestedRenderState(state), dialect).sql}) as ${dialect.quoteIdentifier(lateral.name)}`
1244
1680
  }
1245
1681
  if (typeof source === "object" && source !== null && (source as { readonly kind?: string }).kind === "values") {
1246
1682
  const values = source as unknown as {
@@ -1266,13 +1702,47 @@ const renderSourceReference = (
1266
1702
  if (dialect.name !== "postgres") {
1267
1703
  throw new Error("Unsupported table function source for SQL rendering")
1268
1704
  }
1705
+ const functionName = renderFunctionName(tableFunction.functionName)
1269
1706
  const columnNames = Object.keys(tableFunction.columns)
1270
- 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(", ")})`
1271
1708
  }
1272
1709
  const schemaName = typeof source === "object" && source !== null && Table.TypeId in source
1273
1710
  ? (source as Table.AnyTable)[Table.TypeId].schemaName
1274
1711
  : undefined
1275
- 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
+ )
1730
+ }
1731
+
1732
+ const renderSubqueryExpressionPlan = (
1733
+ plan: Query.Plan.Any,
1734
+ state: RenderState,
1735
+ dialect: SqlDialect
1736
+ ): string => {
1737
+ const statement = Query.getQueryState(plan).statement
1738
+ if (statement !== "select" && statement !== "set") {
1739
+ throw new Error("subquery expressions only accept select-like query plans")
1740
+ }
1741
+ return renderQueryAst(
1742
+ Query.getAst(plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1743
+ state,
1744
+ dialect
1745
+ ).sql
1276
1746
  }
1277
1747
 
1278
1748
  /**
@@ -1295,133 +1765,145 @@ export const renderExpression = (
1295
1765
  return jsonSql
1296
1766
  }
1297
1767
  const ast = rawAst as ExpressionAst.Any
1298
- const renderComparisonOperator = (operator: "eq" | "neq" | "lt" | "lte" | "gt" | "gte"): "=" | "<>" | "<" | "<=" | ">" | ">=" =>
1299
- operator === "eq"
1300
- ? "="
1301
- : operator === "neq"
1302
- ? "<>"
1303
- : operator === "lt"
1304
- ? "<"
1305
- : operator === "lte"
1306
- ? "<="
1307
- : operator === "gt"
1308
- ? ">"
1309
- : ">="
1310
- 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) {
1311
1778
  case "column":
1312
- return ast.tableName.length === 0
1313
- ? dialect.quoteIdentifier(ast.columnName)
1314
- : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1779
+ return state.rowLocalColumns || ast.tableName.length === 0
1780
+ ? quoteColumn(ast.columnName, state, dialect, ast.tableName)
1781
+ : `${dialect.quoteIdentifier(casedTableReferenceName(ast.tableName, state))}.${quoteColumn(ast.columnName, state, dialect, ast.tableName)}`
1315
1782
  case "literal":
1783
+ if (typeof ast.value === "number" && !Number.isFinite(ast.value)) {
1784
+ throw new Error("Expected a finite numeric value")
1785
+ }
1316
1786
  return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1317
1787
  case "excluded":
1318
- return dialect.name === "mysql"
1319
- ? `values(${dialect.quoteIdentifier(ast.columnName)})`
1320
- : `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)}`
1321
1792
  case "cast":
1322
- 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)})`
1323
1794
  case "function":
1324
- return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
1795
+ return renderFunctionCall(ast.name, ast.args, state, dialect)
1325
1796
  case "eq":
1326
- return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
1797
+ return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
1327
1798
  case "neq":
1328
- return `(${renderExpression(ast.left, state, dialect)} <> ${renderExpression(ast.right, state, dialect)})`
1799
+ return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
1329
1800
  case "lt":
1330
- return `(${renderExpression(ast.left, state, dialect)} < ${renderExpression(ast.right, state, dialect)})`
1801
+ return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
1331
1802
  case "lte":
1332
- return `(${renderExpression(ast.left, state, dialect)} <= ${renderExpression(ast.right, state, dialect)})`
1803
+ return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
1333
1804
  case "gt":
1334
- return `(${renderExpression(ast.left, state, dialect)} > ${renderExpression(ast.right, state, dialect)})`
1805
+ return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
1335
1806
  case "gte":
1336
- return `(${renderExpression(ast.left, state, dialect)} >= ${renderExpression(ast.right, state, dialect)})`
1807
+ return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
1337
1808
  case "like":
1338
- return `(${renderExpression(ast.left, state, dialect)} like ${renderExpression(ast.right, state, dialect)})`
1339
- 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)
1340
1812
  return dialect.name === "postgres"
1341
- ? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
1342
- : `(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
+ }
1343
1816
  case "regexMatch":
1817
+ expectBinaryExpressions("regexMatch", ast.left, ast.right)
1818
+ if (dialect.name === "sqlite") {
1819
+ throw new Error("Unsupported sqlite regex operator")
1820
+ }
1344
1821
  return dialect.name === "postgres"
1345
1822
  ? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
1346
1823
  : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1347
1824
  case "regexIMatch":
1825
+ expectBinaryExpressions("regexIMatch", ast.left, ast.right)
1826
+ if (dialect.name === "sqlite") {
1827
+ throw new Error("Unsupported sqlite regex operator")
1828
+ }
1348
1829
  return dialect.name === "postgres"
1349
1830
  ? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
1350
1831
  : `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
1351
1832
  case "regexNotMatch":
1833
+ expectBinaryExpressions("regexNotMatch", ast.left, ast.right)
1834
+ if (dialect.name === "sqlite") {
1835
+ throw new Error("Unsupported sqlite regex operator")
1836
+ }
1352
1837
  return dialect.name === "postgres"
1353
1838
  ? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
1354
1839
  : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1355
1840
  case "regexNotIMatch":
1841
+ expectBinaryExpressions("regexNotIMatch", ast.left, ast.right)
1842
+ if (dialect.name === "sqlite") {
1843
+ throw new Error("Unsupported sqlite regex operator")
1844
+ }
1356
1845
  return dialect.name === "postgres"
1357
1846
  ? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
1358
1847
  : `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
1359
1848
  case "isDistinctFrom":
1360
- return dialect.name === "mysql"
1361
- ? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
1362
- : `(${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)
1363
1850
  case "isNotDistinctFrom":
1364
- return dialect.name === "mysql"
1365
- ? `(${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)})`
1366
- : `(${renderExpression(ast.left, state, dialect)} is not distinct from ${renderExpression(ast.right, state, dialect)})`
1367
- 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)
1368
1854
  if (dialect.name === "postgres") {
1369
- const left = isJsonExpression(ast.left)
1370
- ? renderPostgresJsonValue(ast.left, state, dialect)
1371
- : renderExpression(ast.left, state, dialect)
1372
- const right = isJsonExpression(ast.right)
1373
- ? renderPostgresJsonValue(ast.right, state, dialect)
1374
- : 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)
1375
1861
  return `(${left} @> ${right})`
1376
1862
  }
1377
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1378
- return `json_contains(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1379
- }
1380
1863
  throw new Error("Unsupported container operator for SQL rendering")
1381
- case "containedBy":
1864
+ }
1865
+ case "containedBy": {
1866
+ const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
1382
1867
  if (dialect.name === "postgres") {
1383
- const left = isJsonExpression(ast.left)
1384
- ? renderPostgresJsonValue(ast.left, state, dialect)
1385
- : renderExpression(ast.left, state, dialect)
1386
- const right = isJsonExpression(ast.right)
1387
- ? renderPostgresJsonValue(ast.right, state, dialect)
1388
- : 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)
1389
1874
  return `(${left} <@ ${right})`
1390
1875
  }
1391
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1392
- return `json_contains(${renderExpression(ast.right, state, dialect)}, ${renderExpression(ast.left, state, dialect)})`
1393
- }
1394
1876
  throw new Error("Unsupported container operator for SQL rendering")
1395
- case "overlaps":
1877
+ }
1878
+ case "overlaps": {
1879
+ const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
1396
1880
  if (dialect.name === "postgres") {
1397
- const left = isJsonExpression(ast.left)
1398
- ? renderPostgresJsonValue(ast.left, state, dialect)
1399
- : renderExpression(ast.left, state, dialect)
1400
- const right = isJsonExpression(ast.right)
1401
- ? renderPostgresJsonValue(ast.right, state, dialect)
1402
- : 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)
1403
1887
  return `(${left} && ${right})`
1404
1888
  }
1405
- if (dialect.name === "mysql" && isJsonExpression(ast.left) && isJsonExpression(ast.right)) {
1406
- return `json_overlaps(${renderExpression(ast.left, state, dialect)}, ${renderExpression(ast.right, state, dialect)})`
1407
- }
1408
1889
  throw new Error("Unsupported container operator for SQL rendering")
1890
+ }
1409
1891
  case "isNull":
1410
- return `(${renderExpression(ast.value, state, dialect)} is null)`
1892
+ return `(${renderExpression(expectValueExpression("isNull", ast.value), state, dialect)} is null)`
1411
1893
  case "isNotNull":
1412
- return `(${renderExpression(ast.value, state, dialect)} is not null)`
1894
+ return `(${renderExpression(expectValueExpression("isNotNull", ast.value), state, dialect)} is not null)`
1413
1895
  case "not":
1414
- return `(not ${renderExpression(ast.value, state, dialect)})`
1896
+ return `(not ${renderExpression(expectValueExpression("not", ast.value), state, dialect)})`
1415
1897
  case "upper":
1416
- return `upper(${renderExpression(ast.value, state, dialect)})`
1898
+ return `upper(${renderExpression(expectValueExpression("upper", ast.value), state, dialect)})`
1417
1899
  case "lower":
1418
- return `lower(${renderExpression(ast.value, state, dialect)})`
1900
+ return `lower(${renderExpression(expectValueExpression("lower", ast.value), state, dialect)})`
1419
1901
  case "count":
1420
- return `count(${renderExpression(ast.value, state, dialect)})`
1902
+ return `count(${renderExpression(expectValueExpression("count", ast.value), state, dialect)})`
1421
1903
  case "max":
1422
- return `max(${renderExpression(ast.value, state, dialect)})`
1904
+ return `max(${renderExpression(expectValueExpression("max", ast.value), state, dialect)})`
1423
1905
  case "min":
1424
- return `min(${renderExpression(ast.value, state, dialect)})`
1906
+ return `min(${renderExpression(expectValueExpression("min", ast.value), state, dialect)})`
1425
1907
  case "and":
1426
1908
  return `(${ast.values.map((value: Expression.Any) => renderExpression(value, state, dialect)).join(" and ")})`
1427
1909
  case "or":
@@ -1441,45 +1923,39 @@ export const renderExpression = (
1441
1923
  `when ${renderExpression(branch.when, state, dialect)} then ${renderExpression(branch.then, state, dialect)}`
1442
1924
  ).join(" ")} else ${renderExpression(ast.else, state, dialect)} end`
1443
1925
  case "exists":
1444
- return `exists (${renderQueryAst(
1445
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1446
- state,
1447
- dialect
1448
- ).sql})`
1926
+ return `exists (${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1449
1927
  case "scalarSubquery":
1450
- return `(${renderQueryAst(
1451
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1452
- state,
1453
- dialect
1454
- ).sql})`
1928
+ return `(${renderSubqueryExpressionPlan(ast.plan, state, dialect)})`
1455
1929
  case "inSubquery":
1456
- return `(${renderExpression(ast.left, state, dialect)} in (${renderQueryAst(
1457
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1458
- state,
1459
- dialect
1460
- ).sql}))`
1461
- case "comparisonAny":
1462
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} any (${renderQueryAst(
1463
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1464
- state,
1465
- dialect
1466
- ).sql}))`
1467
- case "comparisonAll":
1468
- return `(${renderExpression(ast.left, state, dialect)} ${renderComparisonOperator(ast.operator)} all (${renderQueryAst(
1469
- Query.getAst(ast.plan) as QueryAst.Ast<Record<string, unknown>, any, QueryAst.QueryStatement>,
1470
- state,
1471
- dialect
1472
- ).sql}))`
1473
- case "window": {
1474
- if (!Array.isArray(ast.partitionBy) || !Array.isArray(ast.orderBy) || typeof ast.function !== "string") {
1475
- break
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)
1934
+ if (dialect.name === "sqlite") {
1935
+ throw new Error("Unsupported sqlite quantified comparison")
1936
+ }
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)
1942
+ if (dialect.name === "sqlite") {
1943
+ throw new Error("Unsupported sqlite quantified comparison")
1476
1944
  }
1945
+ return `(${renderExpression(left, state, dialect)} ${operator} all (${renderSubqueryExpressionPlan(ast.plan, state, dialect)}))`
1946
+ }
1947
+ case "window": {
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
+ }[]
1477
1953
  const clauses: string[] = []
1478
- if (ast.partitionBy.length > 0) {
1479
- 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(", ")}`)
1480
1956
  }
1481
- if (ast.orderBy.length > 0) {
1482
- clauses.push(`order by ${ast.orderBy.map((entry) =>
1957
+ if (orderBy.length > 0) {
1958
+ clauses.push(`order by ${orderBy.map((entry) =>
1483
1959
  `${renderExpression(entry.value, state, dialect)} ${entry.direction}`
1484
1960
  ).join(", ")}`)
1485
1961
  }
@@ -1492,7 +1968,7 @@ export const renderExpression = (
1492
1968
  case "denseRank":
1493
1969
  return `dense_rank() over (${specification})`
1494
1970
  case "over":
1495
- return `${renderExpression(ast.value!, state, dialect)} over (${specification})`
1971
+ return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
1496
1972
  }
1497
1973
  break
1498
1974
  }