uql-orm 0.55.0 → 0.57.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 (109) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +4 -4
  4. package/dist/bunSql/bunSql.util.js +1 -1
  5. package/dist/cockroachdb/cockroachDialect.d.ts +5 -2
  6. package/dist/cockroachdb/cockroachDialect.js +2 -10
  7. package/dist/d1/d1SqliteDialect.d.ts +1 -0
  8. package/dist/d1/d1SqliteDialect.js +2 -0
  9. package/dist/dialect/abstractSqlDialect.d.ts +195 -32
  10. package/dist/dialect/abstractSqlDialect.js +406 -199
  11. package/dist/dialect/aliases.d.ts +10 -7
  12. package/dist/dialect/aliases.js +12 -7
  13. package/dist/dialect/hydrateColumn.d.ts +8 -2
  14. package/dist/dialect/hydrateColumn.js +33 -1
  15. package/dist/dialect/jsonSql.d.ts +13 -5
  16. package/dist/dialect/jsonSql.js +24 -7
  17. package/dist/dialect/mysqlLikeSqlDialect.d.ts +30 -2
  18. package/dist/dialect/mysqlLikeSqlDialect.js +58 -7
  19. package/dist/dialect/pgLikeSqlDialect.d.ts +20 -20
  20. package/dist/dialect/pgLikeSqlDialect.js +25 -50
  21. package/dist/dialect/pgVectorMetrics.d.ts +13 -0
  22. package/dist/dialect/pgVectorMetrics.js +17 -0
  23. package/dist/dialect/queryContext.d.ts +3 -7
  24. package/dist/dialect/queryContext.js +13 -8
  25. package/dist/dialect/queryJoins.d.ts +8 -4
  26. package/dist/dialect/queryJoins.js +27 -15
  27. package/dist/dialect/vectorSqlDialect.d.ts +2 -2
  28. package/dist/dialect/vectorSqlDialect.js +2 -3
  29. package/dist/entity/index.d.ts +1 -1
  30. package/dist/entity/index.js +1 -1
  31. package/dist/entity/metadata/definition.d.ts +4 -2
  32. package/dist/entity/metadata/definition.js +27 -29
  33. package/dist/maria/mariaDialect.d.ts +13 -6
  34. package/dist/maria/mariaDialect.js +29 -9
  35. package/dist/migrate/builder/splitSqlStatements.js +2 -2
  36. package/dist/migrate/cli.d.ts +2 -3
  37. package/dist/migrate/cli.js +4 -11
  38. package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
  39. package/dist/migrate/ddl/index.d.ts +1 -5
  40. package/dist/migrate/ddl/index.js +14 -25
  41. package/dist/migrate/ddl/indexDdl.d.ts +11 -2
  42. package/dist/migrate/ddl/indexDdl.js +17 -1
  43. package/dist/migrate/ddl/mssqlIndexDdl.d.ts +10 -0
  44. package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
  45. package/dist/migrate/ddl/mysqlIndexDdl.d.ts +10 -17
  46. package/dist/migrate/ddl/mysqlIndexDdl.js +16 -27
  47. package/dist/migrate/ddl/pgIndexDdl.d.ts +18 -3
  48. package/dist/migrate/ddl/pgIndexDdl.js +29 -3
  49. package/dist/migrate/drift/driftDetector.js +21 -8
  50. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  51. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
  52. package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
  53. package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
  54. package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
  55. package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
  56. package/dist/migrate/migrator.d.ts +7 -4
  57. package/dist/migrate/migrator.js +9 -14
  58. package/dist/migrate/schemaGenerator.d.ts +3 -3
  59. package/dist/migrate/schemaGenerator.js +7 -13
  60. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -3
  61. package/dist/mongo/mongoDialect.d.ts +31 -18
  62. package/dist/mongo/mongoDialect.js +147 -108
  63. package/dist/mongo/mongodbQuerier.d.ts +10 -17
  64. package/dist/mongo/mongodbQuerier.js +34 -108
  65. package/dist/mssql/mssqlDialect.d.ts +16 -0
  66. package/dist/mssql/mssqlDialect.js +26 -4
  67. package/dist/mysql/mysqlDialect.d.ts +2 -0
  68. package/dist/mysql/mysqlDialect.js +4 -0
  69. package/dist/querier/abstractQuerier.d.ts +20 -36
  70. package/dist/querier/abstractQuerier.js +44 -143
  71. package/dist/querier/abstractQuerierPool.d.ts +2 -2
  72. package/dist/querier/abstractSqlQuerier.d.ts +11 -22
  73. package/dist/querier/abstractSqlQuerier.js +49 -53
  74. package/dist/schema/canonicalType.js +4 -6
  75. package/dist/schema/dependencyGraph.js +2 -4
  76. package/dist/schema/indexDifferences.js +5 -5
  77. package/dist/schema/schemaASTBuilder.js +34 -12
  78. package/dist/schema/schemaASTDiffer.d.ts +10 -2
  79. package/dist/schema/schemaASTDiffer.js +17 -16
  80. package/dist/schema/types.d.ts +1 -1
  81. package/dist/sqlite/sqliteDialect.d.ts +20 -1
  82. package/dist/sqlite/sqliteDialect.js +40 -8
  83. package/dist/turso/tursoDialect.d.ts +2 -0
  84. package/dist/turso/tursoDialect.js +2 -0
  85. package/dist/type/config.d.ts +2 -2
  86. package/dist/type/dialect.d.ts +4 -5
  87. package/dist/type/entity.d.ts +2 -1
  88. package/dist/type/migratorDialect.d.ts +4 -0
  89. package/dist/type/querier.d.ts +6 -6
  90. package/dist/type/query.d.ts +25 -48
  91. package/dist/type/query.js +10 -5
  92. package/dist/type/queryAggregate.d.ts +10 -10
  93. package/dist/type/queryAggregate.js +1 -1
  94. package/dist/type/universalQuerier.d.ts +4 -4
  95. package/dist/util/dialect.util.d.ts +8 -2
  96. package/dist/util/dialect.util.js +19 -0
  97. package/dist/util/field.util.d.ts +5 -0
  98. package/dist/util/field.util.js +19 -0
  99. package/dist/util/logger.d.ts +10 -1
  100. package/dist/util/logger.js +18 -0
  101. package/dist/util/object.util.d.ts +4 -0
  102. package/dist/util/object.util.js +8 -0
  103. package/dist/util/relationQuery.util.d.ts +15 -68
  104. package/dist/util/relationQuery.util.js +35 -83
  105. package/dist/util/rowKey.util.d.ts +1 -11
  106. package/dist/util/rowKey.util.js +1 -13
  107. package/package.json +1 -1
  108. package/dist/querier/relationCount.d.ts +0 -16
  109. package/dist/querier/relationCount.js +0 -121
