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.
- package/README.md +1 -1
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/bunSql/bunSql.util.js +1 -1
- package/dist/cockroachdb/cockroachDialect.d.ts +5 -2
- package/dist/cockroachdb/cockroachDialect.js +2 -10
- package/dist/d1/d1SqliteDialect.d.ts +1 -0
- package/dist/d1/d1SqliteDialect.js +2 -0
- package/dist/dialect/abstractSqlDialect.d.ts +195 -32
- package/dist/dialect/abstractSqlDialect.js +406 -199
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +12 -7
- package/dist/dialect/hydrateColumn.d.ts +8 -2
- package/dist/dialect/hydrateColumn.js +33 -1
- package/dist/dialect/jsonSql.d.ts +13 -5
- package/dist/dialect/jsonSql.js +24 -7
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +30 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +58 -7
- package/dist/dialect/pgLikeSqlDialect.d.ts +20 -20
- package/dist/dialect/pgLikeSqlDialect.js +25 -50
- package/dist/dialect/pgVectorMetrics.d.ts +13 -0
- package/dist/dialect/pgVectorMetrics.js +17 -0
- package/dist/dialect/queryContext.d.ts +3 -7
- package/dist/dialect/queryContext.js +13 -8
- package/dist/dialect/queryJoins.d.ts +8 -4
- package/dist/dialect/queryJoins.js +27 -15
- package/dist/dialect/vectorSqlDialect.d.ts +2 -2
- package/dist/dialect/vectorSqlDialect.js +2 -3
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +4 -2
- package/dist/entity/metadata/definition.js +27 -29
- package/dist/maria/mariaDialect.d.ts +13 -6
- package/dist/maria/mariaDialect.js +29 -9
- package/dist/migrate/builder/splitSqlStatements.js +2 -2
- package/dist/migrate/cli.d.ts +2 -3
- package/dist/migrate/cli.js +4 -11
- package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
- package/dist/migrate/ddl/index.d.ts +1 -5
- package/dist/migrate/ddl/index.js +14 -25
- package/dist/migrate/ddl/indexDdl.d.ts +11 -2
- package/dist/migrate/ddl/indexDdl.js +17 -1
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +10 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +10 -17
- package/dist/migrate/ddl/mysqlIndexDdl.js +16 -27
- package/dist/migrate/ddl/pgIndexDdl.d.ts +18 -3
- package/dist/migrate/ddl/pgIndexDdl.js +29 -3
- package/dist/migrate/drift/driftDetector.js +21 -8
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
- package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
- package/dist/migrate/migrator.d.ts +7 -4
- package/dist/migrate/migrator.js +9 -14
- package/dist/migrate/schemaGenerator.d.ts +3 -3
- package/dist/migrate/schemaGenerator.js +7 -13
- package/dist/migrate/schemaGeneratorAsync.d.ts +2 -3
- package/dist/mongo/mongoDialect.d.ts +31 -18
- package/dist/mongo/mongoDialect.js +147 -108
- package/dist/mongo/mongodbQuerier.d.ts +10 -17
- package/dist/mongo/mongodbQuerier.js +34 -108
- package/dist/mssql/mssqlDialect.d.ts +16 -0
- package/dist/mssql/mssqlDialect.js +26 -4
- package/dist/mysql/mysqlDialect.d.ts +2 -0
- package/dist/mysql/mysqlDialect.js +4 -0
- package/dist/querier/abstractQuerier.d.ts +20 -36
- package/dist/querier/abstractQuerier.js +44 -143
- package/dist/querier/abstractQuerierPool.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.d.ts +11 -22
- package/dist/querier/abstractSqlQuerier.js +49 -53
- package/dist/schema/canonicalType.js +4 -6
- package/dist/schema/dependencyGraph.js +2 -4
- package/dist/schema/indexDifferences.js +5 -5
- package/dist/schema/schemaASTBuilder.js +34 -12
- package/dist/schema/schemaASTDiffer.d.ts +10 -2
- package/dist/schema/schemaASTDiffer.js +17 -16
- package/dist/schema/types.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.d.ts +20 -1
- package/dist/sqlite/sqliteDialect.js +40 -8
- package/dist/turso/tursoDialect.d.ts +2 -0
- package/dist/turso/tursoDialect.js +2 -0
- package/dist/type/config.d.ts +2 -2
- package/dist/type/dialect.d.ts +4 -5
- package/dist/type/entity.d.ts +2 -1
- package/dist/type/migratorDialect.d.ts +4 -0
- package/dist/type/querier.d.ts +6 -6
- package/dist/type/query.d.ts +25 -48
- package/dist/type/query.js +10 -5
- package/dist/type/queryAggregate.d.ts +10 -10
- package/dist/type/queryAggregate.js +1 -1
- package/dist/type/universalQuerier.d.ts +4 -4
- package/dist/util/dialect.util.d.ts +8 -2
- package/dist/util/dialect.util.js +19 -0
- package/dist/util/field.util.d.ts +5 -0
- package/dist/util/field.util.js +19 -0
- package/dist/util/logger.d.ts +10 -1
- package/dist/util/logger.js +18 -0
- package/dist/util/object.util.d.ts +4 -0
- package/dist/util/object.util.js +8 -0
- package/dist/util/relationQuery.util.d.ts +15 -68
- package/dist/util/relationQuery.util.js +35 -83
- package/dist/util/rowKey.util.d.ts +1 -11
- package/dist/util/rowKey.util.js +1 -13
- package/package.json +1 -1
- package/dist/querier/relationCount.d.ts +0 -16
- package/dist/querier/relationCount.js +0 -121
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbstractSqlDialect } from '../../dialect/abstractSqlDialect.js';
|
|
2
|
-
import type
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
29
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
47
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
85
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|
|
80
|
-
for (const
|
|
81
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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,
|
|
107
|
-
for (const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|