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.
- package/README.md +4 -0
- package/dist/index.js +8065 -0
- package/dist/mysql.js +4036 -2418
- package/dist/postgres/metadata.js +2536 -625
- package/dist/postgres.js +8248 -7857
- package/dist/sqlite.js +8854 -0
- package/dist/standard.js +8019 -0
- package/package.json +15 -3
- package/src/casing.ts +71 -0
- package/src/index.ts +2 -0
- package/src/internal/casing.ts +89 -0
- package/src/internal/column-state.ts +11 -6
- package/src/internal/column.ts +44 -7
- package/src/internal/datatypes/define.ts +2 -1
- package/src/internal/datatypes/enrich.ts +23 -0
- package/src/internal/datatypes/lookup.ts +14 -7
- package/src/internal/derived-table.ts +7 -13
- package/src/internal/dialect-renderers/mysql.ts +2046 -0
- package/src/{postgres/internal/sql-expression-renderer.ts → internal/dialect-renderers/postgres.ts} +867 -283
- package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts} +834 -358
- package/src/internal/dialect.ts +37 -0
- package/src/internal/dsl-mutation-runtime.ts +29 -10
- package/src/internal/dsl-plan-runtime.ts +41 -24
- package/src/internal/dsl-query-runtime.ts +11 -31
- package/src/internal/dsl-transaction-ddl-runtime.ts +61 -15
- package/src/internal/executor.ts +57 -15
- package/src/internal/expression-ast.ts +3 -2
- package/src/internal/grouping-key.ts +216 -9
- package/src/internal/implication-runtime.ts +3 -2
- package/src/internal/json/types.ts +155 -40
- package/src/internal/predicate/context.ts +14 -1
- package/src/internal/predicate/key.ts +19 -2
- package/src/internal/predicate/runtime.ts +30 -3
- package/src/internal/query.d.ts +38 -11
- package/src/internal/query.ts +315 -54
- package/src/internal/renderer.ts +51 -6
- package/src/internal/runtime/driver-value-mapping.ts +58 -0
- package/src/internal/runtime/normalize.ts +74 -43
- package/src/internal/runtime/schema.ts +5 -3
- package/src/internal/runtime/value.ts +153 -30
- package/src/internal/scalar.ts +6 -1
- package/src/internal/schema-derivation.d.ts +12 -61
- package/src/internal/schema-derivation.ts +90 -38
- package/src/internal/schema-expression.ts +2 -2
- package/src/internal/sql-expression-renderer.ts +19 -0
- package/src/internal/standard-dsl.ts +6885 -0
- package/src/internal/table-options.ts +229 -62
- package/src/internal/table.d.ts +33 -32
- package/src/internal/table.ts +469 -160
- package/src/mysql/column-extension.ts +3 -0
- package/src/mysql/column.ts +27 -12
- package/src/mysql/datatypes/index.ts +24 -2
- package/src/mysql/errors/catalog.ts +5 -5
- package/src/mysql/errors/normalize.ts +2 -2
- package/src/mysql/executor.ts +7 -5
- package/src/mysql/internal/dialect.ts +9 -4
- package/src/mysql/internal/dsl.ts +906 -324
- package/src/mysql/internal/renderer.ts +7 -2
- package/src/mysql/json.ts +37 -0
- package/src/mysql/query-extension.ts +16 -0
- package/src/mysql/query.ts +9 -2
- package/src/mysql/renderer.ts +31 -4
- package/src/mysql.ts +4 -12
- package/src/postgres/column-extension.ts +28 -0
- package/src/postgres/column.ts +9 -13
- package/src/postgres/datatypes/index.d.ts +2 -1
- package/src/postgres/datatypes/index.ts +3 -2
- package/src/postgres/errors/normalize.ts +2 -2
- package/src/postgres/executor.ts +55 -10
- package/src/postgres/function/core.ts +20 -4
- package/src/postgres/function/index.ts +1 -17
- package/src/postgres/internal/dialect.ts +9 -4
- package/src/postgres/internal/dsl.ts +850 -359
- package/src/postgres/internal/renderer.ts +7 -2
- package/src/postgres/internal/schema-ddl.ts +22 -9
- package/src/postgres/internal/schema-model.ts +244 -10
- package/src/postgres/json.ts +100 -24
- package/src/postgres/jsonb.ts +38 -0
- package/src/postgres/query-extension.ts +2 -0
- package/src/postgres/query.ts +9 -2
- package/src/postgres/renderer.ts +31 -4
- package/src/postgres/schema-management.ts +108 -16
- package/src/postgres/schema.ts +98 -15
- package/src/postgres/table.ts +203 -398
- package/src/postgres/type.ts +8 -7
- package/src/postgres.ts +9 -11
- package/src/sqlite/column-extension.ts +3 -0
- package/src/sqlite/column.ts +127 -0
- package/src/sqlite/datatypes/index.ts +80 -0
- package/src/sqlite/datatypes/spec.ts +98 -0
- package/src/sqlite/errors/catalog.ts +103 -0
- package/src/sqlite/errors/fields.ts +19 -0
- package/src/sqlite/errors/index.ts +19 -0
- package/src/sqlite/errors/normalize.ts +229 -0
- package/src/sqlite/errors/requirements.ts +71 -0
- package/src/sqlite/errors/types.ts +29 -0
- package/src/sqlite/executor.ts +229 -0
- package/src/sqlite/function/aggregate.ts +2 -0
- package/src/sqlite/function/core.ts +2 -0
- package/src/sqlite/function/index.ts +19 -0
- package/src/sqlite/function/string.ts +2 -0
- package/src/sqlite/function/temporal.ts +100 -0
- package/src/sqlite/function/window.ts +2 -0
- package/src/sqlite/internal/dialect.ts +42 -0
- package/src/sqlite/internal/dsl.ts +6979 -0
- package/src/sqlite/internal/renderer.ts +51 -0
- package/src/sqlite/json.ts +39 -0
- package/src/sqlite/query-extension.ts +2 -0
- package/src/sqlite/query.ts +196 -0
- package/src/sqlite/renderer.ts +51 -0
- package/src/sqlite.ts +14 -0
- package/src/standard/column.ts +163 -0
- package/src/standard/datatypes/index.ts +83 -0
- package/src/standard/datatypes/spec.ts +98 -0
- package/src/standard/dialect.ts +40 -0
- package/src/standard/function/aggregate.ts +2 -0
- package/src/standard/function/core.ts +2 -0
- package/src/standard/function/index.ts +18 -0
- package/src/standard/function/string.ts +2 -0
- package/src/standard/function/temporal.ts +78 -0
- package/src/standard/function/window.ts +2 -0
- package/src/standard/internal/renderer.ts +45 -0
- package/src/standard/query.ts +152 -0
- package/src/standard/renderer.ts +21 -0
- package/src/standard/table.ts +147 -0
- package/src/standard.ts +18 -0
- package/src/internal/aggregation-validation.ts +0 -57
- package/src/mysql/table.ts +0 -157
package/src/{mysql/internal/sql-expression-renderer.ts → internal/dialect-renderers/sqlite.ts}
RENAMED
|
@@ -1,44 +1,57 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import
|
|
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 "
|
|
13
|
-
import {
|
|
14
|
-
import { type
|
|
15
|
-
import * as SchemaExpression from "
|
|
16
|
-
import type
|
|
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 === "
|
|
23
|
-
return "
|
|
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:
|
|
38
|
+
dbType: unknown
|
|
31
39
|
): string => {
|
|
32
|
-
|
|
33
|
-
|
|
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 (
|
|
44
|
+
switch (kind) {
|
|
36
45
|
case "text":
|
|
37
|
-
return "
|
|
46
|
+
return "text"
|
|
38
47
|
case "uuid":
|
|
39
|
-
return "
|
|
48
|
+
return "text"
|
|
40
49
|
case "numeric":
|
|
41
|
-
return "
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
|
|
85
|
-
column.metadata.ddlType
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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${
|
|
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
|
|
145
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 +=
|
|
588
|
+
path += renderSegment(segment)
|
|
254
589
|
}
|
|
255
590
|
return path
|
|
256
591
|
}
|
|
257
592
|
|
|
258
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
381
|
-
args:
|
|
784
|
+
name: unknown,
|
|
785
|
+
args: unknown,
|
|
382
786
|
state: RenderState,
|
|
383
787
|
dialect: SqlDialect
|
|
384
788
|
): string => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
809
|
+
return "datetime('now', 'localtime')"
|
|
810
|
+
case "now":
|
|
811
|
+
return "current_timestamp"
|
|
412
812
|
default:
|
|
413
|
-
return `${
|
|
813
|
+
return `${functionName}()`
|
|
414
814
|
}
|
|
415
815
|
}
|
|
416
|
-
return `${
|
|
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 === "
|
|
465
|
-
const extracted = `json_extract(${baseSql}, ${
|
|
466
|
-
return
|
|
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[${
|
|
890
|
+
return `(${baseSql} ?| array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
487
891
|
}
|
|
488
892
|
if (kind === "jsonHasAllKeys") {
|
|
489
|
-
return `(${baseSql} ?& array[${
|
|
893
|
+
return `(${baseSql} ?& array[${keyNames.map((key) => renderPostgresTextLiteral(key, state, dialect)).join(", ")}])`
|
|
490
894
|
}
|
|
491
|
-
return `(${baseSql} ? ${renderPostgresTextLiteral(
|
|
895
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(keyNames[0]!, state, dialect)})`
|
|
492
896
|
}
|
|
493
|
-
if (dialect.name === "
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
return `
|
|
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 === "
|
|
509
|
-
return `
|
|
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 =
|
|
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 === "
|
|
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 =
|
|
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 === "
|
|
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 === "
|
|
550
|
-
return `
|
|
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 === "
|
|
561
|
-
return `
|
|
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 === "
|
|
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 === "
|
|
588
|
-
|
|
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 === "
|
|
602
|
-
|
|
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 === "
|
|
631
|
-
return `json_remove(${renderExpression(base, state, dialect)}, ${segments
|
|
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 === "
|
|
657
|
-
|
|
658
|
-
|
|
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 === "
|
|
674
|
-
return `
|
|
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 === "
|
|
713
|
-
? `${dialect.quoteIdentifier(entry.tableName)}.${
|
|
714
|
-
:
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
modes.push(`isolation level ${clause.isolationLevel}`)
|
|
1163
|
+
if (clause.readOnly !== undefined) {
|
|
1164
|
+
normalizeStatementFlag(clause.readOnly)
|
|
778
1165
|
}
|
|
779
|
-
if (clause.readOnly
|
|
780
|
-
|
|
1166
|
+
if (clause.isolationLevel !== undefined || clause.readOnly !== undefined) {
|
|
1167
|
+
throw new Error("Unsupported sqlite transaction options")
|
|
781
1168
|
}
|
|
782
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" : ""}
|
|
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 (
|
|
909
|
-
const columns =
|
|
910
|
-
const rows =
|
|
911
|
-
`(${row.values.map((entry) => renderExpression(entry.value,
|
|
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 (
|
|
915
|
-
const columns =
|
|
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(
|
|
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 (
|
|
927
|
-
const
|
|
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 =
|
|
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
|
|
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
|
-
`(${
|
|
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
|
|
951
|
-
const
|
|
952
|
-
|
|
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 (
|
|
959
|
-
const
|
|
960
|
-
|
|
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 =
|
|
964
|
-
? ` on conflict on constraint ${dialect.quoteIdentifier(
|
|
965
|
-
:
|
|
966
|
-
? ` on conflict (${
|
|
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 +=
|
|
1366
|
+
sql += conflict.action === "doNothing"
|
|
970
1367
|
? " do nothing"
|
|
971
|
-
: ` do update set ${updateValues}${
|
|
972
|
-
} else if (
|
|
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
|
|
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 =
|
|
1394
|
+
const modifiers = ""
|
|
995
1395
|
const extraSources = renderFromSources(fromSources, state, dialect)
|
|
996
1396
|
const joinSources = updateAst.joins.map((join) =>
|
|
997
|
-
join.kind === "
|
|
998
|
-
?
|
|
999
|
-
|
|
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 ${
|
|
1433
|
+
sql += ` limit ${renderSqliteMutationLimit(updateAst.limit, state, dialect)}`
|
|
1030
1434
|
}
|
|
1031
|
-
const returning = renderSelectionList(updateAst.select as Record<string, unknown>, state, dialect
|
|
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 =
|
|
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 === "
|
|
1052
|
-
?
|
|
1053
|
-
|
|
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 ${
|
|
1489
|
+
sql += ` limit ${renderSqliteMutationLimit(deleteAst.limit, state, dialect)}`
|
|
1076
1490
|
}
|
|
1077
|
-
const returning = renderSelectionList(deleteAst.select as Record<string, unknown>, state, dialect
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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)}
|
|
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
|
-
|
|
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
|
-
|
|
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 `${
|
|
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
|
-
|
|
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:
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
:
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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
|
-
?
|
|
1314
|
-
: `${dialect.quoteIdentifier(ast.tableName)}.${
|
|
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
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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,
|
|
1795
|
+
return renderFunctionCall(ast.name, ast.args, state, dialect)
|
|
1325
1796
|
case "eq":
|
|
1326
|
-
return
|
|
1797
|
+
return renderBinaryExpression("eq", "=", ast.left, ast.right, state, dialect)
|
|
1327
1798
|
case "neq":
|
|
1328
|
-
return
|
|
1799
|
+
return renderBinaryExpression("neq", "<>", ast.left, ast.right, state, dialect)
|
|
1329
1800
|
case "lt":
|
|
1330
|
-
return
|
|
1801
|
+
return renderBinaryExpression("lt", "<", ast.left, ast.right, state, dialect)
|
|
1331
1802
|
case "lte":
|
|
1332
|
-
return
|
|
1803
|
+
return renderBinaryExpression("lte", "<=", ast.left, ast.right, state, dialect)
|
|
1333
1804
|
case "gt":
|
|
1334
|
-
return
|
|
1805
|
+
return renderBinaryExpression("gt", ">", ast.left, ast.right, state, dialect)
|
|
1335
1806
|
case "gte":
|
|
1336
|
-
return
|
|
1807
|
+
return renderBinaryExpression("gte", ">=", ast.left, ast.right, state, dialect)
|
|
1337
1808
|
case "like":
|
|
1338
|
-
return
|
|
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(
|
|
1342
|
-
: `(lower(${renderExpression(
|
|
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
|
|
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
|
|
1365
|
-
|
|
1366
|
-
|
|
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(
|
|
1370
|
-
? renderPostgresJsonValue(
|
|
1371
|
-
: renderExpression(
|
|
1372
|
-
const right = isJsonExpression(
|
|
1373
|
-
? renderPostgresJsonValue(
|
|
1374
|
-
: renderExpression(
|
|
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
|
-
|
|
1864
|
+
}
|
|
1865
|
+
case "containedBy": {
|
|
1866
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("containedBy", ast.left, ast.right)
|
|
1382
1867
|
if (dialect.name === "postgres") {
|
|
1383
|
-
const left = isJsonExpression(
|
|
1384
|
-
? renderPostgresJsonValue(
|
|
1385
|
-
: renderExpression(
|
|
1386
|
-
const right = isJsonExpression(
|
|
1387
|
-
? renderPostgresJsonValue(
|
|
1388
|
-
: renderExpression(
|
|
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
|
-
|
|
1877
|
+
}
|
|
1878
|
+
case "overlaps": {
|
|
1879
|
+
const [leftExpression, rightExpression] = expectBinaryExpressions("overlaps", ast.left, ast.right)
|
|
1396
1880
|
if (dialect.name === "postgres") {
|
|
1397
|
-
const left = isJsonExpression(
|
|
1398
|
-
? renderPostgresJsonValue(
|
|
1399
|
-
: renderExpression(
|
|
1400
|
-
const right = isJsonExpression(
|
|
1401
|
-
? renderPostgresJsonValue(
|
|
1402
|
-
: renderExpression(
|
|
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 (${
|
|
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 `(${
|
|
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 (${
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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 (
|
|
1479
|
-
clauses.push(`partition by ${
|
|
1954
|
+
if (partitionBy.length > 0) {
|
|
1955
|
+
clauses.push(`partition by ${partitionBy.map((value) => renderExpression(value, state, dialect)).join(", ")}`)
|
|
1480
1956
|
}
|
|
1481
|
-
if (
|
|
1482
|
-
clauses.push(`order by ${
|
|
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
|
|
1971
|
+
return `${renderExpression(ast.value as Expression.Any, state, dialect)} over (${specification})`
|
|
1496
1972
|
}
|
|
1497
1973
|
break
|
|
1498
1974
|
}
|