@@ -1,5 +1,5 @@
1
1
  import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
2
- import type { IndexType } from '../../schema/types.js';
2
+ import { type IndexType } from '../../schema/types.js';
3
3
  import { type IndexColumnSchema, type IndexFeature, type IndexJsonArray, type IndexSchema } from '../../type/index.js';
4
4
  /**
5
5
  * `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
@@ -20,6 +20,15 @@ export declare class IndexDdl<D extends AbstractSqlDialect = AbstractSqlDialect>
20
20
  * emitted: each of them is a hard error at the server, not a slower plan.
21
21
  */
22
22
  protected readonly indexFeatures: ReadonlySet<IndexFeature>;
23
+ /**
24
+ * Index types this dialect's `CREATE INDEX` takes. SQLite's grammar has no `USING` clause, so every
25
+ * type builds the plain index it has there, which is what lets an entity written for Postgres
26
+ * migrate unchanged. An engine that would reject a type narrows this, and the type is refused.
27
+ */
28
+ protected readonly indexTypes: ReadonlySet<IndexType>;
29
+ /** What to declare instead of a type this dialect lacks, appended to its refusal. */
30
+ protected readonly indexTypeHints: ReadonlyMap<IndexType, string>;
31
+ private assertIndexType;
23
32
  private assertIndexFeatures;
24
33
  /**
25
34
  * Index types this dialect spells as a keyword of their own (`FULLTEXT INDEX`, `VECTOR INDEX`)
@@ -51,7 +60,7 @@ export declare class IndexDdl<D extends AbstractSqlDialect = AbstractSqlDialect>
51
60
  protected indexInclude(_index: IndexSchema): string;
52
61
  /** ` USING <method>`, which SQLite's grammar has no place for at all. */
53
62
  protected indexAccessMethod(_index: IndexSchema): string;
54
- /** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
63
+ /** What trails the columns: pgvector's ` WITH (m = ...)`, MySQL's ` USING btree`, MariaDB's ` M=8`. */
55
64
  protected indexTuning(_index: IndexSchema): string;
