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.
Files changed (54) hide show
  1. package/README.md +6 -1431
  2. package/dist/mysql.js +1678 -355
  3. package/dist/postgres/metadata.js +2724 -0
  4. package/dist/postgres.js +7197 -5433
  5. package/package.json +8 -10
  6. package/src/internal/column-state.ts +84 -10
  7. package/src/internal/column.ts +556 -34
  8. package/src/internal/datatypes/define.ts +0 -30
  9. package/src/internal/executor.ts +45 -11
  10. package/src/internal/expression-ast.ts +4 -0
  11. package/src/internal/expression.ts +1 -1
  12. package/src/internal/implication-runtime.ts +171 -0
  13. package/src/internal/mysql-query.ts +7173 -0
  14. package/src/internal/mysql-renderer.ts +2 -2
  15. package/src/internal/plan.ts +14 -4
  16. package/src/internal/{query-factory.ts → postgres-query.ts} +619 -167
  17. package/src/internal/postgres-renderer.ts +2 -2
  18. package/src/internal/postgres-schema-model.ts +144 -0
  19. package/src/internal/predicate-analysis.ts +10 -0
  20. package/src/internal/predicate-context.ts +112 -36
  21. package/src/internal/predicate-formula.ts +31 -19
  22. package/src/internal/predicate-normalize.ts +177 -106
  23. package/src/internal/predicate-runtime.ts +676 -0
  24. package/src/internal/query.ts +455 -39
  25. package/src/internal/renderer.ts +2 -2
  26. package/src/internal/runtime-schema.ts +74 -20
  27. package/src/internal/schema-ddl.ts +55 -0
  28. package/src/internal/schema-derivation.ts +93 -21
  29. package/src/internal/schema-expression.ts +44 -0
  30. package/src/internal/sql-expression-renderer.ts +95 -31
  31. package/src/internal/table-options.ts +87 -7
  32. package/src/internal/table.ts +104 -41
  33. package/src/mysql/column.ts +1 -0
  34. package/src/mysql/datatypes/index.ts +17 -2
  35. package/src/mysql/function/core.ts +1 -0
  36. package/src/mysql/function/index.ts +1 -0
  37. package/src/mysql/private/query.ts +1 -13
  38. package/src/mysql/query.ts +5 -0
  39. package/src/postgres/cast.ts +31 -0
  40. package/src/postgres/column.ts +26 -0
  41. package/src/postgres/datatypes/index.ts +40 -5
  42. package/src/postgres/function/core.ts +12 -0
  43. package/src/postgres/function/index.ts +2 -1
  44. package/src/postgres/function/json.ts +499 -2
  45. package/src/postgres/metadata.ts +31 -0
  46. package/src/postgres/private/query.ts +1 -13
  47. package/src/postgres/query.ts +5 -2
  48. package/src/postgres/schema-expression.ts +16 -0
  49. package/src/postgres/schema-management.ts +204 -0
  50. package/src/postgres/schema.ts +35 -0
  51. package/src/postgres/table.ts +307 -41
  52. package/src/postgres/type.ts +4 -0
  53. package/src/postgres.ts +14 -0
  54. 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.generatedValue) {
58
- clauses.push(`generated always as (${renderExpression(column.metadata.generatedValue, state, dialect)}) stored`)
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 ${renderExpression(column.metadata.defaultValue, state, dialect)}`)
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 (${renderCheckPredicate(option.predicate, state, dialect)})`
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 ? `cast(${queried} as text)` : queried
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} ?| ${renderPostgresJsonPathArray(keys, state, dialect)})`
430
+ return `(${baseSql} ?| array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
391
431
  }
392
432
  if (kind === "jsonHasAllKeys") {
393
- return `(${baseSql} ?& ${renderPostgresJsonPathArray(keys, state, dialect)})`
433
+ return `(${baseSql} ?& array[${keys.map((key) => renderPostgresTextLiteral(String(key), state, dialect)).join(", ")}])`
394
434
  }
395
- return `(${baseSql} ? ${dialect.renderLiteral(keys[0]!, state)})`
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 `jsonb_build_object(${renderedEntries.join(", ")})`
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 `jsonb_build_array(${renderedValues})`
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
- return `jsonb_typeof(${renderPostgresJsonValue(base, state, dialect)})`
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 jsonb = renderPostgresJsonValue(base, state, dialect)
485
- return `(case when jsonb_typeof(${jsonb}) = 'array' then jsonb_array_length(${jsonb}) when jsonb_typeof(${jsonb}) = 'object' then jsonb_object_length(${jsonb}) else null end)`
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 jsonb = renderPostgresJsonValue(base, state, dialect)
497
- return `(case when jsonb_typeof(${jsonb}) = 'object' then array(select jsonb_object_keys(${jsonb})) else null end)`
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 `jsonb_strip_nulls(${renderPostgresJsonValue(base, state, dialect)})`
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
- switch (ast.kind) {
1250
+ switch (ast.kind) {
1205
1251
  case "column":
1206
- return `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
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: ColumnList
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: AnyExpression
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
- if (option.columns.length === 0) {
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 option.columns) {
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 !== option.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": {