uql-orm 0.79.0 → 0.81.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/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/cockroachdb/cockroachDialect.js +5 -1
- package/dist/dialect/abstractDialect.d.ts +1 -31
- package/dist/dialect/abstractDialect.js +3 -27
- package/dist/dialect/abstractSqlDialect.d.ts +25 -56
- package/dist/dialect/abstractSqlDialect.js +78 -145
- package/dist/dialect/aliases.d.ts +5 -0
- package/dist/dialect/aliases.js +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +4 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +14 -1
- package/dist/dialect/operators.d.ts +66 -0
- package/dist/dialect/operators.js +129 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +4 -1
- package/dist/dialect/pgLikeSqlDialect.js +16 -3
- package/dist/dialect/vectorSqlDialect.d.ts +2 -0
- package/dist/dialect/vectorSqlDialect.js +4 -0
- package/dist/entity/decorator/entity.d.ts +6 -1
- package/dist/entity/decorator/entity.js +12 -1
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +6 -1
- package/dist/entity/metadata/definition.js +19 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +24 -0
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
- package/dist/migrate/codegen/entityTypes.js +1 -2
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
- package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +6 -4
- package/dist/migrate/ddl/mssqlTableDdl.js +25 -14
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +7 -6
- package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
- package/dist/migrate/ddl/pgIndexDdl.js +8 -6
- package/dist/migrate/ddl/tableDdl.d.ts +5 -2
- package/dist/migrate/ddl/tableDdl.js +13 -7
- package/dist/migrate/drift/driftDetector.d.ts +4 -5
- package/dist/migrate/drift/driftDetector.js +21 -21
- package/dist/migrate/generator/definitionToNode.d.ts +1 -1
- package/dist/migrate/generator/definitionToNode.js +9 -20
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +5 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +20 -18
- package/dist/migrate/index.d.ts +2 -1
- package/dist/migrate/index.js +1 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +16 -6
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +18 -4
- package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/mongoIntrospector.js +7 -3
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +9 -0
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +16 -5
- package/dist/migrate/introspection/mysqlIntrospector.js +39 -2
- package/dist/migrate/introspection/postgresIntrospector.d.ts +30 -21
- package/dist/migrate/introspection/postgresIntrospector.js +70 -43
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +13 -8
- package/dist/migrate/migrator.d.ts +33 -1
- package/dist/migrate/migrator.js +111 -43
- package/dist/migrate/schemaChange.d.ts +18 -0
- package/dist/migrate/schemaChange.js +37 -0
- package/dist/migrate/schemaGenerator.d.ts +25 -15
- package/dist/migrate/schemaGenerator.js +130 -182
- package/dist/migrate/storage/databaseStorage.d.ts +4 -0
- package/dist/migrate/storage/databaseStorage.js +14 -8
- package/dist/migrate/triggerSql.d.ts +24 -0
- package/dist/migrate/triggerSql.js +229 -0
- package/dist/mongo/mongoDialect.d.ts +0 -21
- package/dist/mongo/mongoDialect.js +105 -100
- package/dist/mongo/mongodbQuerier.js +17 -1
- package/dist/mssql/mssqlDialect.d.ts +18 -7
- package/dist/mssql/mssqlDialect.js +77 -33
- package/dist/mssql/mssqlQuerier.js +2 -2
- package/dist/schema/canonicalType.d.ts +6 -1
- package/dist/schema/canonicalType.js +14 -0
- package/dist/schema/indexDifferences.d.ts +22 -6
- package/dist/schema/indexDifferences.js +23 -8
- package/dist/schema/matchByKey.d.ts +10 -0
- package/dist/schema/matchByKey.js +18 -0
- package/dist/schema/schemaAST.d.ts +6 -2
- package/dist/schema/schemaAST.js +7 -3
- package/dist/schema/schemaASTBuilder.d.ts +4 -8
- package/dist/schema/schemaASTBuilder.js +20 -29
- package/dist/schema/schemaASTDiffer.d.ts +2 -3
- package/dist/schema/schemaASTDiffer.js +15 -36
- package/dist/schema/types.d.ts +14 -15
- package/dist/sqlite/sqliteDialect.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +12 -3
- package/dist/type/dialect.d.ts +69 -9
- package/dist/type/entity.d.ts +96 -3
- package/dist/type/migration.d.ts +43 -44
- package/dist/type/query.d.ts +13 -4
- package/dist/type/queryWhere.d.ts +4 -2
- package/dist/util/ddlExpression.util.d.ts +5 -1
- package/dist/util/ddlExpression.util.js +6 -2
- package/dist/util/field.util.d.ts +9 -1
- package/dist/util/field.util.js +14 -2
- package/dist/util/fieldOption.util.d.ts +2 -2
- package/dist/util/fieldOption.util.js +2 -2
- package/dist/util/raw.d.ts +9 -1
- package/dist/util/raw.js +36 -11
- package/dist/util/sql.util.d.ts +12 -0
- package/dist/util/sql.util.js +24 -3
- package/dist/util/uqlError.d.ts +2 -0
- package/dist/util/uqlError.js +4 -0
- package/package.json +4 -4
- package/skills/uql-orm/SKILL.md +11 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IndexFacet } from '../../schema/indexDifferences.js';
|
|
1
2
|
import type { ColumnSchema, ForeignKeySchema, IndexSchema } from '../../type/index.js';
|
|
2
3
|
import { AbstractSqlSchemaIntrospector, type JoinedForeignKeyRow, type TableRowReader } from './abstractSqlSchemaIntrospector.js';
|
|
3
4
|
/**
|
|
@@ -6,6 +7,7 @@ import { AbstractSqlSchemaIntrospector, type JoinedForeignKeyRow, type TableRowR
|
|
|
6
7
|
*/
|
|
7
8
|
export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
8
9
|
protected readonly defaultSchemaExpr = "DATABASE()";
|
|
10
|
+
protected triggersQuery(): string;
|
|
9
11
|
protected getTableNamesQuery(): string;
|
|
10
12
|
protected tableExistsQuery(): string;
|
|
11
13
|
protected parseTableExistsResult([row]: {
|
|
@@ -16,11 +18,7 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
|
|
|
16
18
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
17
19
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
18
20
|
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: MysqlColumnRow[]): Promise<ColumnSchema[]>;
|
|
19
|
-
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results:
|
|
20
|
-
index_name: string;
|
|
21
|
-
columns: string;
|
|
22
|
-
is_unique: number;
|
|
23
|
-
}[]): Promise<IndexSchema[]>;
|
|
21
|
+
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: MysqlIndexRow[]): Promise<IndexSchema[]>;
|
|
24
22
|
protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: JoinedForeignKeyRow[]): Promise<ForeignKeySchema[]>;
|
|
25
23
|
/**
|
|
26
24
|
* MariaDB prints a string default as the literal it is (`'it''s'`). MySQL prints one bare, save an
|
|
@@ -51,6 +49,19 @@ type MysqlColumnRow = {
|
|
|
51
49
|
* JSON, got LONGTEXT", flagged as data loss) on a table uql created itself.
|
|
52
50
|
*/
|
|
53
51
|
export declare class MariadbSchemaIntrospector extends MysqlSchemaIntrospector {
|
|
52
|
+
/** Whether an index is MariaDB's vector index, and the distance it was built for. */
|
|
53
|
+
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
54
|
+
/**
|
|
55
|
+
* A vector index's distance is kept only in the table's own definition, ``VECTOR KEY `ix` (`vec`)
|
|
56
|
+
* `DISTANCE`='cosine'``, and left out there for MariaDB's default, euclidean.
|
|
57
|
+
*/
|
|
58
|
+
protected mapIndexesResult(read: TableRowReader, tableName: string, results: MysqlIndexRow[]): Promise<IndexSchema[]>;
|
|
54
59
|
protected mapColumnsResult(read: TableRowReader, tableName: string, results: MysqlColumnRow[]): Promise<ColumnSchema[]>;
|
|
55
60
|
}
|
|
61
|
+
type MysqlIndexRow = {
|
|
62
|
+
index_name: string;
|
|
63
|
+
columns: string;
|
|
64
|
+
is_unique: number;
|
|
65
|
+
method: string;
|
|
66
|
+
};
|
|
56
67
|
export {};
|
|
@@ -7,6 +7,15 @@ import { AbstractSqlSchemaIntrospector, } from './abstractSqlSchemaIntrospector.
|
|
|
7
7
|
export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
8
8
|
// A MySQL "schema" is a database, so the connection's own is what `DATABASE()` reports.
|
|
9
9
|
defaultSchemaExpr = 'DATABASE()';
|
|
10
|
+
triggersQuery() {
|
|
11
|
+
return /*sql*/ `
|
|
12
|
+
SELECT TRIGGER_NAME AS name,
|
|
13
|
+
CONCAT('CREATE TRIGGER \`', TRIGGER_SCHEMA, '\`.\`', TRIGGER_NAME, '\` ', ACTION_TIMING, ' ', EVENT_MANIPULATION,
|
|
14
|
+
' ON \`', EVENT_OBJECT_SCHEMA, '\`.\`', EVENT_OBJECT_TABLE, '\` FOR EACH ROW ', ACTION_STATEMENT) AS definition
|
|
15
|
+
FROM information_schema.TRIGGERS
|
|
16
|
+
WHERE TRIGGER_SCHEMA = ${this.schemaExpr} AND EVENT_OBJECT_TABLE = ${this.dialect.placeholder(1)}
|
|
17
|
+
`;
|
|
18
|
+
}
|
|
10
19
|
getTableNamesQuery() {
|
|
11
20
|
return /*sql*/ `
|
|
12
21
|
SELECT TABLE_NAME as table_name
|
|
@@ -53,7 +62,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
53
62
|
SELECT
|
|
54
63
|
INDEX_NAME as index_name,
|
|
55
64
|
GROUP_CONCAT(COALESCE(COLUMN_NAME, '') ORDER BY SEQ_IN_INDEX) as columns,
|
|
56
|
-
NOT NON_UNIQUE as is_unique
|
|
65
|
+
NOT NON_UNIQUE as is_unique,
|
|
66
|
+
MAX(INDEX_TYPE) as method
|
|
57
67
|
FROM information_schema.STATISTICS
|
|
58
68
|
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
59
69
|
AND TABLE_NAME = ?
|
|
@@ -101,7 +111,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
101
111
|
isPrimaryKey: row.column_key === 'PRI',
|
|
102
112
|
isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
|
|
103
113
|
isUnique: row.column_key === 'UNI',
|
|
104
|
-
|
|
114
|
+
// A `VECTOR`'s is its bytes, four a dimension, which `column_type` already states as dimensions.
|
|
115
|
+
length: /^vector/i.test(row.column_type) ? undefined : this.toNumber(row.character_maximum_length),
|
|
105
116
|
precision: this.toNumber(row.numeric_precision),
|
|
106
117
|
scale: this.toNumber(row.numeric_scale),
|
|
107
118
|
comment: row.column_comment || undefined,
|
|
@@ -111,6 +122,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
111
122
|
async mapIndexesResult(_read, _tableName, results) {
|
|
112
123
|
return results.map((row) => ({
|
|
113
124
|
name: row.index_name,
|
|
125
|
+
...(row.method === 'VECTOR' && { type: 'vector' }),
|
|
114
126
|
// A functional or multi-valued key part has no `COLUMN_NAME` - the `COALESCE` above keeps its
|
|
115
127
|
// place in the list, and it is reported as the expression it is, which is what stops diffing
|
|
116
128
|
// from comparing an entry list the server cannot state against the entity's own.
|
|
@@ -155,6 +167,31 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
155
167
|
* JSON, got LONGTEXT", flagged as data loss) on a table uql created itself.
|
|
156
168
|
*/
|
|
157
169
|
export class MariadbSchemaIntrospector extends MysqlSchemaIntrospector {
|
|
170
|
+
/** Whether an index is MariaDB's vector index, and the distance it was built for. */
|
|
171
|
+
indexFacets = new Set(['vector', 'distance']);
|
|
172
|
+
/**
|
|
173
|
+
* A vector index's distance is kept only in the table's own definition, ``VECTOR KEY `ix` (`vec`)
|
|
174
|
+
* `DISTANCE`='cosine'``, and left out there for MariaDB's default, euclidean.
|
|
175
|
+
*/
|
|
176
|
+
async mapIndexesResult(read, tableName, results) {
|
|
177
|
+
const indexes = await super.mapIndexesResult(read, tableName, results);
|
|
178
|
+
if (!indexes.some((index) => index.type === 'vector')) {
|
|
179
|
+
return indexes;
|
|
180
|
+
}
|
|
181
|
+
const qualified = [this.schema, tableName]
|
|
182
|
+
.filter((name) => name !== undefined)
|
|
183
|
+
.map((name) => this.dialect.escapeId(name));
|
|
184
|
+
const [row] = await read(/*sql*/ `SHOW CREATE TABLE ${qualified.join('.')}`);
|
|
185
|
+
const lines = row['Create Table'].split('\n');
|
|
186
|
+
return indexes.map((index) => {
|
|
187
|
+
if (index.type !== 'vector') {
|
|
188
|
+
return index;
|
|
189
|
+
}
|
|
190
|
+
const line = lines.find((it) => it.includes(`VECTOR KEY \`${index.name}\``));
|
|
191
|
+
const metric = line?.match(/`DISTANCE`='(\w+)'/)?.[1] ?? 'euclidean';
|
|
192
|
+
return { ...index, distance: this.dialect.indexedDistance(metric) };
|
|
193
|
+
});
|
|
194
|
+
}
|
|
158
195
|
async mapColumnsResult(read, tableName, results) {
|
|
159
196
|
const columns = await super.mapColumnsResult(read, tableName, results);
|
|
160
197
|
const checks = await read(
|
|
@@ -11,6 +11,7 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
11
11
|
* database reprints in its own words, so they are not comparable and are not claimed here.
|
|
12
12
|
*/
|
|
13
13
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
14
|
+
protected triggersQuery(): string;
|
|
14
15
|
protected getTableNamesQuery(): string;
|
|
15
16
|
protected tableExistsQuery(): string;
|
|
16
17
|
protected parseTableExistsResult([row]: RawRow[]): boolean;
|
|
@@ -21,7 +22,11 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
21
22
|
* database scans meet that table every time something else is migrating.
|
|
22
23
|
*
|
|
23
24
|
* `attgenerated` rather than `is_generated`, which cannot part a stored generated column from the
|
|
24
|
-
* virtual one Postgres 18 added and uql never declares. CockroachDB states it too.
|
|
25
|
+
* virtual one Postgres 18 added and uql never declares. CockroachDB states it too. `format_type` for an
|
|
26
|
+
* extension type's modifier, which `information_schema` drops: a `vector(256)` read back as `vector`.
|
|
27
|
+
* CockroachDB names that type `vector` where Postgres says `USER-DEFINED`.
|
|
28
|
+
*
|
|
29
|
+
* A column is unique by a unique index over it alone, a constraint's or its own, as every engine reads it.
|
|
25
30
|
*/
|
|
26
31
|
protected getColumnsQuery(_tableName: string): string;
|
|
27
32
|
/**
|
|
@@ -31,24 +36,27 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
31
36
|
* while `pg_get_indexdef` reprints an identifier *quoted*, so a camelCase column came back as
|
|
32
37
|
* `"tenantId"` and matched no column of the table. Prisma and drizzle-kit both split it this way.
|
|
33
38
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
39
|
+
* The key's index and an `EXCLUDE`'s are left out. A `UNIQUE` constraint's index stays, as SQL Server
|
|
40
|
+
* and the MySQL family report theirs: the diff reads one over a single column as that column's
|
|
41
|
+
* uniqueness, and one over several as the unique `@Index` it is.
|
|
37
42
|
*/
|
|
38
43
|
protected getIndexesQuery(_tableName: string): string;
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* compare. This one decides which indexes are reported at all.
|
|
46
|
-
*/
|
|
47
|
-
protected readonly constraintIndexTypes: readonly string[];
|
|
44
|
+
/** Whether an entry sorts nulls first, which Postgres states on every entry. */
|
|
45
|
+
protected readonly nullsFirstSql: string;
|
|
46
|
+
/** An index's access method, which is the type it declares. */
|
|
47
|
+
protected readonly indexMethodSql: string;
|
|
48
|
+
/** An entry's operator class, where it is not the default for its type. */
|
|
49
|
+
protected readonly opsClassSql: string;
|
|
48
50
|
/** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
|
|
49
51
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
50
52
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
51
53
|
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
|
|
54
|
+
/**
|
|
55
|
+
* A vector index keeps its distance as its vector column's operator class, `{type}_{metric}_ops`, which
|
|
56
|
+
* is how `PgIndexDdl` writes the entity's `distance`; read back as that `distance`, so the two compare.
|
|
57
|
+
* No class named is the engine's default, L2.
|
|
58
|
+
*/
|
|
59
|
+
private withVectorDistance;
|
|
52
60
|
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: PostgresIndexRow[]): Promise<IndexSchema[]>;
|
|
53
61
|
protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: PostgresForeignKeyRow[]): Promise<ForeignKeySchema[]>;
|
|
54
62
|
protected normalizeType(dataType: string, udtName: string): string;
|
|
@@ -72,18 +80,18 @@ declare const FOREIGN_KEY_ACTION_CODES: {
|
|
|
72
80
|
* CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
|
|
73
81
|
* still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
|
|
74
82
|
* on an ASC column where Postgres sorts them last. Reading a nulls order back would therefore report
|
|
75
|
-
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
76
|
-
* method, `prefix`, needs nothing: a method that is not a known index type is reported as no type.
|
|
83
|
+
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
77
84
|
*/
|
|
78
85
|
export declare class CockroachSchemaIntrospector extends PostgresSchemaIntrospector {
|
|
79
86
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
87
|
+
/** None: it rejects a stated nulls order, so reading one back gives an index it would refuse to rebuild. */
|
|
88
|
+
protected readonly nullsFirstSql = "NULL::BOOL";
|
|
80
89
|
/**
|
|
81
|
-
* `
|
|
82
|
-
*
|
|
83
|
-
* user asked for and report it missing forever. It leaves no way to tell the two apart, so the
|
|
84
|
-
* index a `@Field({ unique })` builds underneath itself stays visible there.
|
|
90
|
+
* Every index reports the access method `prefix` and no operator class, so a vector index is read off
|
|
91
|
+
* its definition, `USING cspann (vec vector_cosine_ops)`: its type, and its last key's class.
|
|
85
92
|
*/
|
|
86
|
-
protected readonly
|
|
93
|
+
protected readonly indexMethodSql = "CASE WHEN pg_get_indexdef(ix.indexrelid) LIKE '% USING cspann %' THEN 'vector' ELSE am.amname END";
|
|
94
|
+
protected readonly opsClassSql = "CASE WHEN k.n = ix.indnkeyatts THEN substring(pg_get_indexdef(ix.indexrelid) from '(\\w+_ops)\\)') END";
|
|
87
95
|
}
|
|
88
96
|
type PostgresForeignKeyRow = {
|
|
89
97
|
constraint_name: string;
|
|
@@ -103,13 +111,14 @@ type PostgresIndexRow = {
|
|
|
103
111
|
is_expression: boolean;
|
|
104
112
|
entry: string;
|
|
105
113
|
descending: boolean;
|
|
106
|
-
nulls_first: boolean;
|
|
114
|
+
nulls_first: boolean | null;
|
|
107
115
|
ops_class: string | null;
|
|
108
116
|
};
|
|
109
117
|
type PostgresColumnRow = {
|
|
110
118
|
column_name: string;
|
|
111
119
|
data_type: string;
|
|
112
120
|
udt_name: string;
|
|
121
|
+
formatted_type: string | null;
|
|
113
122
|
is_nullable: string;
|
|
114
123
|
column_default: string | null;
|
|
115
124
|
is_primary_key: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { INDEX_TYPES } from '../../schema/types.js';
|
|
2
|
+
import { isVectorIndexType } from '../../type/vector.js';
|
|
2
3
|
import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.js';
|
|
3
4
|
/**
|
|
4
5
|
* PostgreSQL schema introspector
|
|
@@ -15,7 +16,17 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
15
16
|
'opsClass',
|
|
16
17
|
'accessMethod',
|
|
17
18
|
'include',
|
|
19
|
+
'distance',
|
|
18
20
|
]);
|
|
21
|
+
triggersQuery() {
|
|
22
|
+
return /*sql*/ `
|
|
23
|
+
SELECT t.tgname AS name, pg_get_triggerdef(t.oid) AS definition, pg_get_functiondef(t.tgfoid) AS requires
|
|
24
|
+
FROM pg_trigger t
|
|
25
|
+
JOIN pg_class c ON c.oid = t.tgrelid
|
|
26
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
27
|
+
WHERE NOT t.tgisinternal AND n.nspname = ${this.schemaExpr} AND c.relname = ${this.dialect.placeholder(1)}
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
19
30
|
getTableNamesQuery() {
|
|
20
31
|
return /*sql*/ `
|
|
21
32
|
SELECT table_name
|
|
@@ -44,7 +55,11 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
44
55
|
* database scans meet that table every time something else is migrating.
|
|
45
56
|
*
|
|
46
57
|
* `attgenerated` rather than `is_generated`, which cannot part a stored generated column from the
|
|
47
|
-
* virtual one Postgres 18 added and uql never declares. CockroachDB states it too.
|
|
58
|
+
* virtual one Postgres 18 added and uql never declares. CockroachDB states it too. `format_type` for an
|
|
59
|
+
* extension type's modifier, which `information_schema` drops: a `vector(256)` read back as `vector`.
|
|
60
|
+
* CockroachDB names that type `vector` where Postgres says `USER-DEFINED`.
|
|
61
|
+
*
|
|
62
|
+
* A column is unique by a unique index over it alone, a constraint's or its own, as every engine reads it.
|
|
48
63
|
*/
|
|
49
64
|
getColumnsQuery(_tableName) {
|
|
50
65
|
return /*sql*/ `
|
|
@@ -59,11 +74,9 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
59
74
|
c.numeric_scale,
|
|
60
75
|
c.is_identity,
|
|
61
76
|
c.identity_generation,
|
|
62
|
-
CASE WHEN
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
AND a.attname = c.column_name
|
|
66
|
-
) = 's' THEN c.generation_expression END AS generated_as,
|
|
77
|
+
CASE WHEN a.attgenerated = 's' THEN c.generation_expression END AS generated_as,
|
|
78
|
+
CASE WHEN c.data_type IN ('USER-DEFINED', 'vector') AND a.atttypmod > -1
|
|
79
|
+
THEN format_type(a.atttypid, a.atttypmod) END AS formatted_type,
|
|
67
80
|
EXISTS (
|
|
68
81
|
SELECT 1 FROM information_schema.table_constraints tc
|
|
69
82
|
JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
|
|
@@ -73,19 +86,19 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
73
86
|
AND kcu.column_name = c.column_name
|
|
74
87
|
) AS is_primary_key,
|
|
75
88
|
EXISTS (
|
|
76
|
-
SELECT 1 FROM
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
AND
|
|
80
|
-
AND tc.constraint_type = 'UNIQUE'
|
|
81
|
-
GROUP BY tc.constraint_name
|
|
82
|
-
HAVING COUNT(*) = 1 AND MIN(kcu.column_name) = c.column_name
|
|
89
|
+
SELECT 1 FROM pg_catalog.pg_index ix
|
|
90
|
+
WHERE ix.indrelid = a.attrelid AND ix.indisunique AND NOT ix.indisprimary
|
|
91
|
+
AND ix.indnkeyatts = 1 AND ix.indkey[0] = a.attnum
|
|
92
|
+
AND ix.indpred IS NULL AND ix.indexprs IS NULL
|
|
83
93
|
) AS is_unique,
|
|
84
94
|
pg_catalog.col_description(
|
|
85
95
|
to_regclass(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name)),
|
|
86
96
|
c.ordinal_position
|
|
87
97
|
) AS column_comment
|
|
88
98
|
FROM information_schema.columns c
|
|
99
|
+
LEFT JOIN pg_catalog.pg_attribute a
|
|
100
|
+
ON a.attrelid = to_regclass(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))
|
|
101
|
+
AND a.attname = c.column_name
|
|
89
102
|
WHERE c.table_schema = ${this.schemaExpr}
|
|
90
103
|
AND c.table_name = $1
|
|
91
104
|
ORDER BY c.ordinal_position
|
|
@@ -98,23 +111,23 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
98
111
|
* while `pg_get_indexdef` reprints an identifier *quoted*, so a camelCase column came back as
|
|
99
112
|
* `"tenantId"` and matched no column of the table. Prisma and drizzle-kit both split it this way.
|
|
100
113
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
114
|
+
* The key's index and an `EXCLUDE`'s are left out. A `UNIQUE` constraint's index stays, as SQL Server
|
|
115
|
+
* and the MySQL family report theirs: the diff reads one over a single column as that column's
|
|
116
|
+
* uniqueness, and one over several as the unique `@Index` it is.
|
|
104
117
|
*/
|
|
105
118
|
getIndexesQuery(_tableName) {
|
|
106
119
|
return /*sql*/ `
|
|
107
120
|
SELECT
|
|
108
121
|
i.relname AS index_name,
|
|
109
122
|
ix.indisunique AS is_unique,
|
|
110
|
-
|
|
123
|
+
${this.indexMethodSql} AS method,
|
|
111
124
|
pg_get_expr(ix.indpred, ix.indrelid, true) AS predicate,
|
|
112
125
|
k.n <= ix.indnkeyatts AS is_key,
|
|
113
126
|
k.attnum = 0 AS is_expression,
|
|
114
127
|
COALESCE(a.attname::text, pg_get_indexdef(ix.indexrelid, k.n::int, true)) AS entry,
|
|
115
128
|
(ix.indoption[k.n - 1] & 1) <> 0 AS descending,
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
${this.nullsFirstSql} AS nulls_first,
|
|
130
|
+
${this.opsClassSql} AS ops_class
|
|
118
131
|
FROM pg_class t
|
|
119
132
|
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
120
133
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
@@ -127,22 +140,17 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
127
140
|
AND n.nspname = ${this.schemaExpr}
|
|
128
141
|
AND NOT ix.indisprimary
|
|
129
142
|
AND NOT EXISTS (
|
|
130
|
-
SELECT 1 FROM pg_constraint con
|
|
131
|
-
WHERE con.conindid = ix.indexrelid
|
|
132
|
-
AND con.contype IN (${this.constraintIndexTypes.map((type) => `'${type}'`).join(', ')})
|
|
143
|
+
SELECT 1 FROM pg_constraint con WHERE con.conindid = ix.indexrelid AND con.contype = 'x'
|
|
133
144
|
)
|
|
134
145
|
ORDER BY i.relname, k.n
|
|
135
146
|
`;
|
|
136
147
|
}
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* compare. This one decides which indexes are reported at all.
|
|
144
|
-
*/
|
|
145
|
-
constraintIndexTypes = ['p', 'u', 'x'];
|
|
148
|
+
/** Whether an entry sorts nulls first, which Postgres states on every entry. */
|
|
149
|
+
nullsFirstSql = '(ix.indoption[k.n - 1] & 2) <> 0';
|
|
150
|
+
/** An index's access method, which is the type it declares. */
|
|
151
|
+
indexMethodSql = 'am.amname';
|
|
152
|
+
/** An entry's operator class, where it is not the default for its type. */
|
|
153
|
+
opsClassSql = 'CASE WHEN op.opcdefault THEN NULL ELSE op.opcname END';
|
|
146
154
|
/** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
|
|
147
155
|
getForeignKeysQuery(_tableName) {
|
|
148
156
|
const columnsOf = (keys, table) => /*sql*/ `ARRAY_TO_JSON(ARRAY(
|
|
@@ -184,7 +192,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
184
192
|
async mapColumnsResult(_read, _tableName, results) {
|
|
185
193
|
return results.map((row) => ({
|
|
186
194
|
name: row.column_name,
|
|
187
|
-
type: this.normalizeType(row.data_type, row.udt_name),
|
|
195
|
+
type: row.formatted_type?.toUpperCase() ?? this.normalizeType(row.data_type, row.udt_name),
|
|
188
196
|
nullable: row.is_nullable === 'YES',
|
|
189
197
|
defaultValue: this.parseDefaultValue(row.column_default),
|
|
190
198
|
isPrimaryKey: row.is_primary_key,
|
|
@@ -197,11 +205,25 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
197
205
|
generatedAs: row.generated_as ?? undefined,
|
|
198
206
|
}));
|
|
199
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* A vector index keeps its distance as its vector column's operator class, `{type}_{metric}_ops`, which
|
|
210
|
+
* is how `PgIndexDdl` writes the entity's `distance`; read back as that `distance`, so the two compare.
|
|
211
|
+
* No class named is the engine's default, L2.
|
|
212
|
+
*/
|
|
213
|
+
withVectorDistance(index) {
|
|
214
|
+
if (!isVectorIndexType(index.type)) {
|
|
215
|
+
return index;
|
|
216
|
+
}
|
|
217
|
+
const opsClass = index.entries.find((entry) => entry.opsClass)?.opsClass;
|
|
218
|
+
const distance = this.dialect.indexedDistance(opsClass ? /_([a-z0-9]+)_ops$/.exec(opsClass)?.[1] : 'l2');
|
|
219
|
+
const entries = index.entries.map(({ opsClass: _opsClass, ...entry }) => entry);
|
|
220
|
+
return distance ? { ...index, distance, entries } : index;
|
|
221
|
+
}
|
|
200
222
|
async mapIndexesResult(_read, _tableName, results) {
|
|
201
223
|
// One row per index entry, ordered by position, so the rows of an index are its entries in order.
|
|
202
224
|
return [...Map.groupBy(results, (row) => row.index_name)].map(([name, rows]) => {
|
|
203
225
|
const include = rows.filter((row) => !row.is_key).map((row) => row.entry);
|
|
204
|
-
return {
|
|
226
|
+
return this.withVectorDistance({
|
|
205
227
|
name,
|
|
206
228
|
entries: rows.filter((row) => row.is_key).map(mapIndexEntry),
|
|
207
229
|
unique: rows[0].is_unique,
|
|
@@ -209,7 +231,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
209
231
|
where: rows[0].predicate ?? undefined,
|
|
210
232
|
include: include.length > 0 ? include : undefined,
|
|
211
233
|
...fulltextIndex(rows),
|
|
212
|
-
};
|
|
234
|
+
});
|
|
213
235
|
});
|
|
214
236
|
}
|
|
215
237
|
async mapForeignKeysResult(_read, _tableName, results) {
|
|
@@ -305,7 +327,7 @@ function mapIndexEntry(row) {
|
|
|
305
327
|
column: row.entry,
|
|
306
328
|
...(row.is_expression && { expression: true }),
|
|
307
329
|
order: row.descending ? 'desc' : 'asc',
|
|
308
|
-
nulls: row.nulls_first ? 'first' : 'last',
|
|
330
|
+
...(row.nulls_first !== null && { nulls: row.nulls_first ? 'first' : 'last' }),
|
|
309
331
|
...(row.ops_class && { opsClass: row.ops_class }),
|
|
310
332
|
};
|
|
311
333
|
}
|
|
@@ -313,16 +335,21 @@ function mapIndexEntry(row) {
|
|
|
313
335
|
* CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
|
|
314
336
|
* still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
|
|
315
337
|
* on an ASC column where Postgres sorts them last. Reading a nulls order back would therefore report
|
|
316
|
-
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
317
|
-
* method, `prefix`, needs nothing: a method that is not a known index type is reported as no type.
|
|
338
|
+
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
318
339
|
*/
|
|
319
340
|
export class CockroachSchemaIntrospector extends PostgresSchemaIntrospector {
|
|
320
|
-
indexFacets = new Set([
|
|
341
|
+
indexFacets = new Set([
|
|
342
|
+
'order',
|
|
343
|
+
'include',
|
|
344
|
+
'vector',
|
|
345
|
+
'distance',
|
|
346
|
+
]);
|
|
347
|
+
/** None: it rejects a stated nulls order, so reading one back gives an index it would refuse to rebuild. */
|
|
348
|
+
nullsFirstSql = 'NULL::BOOL';
|
|
321
349
|
/**
|
|
322
|
-
* `
|
|
323
|
-
*
|
|
324
|
-
* user asked for and report it missing forever. It leaves no way to tell the two apart, so the
|
|
325
|
-
* index a `@Field({ unique })` builds underneath itself stays visible there.
|
|
350
|
+
* Every index reports the access method `prefix` and no operator class, so a vector index is read off
|
|
351
|
+
* its definition, `USING cspann (vec vector_cosine_ops)`: its type, and its last key's class.
|
|
326
352
|
*/
|
|
327
|
-
|
|
353
|
+
indexMethodSql = `CASE WHEN pg_get_indexdef(ix.indexrelid) LIKE '% USING cspann %' THEN 'vector' ELSE am.amname END`;
|
|
354
|
+
opsClassSql = `CASE WHEN k.n = ix.indnkeyatts THEN substring(pg_get_indexdef(ix.indexrelid) from '(\\w+_ops)\\)') END`;
|
|
328
355
|
}
|
|
@@ -8,6 +8,7 @@ export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospec
|
|
|
8
8
|
/** Whether an index is libSQL's vector index, where the engine has one; elsewhere a declared one is built plain. */
|
|
9
9
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
10
10
|
/** Not SQLite's own tables, nor the ones libSQL keeps a vector index in: its metadata and `<index>_shadow`. */
|
|
11
|
+
protected triggersQuery(): string;
|
|
11
12
|
protected getTableNamesQuery(): string;
|
|
12
13
|
protected tableExistsQuery(): string;
|
|
13
14
|
protected parseTableExistsResult([row]: SqliteCountRow[]): boolean;
|
|
@@ -5,8 +5,11 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
5
5
|
*/
|
|
6
6
|
export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
7
7
|
/** Whether an index is libSQL's vector index, where the engine has one; elsewhere a declared one is built plain. */
|
|
8
|
-
indexFacets = new Set(this.dialect.hasVectorIndex() ? ['vector'] : []);
|
|
8
|
+
indexFacets = new Set(this.dialect.hasVectorIndex() ? ['vector', 'distance'] : []);
|
|
9
9
|
/** Not SQLite's own tables, nor the ones libSQL keeps a vector index in: its metadata and `<index>_shadow`. */
|
|
10
|
+
triggersQuery() {
|
|
11
|
+
return /*sql*/ `SELECT name, sql AS definition FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?`;
|
|
12
|
+
}
|
|
10
13
|
getTableNamesQuery() {
|
|
11
14
|
return /*sql*/ `
|
|
12
15
|
SELECT name
|
|
@@ -86,15 +89,12 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
86
89
|
const indexSchemas = [];
|
|
87
90
|
for (const index of results) {
|
|
88
91
|
const columns = await this.getIndexColumns(read, index.name);
|
|
89
|
-
//
|
|
90
|
-
// Skip primary key indexes ('pk') and single-column unique constraints
|
|
91
|
-
const isUserCreated = index.origin === 'c';
|
|
92
|
-
const isCompositeUnique = index.origin === 'u' && columns.length > 1;
|
|
92
|
+
// A unique constraint's index ('u') is reported as every engine reports it, and only the key's ('pk') left out.
|
|
93
93
|
// `PRAGMA index_info` names an expression entry `null` (its `cid` is -2), and the expression text
|
|
94
94
|
// lives only in `sqlite_master.sql`. Reporting `{ column: null }` put a column literally named
|
|
95
95
|
// `null` into the diff, so an index UQL cannot describe is left out, libSQL's vector index aside.
|
|
96
96
|
const named = columns.filter((column) => column.name !== null);
|
|
97
|
-
if (
|
|
97
|
+
if (index.origin === 'pk') {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
if (named.length === columns.length) {
|
|
@@ -168,8 +168,13 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
168
168
|
return undefined;
|
|
169
169
|
}
|
|
170
170
|
const metric = row.sql?.match(/'metric=(\w+)'/i)?.[1]?.toLowerCase();
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
return {
|
|
172
|
+
name: indexName,
|
|
173
|
+
entries: [{ column }],
|
|
174
|
+
unique: false,
|
|
175
|
+
type: 'vector',
|
|
176
|
+
distance: this.dialect.indexedDistance(metric),
|
|
177
|
+
};
|
|
173
178
|
}
|
|
174
179
|
/** The statement that created the table, which is where SQLite keeps every expression it was given. */
|
|
175
180
|
async getTableDdl(read, tableName) {
|
|
@@ -63,6 +63,33 @@ export declare class Migrator {
|
|
|
63
63
|
* Generate a migration based on entity schema differences
|
|
64
64
|
*/
|
|
65
65
|
generateFromEntities(name: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Each entity on a table this plan does not create - a new one carries its triggers in its `CREATE` -
|
|
68
|
+
* beside the triggers uql has installed there, read once per schema. Kept only where either side has
|
|
69
|
+
* any: one declaring none on a table holding none has nothing to reconcile.
|
|
70
|
+
*/
|
|
71
|
+
private installedTriggers;
|
|
72
|
+
/**
|
|
73
|
+
* The alters, with the triggers reconciled around them. Postgres refuses to retype or drop a column a
|
|
74
|
+
* trigger names, so every trigger on a table whose columns change comes off before the alters and what
|
|
75
|
+
* its entity declares goes back on after. `down` is lazy: SQLite cannot express every alter's inverse.
|
|
76
|
+
*/
|
|
77
|
+
private alterPlan;
|
|
78
|
+
/**
|
|
79
|
+
* Each entity's triggers taken from what the catalogue holds to what it declares. Against the catalogue
|
|
80
|
+
* rather than a diff, because a trigger hangs off a table whose columns may be unchanged: a body edited
|
|
81
|
+
* on a settled table appears in no diff at all.
|
|
82
|
+
*/
|
|
83
|
+
private reconcileTriggers;
|
|
84
|
+
/**
|
|
85
|
+
* The inverse: what the reconcile created dropped, and what it dropped restored as the engine reprints
|
|
86
|
+
* it. Read off the catalogue rather than recorded by uql, and exactly right for restoring one.
|
|
87
|
+
*/
|
|
88
|
+
private revertedTriggers;
|
|
89
|
+
/** The entities whose tables are among `created`. */
|
|
90
|
+
private createdEntities;
|
|
91
|
+
/** The table `entity` maps to, as a diff names it. */
|
|
92
|
+
private tableOf;
|
|
66
93
|
/**
|
|
67
94
|
* Get all schema differences between entities and database
|
|
68
95
|
*/
|
|
@@ -82,7 +109,7 @@ export declare class Migrator {
|
|
|
82
109
|
* settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
|
|
83
110
|
*/
|
|
84
111
|
private planEntity;
|
|
85
|
-
/** The
|
|
112
|
+
/** The diff for one entity against the table it already has, and none where the two agree. */
|
|
86
113
|
private alterFromEntity;
|
|
87
114
|
/** The configured entities, with `entity` among them however the migrator was built. */
|
|
88
115
|
private entitiesWith;
|
|
@@ -101,6 +128,11 @@ export declare class Migrator {
|
|
|
101
128
|
* never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
|
|
102
129
|
*/
|
|
103
130
|
private pendingChanges;
|
|
131
|
+
/**
|
|
132
|
+
* Safe mode only adds: a change with a `from` drops or rebuilds what the table holds, so it is held,
|
|
133
|
+
* and so is a key whole, which rebuilds an index over every row and fails where a column holds a null.
|
|
134
|
+
* Without `drop`, a column's drop is held too.
|
|
135
|
+
*/
|
|
104
136
|
protected filterDiff(diff: SchemaDiff, options: {
|
|
105
137
|
safe?: boolean;
|
|
106
138
|
drop?: boolean;
|