effect-qb 0.12.3 → 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 -1283
- package/dist/mysql.js +6376 -4978
- package/dist/postgres/metadata.js +2724 -0
- package/dist/postgres.js +5475 -3636
- package/package.json +13 -8
- package/src/internal/column-state.ts +88 -6
- package/src/internal/column.ts +569 -34
- package/src/internal/datatypes/define.ts +0 -30
- package/src/internal/executor.ts +45 -11
- package/src/internal/expression-ast.ts +15 -0
- package/src/internal/expression.ts +3 -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} +669 -230
- 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 +471 -41
- 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 +123 -35
- package/src/internal/table-options.ts +88 -7
- package/src/internal/table.ts +106 -42
- package/src/mysql/column.ts +3 -1
- package/src/mysql/datatypes/index.ts +17 -2
- package/src/mysql/executor.ts +20 -17
- package/src/mysql/function/aggregate.ts +6 -0
- package/src/mysql/function/core.ts +5 -0
- package/src/mysql/function/index.ts +20 -0
- package/src/mysql/function/json.ts +4 -0
- package/src/mysql/function/string.ts +6 -0
- package/src/mysql/function/temporal.ts +103 -0
- package/src/mysql/function/window.ts +7 -0
- package/src/mysql/private/query.ts +1 -0
- package/src/mysql/query.ts +6 -26
- package/src/mysql.ts +2 -0
- package/src/postgres/cast.ts +31 -0
- package/src/postgres/column.ts +27 -1
- package/src/postgres/datatypes/index.ts +40 -5
- package/src/postgres/executor.ts +19 -17
- package/src/postgres/function/aggregate.ts +6 -0
- package/src/postgres/function/core.ts +16 -0
- package/src/postgres/function/index.ts +20 -0
- package/src/postgres/function/json.ts +501 -0
- package/src/postgres/function/string.ts +6 -0
- package/src/postgres/function/temporal.ts +107 -0
- package/src/postgres/function/window.ts +7 -0
- package/src/postgres/metadata.ts +31 -0
- package/src/postgres/private/query.ts +1 -0
- package/src/postgres/query.ts +6 -28
- 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 +16 -0
|
@@ -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,35 +46,38 @@ 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,
|
|
60
|
+
state: RenderState,
|
|
49
61
|
columnName: string,
|
|
50
62
|
column: Table.AnyTable[typeof Table.TypeId]["fields"][string]
|
|
51
63
|
): string => {
|
|
52
64
|
const clauses = [
|
|
53
65
|
dialect.quoteIdentifier(columnName),
|
|
54
|
-
renderDbType(dialect, column.metadata.dbType)
|
|
66
|
+
column.metadata.ddlType ?? renderDbType(dialect, column.metadata.dbType)
|
|
55
67
|
]
|
|
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`)
|
|
72
|
+
} else if (column.metadata.defaultValue) {
|
|
73
|
+
clauses.push(`default ${renderDdlExpression(column.metadata.defaultValue, state, dialect)}`)
|
|
74
|
+
}
|
|
56
75
|
if (!column.metadata.nullable) {
|
|
57
76
|
clauses.push("not null")
|
|
58
77
|
}
|
|
59
78
|
return clauses.join(" ")
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
const renderCheckPredicate = (
|
|
63
|
-
predicate: unknown,
|
|
64
|
-
state: RenderState,
|
|
65
|
-
dialect: SqlDialect
|
|
66
|
-
): string => {
|
|
67
|
-
if (typeof predicate === "string") {
|
|
68
|
-
return predicate
|
|
69
|
-
}
|
|
70
|
-
if (predicate !== null && typeof predicate === "object" && Expression.TypeId in predicate) {
|
|
71
|
-
return renderExpression(predicate as Expression.Any, state, dialect)
|
|
72
|
-
}
|
|
73
|
-
throw new Error("Unsupported check constraint predicate for DDL rendering")
|
|
74
|
-
}
|
|
75
|
-
|
|
76
81
|
const renderCreateTableSql = (
|
|
77
82
|
targetSource: QueryAst.FromClause,
|
|
78
83
|
state: RenderState,
|
|
@@ -82,26 +87,26 @@ const renderCreateTableSql = (
|
|
|
82
87
|
const table = targetSource.source as Table.AnyTable
|
|
83
88
|
const fields = table[Table.TypeId].fields
|
|
84
89
|
const definitions = Object.entries(fields).map(([columnName, column]) =>
|
|
85
|
-
renderColumnDefinition(dialect, columnName, column)
|
|
90
|
+
renderColumnDefinition(dialect, state, columnName, column)
|
|
86
91
|
)
|
|
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,
|
|
@@ -305,7 +320,48 @@ const renderJsonOpaquePath = (
|
|
|
305
320
|
throw new Error("Unsupported SQL/JSON path input")
|
|
306
321
|
}
|
|
307
322
|
|
|
323
|
+
const renderFunctionCall = (
|
|
324
|
+
name: string,
|
|
325
|
+
args: readonly Expression.Any[],
|
|
326
|
+
state: RenderState,
|
|
327
|
+
dialect: SqlDialect
|
|
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
|
+
}
|
|
347
|
+
const renderedArgs = args.map((arg) => renderExpression(arg, state, dialect)).join(", ")
|
|
348
|
+
if (args.length === 0) {
|
|
349
|
+
switch (name) {
|
|
350
|
+
case "current_date":
|
|
351
|
+
case "current_time":
|
|
352
|
+
case "current_timestamp":
|
|
353
|
+
case "localtime":
|
|
354
|
+
case "localtimestamp":
|
|
355
|
+
return name
|
|
356
|
+
default:
|
|
357
|
+
return `${name}()`
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return `${name}(${renderedArgs})`
|
|
361
|
+
}
|
|
362
|
+
|
|
308
363
|
const renderJsonExpression = (
|
|
364
|
+
expression: Expression.Any,
|
|
309
365
|
ast: Record<string, unknown>,
|
|
310
366
|
state: RenderState,
|
|
311
367
|
dialect: SqlDialect
|
|
@@ -318,6 +374,12 @@ const renderJsonExpression = (
|
|
|
318
374
|
const base = extractJsonBase(ast)
|
|
319
375
|
const segments = extractJsonPathSegments(ast)
|
|
320
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
|
|
321
383
|
|
|
322
384
|
switch (kind) {
|
|
323
385
|
case "jsonGet":
|
|
@@ -341,7 +403,7 @@ const renderJsonExpression = (
|
|
|
341
403
|
}
|
|
342
404
|
const jsonPathLiteral = dialect.renderLiteral(renderJsonPathStringLiteral(segments), state)
|
|
343
405
|
const queried = `jsonb_path_query_first(${renderPostgresJsonValue(base, state, dialect)}, ${jsonPathLiteral})`
|
|
344
|
-
return textMode ? `
|
|
406
|
+
return textMode ? `(${queried} #>> '{}')` : queried
|
|
345
407
|
}
|
|
346
408
|
if (dialect.name === "mysql") {
|
|
347
409
|
const extracted = `json_extract(${baseSql}, ${renderMySqlJsonPath(segments, state, dialect)})`
|
|
@@ -365,12 +427,12 @@ const renderJsonExpression = (
|
|
|
365
427
|
}
|
|
366
428
|
if (dialect.name === "postgres") {
|
|
367
429
|
if (kind === "jsonHasAnyKeys") {
|
|
368
|
-
return `(${baseSql} ?| ${
|
|
430
|
+
return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
|
|
369
431
|
}
|
|
370
432
|
if (kind === "jsonHasAllKeys") {
|
|
371
|
-
return `(${baseSql} ?& ${
|
|
433
|
+
return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
|
|
372
434
|
}
|
|
373
|
-
return `(${baseSql} ? ${
|
|
435
|
+
return `(${baseSql} ? ${renderPostgresTextLiteral(String(keys[0]!), state, dialect)})`
|
|
374
436
|
}
|
|
375
437
|
if (dialect.name === "mysql") {
|
|
376
438
|
const mode = kind === "jsonHasAllKeys" ? "all" : "one"
|
|
@@ -401,7 +463,7 @@ const renderJsonExpression = (
|
|
|
401
463
|
renderExpression(entry.value, state, dialect)
|
|
402
464
|
])
|
|
403
465
|
if (dialect.name === "postgres") {
|
|
404
|
-
return
|
|
466
|
+
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
|
|
405
467
|
}
|
|
406
468
|
if (dialect.name === "mysql") {
|
|
407
469
|
return `json_object(${renderedEntries.join(", ")})`
|
|
@@ -414,7 +476,7 @@ const renderJsonExpression = (
|
|
|
414
476
|
: []
|
|
415
477
|
const renderedValues = values.map((value) => renderExpression(value, state, dialect)).join(", ")
|
|
416
478
|
if (dialect.name === "postgres") {
|
|
417
|
-
return
|
|
479
|
+
return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
|
|
418
480
|
}
|
|
419
481
|
if (dialect.name === "mysql") {
|
|
420
482
|
return `json_array(${renderedValues})`
|
|
@@ -448,7 +510,8 @@ const renderJsonExpression = (
|
|
|
448
510
|
return undefined
|
|
449
511
|
}
|
|
450
512
|
if (dialect.name === "postgres") {
|
|
451
|
-
|
|
513
|
+
const baseSql = renderExpression(base, state, dialect)
|
|
514
|
+
return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_typeof(${baseSql})`
|
|
452
515
|
}
|
|
453
516
|
if (dialect.name === "mysql") {
|
|
454
517
|
return `json_type(${renderExpression(base, state, dialect)})`
|
|
@@ -459,8 +522,11 @@ const renderJsonExpression = (
|
|
|
459
522
|
return undefined
|
|
460
523
|
}
|
|
461
524
|
if (dialect.name === "postgres") {
|
|
462
|
-
const
|
|
463
|
-
|
|
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)`
|
|
464
530
|
}
|
|
465
531
|
if (dialect.name === "mysql") {
|
|
466
532
|
return `json_length(${renderExpression(base, state, dialect)})`
|
|
@@ -471,8 +537,10 @@ const renderJsonExpression = (
|
|
|
471
537
|
return undefined
|
|
472
538
|
}
|
|
473
539
|
if (dialect.name === "postgres") {
|
|
474
|
-
const
|
|
475
|
-
|
|
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)`
|
|
476
544
|
}
|
|
477
545
|
if (dialect.name === "mysql") {
|
|
478
546
|
return `json_keys(${renderExpression(base, state, dialect)})`
|
|
@@ -483,7 +551,7 @@ const renderJsonExpression = (
|
|
|
483
551
|
return undefined
|
|
484
552
|
}
|
|
485
553
|
if (dialect.name === "postgres") {
|
|
486
|
-
return
|
|
554
|
+
return `${postgresBaseKind === "jsonb" ? "jsonb" : "json"}_strip_nulls(${renderExpression(base, state, dialect)})`
|
|
487
555
|
}
|
|
488
556
|
unsupportedJsonFeature(dialect, "jsonStripNulls")
|
|
489
557
|
return undefined
|
|
@@ -1162,7 +1230,7 @@ export const renderExpression = (
|
|
|
1162
1230
|
const rawAst = (expression as Expression.Any & {
|
|
1163
1231
|
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
1164
1232
|
})[ExpressionAst.TypeId] as ExpressionAst.Any | Record<string, unknown>
|
|
1165
|
-
const jsonSql = renderJsonExpression(rawAst as Record<string, unknown>, state, dialect)
|
|
1233
|
+
const jsonSql = renderJsonExpression(expression, rawAst as Record<string, unknown>, state, dialect)
|
|
1166
1234
|
if (jsonSql !== undefined) {
|
|
1167
1235
|
return jsonSql
|
|
1168
1236
|
}
|
|
@@ -1179,9 +1247,11 @@ export const renderExpression = (
|
|
|
1179
1247
|
: operator === "gt"
|
|
1180
1248
|
? ">"
|
|
1181
1249
|
: ">="
|
|
1182
|
-
|
|
1250
|
+
switch (ast.kind) {
|
|
1183
1251
|
case "column":
|
|
1184
|
-
return
|
|
1252
|
+
return ast.tableName.length === 0
|
|
1253
|
+
? dialect.quoteIdentifier(ast.columnName)
|
|
1254
|
+
: `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1185
1255
|
case "literal":
|
|
1186
1256
|
return dialect.renderLiteral(ast.value, state)
|
|
1187
1257
|
case "excluded":
|
|
@@ -1190,6 +1260,8 @@ export const renderExpression = (
|
|
|
1190
1260
|
: `excluded.${dialect.quoteIdentifier(ast.columnName)}`
|
|
1191
1261
|
case "cast":
|
|
1192
1262
|
return `cast(${renderExpression(ast.value, state, dialect)} as ${renderCastType(dialect, ast.target)})`
|
|
1263
|
+
case "function":
|
|
1264
|
+
return renderFunctionCall(ast.name, Array.isArray(ast.args) ? ast.args : [], state, dialect)
|
|
1193
1265
|
case "eq":
|
|
1194
1266
|
return `(${renderExpression(ast.left, state, dialect)} = ${renderExpression(ast.right, state, dialect)})`
|
|
1195
1267
|
case "neq":
|
|
@@ -1208,6 +1280,22 @@ export const renderExpression = (
|
|
|
1208
1280
|
return dialect.name === "postgres"
|
|
1209
1281
|
? `(${renderExpression(ast.left, state, dialect)} ilike ${renderExpression(ast.right, state, dialect)})`
|
|
1210
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)})`
|
|
1211
1299
|
case "isDistinctFrom":
|
|
1212
1300
|
return dialect.name === "mysql"
|
|
1213
1301
|
? `(not (${renderExpression(ast.left, state, dialect)} <=> ${renderExpression(ast.right, state, dialect)}))`
|
|
@@ -4,39 +4,78 @@ import {
|
|
|
4
4
|
type AnyColumnDefinition,
|
|
5
5
|
type IsNullable
|
|
6
6
|
} from "./column-state.js"
|
|
7
|
+
import type { Any as AnyExpression } from "./expression.js"
|
|
8
|
+
import type { Any as AnySchemaExpression } from "./schema-expression.js"
|
|
7
9
|
import type { TableFieldMap } from "./schema-derivation.js"
|
|
8
10
|
|
|
9
11
|
/** Non-empty list of column names. */
|
|
10
12
|
export type ColumnList = readonly [string, ...string[]]
|
|
11
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
|
+
|
|
12
32
|
/** Normalized table-level option record. */
|
|
13
33
|
export type TableOptionSpec =
|
|
14
34
|
| {
|
|
15
35
|
readonly kind: "index"
|
|
16
|
-
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[]]
|
|
17
43
|
}
|
|
18
44
|
| {
|
|
19
45
|
readonly kind: "unique"
|
|
20
46
|
readonly columns: ColumnList
|
|
47
|
+
readonly name?: string
|
|
48
|
+
readonly nullsNotDistinct?: boolean
|
|
49
|
+
readonly deferrable?: boolean
|
|
50
|
+
readonly initiallyDeferred?: boolean
|
|
21
51
|
}
|
|
22
52
|
| {
|
|
23
53
|
readonly kind: "primaryKey"
|
|
24
54
|
readonly columns: ColumnList
|
|
55
|
+
readonly name?: string
|
|
56
|
+
readonly deferrable?: boolean
|
|
57
|
+
readonly initiallyDeferred?: boolean
|
|
25
58
|
}
|
|
26
59
|
| {
|
|
27
60
|
readonly kind: "foreignKey"
|
|
28
61
|
readonly columns: ColumnList
|
|
62
|
+
readonly name?: string
|
|
29
63
|
readonly references: () => {
|
|
30
64
|
readonly tableName: string
|
|
31
65
|
readonly schemaName?: string
|
|
32
66
|
readonly columns: ColumnList
|
|
33
67
|
readonly knownColumns?: readonly string[]
|
|
34
68
|
}
|
|
69
|
+
readonly onUpdate?: ReferentialAction
|
|
70
|
+
readonly onDelete?: ReferentialAction
|
|
71
|
+
readonly deferrable?: boolean
|
|
72
|
+
readonly initiallyDeferred?: boolean
|
|
35
73
|
}
|
|
36
74
|
| {
|
|
37
75
|
readonly kind: "check"
|
|
38
76
|
readonly name: string
|
|
39
|
-
readonly predicate:
|
|
77
|
+
readonly predicate: DdlExpressionLike
|
|
78
|
+
readonly noInherit?: boolean
|
|
40
79
|
}
|
|
41
80
|
|
|
42
81
|
/** Thin wrapper used by the public `Table.*` option builders. */
|
|
@@ -108,7 +147,11 @@ export const collectInlineOptions = <Fields extends TableFieldMap>(
|
|
|
108
147
|
if (column.metadata.unique && !column.metadata.primaryKey) {
|
|
109
148
|
options.push({
|
|
110
149
|
kind: "unique",
|
|
111
|
-
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
|
|
112
155
|
})
|
|
113
156
|
}
|
|
114
157
|
if (column.metadata.references) {
|
|
@@ -124,7 +167,27 @@ export const collectInlineOptions = <Fields extends TableFieldMap>(
|
|
|
124
167
|
schemaName: bound.schemaName,
|
|
125
168
|
columns: [bound.columnName]
|
|
126
169
|
}
|
|
127
|
-
}
|
|
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
|
|
128
191
|
})
|
|
129
192
|
}
|
|
130
193
|
}
|
|
@@ -173,17 +236,20 @@ export const validateOptions = <Fields extends TableFieldMap>(
|
|
|
173
236
|
case "primaryKey":
|
|
174
237
|
case "unique":
|
|
175
238
|
case "foreignKey": {
|
|
176
|
-
|
|
239
|
+
const columns = option.kind === "index"
|
|
240
|
+
? option.columns ?? []
|
|
241
|
+
: option.columns
|
|
242
|
+
if (columns.length === 0 && option.kind !== "index") {
|
|
177
243
|
throw new Error(`Option '${option.kind}' on table '${tableName}' requires at least one column`)
|
|
178
244
|
}
|
|
179
|
-
for (const column of
|
|
245
|
+
for (const column of columns) {
|
|
180
246
|
if (!knownColumns.has(column)) {
|
|
181
247
|
throw new Error(`Unknown column '${column}' on table '${tableName}'`)
|
|
182
248
|
}
|
|
183
249
|
}
|
|
184
250
|
if (option.kind === "foreignKey") {
|
|
185
251
|
const reference = option.references()
|
|
186
|
-
if (reference.columns.length !==
|
|
252
|
+
if (reference.columns.length !== columns.length) {
|
|
187
253
|
throw new Error(`Foreign key on table '${tableName}' must reference the same number of columns`)
|
|
188
254
|
}
|
|
189
255
|
if (reference.knownColumns) {
|
|
@@ -195,6 +261,21 @@ export const validateOptions = <Fields extends TableFieldMap>(
|
|
|
195
261
|
}
|
|
196
262
|
}
|
|
197
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
|
+
}
|
|
198
279
|
break
|
|
199
280
|
}
|
|
200
281
|
case "check": {
|