effect-qb 0.13.0 → 0.14.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 +6 -1431
- package/dist/mysql.js +1678 -355
- package/dist/postgres/metadata.js +2724 -0
- package/dist/postgres.js +7197 -5433
- package/package.json +8 -10
- package/src/internal/column-state.ts +84 -10
- package/src/internal/column.ts +556 -34
- package/src/internal/datatypes/define.ts +0 -30
- package/src/internal/executor.ts +45 -11
- package/src/internal/expression-ast.ts +4 -0
- package/src/internal/expression.ts +1 -1
- package/src/internal/implication-runtime.ts +171 -0
- package/src/internal/mysql-query.ts +7173 -0
- package/src/internal/mysql-renderer.ts +2 -2
- package/src/internal/plan.ts +14 -4
- package/src/internal/{query-factory.ts → postgres-query.ts} +619 -167
- package/src/internal/postgres-renderer.ts +2 -2
- package/src/internal/postgres-schema-model.ts +144 -0
- package/src/internal/predicate-analysis.ts +10 -0
- package/src/internal/predicate-context.ts +112 -36
- package/src/internal/predicate-formula.ts +31 -19
- package/src/internal/predicate-normalize.ts +177 -106
- package/src/internal/predicate-runtime.ts +676 -0
- package/src/internal/query.ts +455 -39
- package/src/internal/renderer.ts +2 -2
- package/src/internal/runtime-schema.ts +74 -20
- package/src/internal/schema-ddl.ts +55 -0
- package/src/internal/schema-derivation.ts +93 -21
- package/src/internal/schema-expression.ts +44 -0
- package/src/internal/sql-expression-renderer.ts +95 -31
- package/src/internal/table-options.ts +87 -7
- package/src/internal/table.ts +104 -41
- package/src/mysql/column.ts +1 -0
- package/src/mysql/datatypes/index.ts +17 -2
- package/src/mysql/function/core.ts +1 -0
- package/src/mysql/function/index.ts +1 -0
- package/src/mysql/private/query.ts +1 -13
- package/src/mysql/query.ts +5 -0
- package/src/postgres/cast.ts +31 -0
- package/src/postgres/column.ts +26 -0
- package/src/postgres/datatypes/index.ts +40 -5
- package/src/postgres/function/core.ts +12 -0
- package/src/postgres/function/index.ts +2 -1
- package/src/postgres/function/json.ts +499 -2
- package/src/postgres/metadata.ts +31 -0
- package/src/postgres/private/query.ts +1 -13
- package/src/postgres/query.ts +5 -2
- package/src/postgres/schema-expression.ts +16 -0
- package/src/postgres/schema-management.ts +204 -0
- package/src/postgres/schema.ts +35 -0
- package/src/postgres/table.ts +307 -41
- package/src/postgres/type.ts +4 -0
- package/src/postgres.ts +14 -0
- package/CHANGELOG.md +0 -134
|
@@ -7,6 +7,8 @@ import * as ExpressionAst from "./expression-ast.js"
|
|
|
7
7
|
import * as JsonPath from "./json/path.js"
|
|
8
8
|
import { flattenSelection, type Projection } from "./projections.js"
|
|
9
9
|
import { type SelectionValue, validateAggregationSelection } from "./aggregation-validation.js"
|
|
10
|
+
import * as SchemaExpression from "./schema-expression.js"
|
|
11
|
+
import type { DdlExpressionLike } from "./table-options.js"
|
|
10
12
|
|
|
11
13
|
const renderDbType = (
|
|
12
14
|
dialect: SqlDialect,
|
|
@@ -44,6 +46,15 @@ const renderCastType = (
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
const renderDdlExpression = (
|
|
50
|
+
expression: DdlExpressionLike,
|
|
51
|
+
state: RenderState,
|
|
52
|
+
dialect: SqlDialect
|
|
53
|
+
): string =>
|
|
54
|
+
SchemaExpression.isSchemaExpression(expression)
|
|
55
|
+
? SchemaExpression.render(expression)
|
|
56
|
+
: renderExpression(expression, state, dialect)
|
|
57
|
+
|
|
47
58
|
const renderColumnDefinition = (
|
|
48
59
|
dialect: SqlDialect,
|
|
49
60
|
state: RenderState,
|
|
@@ -52,12 +63,14 @@ const renderColumnDefinition = (
|
|
|
52
63
|
): string => {
|
|
53
64
|
const clauses = [
|
|
54
65
|
dialect.quoteIdentifier(columnName),
|
|
55
|
-
renderDbType(dialect, column.metadata.dbType)
|
|
66
|
+
column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
|
|
56
67
|
]
|
|
57
|
-
if (column.metadata.
|
|
58
|
-
clauses.push(`generated
|
|
68
|
+
if (column.metadata.identity) {
|
|
69
|
+
clauses.push(`generated ${column.metadata.identity.generation === "byDefault" ? "by default" : "always"} as identity`)
|
|
70
|
+
} else if (column.metadata.generatedValue) {
|
|
71
|
+
clauses.push(`generated always as (${renderDdlExpression(column.metadata.generatedValue, state, dialect)}) stored`)
|
|
59
72
|
} else if (column.metadata.defaultValue) {
|
|
60
|
-
clauses.push(`default ${
|
|
73
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
|
|
61
74
|
}
|
|
62
75
|
if (!column.metadata.nullable) {
|
|
63
76
|
clauses.push("not null")
|
|
@@ -65,14 +78,6 @@ const renderColumnDefinition = (
|
|
|
65
78
|
return clauses.join(" ")
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
const renderCheckPredicate = (
|
|
69
|
-
predicate: Expression.Any,
|
|
70
|
-
state: RenderState,
|
|
71
|
-
dialect: SqlDialect
|
|
72
|
-
): string => {
|
|
73
|
-
return renderExpression(predicate, state, dialect)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
81
|
const renderCreateTableSql = (
|
|
77
82
|
targetSource: QueryAst.FromClause,
|
|
78
83
|
state: RenderState,
|
|
@@ -87,21 +92,21 @@ const renderCreateTableSql = (
|
|
|
87
92
|
for (const option of table[Table.OptionsSymbol]) {
|
|
88
93
|
switch (option.kind) {
|
|
89
94
|
case "primaryKey":
|
|
90
|
-
definitions.push(`primary key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`)
|
|
95
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}primary key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
91
96
|
break
|
|
92
97
|
case "unique":
|
|
93
|
-
definitions.push(`unique (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`)
|
|
98
|
+
definitions.push(`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}unique${option.nullsNotDistinct ? " nulls not distinct" : ""} (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`)
|
|
94
99
|
break
|
|
95
100
|
case "foreignKey": {
|
|
96
101
|
const reference = option.references()
|
|
97
102
|
definitions.push(
|
|
98
|
-
`foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})`
|
|
103
|
+
`${option.name ? `constraint ${dialect.quoteIdentifier(option.name)} ` : ""}foreign key (${option.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")}) references ${dialect.renderTableReference(reference.tableName, reference.tableName, reference.schemaName)} (${reference.columns.map((column) => dialect.quoteIdentifier(column)).join(", ")})${option.onDelete ? ` on delete ${option.onDelete.replace(/[A-Z]/g, (value) => ` ${value.toLowerCase()}`).trim()}` : ""}${option.onUpdate ? ` on update ${option.onUpdate.replace(/[A-Z]/g, (value) => ` ${value.toLowerCase()}`).trim()}` : ""}${option.deferrable ? ` deferrable${option.initiallyDeferred ? " initially deferred" : ""}` : ""}`
|
|
99
104
|
)
|
|
100
105
|
break
|
|
101
106
|
}
|
|
102
107
|
case "check":
|
|
103
108
|
definitions.push(
|
|
104
|
-
`constraint ${dialect.quoteIdentifier(option.name)} check (${
|
|
109
|
+
`constraint ${dialect.quoteIdentifier(option.name)} check (${renderDdlExpression(option.predicate, state, dialect)})${option.noInherit ? " no inherit" : ""}`
|
|
105
110
|
)
|
|
106
111
|
break
|
|
107
112
|
case "index":
|
|
@@ -258,6 +263,12 @@ const renderPostgresJsonPathArray = (
|
|
|
258
263
|
}
|
|
259
264
|
}).join(", ")}]`
|
|
260
265
|
|
|
266
|
+
const renderPostgresTextLiteral = (
|
|
267
|
+
value: string,
|
|
268
|
+
state: RenderState,
|
|
269
|
+
dialect: SqlDialect
|
|
270
|
+
): string => `cast(${dialect.renderLiteral(value, state)} as text)`
|
|
271
|
+
|
|
261
272
|
const renderPostgresJsonAccessStep = (
|
|
262
273
|
segment: JsonPath.AnySegment,
|
|
263
274
|
textMode: boolean,
|
|
@@ -288,6 +299,10 @@ const renderPostgresJsonValue = (
|
|
|
288
299
|
: `cast(${rendered} as jsonb)`
|
|
289
300
|
}
|
|
290
301
|
|
|
302
|
+
const renderPostgresJsonKind = (
|
|
303
|
+
value: Expression.Any
|
|
304
|
+
): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
|
|
305
|
+
|
|
291
306
|
const renderJsonOpaquePath = (
|
|
292
307
|
value: unknown,
|
|
293
308
|
state: RenderState,
|
|
@@ -311,6 +326,24 @@ const renderFunctionCall = (
|
|
|
311
326
|
state: RenderState,
|
|
312
327
|
dialect: SqlDialect
|
|
313
328
|
): string => {
|
|
329
|
+
if (name === "array") {
|
|
330
|
+
return `ARRAY[${args.map((arg) => renderExpression(arg, state, dialect)).join(", ")}]`
|
|
331
|
+
}
|
|
332
|
+
if (name === "extract" && args.length === 2) {
|
|
333
|
+
const field = args[0]
|
|
334
|
+
const source = args[1]
|
|
335
|
+
if (field === undefined) {
|
|
336
|
+
throw new Error("Unsupported SQL extract expression")
|
|
337
|
+
}
|
|
338
|
+
if (source === undefined) {
|
|
339
|
+
throw new Error("Unsupported SQL extract expression")
|
|
340
|
+
}
|
|
341
|
+
const fieldRuntime = isExpression(field) && field[Expression.TypeId].dbType.kind === "text" && typeof field[Expression.TypeId].runtime === "string"
|
|
342
|
+
? field[Expression.TypeId].runtime
|
|
343
|
+
: undefined
|
|
344
|
+
const renderedField = fieldRuntime ?? renderExpression(field, state, dialect)
|
|
345
|
+
return `extract(${renderedField} from ${renderExpression(source, state, dialect)})`
|
|
346
|
+
}
|
|
314
347
|
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
315
348
|
if (args.length === 0) {
|
|
316
349
|
switch (name) {
|
|
@@ -328,6 +361,7 @@ const renderFunctionCall = (
|
|
|
328
361
|
}
|
|
329
362
|
|
|
330
363
|
const renderJsonExpression = (
|
|
364
|
+
expression: Expression.Any,
|
|
331
365
|
ast: Record<string, unknown>,
|
|
332
366
|
state: RenderState,
|
|
333
367
|
dialect: SqlDialect
|
|
@@ -340,6 +374,12 @@ const renderJsonExpression = (
|
|
|
340
374
|
const base = extractJsonBase(ast)
|
|
341
375
|
const segments = extractJsonPathSegments(ast)
|
|
342
376
|
const exact = segments.every((segment) => segment.kind === "key" || segment.kind === "index")
|
|
377
|
+
const postgresExpressionKind = dialect.name === "postgres" && isJsonExpression(expression)
|
|
378
|
+
? renderPostgresJsonKind(expression)
|
|
379
|
+
: undefined
|
|
380
|
+
const postgresBaseKind = dialect.name === "postgres" && isJsonExpression(base)
|
|
381
|
+
? renderPostgresJsonKind(base)
|
|
382
|
+
: undefined
|
|
343
383
|
|
|
344
384
|
switch (kind) {
|
|
345
385
|
case "jsonGet":
|
|
@@ -363,7 +403,7 @@ const renderJsonExpression = (
|
|
|
363
403
|
}
|
|
364
404
|
const jsonPathLiteral = dialect.renderLiteral(renderJsonPathStringLiteral(segments), state)
|
|
365
405
|
const queried = `jsonb_path_query_first(${renderPostgresJsonValue(base, state, dialect)}, ${jsonPathLiteral})`
|
|
366
|
-
return textMode ? `
|
|
406
|
+
return textMode ? `(${queried} #>> '{}')` : queried
|
|
367
407
|
}
|
|
368
408
|
if (dialect.name === "mysql") {
|
|
369
409
|
const extracted = `json_extract(${baseSql}, ${renderMySqlJsonPath(segments, state, dialect)})`
|
|
@@ -387,12 +427,12 @@ const renderJsonExpression = (
|
|
|
387
427
|
}
|
|
388
428
|
if (dialect.name === "postgres") {
|
|
389
429
|
if (kind === "jsonHasAnyKeys") {
|
|
390
|
-
return `(${baseSql} ?| ${
|
|
430
|
+
return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
|
|
391
431
|
}
|
|
392
432
|
if (kind === "jsonHasAllKeys") {
|
|
393
|
-
return `(${baseSql} ?& ${
|
|
433
|
+
return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
|
|
394
434
|
}
|
|
395
|
-
return `(${baseSql} ? ${
|
|
435
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
|
|
396
436
|
}
|
|
397
437
|
if (dialect.name === "mysql") {
|
|
398
438
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
@@ -423,7 +463,7 @@ const renderJsonExpression = (
|
|
|
423
463
|
renderExpression(entry.value, state, dialect)
|
|
424
464
|
])
|
|
425
465
|
if (dialect.name === "postgres") {
|
|
426
|
-
return
|
|
466
|
+
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
|
|
427
467
|
}
|
|
428
468
|
if (dialect.name === "mysql") {
|
|
429
469
|
return `json_object(${renderedEntries.join(", ")})`
|
|
@@ -436,7 +476,7 @@ const renderJsonExpression = (
|
|
|
436
476
|
: []
|
|
437
477
|
const renderedValues = values.map((value) => renderExpression(value, state, dialect)).join(", ")
|
|
438
478
|
if (dialect.name === "postgres") {
|
|
439
|
-
return
|
|
479
|
+
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
440
480
|
}
|
|
441
481
|
if (dialect.name === "mysql") {
|
|
442
482
|
return `json_array(${renderedValues})`
|
|
@@ -470,7 +510,8 @@ const renderJsonExpression = (
|
|
|
470
510
|
return undefined
|
|
471
511
|
}
|
|
472
512
|
if (dialect.name === "postgres") {
|
|
473
|
-
|
|
513
|
+
const baseSql = renderExpression(base, state, dialect)
|
|
514
|
+
return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof(${baseSql})`
|
|
474
515
|
}
|
|
475
516
|
if (dialect.name === "mysql") {
|
|
476
517
|
return `json_type(${renderExpression(base, state, dialect)})`
|
|
@@ -481,8 +522,11 @@ const renderJsonExpression = (
|
|
|
481
522
|
return undefined
|
|
482
523
|
}
|
|
483
524
|
if (dialect.name === "postgres") {
|
|
484
|
-
const
|
|
485
|
-
|
|
525
|
+
const baseSql = renderExpression(base, state, dialect)
|
|
526
|
+
const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
|
|
527
|
+
const arrayLength = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_array_length`
|
|
528
|
+
const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
|
|
529
|
+
return `(case when ${typeOf}(${baseSql}) = 'array' then ${arrayLength}(${baseSql}) when ${typeOf}(${baseSql}) = 'object' then (select count(*)::int from ${objectKeys}(${baseSql})) else null end)`
|
|
486
530
|
}
|
|
487
531
|
if (dialect.name === "mysql") {
|
|
488
532
|
return `json_length(${renderExpression(base, state, dialect)})`
|
|
@@ -493,8 +537,10 @@ const renderJsonExpression = (
|
|
|
493
537
|
return undefined
|
|
494
538
|
}
|
|
495
539
|
if (dialect.name === "postgres") {
|
|
496
|
-
const
|
|
497
|
-
|
|
540
|
+
const baseSql = renderExpression(base, state, dialect)
|
|
541
|
+
const typeOf = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof`
|
|
542
|
+
const objectKeys = `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_object_keys`
|
|
543
|
+
return `(case when ${typeOf}(${baseSql}) = 'object' then array(select ${objectKeys}(${baseSql})) else null end)`
|
|
498
544
|
}
|
|
499
545
|
if (dialect.name === "mysql") {
|
|
500
546
|
return `json_keys(${renderExpression(base, state, dialect)})`
|
|
@@ -505,7 +551,7 @@ const renderJsonExpression = (
|
|
|
505
551
|
return undefined
|
|
506
552
|
}
|
|
507
553
|
if (dialect.name === "postgres") {
|
|
508
|
-
return
|
|
554
|
+
return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_strip_nulls(${renderExpression(base, state, dialect)})`
|
|
509
555
|
}
|
|
510
556
|
unsupportedJsonFeature(dialect, "jsonStripNulls")
|
|
511
557
|
return undefined
|
|
@@ -1184,7 +1230,7 @@ export const renderExpression = (
|
|
|
1184
1230
|
const rawAst = (expression as Expression.Any & {
|
|
1185
1231
|
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
1186
1232
|
})[ExpressionAst.TypeId] as ExpressionAst.Any | Record<string, unknown>
|
|
1187
|
-
const jsonSql = renderJsonExpression(rawAst as Record<string, unknown>, state, dialect)
|
|
1233
|
+
const jsonSql = renderJsonExpression(expression, rawAst as Record<string, unknown>, state, dialect)
|
|
1188
1234
|
if (jsonSql !== undefined) {
|
|
1189
1235
|
return jsonSql
|
|
1190
1236
|
}
|
|
@@ -1201,9 +1247,11 @@ export const renderExpression = (
|
|
|
1201
1247
|
: operator === "gt"
|
|
1202
1248
|
? ">"
|
|
1203
1249
|
: ">="
|
|
1204
|
-
|
|
1250
|
+
switch (ast.kind) {
|
|
1205
1251
|
case "column":
|
|
1206
|
-
return
|
|
1252
|
+
return ast.tableName.length === 0
|
|
1253
|
+
? dialect.quoteIdentifier(ast.columnName)
|
|
1254
|
+
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1207
1255
|
case "literal":
|
|
1208
1256
|
return dialect.renderLiteral(ast.value, state)
|
|
1209
1257
|
case "excluded":
|
|
@@ -1232,6 +1280,22 @@ export const renderExpression = (
|
|
|
1232
1280
|
return dialect.name === "postgres"
|
|
1233
1281
|
? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
|
|
1234
1282
|
: `(lower(${renderExpression(ast.left, state, dialect)}) like lower(${renderExpression(ast.right, state, dialect)}))`
|
|
1283
|
+
case "regexMatch":
|
|
1284
|
+
return dialect.name === "postgres"
|
|
1285
|
+
? `(${renderExpression(ast.left, state, dialect)} ~ ${renderExpression(ast.right, state, dialect)})`
|
|
1286
|
+
: `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1287
|
+
case "regexIMatch":
|
|
1288
|
+
return dialect.name === "postgres"
|
|
1289
|
+
? `(${renderExpression(ast.left, state, dialect)} ~* ${renderExpression(ast.right, state, dialect)})`
|
|
1290
|
+
: `(${renderExpression(ast.left, state, dialect)} regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1291
|
+
case "regexNotMatch":
|
|
1292
|
+
return dialect.name === "postgres"
|
|
1293
|
+
? `(${renderExpression(ast.left, state, dialect)} !~ ${renderExpression(ast.right, state, dialect)})`
|
|
1294
|
+
: `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1295
|
+
case "regexNotIMatch":
|
|
1296
|
+
return dialect.name === "postgres"
|
|
1297
|
+
? `(${renderExpression(ast.left, state, dialect)} !~* ${renderExpression(ast.right, state, dialect)})`
|
|
1298
|
+
: `(${renderExpression(ast.left, state, dialect)} not regexp ${renderExpression(ast.right, state, dialect)})`
|
|
1235
1299
|
case "isDistinctFrom":
|
|
1236
1300
|
return dialect.name === "mysql"
|
|
1237
1301
|
? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
|
|
@@ -5,39 +5,77 @@ import {
|
|
|
5
5
|
type IsNullable
|
|
6
6
|
} from "./column-state.js"
|
|
7
7
|
import type { Any as AnyExpression } from "./expression.js"
|
|
8
|
+
import type { Any as AnySchemaExpression } from "./schema-expression.js"
|
|
8
9
|
import type { TableFieldMap } from "./schema-derivation.js"
|
|
9
10
|
|
|
10
11
|
/** Non-empty list of column names. */
|
|
11
12
|
export type ColumnList = readonly [string, ...string[]]
|
|
12
13
|
|
|
14
|
+
export type DdlExpressionLike = AnyExpression | AnySchemaExpression
|
|
15
|
+
|
|
16
|
+
export type ReferentialAction = "noAction" | "restrict" | "cascade" | "setNull" | "setDefault"
|
|
17
|
+
|
|
18
|
+
export type IndexKeySpec =
|
|
19
|
+
| {
|
|
20
|
+
readonly kind: "column"
|
|
21
|
+
readonly column: string
|
|
22
|
+
readonly order?: "asc" | "desc"
|
|
23
|
+
readonly nulls?: "first" | "last"
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
readonly kind: "expression"
|
|
27
|
+
readonly expression: DdlExpressionLike
|
|
28
|
+
readonly order?: "asc" | "desc"
|
|
29
|
+
readonly nulls?: "first" | "last"
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
/** Normalized table-level option record. */
|
|
14
33
|
export type TableOptionSpec =
|
|
15
34
|
| {
|
|
16
35
|
readonly kind: "index"
|
|
17
|
-
readonly columns
|
|
36
|
+
readonly columns?: ColumnList
|
|
37
|
+
readonly name?: string
|
|
38
|
+
readonly unique?: boolean
|
|
39
|
+
readonly method?: string
|
|
40
|
+
readonly include?: readonly string[]
|
|
41
|
+
readonly predicate?: DdlExpressionLike
|
|
42
|
+
readonly keys?: readonly [IndexKeySpec, ...IndexKeySpec[]]
|
|
18
43
|
}
|
|
19
44
|
| {
|
|
20
45
|
readonly kind: "unique"
|
|
21
46
|
readonly columns: ColumnList
|
|
47
|
+
readonly name?: string
|
|
48
|
+
readonly nullsNotDistinct?: boolean
|
|
49
|
+
readonly deferrable?: boolean
|
|
50
|
+
readonly initiallyDeferred?: boolean
|
|
22
51
|
}
|
|
23
52
|
| {
|
|
24
53
|
readonly kind: "primaryKey"
|
|
25
54
|
readonly columns: ColumnList
|
|
55
|
+
readonly name?: string
|
|
56
|
+
readonly deferrable?: boolean
|
|
57
|
+
readonly initiallyDeferred?: boolean
|
|
26
58
|
}
|
|
27
59
|
| {
|
|
28
60
|
readonly kind: "foreignKey"
|
|
29
61
|
readonly columns: ColumnList
|
|
62
|
+
readonly name?: string
|
|
30
63
|
readonly references: () => {
|
|
31
64
|
readonly tableName: string
|
|
32
65
|
readonly schemaName?: string
|
|
33
66
|
readonly columns: ColumnList
|
|
34
67
|
readonly knownColumns?: readonly string[]
|
|
35
68
|
}
|
|
69
|
+
readonly onUpdate?: ReferentialAction
|
|
70
|
+
readonly onDelete?: ReferentialAction
|
|
71
|
+
readonly deferrable?: boolean
|
|
72
|
+
readonly initiallyDeferred?: boolean
|
|
36
73
|
}
|
|
37
74
|
| {
|
|
38
75
|
readonly kind: "check"
|
|
39
76
|
readonly name: string
|
|
40
|
-
readonly predicate:
|
|
77
|
+
readonly predicate: DdlExpressionLike
|
|
78
|
+
readonly noInherit?: boolean
|
|
41
79
|
}
|
|
42
80
|
|
|
43
81
|
/** Thin wrapper used by the public `Table.*` option builders. */
|
|
@@ -109,7 +147,11 @@ export const collectInlineOptions = <Fields extends TableFieldMap>(
|
|
|
109
147
|
if (column.metadata.unique && !column.metadata.primaryKey) {
|
|
110
148
|
options.push({
|
|
111
149
|
kind: "unique",
|
|
112
|
-
columns: [columnName]
|
|
150
|
+
columns: [columnName],
|
|
151
|
+
name: column.metadata.uniqueConstraint?.name,
|
|
152
|
+
nullsNotDistinct: column.metadata.uniqueConstraint?.nullsNotDistinct,
|
|
153
|
+
deferrable: column.metadata.uniqueConstraint?.deferrable,
|
|
154
|
+
initiallyDeferred: column.metadata.uniqueConstraint?.initiallyDeferred
|
|
113
155
|
})
|
|
114
156
|
}
|
|
115
157
|
if (column.metadata.references) {
|
|
@@ -125,7 +167,27 @@ export const collectInlineOptions = <Fields extends TableFieldMap>(
|
|
|
125
167
|
schemaName: bound.schemaName,
|
|
126
168
|
columns: [bound.columnName]
|
|
127
169
|
}
|
|
128
|
-
}
|
|
170
|
+
},
|
|
171
|
+
name: column.metadata.references.name,
|
|
172
|
+
onUpdate: column.metadata.references.onUpdate,
|
|
173
|
+
onDelete: column.metadata.references.onDelete,
|
|
174
|
+
deferrable: column.metadata.references.deferrable,
|
|
175
|
+
initiallyDeferred: column.metadata.references.initiallyDeferred
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
if (column.metadata.index) {
|
|
179
|
+
options.push({
|
|
180
|
+
kind: "index",
|
|
181
|
+
keys: [{
|
|
182
|
+
kind: "column",
|
|
183
|
+
column: columnName,
|
|
184
|
+
order: column.metadata.index.order,
|
|
185
|
+
nulls: column.metadata.index.nulls
|
|
186
|
+
}],
|
|
187
|
+
name: column.metadata.index.name,
|
|
188
|
+
method: column.metadata.index.method,
|
|
189
|
+
include: column.metadata.index.include,
|
|
190
|
+
predicate: column.metadata.index.predicate
|
|
129
191
|
})
|
|
130
192
|
}
|
|
131
193
|
}
|
|
@@ -174,17 +236,20 @@ export const validateOptions = <Fields extends TableFieldMap>(
|
|
|
174
236
|
case "primaryKey":
|
|
175
237
|
case "unique":
|
|
176
238
|
case "foreignKey": {
|
|
177
|
-
|
|
239
|
+
const columns = option.kind === "index"
|
|
240
|
+
? option.columns ?? []
|
|
241
|
+
: option.columns
|
|
242
|
+
if (columns.length === 0 && option.kind !== "index") {
|
|
178
243
|
throw new Error(`Option '${option.kind}' on table '${tableName}' requires at least one column`)
|
|
179
244
|
}
|
|
180
|
-
for (const column of
|
|
245
|
+
for (const column of columns) {
|
|
181
246
|
if (!knownColumns.has(column)) {
|
|
182
247
|
throw new Error(`Unknown column '${column}' on table '${tableName}'`)
|
|
183
248
|
}
|
|
184
249
|
}
|
|
185
250
|
if (option.kind === "foreignKey") {
|
|
186
251
|
const reference = option.references()
|
|
187
|
-
if (reference.columns.length !==
|
|
252
|
+
if (reference.columns.length !== columns.length) {
|
|
188
253
|
throw new Error(`Foreign key on table '${tableName}' must reference the same number of columns`)
|
|
189
254
|
}
|
|
190
255
|
if (reference.knownColumns) {
|
|
@@ -196,6 +261,21 @@ export const validateOptions = <Fields extends TableFieldMap>(
|
|
|
196
261
|
}
|
|
197
262
|
}
|
|
198
263
|
}
|
|
264
|
+
if (option.kind === "index") {
|
|
265
|
+
for (const column of option.include ?? []) {
|
|
266
|
+
if (!knownColumns.has(column)) {
|
|
267
|
+
throw new Error(`Unknown included column '${column}' on table '${tableName}'`)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const key of option.keys ?? []) {
|
|
271
|
+
if (key.kind === "column" && !knownColumns.has(key.column)) {
|
|
272
|
+
throw new Error(`Unknown index key column '${key.column}' on table '${tableName}'`)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (option.columns === undefined && (option.keys === undefined || option.keys.length === 0)) {
|
|
276
|
+
throw new Error(`Index on table '${tableName}' requires at least one column or key`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
199
279
|
break
|
|
200
280
|
}
|
|
201
281
|
case "check": {
|