uql-orm 0.80.0 → 0.82.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 +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/dialect/abstractSqlDialect.d.ts +15 -5
- package/dist/dialect/abstractSqlDialect.js +109 -40
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/vectorSqlDialect.d.ts +2 -0
- package/dist/dialect/vectorSqlDialect.js +4 -0
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +26 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
- package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +4 -4
- package/dist/migrate/ddl/mssqlTableDdl.js +20 -14
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +12 -10
- package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
- package/dist/migrate/ddl/pgIndexDdl.js +10 -7
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.d.ts +3 -2
- package/dist/migrate/ddl/tableDdl.js +11 -8
- 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/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +17 -24
- package/dist/migrate/index.d.ts +2 -1
- package/dist/migrate/index.js +1 -0
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -7
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +13 -19
- package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +5 -4
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +15 -5
- package/dist/migrate/introspection/mysqlIntrospector.js +32 -4
- package/dist/migrate/introspection/postgresIntrospector.d.ts +29 -21
- package/dist/migrate/introspection/postgresIntrospector.js +63 -46
- package/dist/migrate/introspection/sqliteIntrospector.js +11 -9
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.d.ts +5 -0
- package/dist/migrate/migrator.js +35 -45
- package/dist/migrate/schemaChange.d.ts +18 -0
- package/dist/migrate/schemaChange.js +37 -0
- package/dist/migrate/schemaGenerator.d.ts +13 -14
- package/dist/migrate/schemaGenerator.js +88 -181
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +3 -7
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- 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 +2 -0
- package/dist/schema/schemaASTBuilder.js +17 -11
- 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/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +15 -11
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/migration.d.ts +32 -50
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/ddlExpression.util.d.ts +5 -1
- package/dist/util/ddlExpression.util.js +6 -2
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.js +3 -2
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +3 -3
|
@@ -9,11 +9,11 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
9
9
|
defaultSchemaExpr = 'DATABASE()';
|
|
10
10
|
triggersQuery() {
|
|
11
11
|
return /*sql*/ `
|
|
12
|
-
SELECT
|
|
12
|
+
SELECT TRIGGER_NAME AS name,
|
|
13
13
|
CONCAT('CREATE TRIGGER \`', TRIGGER_SCHEMA, '\`.\`', TRIGGER_NAME, '\` ', ACTION_TIMING, ' ', EVENT_MANIPULATION,
|
|
14
14
|
' ON \`', EVENT_OBJECT_SCHEMA, '\`.\`', EVENT_OBJECT_TABLE, '\` FOR EACH ROW ', ACTION_STATEMENT) AS definition
|
|
15
15
|
FROM information_schema.TRIGGERS
|
|
16
|
-
WHERE TRIGGER_SCHEMA = ${this.schemaExpr}
|
|
16
|
+
WHERE TRIGGER_SCHEMA = ${this.schemaExpr} AND EVENT_OBJECT_TABLE = ${this.dialect.placeholder(1)}
|
|
17
17
|
`;
|
|
18
18
|
}
|
|
19
19
|
getTableNamesQuery() {
|
|
@@ -62,7 +62,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
62
62
|
SELECT
|
|
63
63
|
INDEX_NAME as index_name,
|
|
64
64
|
GROUP_CONCAT(COALESCE(COLUMN_NAME, '') ORDER BY SEQ_IN_INDEX) as columns,
|
|
65
|
-
NOT NON_UNIQUE as is_unique
|
|
65
|
+
NOT NON_UNIQUE as is_unique,
|
|
66
|
+
MAX(INDEX_TYPE) as method
|
|
66
67
|
FROM information_schema.STATISTICS
|
|
67
68
|
WHERE TABLE_SCHEMA = ${this.schemaExpr}
|
|
68
69
|
AND TABLE_NAME = ?
|
|
@@ -110,7 +111,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
110
111
|
isPrimaryKey: row.column_key === 'PRI',
|
|
111
112
|
isAutoIncrement: row.extra.toLowerCase().includes('auto_increment'),
|
|
112
113
|
isUnique: row.column_key === 'UNI',
|
|
113
|
-
|
|
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),
|
|
114
116
|
precision: this.toNumber(row.numeric_precision),
|
|
115
117
|
scale: this.toNumber(row.numeric_scale),
|
|
116
118
|
comment: row.column_comment || undefined,
|
|
@@ -120,6 +122,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
120
122
|
async mapIndexesResult(_read, _tableName, results) {
|
|
121
123
|
return results.map((row) => ({
|
|
122
124
|
name: row.index_name,
|
|
125
|
+
...(row.method === 'VECTOR' && { type: 'vector' }),
|
|
123
126
|
// A functional or multi-valued key part has no `COLUMN_NAME` - the `COALESCE` above keeps its
|
|
124
127
|
// place in the list, and it is reported as the expression it is, which is what stops diffing
|
|
125
128
|
// from comparing an entry list the server cannot state against the entity's own.
|
|
@@ -164,6 +167,31 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
164
167
|
* JSON, got LONGTEXT", flagged as data loss) on a table uql created itself.
|
|
165
168
|
*/
|
|
166
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
|
+
}
|
|
167
195
|
async mapColumnsResult(read, tableName, results) {
|
|
168
196
|
const columns = await super.mapColumnsResult(read, tableName, results);
|
|
169
197
|
const checks = await read(
|
|
@@ -22,7 +22,11 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
22
22
|
* database scans meet that table every time something else is migrating.
|
|
23
23
|
*
|
|
24
24
|
* `attgenerated` rather than `is_generated`, which cannot part a stored generated column from the
|
|
25
|
-
* 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.
|
|
26
30
|
*/
|
|
27
31
|
protected getColumnsQuery(_tableName: string): string;
|
|
28
32
|
/**
|
|
@@ -32,24 +36,27 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
32
36
|
* while `pg_get_indexdef` reprints an identifier *quoted*, so a camelCase column came back as
|
|
33
37
|
* `"tenantId"` and matched no column of the table. Prisma and drizzle-kit both split it this way.
|
|
34
38
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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.
|
|
38
42
|
*/
|
|
39
43
|
protected getIndexesQuery(_tableName: string): string;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* compare. This one decides which indexes are reported at all.
|
|
47
|
-
*/
|
|
48
|
-
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;
|
|
49
50
|
/** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
|
|
50
51
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
51
52
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
52
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;
|
|
53
60
|
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: PostgresIndexRow[]): Promise<IndexSchema[]>;
|
|
54
61
|
protected mapForeignKeysResult(_read: TableRowReader, _tableName: string, results: PostgresForeignKeyRow[]): Promise<ForeignKeySchema[]>;
|
|
55
62
|
protected normalizeType(dataType: string, udtName: string): string;
|
|
@@ -73,18 +80,18 @@ declare const FOREIGN_KEY_ACTION_CODES: {
|
|
|
73
80
|
* CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
|
|
74
81
|
* still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
|
|
75
82
|
* on an ASC column where Postgres sorts them last. Reading a nulls order back would therefore report
|
|
76
|
-
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
77
|
-
* 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.
|
|
78
84
|
*/
|
|
79
85
|
export declare class CockroachSchemaIntrospector extends PostgresSchemaIntrospector {
|
|
80
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";
|
|
81
89
|
/**
|
|
82
|
-
* `
|
|
83
|
-
*
|
|
84
|
-
* user asked for and report it missing forever. It leaves no way to tell the two apart, so the
|
|
85
|
-
* 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.
|
|
86
92
|
*/
|
|
87
|
-
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";
|
|
88
95
|
}
|
|
89
96
|
type PostgresForeignKeyRow = {
|
|
90
97
|
constraint_name: string;
|
|
@@ -104,13 +111,14 @@ type PostgresIndexRow = {
|
|
|
104
111
|
is_expression: boolean;
|
|
105
112
|
entry: string;
|
|
106
113
|
descending: boolean;
|
|
107
|
-
nulls_first: boolean;
|
|
114
|
+
nulls_first: boolean | null;
|
|
108
115
|
ops_class: string | null;
|
|
109
116
|
};
|
|
110
117
|
type PostgresColumnRow = {
|
|
111
118
|
column_name: string;
|
|
112
119
|
data_type: string;
|
|
113
120
|
udt_name: string;
|
|
121
|
+
formatted_type: string | null;
|
|
114
122
|
is_nullable: string;
|
|
115
123
|
column_default: string | null;
|
|
116
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,15 +16,15 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
15
16
|
'opsClass',
|
|
16
17
|
'accessMethod',
|
|
17
18
|
'include',
|
|
19
|
+
'distance',
|
|
18
20
|
]);
|
|
19
21
|
triggersQuery() {
|
|
20
22
|
return /*sql*/ `
|
|
21
|
-
SELECT
|
|
22
|
-
pg_get_functiondef(t.tgfoid) AS requires
|
|
23
|
+
SELECT t.tgname AS name, pg_get_triggerdef(t.oid) AS definition, pg_get_functiondef(t.tgfoid) AS requires
|
|
23
24
|
FROM pg_trigger t
|
|
24
25
|
JOIN pg_class c ON c.oid = t.tgrelid
|
|
25
26
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
26
|
-
WHERE NOT t.tgisinternal AND n.nspname = ${this.schemaExpr}
|
|
27
|
+
WHERE NOT t.tgisinternal AND n.nspname = ${this.schemaExpr} AND c.relname = ${this.dialect.placeholder(1)}
|
|
27
28
|
`;
|
|
28
29
|
}
|
|
29
30
|
getTableNamesQuery() {
|
|
@@ -54,7 +55,11 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
54
55
|
* database scans meet that table every time something else is migrating.
|
|
55
56
|
*
|
|
56
57
|
* `attgenerated` rather than `is_generated`, which cannot part a stored generated column from the
|
|
57
|
-
* 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.
|
|
58
63
|
*/
|
|
59
64
|
getColumnsQuery(_tableName) {
|
|
60
65
|
return /*sql*/ `
|
|
@@ -69,11 +74,9 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
69
74
|
c.numeric_scale,
|
|
70
75
|
c.is_identity,
|
|
71
76
|
c.identity_generation,
|
|
72
|
-
CASE WHEN
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
AND a.attname = c.column_name
|
|
76
|
-
) = '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,
|
|
77
80
|
EXISTS (
|
|
78
81
|
SELECT 1 FROM information_schema.table_constraints tc
|
|
79
82
|
JOIN information_schema.key_column_usage kcu USING (constraint_schema, constraint_name)
|
|
@@ -83,19 +86,19 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
83
86
|
AND kcu.column_name = c.column_name
|
|
84
87
|
) AS is_primary_key,
|
|
85
88
|
EXISTS (
|
|
86
|
-
SELECT 1 FROM
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
AND
|
|
90
|
-
AND tc.constraint_type = 'UNIQUE'
|
|
91
|
-
GROUP BY tc.constraint_name
|
|
92
|
-
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
|
|
93
93
|
) AS is_unique,
|
|
94
94
|
pg_catalog.col_description(
|
|
95
95
|
to_regclass(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name)),
|
|
96
96
|
c.ordinal_position
|
|
97
97
|
) AS column_comment
|
|
98
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
|
|
99
102
|
WHERE c.table_schema = ${this.schemaExpr}
|
|
100
103
|
AND c.table_name = $1
|
|
101
104
|
ORDER BY c.ordinal_position
|
|
@@ -108,23 +111,23 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
108
111
|
* while `pg_get_indexdef` reprints an identifier *quoted*, so a camelCase column came back as
|
|
109
112
|
* `"tenantId"` and matched no column of the table. Prisma and drizzle-kit both split it this way.
|
|
110
113
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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.
|
|
114
117
|
*/
|
|
115
118
|
getIndexesQuery(_tableName) {
|
|
116
119
|
return /*sql*/ `
|
|
117
120
|
SELECT
|
|
118
121
|
i.relname AS index_name,
|
|
119
122
|
ix.indisunique AS is_unique,
|
|
120
|
-
|
|
123
|
+
${this.indexMethodSql} AS method,
|
|
121
124
|
pg_get_expr(ix.indpred, ix.indrelid, true) AS predicate,
|
|
122
125
|
k.n <= ix.indnkeyatts AS is_key,
|
|
123
126
|
k.attnum = 0 AS is_expression,
|
|
124
127
|
COALESCE(a.attname::text, pg_get_indexdef(ix.indexrelid, k.n::int, true)) AS entry,
|
|
125
128
|
(ix.indoption[k.n - 1] & 1) <> 0 AS descending,
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
${this.nullsFirstSql} AS nulls_first,
|
|
130
|
+
${this.opsClassSql} AS ops_class
|
|
128
131
|
FROM pg_class t
|
|
129
132
|
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
130
133
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
@@ -137,22 +140,17 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
137
140
|
AND n.nspname = ${this.schemaExpr}
|
|
138
141
|
AND NOT ix.indisprimary
|
|
139
142
|
AND NOT EXISTS (
|
|
140
|
-
SELECT 1 FROM pg_constraint con
|
|
141
|
-
WHERE con.conindid = ix.indexrelid
|
|
142
|
-
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'
|
|
143
144
|
)
|
|
144
145
|
ORDER BY i.relname, k.n
|
|
145
146
|
`;
|
|
146
147
|
}
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* compare. This one decides which indexes are reported at all.
|
|
154
|
-
*/
|
|
155
|
-
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';
|
|
156
154
|
/** From `pg_constraint`, whose key arrays keep each column paired with the one it references. */
|
|
157
155
|
getForeignKeysQuery(_tableName) {
|
|
158
156
|
const columnsOf = (keys, table) => /*sql*/ `ARRAY_TO_JSON(ARRAY(
|
|
@@ -194,7 +192,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
194
192
|
async mapColumnsResult(_read, _tableName, results) {
|
|
195
193
|
return results.map((row) => ({
|
|
196
194
|
name: row.column_name,
|
|
197
|
-
type: this.normalizeType(row.data_type, row.udt_name),
|
|
195
|
+
type: row.formatted_type?.toUpperCase() ?? this.normalizeType(row.data_type, row.udt_name),
|
|
198
196
|
nullable: row.is_nullable === 'YES',
|
|
199
197
|
defaultValue: this.parseDefaultValue(row.column_default),
|
|
200
198
|
isPrimaryKey: row.is_primary_key,
|
|
@@ -207,11 +205,25 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
207
205
|
generatedAs: row.generated_as ?? undefined,
|
|
208
206
|
}));
|
|
209
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
|
+
}
|
|
210
222
|
async mapIndexesResult(_read, _tableName, results) {
|
|
211
223
|
// One row per index entry, ordered by position, so the rows of an index are its entries in order.
|
|
212
224
|
return [...Map.groupBy(results, (row) => row.index_name)].map(([name, rows]) => {
|
|
213
225
|
const include = rows.filter((row) => !row.is_key).map((row) => row.entry);
|
|
214
|
-
return {
|
|
226
|
+
return this.withVectorDistance({
|
|
215
227
|
name,
|
|
216
228
|
entries: rows.filter((row) => row.is_key).map(mapIndexEntry),
|
|
217
229
|
unique: rows[0].is_unique,
|
|
@@ -219,7 +231,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
219
231
|
where: rows[0].predicate ?? undefined,
|
|
220
232
|
include: include.length > 0 ? include : undefined,
|
|
221
233
|
...fulltextIndex(rows),
|
|
222
|
-
};
|
|
234
|
+
});
|
|
223
235
|
});
|
|
224
236
|
}
|
|
225
237
|
async mapForeignKeysResult(_read, _tableName, results) {
|
|
@@ -315,7 +327,7 @@ function mapIndexEntry(row) {
|
|
|
315
327
|
column: row.entry,
|
|
316
328
|
...(row.is_expression && { expression: true }),
|
|
317
329
|
order: row.descending ? 'desc' : 'asc',
|
|
318
|
-
nulls: row.nulls_first ? 'first' : 'last',
|
|
330
|
+
...(row.nulls_first !== null && { nulls: row.nulls_first ? 'first' : 'last' }),
|
|
319
331
|
...(row.ops_class && { opsClass: row.ops_class }),
|
|
320
332
|
};
|
|
321
333
|
}
|
|
@@ -323,16 +335,21 @@ function mapIndexEntry(row) {
|
|
|
323
335
|
* CockroachDB answers the same catalogue queries and differs only in what it can express: v26.2.5
|
|
324
336
|
* still rejects `NULLS FIRST/LAST` and operator classes as "unimplemented", and it sorts nulls first
|
|
325
337
|
* on an ASC column where Postgres sorts them last. Reading a nulls order back would therefore report
|
|
326
|
-
* every ascending index as drifted, against an entity that could not have asked for one.
|
|
327
|
-
* 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.
|
|
328
339
|
*/
|
|
329
340
|
export class CockroachSchemaIntrospector extends PostgresSchemaIntrospector {
|
|
330
|
-
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';
|
|
331
349
|
/**
|
|
332
|
-
* `
|
|
333
|
-
*
|
|
334
|
-
* user asked for and report it missing forever. It leaves no way to tell the two apart, so the
|
|
335
|
-
* 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.
|
|
336
352
|
*/
|
|
337
|
-
|
|
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`;
|
|
338
355
|
}
|
|
@@ -5,10 +5,10 @@ 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
10
|
triggersQuery() {
|
|
11
|
-
return /*sql*/ `SELECT
|
|
11
|
+
return /*sql*/ `SELECT name, sql AS definition FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ?`;
|
|
12
12
|
}
|
|
13
13
|
getTableNamesQuery() {
|
|
14
14
|
return /*sql*/ `
|
|
@@ -89,15 +89,12 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
89
89
|
const indexSchemas = [];
|
|
90
90
|
for (const index of results) {
|
|
91
91
|
const columns = await this.getIndexColumns(read, index.name);
|
|
92
|
-
//
|
|
93
|
-
// Skip primary key indexes ('pk') and single-column unique constraints
|
|
94
|
-
const isUserCreated = index.origin === 'c';
|
|
95
|
-
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.
|
|
96
93
|
// `PRAGMA index_info` names an expression entry `null` (its `cid` is -2), and the expression text
|
|
97
94
|
// lives only in `sqlite_master.sql`. Reporting `{ column: null }` put a column literally named
|
|
98
95
|
// `null` into the diff, so an index UQL cannot describe is left out, libSQL's vector index aside.
|
|
99
96
|
const named = columns.filter((column) => column.name !== null);
|
|
100
|
-
if (
|
|
97
|
+
if (index.origin === 'pk') {
|
|
101
98
|
continue;
|
|
102
99
|
}
|
|
103
100
|
if (named.length === columns.length) {
|
|
@@ -171,8 +168,13 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
171
168
|
return undefined;
|
|
172
169
|
}
|
|
173
170
|
const metric = row.sql?.match(/'metric=(\w+)'/i)?.[1]?.toLowerCase();
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
return {
|
|
172
|
+
name: indexName,
|
|
173
|
+
entries: [{ column }],
|
|
174
|
+
unique: false,
|
|
175
|
+
type: 'vector',
|
|
176
|
+
distance: this.dialect.indexedDistance(metric),
|
|
177
|
+
};
|
|
176
178
|
}
|
|
177
179
|
/** The statement that created the table, which is where SQLite keeps every expression it was given. */
|
|
178
180
|
async getTableDdl(read, tableName) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isMongoQuerier, isSqlQuerier, } from '../type/index.js';
|
|
2
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
2
3
|
import { withMongoQuerierForMigrations, withSqlQuerierForMigrations } from './acquireQuerierForMigrations.js';
|
|
3
4
|
import { MigrationBuilder } from './builder/migrationBuilder.js';
|
|
4
5
|
import { migrationSource } from './codegen/migrationFile.js';
|
|
@@ -46,5 +47,5 @@ export async function migrationBuilderFor(querier) {
|
|
|
46
47
|
if (isMongoQuerier(querier)) {
|
|
47
48
|
return new MigrationBuilder(await mongoSchemaGenerator(), mongoSession(querier).run);
|
|
48
49
|
}
|
|
49
|
-
throw new
|
|
50
|
+
throw new UqlUsageError('A migration builder needs a SQL or a MongoDB querier');
|
|
50
51
|
}
|
|
@@ -128,6 +128,11 @@ export declare class Migrator {
|
|
|
128
128
|
* never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
|
|
129
129
|
*/
|
|
130
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
|
+
*/
|
|
131
136
|
protected filterDiff(diff: SchemaDiff, options: {
|
|
132
137
|
safe?: boolean;
|
|
133
138
|
drop?: boolean;
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -5,9 +5,11 @@ import { getEntities, getMeta } from '../entity/index.js';
|
|
|
5
5
|
import { SchemaAST } from '../schema/index.js';
|
|
6
6
|
import { hasTriggers } from '../util/field.util.js';
|
|
7
7
|
import { LoggerWrapper } from '../util/index.js';
|
|
8
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
8
9
|
import { buildMigrationModule } from './codegen/migrationFile.js';
|
|
9
10
|
import { introspectorFor } from './introspection/registry.js';
|
|
10
11
|
import { migrationBuilderFor, migrationTargetFor } from './migrationTarget.js';
|
|
12
|
+
import { dropped, nonEmpty, reverseDiff, sides } from './schemaChange.js';
|
|
11
13
|
/**
|
|
12
14
|
* Main class for managing database migrations
|
|
13
15
|
*/
|
|
@@ -95,7 +97,7 @@ export class Migrator {
|
|
|
95
97
|
if (options.to) {
|
|
96
98
|
const toIndex = selected.findIndex((m) => m.name === options.to);
|
|
97
99
|
if (toIndex === -1) {
|
|
98
|
-
throw new
|
|
100
|
+
throw new UqlUsageError(`Migration '${options.to}' not found`);
|
|
99
101
|
}
|
|
100
102
|
selected = selected.slice(0, toIndex + 1);
|
|
101
103
|
}
|
|
@@ -215,17 +217,10 @@ export class Migrator {
|
|
|
215
217
|
// Tables this plan creates are left out: their `CREATE` carries their triggers, and asking the
|
|
216
218
|
// catalogue about a table that is not there yet fails outright on some engines.
|
|
217
219
|
const wanted = entities.filter((entity) => !fresh.has(this.tableOf(entity)));
|
|
218
|
-
const bySchema = new Map();
|
|
219
220
|
const state = [];
|
|
220
221
|
for (const entity of wanted) {
|
|
221
222
|
const meta = getMeta(entity);
|
|
222
|
-
const
|
|
223
|
-
let owned = bySchema.get(schema);
|
|
224
|
-
if (!owned) {
|
|
225
|
-
owned = await this.schemaIntrospectorFor(schema).ownedTriggers();
|
|
226
|
-
bySchema.set(schema, owned);
|
|
227
|
-
}
|
|
228
|
-
const installed = owned.get(dialect.resolveTableAlias(meta)) ?? new Map();
|
|
223
|
+
const installed = await this.schemaIntrospectorFor(dialect.resolveSchema(meta)).ownedTriggers(dialect.resolveTableAlias(meta));
|
|
229
224
|
// An installed trigger alone keeps it: an entity that stopped declaring one has it to drop.
|
|
230
225
|
if (installed.size || hasTriggers(meta)) {
|
|
231
226
|
state.push({ entity, installed });
|
|
@@ -239,7 +234,7 @@ export class Migrator {
|
|
|
239
234
|
* its entity declares goes back on after. `down` is lazy: SQLite cannot express every alter's inverse.
|
|
240
235
|
*/
|
|
241
236
|
alterPlan(generator, altered, state) {
|
|
242
|
-
const changing = new Set(altered.filter((diff) => diff.
|
|
237
|
+
const changing = new Set(altered.filter((diff) => sides(diff.columns, 'from').length).map((diff) => diff.tableName));
|
|
243
238
|
const cleared = state.filter(({ entity }) => changing.has(this.tableOf(entity)));
|
|
244
239
|
const after = state.map((it) => (cleared.includes(it) ? { entity: it.entity, installed: new Map() } : it));
|
|
245
240
|
return {
|
|
@@ -250,7 +245,7 @@ export class Migrator {
|
|
|
250
245
|
],
|
|
251
246
|
down: () => [
|
|
252
247
|
...this.revertedTriggers(generator, after),
|
|
253
|
-
...altered.toReversed().flatMap((diff) => generator.
|
|
248
|
+
...altered.toReversed().flatMap((diff) => generator.generateAlterTable(reverseDiff(diff))),
|
|
254
249
|
...cleared.flatMap(({ installed }) => [...installed.values()].flat().map((sql) => `${sql};`)),
|
|
255
250
|
],
|
|
256
251
|
};
|
|
@@ -400,45 +395,40 @@ export class Migrator {
|
|
|
400
395
|
altered: diffs.filter((diff) => diff.type === 'alter'),
|
|
401
396
|
};
|
|
402
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Safe mode only adds: a change with a `from` drops or rebuilds what the table holds, so it is held,
|
|
400
|
+
* and so is a key whole, which rebuilds an index over every row and fails where a column holds a null.
|
|
401
|
+
* Without `drop`, a column's drop is held too.
|
|
402
|
+
*/
|
|
403
403
|
filterDiff(diff, options) {
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (filteredDiff.columnsToDrop?.length) {
|
|
409
|
-
this.logger.logSkippedMigration(`[AutoSync] Skipped dropping ${filteredDiff.columnsToDrop.length} columns in table '${diff.tableName}': ${filteredDiff.columnsToDrop.join(', ')} (safe mode active)`);
|
|
410
|
-
delete filteredDiff.columnsToDrop;
|
|
411
|
-
}
|
|
412
|
-
if (filteredDiff.columnsToAlter?.length) {
|
|
413
|
-
this.logger.logSkippedMigration(`[AutoSync] Skipped altering ${filteredDiff.columnsToAlter.length} columns in table '${diff.tableName}': ${filteredDiff.columnsToAlter.map((c) => c.to.name).join(', ')} (safe mode active). Use a migration or { safe: false } to apply.`);
|
|
414
|
-
delete filteredDiff.columnsToAlter;
|
|
415
|
-
}
|
|
416
|
-
if (filteredDiff.primaryKey) {
|
|
417
|
-
// Rewriting a key drops a constraint and rebuilds an index over the whole table, and fails
|
|
418
|
-
// outright where the new columns are null on rows that already exist. Firmly not additive.
|
|
419
|
-
this.logger.logSkippedMigration(`[AutoSync] Skipped changing the primary key of '${diff.tableName}' from (${filteredDiff.primaryKey.from.join(', ')}) to (${filteredDiff.primaryKey.to.join(', ')}) (safe mode active). Use a migration or { safe: false } to apply.`);
|
|
420
|
-
delete filteredDiff.primaryKey;
|
|
404
|
+
const safe = options.safe !== false;
|
|
405
|
+
const skip = (what, names, fix) => {
|
|
406
|
+
if (names.length) {
|
|
407
|
+
this.logger.logSkippedMigration(`[AutoSync] Skipped ${names.length} ${what} in table '${diff.tableName}': ${names.join(', ')} (${fix}).`);
|
|
421
408
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
if (filteredDiff.indexesToDrop?.length) {
|
|
429
|
-
// An index recreated under its old name is a drop and an add, held back together.
|
|
430
|
-
const dropped = new Set(filteredDiff.indexesToDrop.map((index) => index.name));
|
|
431
|
-
this.logger.logSkippedMigration(`[AutoSync] Skipped dropping ${dropped.size} indexes in table '${diff.tableName}': ${[...dropped].join(', ')} (safe mode active). Use a migration or { safe: false } to apply.`);
|
|
432
|
-
filteredDiff.indexesToAdd = filteredDiff.indexesToAdd?.filter((index) => !dropped.has(index.name));
|
|
433
|
-
delete filteredDiff.indexesToDrop;
|
|
409
|
+
};
|
|
410
|
+
const safeFix = 'safe mode active. Use a migration or { safe: false } to apply';
|
|
411
|
+
const additive = (what, changes, nameOf) => {
|
|
412
|
+
if (!safe) {
|
|
413
|
+
return changes;
|
|
434
414
|
}
|
|
435
|
-
|
|
415
|
+
skip(`${what} changes`, sides(changes, 'from').map(nameOf), safeFix);
|
|
416
|
+
return nonEmpty((changes ?? []).filter((change) => change.from === undefined));
|
|
417
|
+
};
|
|
418
|
+
const columns = additive('column', diff.columns, (column) => column.name);
|
|
419
|
+
if (safe && diff.primaryKey) {
|
|
420
|
+
skip('primary key changes', [diff.tableName], safeFix);
|
|
436
421
|
}
|
|
437
|
-
if (!options.drop
|
|
438
|
-
|
|
439
|
-
delete filteredDiff.columnsToDrop;
|
|
422
|
+
if (!options.drop) {
|
|
423
|
+
skip('column drops', dropped(columns).map((column) => column.name), 'drop: false. Use { drop: true } to apply');
|
|
440
424
|
}
|
|
441
|
-
return
|
|
425
|
+
return {
|
|
426
|
+
...diff,
|
|
427
|
+
primaryKey: safe ? undefined : diff.primaryKey,
|
|
428
|
+
columns: options.drop ? columns : nonEmpty((columns ?? []).filter((change) => change.to !== undefined)),
|
|
429
|
+
indexes: additive('index', diff.indexes, (index) => index.name),
|
|
430
|
+
foreignKeys: additive('foreign key', diff.foreignKeys, (foreignKey) => foreignKey.name ?? foreignKey.columns.join(', ')),
|
|
431
|
+
};
|
|
442
432
|
}
|
|
443
433
|
/** Runs the statements a generator wrote, in one transaction where the engine takes DDL in one. */
|
|
444
434
|
async executeSyncStatements(statements, options) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Change, SchemaDiff } from '../type/index.js';
|
|
2
|
+
/** Each change's end on `side`, where it has one: what a drop half removes (`from`), or an add half creates (`to`). */
|
|
3
|
+
export declare function sides<T>(changes: readonly Change<T>[] | undefined, side: 'from' | 'to'): T[];
|
|
4
|
+
/** What the changes add: each `to` with no `from`. */
|
|
5
|
+
export declare function added<T>(changes?: readonly Change<T>[]): T[];
|
|
6
|
+
/** What the changes drop: each `from` with no `to`. */
|
|
7
|
+
export declare function dropped<T>(changes?: readonly Change<T>[]): T[];
|
|
8
|
+
/** The changes that alter an object in place, which only a column can. */
|
|
9
|
+
export declare function alterations<T>(changes?: readonly Change<T>[]): {
|
|
10
|
+
readonly from: T;
|
|
11
|
+
readonly to: T;
|
|
12
|
+
}[];
|
|
13
|
+
/** `items`, or nothing where it has none, so an empty change list is left off a diff. */
|
|
14
|
+
export declare function nonEmpty<T>(items: readonly T[]): readonly T[] | undefined;
|
|
15
|
+
/** `change` undone: an add becomes a drop, a drop an add, and an alter runs the other way. */
|
|
16
|
+
export declare function swap<T>({ from, to }: Change<T>): Change<T>;
|
|
17
|
+
/** `diff` undone: every change swapped, which is what a migration's `down` runs. */
|
|
18
|
+
export declare function reverseDiff(diff: SchemaDiff): SchemaDiff;
|