56
65
  /**
57
66
  * The partial-index predicate. Engines without one reject the index in {@link assertIndexFeatures}
@@ -1,4 +1,5 @@
1
1
  import { jsonTypeMode } from '../../dialect/jsonSql.js';
2
+ import { INDEX_TYPES } from '../../schema/types.js';
2
3
  import { INDEX_FEATURE_LABELS, } from '../../type/index.js';
3
4
  import { getKeys } from '../../util/index.js';
4
5
  /**
@@ -29,6 +30,7 @@ export class IndexDdl {
29
30
  this.dialect = dialect;
30
31
  }
31
32
  getCreateIndexStatement(tableName, index, opts = {}) {
33
+ this.assertIndexType(index);
32
34
  this.assertIndexFeatures(index);
33
35
  const unique = index.unique ? 'UNIQUE ' : '';
34
36
  const ifNotExists = (opts.ifNotExists ?? this.dialect.features.indexIfNotExists) ? 'IF NOT EXISTS ' : '';
@@ -47,6 +49,20 @@ export class IndexDdl {
47
49
  'partial',
48
50
  'jsonPath',
49
51
  ]);
52
+ /**
53
+ * Index types this dialect's `CREATE INDEX` takes. SQLite's grammar has no `USING` clause, so every
54
+ * type builds the plain index it has there, which is what lets an entity written for Postgres
55
+ * migrate unchanged. An engine that would reject a type narrows this, and the type is refused.
56
+ */
57
+ indexTypes = new Set(INDEX_TYPES);
58
+ /** What to declare instead of a type this dialect lacks, appended to its refusal. */
59
+ indexTypeHints = new Map();
60
+ assertIndexType(index) {
61
+ if (index.type && !this.indexTypes.has(index.type)) {
62
+ throw new TypeError(`${this.dialect.dialectName} has no ${index.type} index (index "${index.name}")` +
63
+ (this.indexTypeHints.get(index.type) ?? ''));
64
+ }
65
+ }
50
66
  assertIndexFeatures(index) {
51
67
  for (const feature of getKeys(INDEX_FEATURE_PROBES)) {
52
68
  if (INDEX_FEATURE_PROBES[feature](index) && !this.indexFeatures.has(feature)) {
@@ -111,7 +127,7 @@ export class IndexDdl {
111
127
  indexAccessMethod(_index) {
112
128
  return '';
113
129
  }
114
- /** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
130
+ /** What trails the columns: pgvector's ` WITH (m = ...)`, MySQL's ` USING btree`, MariaDB's ` M=8`. */
115
131
  indexTuning(_index) {
116
132
  return '';
117
133
  }
@@ -0,0 +1,10 @@
1
+ import { IndexDdl } from './indexDdl.js';
2
+ /**
3
+ * SQL Server's `CREATE INDEX` is the portable form minus what 2025 rejects: an expression (Msg 16216),
4
+ * the subquery a JSON path compiles to (Msg 1046), and any type but the plain rowstore B-tree, since
5
+ * the index built in its place fails on a `VECTOR` or `nvarchar(max)` column (Msg 1978).
6
+ */
7
+ export declare class MsSqlIndexDdl extends IndexDdl {
8
+ protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
9
+ protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
10
+ }
@@ -0,0 +1,10 @@
1
+ import { IndexDdl } from './indexDdl.js';
2
+ /**
3
+ * SQL Server's `CREATE INDEX` is the portable form minus what 2025 rejects: an expression (Msg 16216),
4
+ * the subquery a JSON path compiles to (Msg 1046), and any type but the plain rowstore B-tree, since
5
+ * the index built in its place fails on a `VECTOR` or `nvarchar(max)` column (Msg 1978).
6
+ */
7
+ export class MsSqlIndexDdl extends IndexDdl {
8
+ indexFeatures = new Set(['partial']);
9
+ indexTypes = new Set(['btree']);
10
+ }
@@ -1,19 +1,13 @@
1
1
  import type { IndexType } from '../../schema/types.js';
2
2
  import type { IndexJsonArray, IndexSchema } from '../../type/index.js';
3
3
  import { IndexDdl } from './indexDdl.js';
4
- /** `CREATE INDEX ... USING btree`, plus the types this family spells as a keyword instead. */
4
+ /** `CREATE INDEX ... (cols) USING btree`, plus the types this family spells as a keyword instead. */
5
5
  export declare class MysqlLikeIndexDdl extends IndexDdl {
6
6
  protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
7
+ protected readonly indexTypes: ReadonlySet<IndexType>;
7
8
  protected readonly indexTypeKeywords: ReadonlyMap<IndexType, string>;
8
- /**
9
- * ` USING btree|hash`, for the types this family does *not* spell as a keyword of its own. A vector
10
- * type that is neither is one this engine has no index for at all, refused here rather than
11
- * compiled into a ` USING hnsw` the server can only answer with a syntax error: which of them a
12
- * dialect *does* have is `indexTypeKeywords`, so declaring one there is all it takes to serve it.
13
- */
14
- protected indexAccessMethod(index: IndexSchema): string;
15
- /** What to do instead, appended to the refusal above. */
16
- protected readonly vectorIndexHint: string;
9
+ /** ` USING btree|hash` trails the columns: between the table and them, it is a syntax error here. */
10
+ protected indexTuning(index: IndexSchema): string;
17
11
  }
18
12
  export declare class MySqlIndexDdl extends MysqlLikeIndexDdl {
19
13
  /** The multi-valued index is the only JSON index MySQL has - see `IndexFeature` for why. */
@@ -25,12 +19,10 @@ export declare class MySqlIndexDdl extends MysqlLikeIndexDdl {
25
19
  */
26
20
  protected jsonArrayIndexExpr(escapedColumn: string, json: IndexJsonArray): string;
27
21
  /**
28
- * MySQL has no vector index of any kind, so one is refused rather than compiled to DDL the server
29
- * rejects: `USING hnsw` is a syntax error, and MariaDB's `VECTOR INDEX` is not MySQL syntax either.
30
- * Verified against 26.7, which does have `VECTOR` columns and `STRING_TO_VECTOR`, but no distance
31
- * function outside HeatWave - hence nothing to index for.
22
+ * MySQL 26.7 has `VECTOR` columns and `STRING_TO_VECTOR`, but no distance function outside
23
+ * HeatWave, hence no vector index to build: `USING hnsw` is a syntax error, `VECTOR INDEX` MariaDB's.
32
24
  */
33
- protected readonly vectorIndexHint = ". Vector search on MySQL needs HeatWave";
25
+ protected readonly indexTypeHints: Map<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
34
26
  }
35
27
  export declare class MariaIndexDdl extends MysqlLikeIndexDdl {
36
28
  /**
@@ -41,12 +33,13 @@ export declare class MariaIndexDdl extends MysqlLikeIndexDdl {
41
33
  protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
42
34
  /** The family's, plus a vector index of its own: `CREATE VECTOR INDEX ... ON t (col)`, 11.7+. */
43
35
  protected readonly indexTypeKeywords: ReadonlyMap<IndexType, string>;
36
+ protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
37
+ /** pgvector's names are not access methods it has; `vector` is its own keyword above. */
38
+ protected readonly indexTypeHints: Map<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
44
39
  /**
45
40
  * `M=n DISTANCE=metric`, trailing its `CREATE VECTOR INDEX`. The metric names are MariaDB's own
46
41
  * (`euclidean`, not `l2`), and an unsupported one throws rather than being dropped, which would
47
42
  * silently build the index on euclidean - its default - instead of what the entity asked for.
48
43
  */
49
44
  protected indexTuning(index: IndexSchema): string;
50
- /** `vector` is its own keyword above; pgvector's names are not access methods it has. */
51
- protected readonly vectorIndexHint = "; declare type: 'vector' instead";
52
45
  }
@@ -1,34 +1,21 @@
1
1
  import { jsonPath } from '../../dialect/jsonSql.js';
2
2
  import { MARIA_VECTOR_METRICS } from '../../maria/mariaVectorMetrics.js';
3
- import { isVectorIndexType, unsupportedVectorMetric } from '../../type/vector.js';
3
+ import { unsupportedVectorMetric, VECTOR_INDEX_TYPES } from '../../type/vector.js';
4
4
  import { IndexDdl } from './indexDdl.js';
5
5
  /**
6
6
  * A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext` is
7
7
  * a syntax error, so it is the keyword that changes rather than the access method.
8
8
  */
9
9
  const MYSQL_LIKE_INDEX_KEYWORDS = new Map([['fulltext', 'FULLTEXT INDEX']]);
10
- /** `CREATE INDEX ... USING btree`, plus the types this family spells as a keyword instead. */
10
+ /** `CREATE INDEX ... (cols) USING btree`, plus the types this family spells as a keyword instead. */
11
11
  export class MysqlLikeIndexDdl extends IndexDdl {
12
12
  indexFeatures = new Set(['expression', 'prefixLength']);
13
+ indexTypes = new Set(['btree', 'hash', 'fulltext']);
13
14
  indexTypeKeywords = MYSQL_LIKE_INDEX_KEYWORDS;
14
- /**
15
- * ` USING btree|hash`, for the types this family does *not* spell as a keyword of its own. A vector
16
- * type that is neither is one this engine has no index for at all, refused here rather than
17
- * compiled into a ` USING hnsw` the server can only answer with a syntax error: which of them a
18
- * dialect *does* have is `indexTypeKeywords`, so declaring one there is all it takes to serve it.
19
- */
20
- indexAccessMethod(index) {
21
- const type = index.type;
22
- if (!type || this.indexTypeKeywords.has(type)) {
23
- return '';
24
- }
25
- if (isVectorIndexType(type)) {
26
- throw new TypeError(`${this.dialect.dialectName} has no ${type} index (index "${index.name}")${this.vectorIndexHint}`);
27
- }
28
- return ` USING ${type}`;
15
+ /** ` USING btree|hash` trails the columns: between the table and them, it is a syntax error here. */
16
+ indexTuning(index) {
17
+ return index.type && !this.indexTypeKeywords.has(index.type) ? ` USING ${index.type}` : '';
29
18
  }
30
- /** What to do instead, appended to the refusal above. */
31
- vectorIndexHint = '';
32
19
  }
33
20
  export class MySqlIndexDdl extends MysqlLikeIndexDdl {
34
21
  /** The multi-valued index is the only JSON index MySQL has - see `IndexFeature` for why. */
@@ -43,12 +30,10 @@ export class MySqlIndexDdl extends MysqlLikeIndexDdl {
43
30
  return `CAST(${source} AS ${arrayCastType(json)} ARRAY)`;
44
31
  }
45
32
  /**
46
- * MySQL has no vector index of any kind, so one is refused rather than compiled to DDL the server
47
- * rejects: `USING hnsw` is a syntax error, and MariaDB's `VECTOR INDEX` is not MySQL syntax either.
48
- * Verified against 26.7, which does have `VECTOR` columns and `STRING_TO_VECTOR`, but no distance
49
- * function outside HeatWave - hence nothing to index for.
33
+ * MySQL 26.7 has `VECTOR` columns and `STRING_TO_VECTOR`, but no distance function outside
34
+ * HeatWave, hence no vector index to build: `USING hnsw` is a syntax error, `VECTOR INDEX` MariaDB's.
50
35
  */
51
- vectorIndexHint = '. Vector search on MySQL needs HeatWave';
36
+ indexTypeHints = new Map(VECTOR_INDEX_TYPES.map((type) => [type, '. Vector search on MySQL needs HeatWave']));
52
37
  }
53
38
  export class MariaIndexDdl extends MysqlLikeIndexDdl {
54
39
  /**
@@ -62,13 +47,19 @@ export class MariaIndexDdl extends MysqlLikeIndexDdl {
62
47
  ...MYSQL_LIKE_INDEX_KEYWORDS,
63
48
  ['vector', 'VECTOR INDEX'],
64
49
  ]);
50
+ indexTypes = new Set(['btree', 'hash', 'fulltext', 'vector']);
51
+ /** pgvector's names are not access methods it has; `vector` is its own keyword above. */
52
+ indexTypeHints = new Map([
53
+ ['hnsw', "; declare type: 'vector' instead"],
54
+ ['ivfflat', "; declare type: 'vector' instead"],
55
+ ]);
65
56
  /**
66
57
  * `M=n DISTANCE=metric`, trailing its `CREATE VECTOR INDEX`. The metric names are MariaDB's own
67
58
  * (`euclidean`, not `l2`), and an unsupported one throws rather than being dropped, which would
68
59
  * silently build the index on euclidean - its default - instead of what the entity asked for.
69
60
  */
70
61
  indexTuning(index) {
71
- let tuning = index.m === undefined ? '' : ` M=${index.m}`;
62
+ let tuning = super.indexTuning(index) + (index.m === undefined ? '' : ` M=${index.m}`);
72
63
  if (index.distance) {
73
64
  const metric = MARIA_VECTOR_METRICS.get(index.distance);
74
65
  if (!metric) {
@@ -78,8 +69,6 @@ export class MariaIndexDdl extends MysqlLikeIndexDdl {
78
69
  }
79
70
  return tuning;
80
71
  }
81
- /** `vector` is its own keyword above; pgvector's names are not access methods it has. */
82
- vectorIndexHint = "; declare type: 'vector' instead";
83
72
  }
84
73
  /**
85
74
  * MySQL's `CAST(... AS <type> ARRAY)` targets, the closed list its multi-valued index takes: no
@@ -1,9 +1,16 @@
1
- import type { PgLikeSqlDialect } from '../../dialect/pgLikeSqlDialect.js';
2
1
  import type { IndexColumnSchema, IndexSchema } from '../../type/index.js';
3
2
  import { IndexDdl } from './indexDdl.js';
4
3
  /** `CREATE INDEX ... USING hnsw ("embedding" vector_cosine_ops) WITH (m = ...)`, pgvector's form. */
5
- export declare class PgIndexDdl extends IndexDdl<PgLikeSqlDialect> {
4
+ export declare class PgIndexDdl extends IndexDdl {
5
+ /** Postgres 18's `pg_am`, with pgvector's two. */
6
+ protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
7
+ protected readonly indexTypeHints: ReadonlyMap<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
6
8
  protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
9
+ /** The metrics its vector index takes, each naming the operator class it is built with. */
10
+ protected readonly vectorMetrics: ReadonlyMap<import("../../type/vector.js").VectorDistance, {
11
+ readonly op: string;
12
+ readonly opsSuffix: string;
13
+ }>;
7
14
  /** pgvector's own index types; CockroachDB's native one widens this. */
8
15
  protected isVectorIndex(index: IndexSchema): boolean;
9
16
  protected indexAccessMethod(index: IndexSchema): string;
@@ -28,9 +35,17 @@ export declare class PgIndexDdl extends IndexDdl<PgLikeSqlDialect> {
28
35
  */
29
36
  export declare class CockroachIndexDdl extends PgIndexDdl {
30
37
  protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
38
+ /** v26.3 answers `hash` and `brin` "unimplemented", `ivfflat` "unrecognized"; `hnsw` builds its vector index. */
39
+ protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
40
+ protected readonly indexTypeHints: Map<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
41
+ protected readonly vectorMetrics: ReadonlyMap<import("../../type/vector.js").VectorDistance, {
42
+ readonly op: string;
43
+ readonly opsSuffix: string;
44
+ }>;
31
45
  private isNativeVectorIndex;
32
46
  protected isVectorIndex(index: IndexSchema): boolean;
33
47
  protected indexKeyword(index: IndexSchema): string;
34
48
  protected indexAccessMethod(index: IndexSchema): string;
35
- protected indexTuning(index: IndexSchema): string;
49
+ /** None of pgvector's knobs: `WITH (m = 16)` answers "invalid storage parameter", `hnsw` included. */
50
+ protected indexTuning(): string;
36
51
  }
@@ -1,7 +1,23 @@
1
+ import { COCKROACH_VECTOR_METRICS, PG_VECTOR_METRICS } from '../../dialect/pgVectorMetrics.js';
1
2
  import { unsupportedVectorMetric } from '../../type/vector.js';
2
3
  import { IndexDdl } from './indexDdl.js';
4
+ /** `$text` computes its `TO_TSVECTOR` per row, which no index over the raw columns serves. */
5
+ const PG_INDEX_TYPE_HINTS = new Map([
6
+ ['fulltext', '. $text needs none there; name the columns it searches with $fields.'],
7
+ ]);
3
8
  /** `CREATE INDEX ... USING hnsw ("embedding" vector_cosine_ops) WITH (m = ...)`, pgvector's form. */
4
9
  export class PgIndexDdl extends IndexDdl {
10
+ /** Postgres 18's `pg_am`, with pgvector's two. */
11
+ indexTypes = new Set([
12
+ 'btree',
13
+ 'hash',
14
+ 'gin',
15
+ 'gist',
16
+ 'brin',
17
+ 'hnsw',
18
+ 'ivfflat',
19
+ ]);
20
+ indexTypeHints = PG_INDEX_TYPE_HINTS;
5
21
  indexFeatures = new Set([
6
22
  'expression',
7
23
  'partial',
@@ -10,6 +26,8 @@ export class PgIndexDdl extends IndexDdl {
10
26
  'include',
11
27
  'jsonPath',
12
28
  ]);
29
+ /** The metrics its vector index takes, each naming the operator class it is built with. */
30
+ vectorMetrics = PG_VECTOR_METRICS;
13
31
  /** pgvector's own index types; CockroachDB's native one widens this. */
14
32
  isVectorIndex(index) {
15
33
  return index.type === 'hnsw' || index.type === 'ivfflat';
@@ -28,7 +46,7 @@ export class PgIndexDdl extends IndexDdl {
28
46
  if (!this.isVectorIndex(index) || !index.distance) {
29
47
  return entry.opsClass ? ` ${entry.opsClass}` : '';
30
48
  }
31
- const metric = this.dialect.vectorMetrics.get(index.distance);
49
+ const metric = this.vectorMetrics.get(index.distance);
32
50
  if (!metric) {
33
51
  throw unsupportedVectorMetric(this.dialect.dialectName, index.distance, index.name);
34
52
  }
@@ -69,6 +87,13 @@ export class PgIndexDdl extends IndexDdl {
69
87
  */
70
88
  export class CockroachIndexDdl extends PgIndexDdl {
71
89
  indexFeatures = new Set(['expression', 'partial', 'include', 'jsonPath']);
90
+ /** v26.3 answers `hash` and `brin` "unimplemented", `ivfflat` "unrecognized"; `hnsw` builds its vector index. */
91
+ indexTypes = new Set(['btree', 'gin', 'gist', 'hnsw', 'vector']);
92
+ indexTypeHints = new Map([
93
+ ...PG_INDEX_TYPE_HINTS,
94
+ ['ivfflat', "; declare type: 'vector' instead"],
95
+ ]);
96
+ vectorMetrics = COCKROACH_VECTOR_METRICS;
72
97
  isNativeVectorIndex(index) {
73
98
  return index.type === 'vector';
74
99
  }
@@ -81,7 +106,8 @@ export class CockroachIndexDdl extends PgIndexDdl {
81
106
  indexAccessMethod(index) {
82
107
  return this.isNativeVectorIndex(index) ? '' : super.indexAccessMethod(index);
83
108
  }
84
- indexTuning(index) {
85
- return this.isNativeVectorIndex(index) ? '' : super.indexTuning(index);
109
+ /** None of pgvector's knobs: `WITH (m = 16)` answers "invalid storage parameter", `hnsw` included. */
110
+ indexTuning() {
111
+ return '';
86
112
  }
87
113
  }
@@ -5,7 +5,7 @@
5
5
  * actual database schema.
6
6
  */
7
7
  import { canonicalToSql, engineType } from '../../schema/canonicalType.js';
8
- import { diffSchemas } from '../../schema/schemaASTDiffer.js';
8
+ import { diffSchemas, referentialActions } from '../../schema/schemaASTDiffer.js';
9
9
  function resolveOptions(options) {
10
10
  return {
11
11
  checkTypes: options.checkTypes ?? true,
@@ -114,7 +114,7 @@ function detectColumnDrifts(diff, opts) {
114
114
  suggestion: 'Add to entity or create migration to drop',
115
115
  });
116
116
  }
117
- else if (colDiff.type === 'alter') {
117
+ else {
118
118
  addAlterColumnDrifts(colDiff, drifts, opts);
119
119
  }
120
120
  }
@@ -124,10 +124,6 @@ function detectColumnDrifts(diff, opts) {
124
124
  * Add drifts for column alterations (type/nullable mismatches).
125
125
  */
126
126
  function addAlterColumnDrifts(colDiff, drifts, opts) {
127
- // Every check below compares the two sides, so there is nothing to report without both.
128
- if (!colDiff.expected || !colDiff.actual) {
129
- return;
130
- }
131
127
  // An auto-increment key is created through the dialect's `serialPrimaryKey`, whose spelling the
132
128
  // entity never states - `BIGINT UNSIGNED AUTO_INCREMENT` on the MySQL family, where the column then
133
129
  // reads back as `BIGINT UNSIGNED` against an entity that can only say `BIGINT`. Comparing the two
@@ -206,7 +202,7 @@ function detectIndexDrifts(diff) {
206
202
  suggestion: 'Add @Field({ index }) or create migration to drop',
207
203
  });
208
204
  }
209
- else if (idxDiff.type === 'alter') {
205
+ else {
210
206
  // No `expected`/`actual` here: the CLI prints those by interpolation, where an `IndexNode`
211
207
  // renders as `[object Object]`. What differs is already spelled out in `description`.
212
208
  drifts.push({
@@ -247,9 +243,26 @@ function detectRelationshipDrifts(diff) {
247
243
  suggestion: 'Add relation to entity or drop FK',
248
244
  });
249
245
  }
246
+ else {
247
+ drifts.push({
248
+ type: 'relationship_mismatch',
249
+ severity: 'warning',
250
+ table: relDiff.fromTable,
251
+ relationship: relDiff.name,
252
+ expected: formatActions(relDiff.expected),
253
+ actual: formatActions(relDiff.actual),
254
+ details: `FK "${relDiff.name}" has other referential actions in the database than in the entity`,
255
+ suggestion: 'Generate a migration, which drops and re-adds the constraint',
256
+ });
257
+ }
250
258
  }
251
259
  return drifts;
252
260
  }
261
+ /** Both actions spelled out, so a side that left one unstated reads the same as one that stated the default. */
262
+ function formatActions(rel) {
263
+ const { onDelete, onUpdate } = referentialActions(rel);
264
+ return `ON DELETE ${onDelete} ON UPDATE ${onUpdate}`;
265
+ }
253
266
  /**
254
267
  * Calculate overall status based on drifts.
255
268
  */
@@ -275,7 +288,7 @@ function createSummary(drifts) {
275
288
  * Format type for display.
276
289
  */
277
290
  function formatType(type, dialect) {
278
- if (!type || !dialect)
291
+ if (!dialect)
279
292
  return 'unknown';
280
293
  return canonicalToSql(type, dialect);
281
294
  }
@@ -147,7 +147,7 @@ export class MongoSchemaGenerator extends AbstractDialect {
147
147
  if (!currentTable) {
148
148
  return { tableName: collectionName, type: 'create' };
149
149
  }
150
- const existingIndexes = new Set(currentTable.indexes?.map((i) => i.name) ?? []);
150
+ const existingIndexes = new Set(currentTable.indexes.map((i) => i.name));
151
151
  const indexesToAdd = this.fieldIndexes(meta, collectionName).filter((index) => !existingIndexes.has(index.name));
152
152
  if (indexesToAdd.length === 0) {
153
153
  return undefined;
@@ -29,7 +29,7 @@ export declare abstract class BaseSqlIntrospector {
29
29
  * Build SchemaAST from table schemas.
30
30
  */
31
31
  protected buildAST(tableSchemas: TableSchema[]): SchemaAST;
32
- private buildTables;
32
+ private buildTable;
33
33
  private buildRelationships;
34
34
  private buildIndexes;
35
35
  }
@@ -43,90 +43,79 @@ export class BaseSqlIntrospector {
43
43
  */
44
44
  buildAST(tableSchemas) {
45
45
  const ast = new SchemaAST();
46
- const tableNodes = new Map();
47
- this.buildTables(ast, tableNodes, tableSchemas);
48
- this.buildRelationships(ast, tableNodes, tableSchemas);
49
- this.buildIndexes(ast, tableNodes, tableSchemas);
46
+ // Every table first, so a foreign key reaches a table introspected after the one it leaves from.
47
+ const tables = tableSchemas.map((schema) => ({ schema, table: this.buildTable(schema) }));
48
+ const tableNodes = new Map(tables.map(({ schema, table }) => [schema.name, table]));
49
+ for (const { table } of tables) {
50
+ ast.addTable(table);
51
+ }
52
+ for (const { schema, table } of tables) {
53
+ this.buildRelationships(ast, tableNodes, schema, table);
54
+ this.buildIndexes(ast, schema, table);
55
+ }
50
56
  return ast;
51
57
  }
52
- buildTables(ast, tableNodes, tableSchemas) {
53
- for (const schema of tableSchemas) {
54
- const table = createTableNode(schema.name, this.schema);
55
- const { columns } = table;
56
- for (const col of schema.columns) {
57
- // Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
58
- // everything but the type crosses unchanged and a field either shape gains cannot be dropped
59
- // here. Listed by hand this had already lost `enum` and `generatedAs`.
60
- const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
61
- const column = {
62
- ...rest,
63
- type: canonicalColumnType(type, col),
64
- table,
65
- referencedBy: [],
66
- };
67
- columns.set(col.name, column);
68
- }
69
- // From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
70
- // different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
71
- // to the flags for an introspector that reports no key of its own.
72
- const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
73
- table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
74
- table.primaryKeyName = schema.primaryKeyName;
75
- tableNodes.set(schema.name, table);
76
- ast.addTable(table);
58
+ buildTable(schema) {
59
+ const table = createTableNode(schema.name, this.schema);
60
+ const { columns } = table;
61
+ for (const col of schema.columns) {
62
+ // Spread, not field by field: a `ColumnSchema` is a `ColumnNode` minus the graph links, so
63
+ // everything but the type crosses unchanged and a field either shape gains cannot be dropped
64
+ // here. Listed by hand this had already lost `enum` and `generatedAs`.
65
+ const { type, length: _length, precision: _precision, scale: _scale, ...rest } = col;
66
+ const column = {
67
+ ...rest,
68
+ type: canonicalColumnType(type, col),
69
+ table,
70
+ referencedBy: [],
71
+ };
72
+ columns.set(col.name, column);
77
73
  }
74
+ // From the ordered list the query returned, not from the per-column flags: `(a, b)` is a
75
+ // different key from `(b, a)`, and a flag says only that a column is *in* the key. Falls back
76
+ // to the flags for an introspector that reports no key of its own.
77
+ const keyColumns = schema.primaryKey ?? schema.columns.filter((col) => col.isPrimaryKey).map((col) => col.name);
78
+ table.primaryKey.push(...keyColumns.flatMap((name) => columns.get(name) ?? []));
79
+ table.primaryKeyName = schema.primaryKeyName;
80
+ return table;
78
81
  }
79
- buildRelationships(ast, tableNodes, tableSchemas) {
80
- for (const schema of tableSchemas) {
81
- if (!schema.foreignKeys)
82
+ buildRelationships(ast, tableNodes, schema, fromTable) {
83
+ for (const fk of schema.foreignKeys ?? []) {
84
+ const toTable = tableNodes.get(fk.references.table);
85
+ if (!toTable)
82
86
  continue;
83
- const fromTable = tableNodes.get(schema.name);
84
- if (!fromTable)
85
- continue;
86
- for (const fk of schema.foreignKeys) {
87
- const toTable = tableNodes.get(fk.references.table);
88
- if (!toTable)
89
- continue;
90
- const fromColumns = fk.columns.flatMap((name) => fromTable.columns.get(name) ?? []);
91
- const toColumns = fk.references.columns.flatMap((name) => toTable.columns.get(name) ?? []);
92
- if (fromColumns.length > 0 && toColumns.length > 0) {
93
- const rel = {
94
- name: fk.name ?? derivedForeignKeyName(schema.name, fk.columns),
95
- type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
96
- from: { table: fromTable, columns: fromColumns },
97
- to: { table: toTable, columns: toColumns },
98
- onDelete: fk.onDelete || 'NO ACTION',
99
- onUpdate: fk.onUpdate || 'NO ACTION',
100
- };
101
- ast.addRelationship(rel);
102
- }
87
+ const fromColumns = fk.columns.flatMap((name) => fromTable.columns.get(name) ?? []);
88
+ const toColumns = fk.references.columns.flatMap((name) => toTable.columns.get(name) ?? []);
89
+ if (fromColumns.length > 0 && toColumns.length > 0) {
90
+ const rel = {
91
+ name: fk.name ?? derivedForeignKeyName(schema.name, fk.columns),
92
+ type: fromColumns[0].isUnique ? 'OneToOne' : 'ManyToOne',
93
+ from: { table: fromTable, columns: fromColumns },
94
+ to: { table: toTable, columns: toColumns },
95
+ onDelete: fk.onDelete || 'NO ACTION',
96
+ onUpdate: fk.onUpdate || 'NO ACTION',
97
+ };
98
+ ast.addRelationship(rel);
103
99
  }
104
100
  }
105
101
  }
106
- buildIndexes(ast, tableNodes, tableSchemas) {
107
- for (const schema of tableSchemas) {
108
- if (!schema.indexes)
109
- continue;
110
- const table = tableNodes.get(schema.name);
111
- if (!table)
112
- continue;
113
- for (const idx of schema.indexes) {
114
- // An expression has no column to resolve. Dropping the entries that name a column this table
115
- // does not have, and the index if that leaves none, is what the entity side does too.
116
- const entries = idx.entries.filter((entry) => entry.expression || table.columns.has(entry.column));
117
- if (entries.length > 0) {
118
- const index = {
119
- name: idx.name,
120
- table,
121
- entries,
122
- unique: idx.unique,
123
- type: idx.type,
124
- where: idx.where,
125
- include: idx.include,
126
- source: 'database',
127
- };
128
- ast.addIndex(index);
129
- }
102
+ buildIndexes(ast, schema, table) {
103
+ for (const idx of schema.indexes ?? []) {
104
+ // An expression has no column to resolve. Dropping the entries that name a column this table
105
+ // does not have, and the index if that leaves none, is what the entity side does too.
106
+ const entries = idx.entries.filter((entry) => entry.expression || table.columns.has(entry.column));
107
+ if (entries.length > 0) {
108
+ const index = {
109
+ name: idx.name,
110
+ table,
111
+ entries,
112
+ unique: idx.unique,
113
+ type: idx.type,
114
+ where: idx.where,
115
+ include: idx.include,
116
+ source: 'database',
117
+ };
118
+ ast.addIndex(index);
130
119
  }
131
120
  }
132
121
  }
@@ -27,7 +27,8 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
27
27
  `;
28
28
  }
29
29
  parseTableExistsResult(results) {
30
- return (this.toNumber(results[0]?.count) ?? 0) > 0;
30
+ // No row, or no count in it, reads as `NaN`, which is not above zero.
31
+ return Number(results[0]?.count) > 0;
31
32
  }
32
33
  /**
33
34
  * From `sys` rather than `INFORMATION_SCHEMA`, which has no identity flag and no per-column view of