uql-orm 0.86.0 → 0.88.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/dialect/abstractSqlDialect.d.ts +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +1 -3
- package/dist/dialect/pgLikeSqlDialect.js +1 -3
- package/dist/migrate/ddl/tableDdl.d.ts +5 -0
- package/dist/migrate/ddl/tableDdl.js +18 -7
- package/dist/migrate/ddl/tableRebuild.d.ts +17 -0
- package/dist/migrate/ddl/tableRebuild.js +56 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +7 -1
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +4 -2
- package/dist/migrate/introspection/baseSqlIntrospector.js +29 -4
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +3 -3
- package/dist/migrate/introspection/sqliteIntrospector.js +9 -7
- package/dist/migrate/migrationTarget.js +27 -1
- package/dist/migrate/migrator.d.ts +20 -3
- package/dist/migrate/migrator.js +108 -17
- package/dist/migrate/schemaChange.d.ts +12 -1
- package/dist/migrate/schemaChange.js +28 -0
- package/dist/migrate/schemaGenerator.d.ts +19 -9
- package/dist/migrate/schemaGenerator.js +81 -42
- package/dist/migrate/triggerSql.js +19 -14
- package/dist/mongo/mongoDialect.js +1 -3
- package/dist/mssql/mssqlDialect.js +1 -3
- package/dist/schema/indexDifferences.d.ts +2 -1
- package/dist/schema/indexDifferences.js +2 -1
- package/dist/schema/matchByKey.d.ts +9 -0
- package/dist/schema/matchByKey.js +18 -0
- package/dist/schema/schemaAST.js +1 -0
- package/dist/schema/schemaASTDiffer.d.ts +13 -0
- package/dist/schema/schemaASTDiffer.js +35 -4
- package/dist/schema/types.d.ts +5 -1
- package/dist/sqlite/sqliteDialect.d.ts +0 -1
- package/dist/sqlite/sqliteDialect.js +1 -4
- package/dist/type/dialect.d.ts +4 -10
- package/dist/type/migration.d.ts +45 -4
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +1 -1
|
@@ -53,8 +53,9 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
53
53
|
const table = dialect.escapedTableName(meta);
|
|
54
54
|
const names = rowNames(dialect);
|
|
55
55
|
const rows = [rowRefs(meta.entity, names.$new), rowRefs(meta.entity, names.$old)];
|
|
56
|
-
const
|
|
57
|
-
const
|
|
56
|
+
const source = rowsFrom(dialect, meta, operation, trigger.of ?? []);
|
|
57
|
+
const sql = dialect.compileDdl(body(...rows), meta.entity, { rows: source });
|
|
58
|
+
const guard = triggerGuard(dialect, meta, trigger, rows, names, source);
|
|
58
59
|
const inBody = features.guards !== 'clause';
|
|
59
60
|
const guarded = !guard || !inBody
|
|
60
61
|
? sql
|
|
@@ -115,12 +116,16 @@ function triggerBody(dialect, meta, trigger, before) {
|
|
|
115
116
|
}
|
|
116
117
|
return body;
|
|
117
118
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
119
|
+
/**
|
|
120
|
+
* The one condition both guards reduce to: any watched column that moved, and whatever `where` asks. On a
|
|
121
|
+
* set-based engine a watched column narrows `source` already, so the guard asks whether it holds a row.
|
|
122
|
+
*/
|
|
123
|
+
function triggerGuard(dialect, meta, trigger, rows, names, source) {
|
|
120
124
|
const moved = movedColumns(dialect, meta, trigger.of ?? [], names);
|
|
125
|
+
const watched = moved && (source ? `EXISTS (SELECT 1 ${source})` : moved);
|
|
121
126
|
return [
|
|
122
|
-
...(
|
|
123
|
-
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(
|
|
127
|
+
...(watched ? [watched] : []),
|
|
128
|
+
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(watched)) : []),
|
|
124
129
|
].join(' AND ');
|
|
125
130
|
}
|
|
126
131
|
/** The PL/pgSQL function holding a body. `OR REPLACE`, since a dropped table leaves its function behind. */
|
|
@@ -210,7 +215,7 @@ function dollarQuote(body) {
|
|
|
210
215
|
}
|
|
211
216
|
/**
|
|
212
217
|
* Whether any watched column moved, null-safely, or `undefined` where none is watched: the two records
|
|
213
|
-
* compared on a row-based engine,
|
|
218
|
+
* compared on a row-based engine, the two tables' rows on a set-based one.
|
|
214
219
|
*/
|
|
215
220
|
function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }) {
|
|
216
221
|
if (!of.length) {
|
|
@@ -220,16 +225,15 @@ function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }) {
|
|
|
220
225
|
const column = dialect.escapedColumnName(meta, key);
|
|
221
226
|
return dialect.neExpr(`${oldName}.${column}`, `${newName}.${column}`);
|
|
222
227
|
});
|
|
223
|
-
|
|
224
|
-
const source = rowsFrom(dialect, meta, 'UPDATE');
|
|
225
|
-
return source ? `EXISTS (SELECT 1 ${source} WHERE ${moved})` : moved;
|
|
228
|
+
return differs.length > 1 ? `(${differs.join(' OR ')})` : differs.join('');
|
|
226
229
|
}
|
|
227
230
|
/**
|
|
228
231
|
* Where a set-based engine's body reads the rows it fires for, as the `FROM` a statement names them in:
|
|
229
|
-
* `inserted` on an insert, `deleted` on a delete, and on an update both, joined on the whole key
|
|
230
|
-
*
|
|
232
|
+
* `inserted` on an insert, `deleted` on a delete, and on an update both, joined on the whole key and on a
|
|
233
|
+
* watched column having moved, so the body writes for those rows alone, as a per-row engine fires for them.
|
|
234
|
+
* None on a row-based engine, whose body reads `NEW` and `OLD` bare.
|
|
231
235
|
*/
|
|
232
|
-
function rowsFrom(dialect, meta, operation) {
|
|
236
|
+
function rowsFrom(dialect, meta, operation, of) {
|
|
233
237
|
if (dialect.features.triggers.rows !== 'set') {
|
|
234
238
|
return undefined;
|
|
235
239
|
}
|
|
@@ -241,5 +245,6 @@ function rowsFrom(dialect, meta, operation) {
|
|
|
241
245
|
const column = dialect.escapedColumnName(meta, id);
|
|
242
246
|
return `${$new}.${column} = ${$old}.${column}`;
|
|
243
247
|
});
|
|
244
|
-
|
|
248
|
+
const moved = movedColumns(dialect, meta, of, { $new, $old });
|
|
249
|
+
return `FROM ${$new} JOIN ${$old} ON ${[...keyed, ...(moved ? [moved] : [])].join(' AND ')}`;
|
|
245
250
|
}
|
|
@@ -19,9 +19,7 @@ export const mongoDialectFeatures = {
|
|
|
19
19
|
indexIfNotExists: false,
|
|
20
20
|
schemas: false, // the connection picks the database, and a collection name takes no dot
|
|
21
21
|
dropTableCascade: false,
|
|
22
|
-
|
|
23
|
-
primaryKeyAlter: false,
|
|
24
|
-
generatedColumnAdd: false,
|
|
22
|
+
rebuildsTables: false,
|
|
25
23
|
commentSyntax: 'none',
|
|
26
24
|
vectorIndexRequiresNotNull: false,
|
|
27
25
|
vectorSupportsLength: false,
|
|
@@ -17,9 +17,7 @@ const MSSQL_FEATURES = {
|
|
|
17
17
|
indexIfNotExists: true,
|
|
18
18
|
schemas: true,
|
|
19
19
|
dropTableCascade: false,
|
|
20
|
-
|
|
21
|
-
primaryKeyAlter: true,
|
|
22
|
-
generatedColumnAdd: true,
|
|
20
|
+
rebuildsTables: false,
|
|
23
21
|
// Extended properties are out-of-band metadata with their own procedures, not comments.
|
|
24
22
|
commentSyntax: 'none',
|
|
25
23
|
vectorIndexRequiresNotNull: false,
|
|
@@ -30,7 +30,7 @@ export declare function pairIndexes<S extends ComparableIndex, T extends Compara
|
|
|
30
30
|
/**
|
|
31
31
|
* The indexes a table lacks, the ones it no longer needs, and the ones to rebuild, differing in what
|
|
32
32
|
* `facets` let the engine report. Only an unpaired index uql named, or whose name the entity claims, is
|
|
33
|
-
* dropped: any other may have been made outside the ORM
|
|
33
|
+
* dropped: any other may have been made outside the ORM, so it is `kept`.
|
|
34
34
|
*/
|
|
35
35
|
export declare function indexChanges<I extends IndexSchema>(table: string, declared: readonly I[], current: readonly IndexNode[], facets: ReadonlySet<IndexFacet>): {
|
|
36
36
|
toAdd: I[];
|
|
@@ -39,6 +39,7 @@ export declare function indexChanges<I extends IndexSchema>(table: string, decla
|
|
|
39
39
|
from: IndexNode;
|
|
40
40
|
to: I;
|
|
41
41
|
}[];
|
|
42
|
+
kept: IndexNode[];
|
|
42
43
|
};
|
|
43
44
|
/**
|
|
44
45
|
* What two indexes differ by, comparing only what both sides state structurally: an expression, a JSON
|
|
@@ -38,7 +38,7 @@ export function pairIndexes(source, target, normalizeName = (name) => name) {
|
|
|
38
38
|
/**
|
|
39
39
|
* The indexes a table lacks, the ones it no longer needs, and the ones to rebuild, differing in what
|
|
40
40
|
* `facets` let the engine report. Only an unpaired index uql named, or whose name the entity claims, is
|
|
41
|
-
* dropped: any other may have been made outside the ORM
|
|
41
|
+
* dropped: any other may have been made outside the ORM, so it is `kept`.
|
|
42
42
|
*/
|
|
43
43
|
export function indexChanges(table, declared, current, facets) {
|
|
44
44
|
const { created, dropped, matched } = pairIndexes(declared, current);
|
|
@@ -47,6 +47,7 @@ export function indexChanges(table, declared, current, facets) {
|
|
|
47
47
|
return {
|
|
48
48
|
toAdd: created,
|
|
49
49
|
toDrop: dropped.filter(owned),
|
|
50
|
+
kept: dropped.filter((index) => !owned(index)),
|
|
50
51
|
toAlter: matched.flatMap(([to, from]) => (describeIndexDifferences(to, from, facets).length ? [{ from, to }] : [])),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -8,3 +8,12 @@ export declare function matchByKey<S, T>(source: Iterable<S>, target: Iterable<T
|
|
|
8
8
|
dropped: T[];
|
|
9
9
|
matched: (readonly [S, T])[];
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* What {@link matchByKey} left unpaired, paired where `same` finds exactly one counterpart on each side:
|
|
13
|
+
* an item two others could be is ambiguous, so it stays created or dropped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function pairUnique<S, T>(created: readonly S[], dropped: readonly T[], same: (source: S, target: T) => boolean): {
|
|
16
|
+
created: S[];
|
|
17
|
+
dropped: T[];
|
|
18
|
+
matched: (readonly [S, T & ({} | null)])[];
|
|
19
|
+
};
|
|
@@ -16,3 +16,21 @@ export function matchByKey(source, target, key) {
|
|
|
16
16
|
}
|
|
17
17
|
return { created, dropped: [...unpaired.values()].flat(), matched };
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* What {@link matchByKey} left unpaired, paired where `same` finds exactly one counterpart on each side:
|
|
21
|
+
* an item two others could be is ambiguous, so it stays created or dropped.
|
|
22
|
+
*/
|
|
23
|
+
export function pairUnique(created, dropped, same) {
|
|
24
|
+
const matched = created.flatMap((source) => {
|
|
25
|
+
const [target, ...others] = dropped.filter((candidate) => same(source, candidate));
|
|
26
|
+
return target !== undefined && !others.length && created.filter((other) => same(other, target)).length === 1
|
|
27
|
+
? [[source, target]]
|
|
28
|
+
: [];
|
|
29
|
+
});
|
|
30
|
+
const paired = new Set(matched.flat());
|
|
31
|
+
return {
|
|
32
|
+
created: created.filter((item) => !paired.has(item)),
|
|
33
|
+
dropped: dropped.filter((item) => !paired.has(item)),
|
|
34
|
+
matched,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ColumnRenames, Rename } from '../type/migration.js';
|
|
1
2
|
import type { SchemaAST } from './schemaAST.js';
|
|
2
3
|
import type { CanonicalType } from './types.js';
|
|
3
4
|
import type { ColumnDiff, ForeignKeyAction, IndexDiff, RelationshipDiff, RelationshipNode, SchemaDiffResult, TableDiff, TableNode } from './types.js';
|
|
@@ -28,6 +29,18 @@ export declare function diffTable(source: TableNode, target: TableNode, options?
|
|
|
28
29
|
readonly columnDiffs: ColumnDiff[];
|
|
29
30
|
readonly indexDiffs: IndexDiff[];
|
|
30
31
|
}) | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The columns renamed in the tables both sides name, by qualified table:
|
|
34
|
+
* a new column identical to exactly one the entity no longer names, and to no other. The dropped side is
|
|
35
|
+
* always the entity's own table, so a wrong guess renames, keeping the data, and never drops it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function columnRenames(desired: SchemaAST, actual: SchemaAST, options?: DiffOptions): ColumnRenames;
|
|
38
|
+
/**
|
|
39
|
+
* Tables the database holds that a new one is identical to but for its name, each only where it is the one
|
|
40
|
+
* match on both sides. Suggested, never applied: the database's side is a table no entity names, which may
|
|
41
|
+
* be another application's rather than one this schema renamed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function tableRenameCandidates(desired: SchemaAST, actual: SchemaAST, options?: DiffOptions): Rename[];
|
|
31
44
|
/** The differences between two lists of foreign keys, matched by their columns and never by the name the engine gave them. */
|
|
32
45
|
export declare function diffRelationshipNodes(source: readonly RelationshipNode[], target: readonly RelationshipNode[], opts?: DiffOptions): RelationshipDiff[];
|
|
33
46
|
/** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { qualifyName } from '../util/sql.util.js';
|
|
1
2
|
import { areTypesEqual, isBreakingTypeChange } from './canonicalType.js';
|
|
2
3
|
import { describeIndexDifferences, pairIndexes } from './indexDifferences.js';
|
|
3
|
-
import { matchByKey } from './matchByKey.js';
|
|
4
|
+
import { matchByKey, pairUnique } from './matchByKey.js';
|
|
4
5
|
import { DEFAULT_FOREIGN_KEY_ACTION } from './types.js';
|
|
5
6
|
/**
|
|
6
7
|
* Default diff options.
|
|
@@ -23,9 +24,7 @@ function relationEnds(relation) {
|
|
|
23
24
|
/** The differences between the expected schema (the entities) and the actual one (the database). */
|
|
24
25
|
export function diffSchemas(source, target, options = {}) {
|
|
25
26
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
26
|
-
const
|
|
27
|
-
const included = (tables) => [...tables].filter((table) => !opts.excludeTables.includes(table.name));
|
|
28
|
-
const { created: tablesToCreate, dropped: tablesToDrop, matched, } = matchByKey(included(source.tables.values()), included(target.tables.values()), (table) => normalizeName(table.name));
|
|
27
|
+
const { created: tablesToCreate, dropped: tablesToDrop, matched } = matchTables(source, target, opts);
|
|
29
28
|
const tablesToAlter = matched
|
|
30
29
|
.map(([sourceTable, targetTable]) => diffTable(sourceTable, targetTable, opts))
|
|
31
30
|
.filter((tableDiff) => tableDiff !== undefined);
|
|
@@ -104,6 +103,38 @@ function diffTableColumns(source, target, opts) {
|
|
|
104
103
|
.filter((diff) => diff !== undefined),
|
|
105
104
|
];
|
|
106
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* The columns renamed in the tables both sides name, by qualified table:
|
|
108
|
+
* a new column identical to exactly one the entity no longer names, and to no other. The dropped side is
|
|
109
|
+
* always the entity's own table, so a wrong guess renames, keeping the data, and never drops it.
|
|
110
|
+
*/
|
|
111
|
+
export function columnRenames(desired, actual, options = {}) {
|
|
112
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
113
|
+
const normalizeName = nameNormalizer(opts);
|
|
114
|
+
return new Map(matchTables(desired, actual, opts).matched.flatMap(([expected, current]) => {
|
|
115
|
+
const { created, dropped } = matchByKey(expected.columns.values(), current.columns.values(), (column) => normalizeName(column.name));
|
|
116
|
+
const { matched } = pairUnique(created, dropped, (to, from) => !diffColumn(expected.name, to, from, opts));
|
|
117
|
+
const table = qualifyName(current.name, current.schema);
|
|
118
|
+
return matched.length ? [[table, matched.map(([to, from]) => ({ from: from.name, to: to.name }))]] : [];
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Tables the database holds that a new one is identical to but for its name, each only where it is the one
|
|
123
|
+
* match on both sides. Suggested, never applied: the database's side is a table no entity names, which may
|
|
124
|
+
* be another application's rather than one this schema renamed.
|
|
125
|
+
*/
|
|
126
|
+
export function tableRenameCandidates(desired, actual, options = {}) {
|
|
127
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
128
|
+
const { created, dropped } = matchTables(desired, actual, opts);
|
|
129
|
+
const same = (to, from) => to.schema === from.schema && !diffTable(to, from, { ...opts, compareIndexes: false });
|
|
130
|
+
return pairUnique(created, dropped, same).matched.map(([to, from]) => ({ from: from.name, to: to.name }));
|
|
131
|
+
}
|
|
132
|
+
/** The two sides' tables paired by name, those `excludeTables` names left out of both. */
|
|
133
|
+
function matchTables(desired, actual, opts) {
|
|
134
|
+
const normalizeName = nameNormalizer(opts);
|
|
135
|
+
const included = (tables) => [...tables].filter((table) => !opts.excludeTables.includes(table.name));
|
|
136
|
+
return matchByKey(included(desired.tables.values()), included(actual.tables.values()), (table) => normalizeName(table.name));
|
|
137
|
+
}
|
|
107
138
|
/** Compare indexes between two tables, paired by {@link pairIndexes}, in what the target's reader reports. */
|
|
108
139
|
function diffTableIndexes(source, target, opts) {
|
|
109
140
|
const { created, dropped, matched } = pairIndexes(source.indexes, target.indexes, nameNormalizer(opts));
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IndexSchema, PrimaryKeySchema } from '../type/migration.js';
|
|
1
|
+
import type { ForeignKeySchema, IndexSchema, PrimaryKeySchema, StoredDefinition } from '../type/migration.js';
|
|
2
2
|
import type { IndexFacet } from './indexDifferences.js';
|
|
3
3
|
/**
|
|
4
4
|
* Type categories universal across SQL dialects.
|
|
@@ -123,6 +123,10 @@ export interface TableNode {
|
|
|
123
123
|
readonly checks: CheckSchema[];
|
|
124
124
|
/** Optional table comment */
|
|
125
125
|
readonly comment?: string;
|
|
126
|
+
/** The statements the engine keeps for the table, where it keeps them; none on a table built from entities. */
|
|
127
|
+
definition?: readonly StoredDefinition[];
|
|
128
|
+
/** The foreign keys to tables this AST does not hold, which a rebuild keeps as they are. */
|
|
129
|
+
readonly externalForeignKeys: ForeignKeySchema[];
|
|
126
130
|
/** Relationships pointing TO this table (other tables referencing this one) */
|
|
127
131
|
incomingRelations: RelationshipNode[];
|
|
128
132
|
/** Relationships pointing FROM this table (this table referencing others) */
|
|
@@ -13,7 +13,6 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
13
13
|
readonly commitTransactionCommand = "COMMIT";
|
|
14
14
|
readonly rollbackTransactionCommand = "ROLLBACK";
|
|
15
15
|
readonly isolationLevelStrategy = "none";
|
|
16
|
-
readonly alterColumnSyntax = "none";
|
|
17
16
|
readonly booleanLiteral = "integer";
|
|
18
17
|
/** SQLite's own cap on a function call before 3.48, which libSQL and `bun:sqlite`'s build still have. */
|
|
19
18
|
readonly maxFunctionArgs: number;
|
|
@@ -21,9 +21,7 @@ export const SQLITE_FEATURES = {
|
|
|
21
21
|
indexIfNotExists: true,
|
|
22
22
|
schemas: false, // SQLite's namespaces are attached database files, not declared objects
|
|
23
23
|
dropTableCascade: false,
|
|
24
|
-
|
|
25
|
-
primaryKeyAlter: false, // nor changing a key: the only route is rebuilding the table
|
|
26
|
-
generatedColumnAdd: false, // accepted in a CREATE TABLE, rejected in an ALTER
|
|
24
|
+
rebuildsTables: true,
|
|
27
25
|
commentSyntax: 'none',
|
|
28
26
|
vectorIndexRequiresNotNull: false,
|
|
29
27
|
vectorSupportsLength: true,
|
|
@@ -62,7 +60,6 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
62
60
|
commitTransactionCommand = 'COMMIT';
|
|
63
61
|
rollbackTransactionCommand = 'ROLLBACK';
|
|
64
62
|
isolationLevelStrategy = 'none';
|
|
65
|
-
alterColumnSyntax = 'none';
|
|
66
63
|
booleanLiteral = 'integer';
|
|
67
64
|
/** SQLite's own cap on a function call before 3.48, which libSQL and `bun:sqlite`'s build still have. */
|
|
68
65
|
maxFunctionArgs = 127;
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -87,18 +87,12 @@ export interface DialectFeatures {
|
|
|
87
87
|
*/
|
|
88
88
|
readonly schemas: boolean;
|
|
89
89
|
readonly dropTableCascade: boolean;
|
|
90
|
-
readonly foreignKeyAlter: boolean;
|
|
91
90
|
/**
|
|
92
|
-
* Whether a
|
|
93
|
-
*
|
|
94
|
-
*
|
|
91
|
+
* Whether the engine changes a column, a key or a foreign key, or adds a stored generated column, only
|
|
92
|
+
* by rebuilding the table, as SQLite does: a generated migration copies the table into a new one, and
|
|
93
|
+
* the migration builder refuses the change by name.
|
|
95
94
|
*/
|
|
96
|
-
readonly
|
|
97
|
-
/**
|
|
98
|
-
* Whether a stored generated column can be added to an existing table. SQLite takes one only in a
|
|
99
|
-
* `CREATE TABLE`, so a sync that would add one is refused by name.
|
|
100
|
-
*/
|
|
101
|
-
readonly generatedColumnAdd: boolean;
|
|
95
|
+
readonly rebuildsTables: boolean;
|
|
102
96
|
/** Where a comment goes: in the declaration (MySQL family), a `COMMENT ON` of its own (Postgres family), or nowhere. */
|
|
103
97
|
readonly commentSyntax: 'inline' | 'statement' | 'none';
|
|
104
98
|
/**
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AnyMigrationOperation } from '../migrate/builder/types.js';
|
|
2
2
|
import type { IndexFacet } from '../schema/indexDifferences.js';
|
|
3
3
|
import type { SchemaAST } from '../schema/schemaAST.js';
|
|
4
|
+
import type { DiffOptions } from '../schema/schemaASTDiffer.js';
|
|
4
5
|
import type { ColumnNode, ForeignKeyAction, IndexType, TableNode } from '../schema/types.js';
|
|
5
6
|
import type { EntityMeta, EntityWhereMeta, FieldOptions, IndexColumnSchema, IndexedVectorField, LoggingOptions, Querier, SqlQuerier, Type, VectorIndexOptions } from './index.js';
|
|
6
7
|
/**
|
|
@@ -121,7 +122,20 @@ export interface TableSchema {
|
|
|
121
122
|
readonly primaryKey?: PrimaryKeySchema;
|
|
122
123
|
readonly indexes?: IndexSchema[];
|
|
123
124
|
readonly foreignKeys?: ForeignKeySchema[];
|
|
125
|
+
/** The statements the engine keeps for the table, where it keeps them: SQLite's `sqlite_master`. */
|
|
126
|
+
readonly definition?: readonly StoredDefinition[];
|
|
124
127
|
}
|
|
128
|
+
/** A statement exactly as the engine keeps it, which only it can say all of: a `CHECK`, an index over an expression. */
|
|
129
|
+
export type StoredDefinition = {
|
|
130
|
+
readonly kind: 'table' | 'index' | 'trigger';
|
|
131
|
+
readonly name: string;
|
|
132
|
+
readonly sql: string;
|
|
133
|
+
};
|
|
134
|
+
/** One side of a rebuilt table: its `CREATE TABLE` and what goes back on it, and the columns holding stored values, under this side's names. */
|
|
135
|
+
export type RebuiltTable = {
|
|
136
|
+
readonly statements: readonly string[];
|
|
137
|
+
readonly columns: readonly string[];
|
|
138
|
+
};
|
|
125
139
|
/**
|
|
126
140
|
* Represents an index in a database table
|
|
127
141
|
*/
|
|
@@ -173,6 +187,17 @@ export interface ForeignKeySchema {
|
|
|
173
187
|
* change is undone by swapping its ends. No engine alters an index, a key or a foreign key in place, so
|
|
174
188
|
* an alter of one is its drop and its add, which safe mode holds back together.
|
|
175
189
|
*/
|
|
190
|
+
/** A column's change, and whether it can lose what the column holds: a drop, or a retype that narrows it. */
|
|
191
|
+
export type ColumnChange = Change<ColumnSchema> & {
|
|
192
|
+
readonly isBreaking?: boolean;
|
|
193
|
+
};
|
|
194
|
+
/** A name changed, `from` the database's `to` the entity's. */
|
|
195
|
+
export type Rename = {
|
|
196
|
+
readonly from: string;
|
|
197
|
+
readonly to: string;
|
|
198
|
+
};
|
|
199
|
+
/** Renamed columns by qualified table name. */
|
|
200
|
+
export type ColumnRenames = ReadonlyMap<string, readonly Rename[]>;
|
|
176
201
|
export interface Change<T> {
|
|
177
202
|
readonly from?: T;
|
|
178
203
|
readonly to?: T;
|
|
@@ -199,9 +224,19 @@ export interface SchemaDiff {
|
|
|
199
224
|
readonly schema?: string;
|
|
200
225
|
readonly type: 'create' | 'alter' | 'drop';
|
|
201
226
|
readonly primaryKey?: Change<PrimaryKeySchema>;
|
|
202
|
-
readonly columns?: readonly
|
|
227
|
+
readonly columns?: readonly ColumnChange[];
|
|
203
228
|
readonly indexes?: readonly Change<IndexSchema>[];
|
|
204
229
|
readonly foreignKeys?: readonly Change<ForeignKeySchema>[];
|
|
230
|
+
/** Columns renamed in place, `from` the database's name `to` the entity's, which the other changes already use. */
|
|
231
|
+
readonly renamedColumns?: readonly Rename[];
|
|
232
|
+
/**
|
|
233
|
+
* The table copied into a new one, which is how an engine that {@link DialectFeatures.rebuildsTables}
|
|
234
|
+
* applies the changes above. Its column renames are carried by the copy.
|
|
235
|
+
*/
|
|
236
|
+
readonly rebuild?: {
|
|
237
|
+
readonly from: RebuiltTable;
|
|
238
|
+
readonly to: RebuiltTable;
|
|
239
|
+
};
|
|
205
240
|
}
|
|
206
241
|
/**
|
|
207
242
|
* What every sync entry point takes: `safe` keeps it additive, `drop` lets it remove a column, and
|
|
@@ -288,10 +323,13 @@ export interface SchemaGenerator {
|
|
|
288
323
|
/**
|
|
289
324
|
* An entity's differences from its table. `desiredAst`, from {@link buildAST}, has to span every entity
|
|
290
325
|
* a foreign key here points at, or those keys read as matching.
|
|
326
|
+
* `renamedColumns` are columns `currentTable` already holds under their new names.
|
|
291
327
|
*/
|
|
292
|
-
diffSchema(entity: Type<object>, currentTable: TableNode | undefined, desiredAst?: SchemaAST): SchemaDiff | undefined;
|
|
328
|
+
diffSchema(entity: Type<object>, currentTable: TableNode | undefined, desiredAst?: SchemaAST, renamedColumns?: readonly Rename[]): SchemaDiff | undefined;
|
|
293
329
|
/** The entities as one AST, built once per run for every {@link diffSchema}. Absent on MongoDB, which diffs only indexes. */
|
|
294
330
|
buildAST?(entities: readonly Type<object>[]): SchemaAST;
|
|
331
|
+
/** How this engine's diff compares types and defaults. Absent where {@link buildAST} is. */
|
|
332
|
+
diffOptions?(): DiffOptions;
|
|
295
333
|
/**
|
|
296
334
|
* The table's key: {@link resolveTableAlias} behind {@link resolveSchema}, which is how a
|
|
297
335
|
* `SchemaAST` stores it and how a diff finds it again.
|
|
@@ -326,8 +364,11 @@ export interface SchemaIntrospector {
|
|
|
326
364
|
* the database side never reports it, and no migration can close the gap.
|
|
327
365
|
*/
|
|
328
366
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
329
|
-
/**
|
|
330
|
-
|
|
367
|
+
/**
|
|
368
|
+
* The whole database, or just the tables named. Names nothing matches are left out. `renames` reads
|
|
369
|
+
* each column under the name it is being renamed to, so a diff compares it as the column it becomes.
|
|
370
|
+
*/
|
|
371
|
+
introspect(tables?: readonly string[], renames?: ColumnRenames): Promise<SchemaAST>;
|
|
331
372
|
/**
|
|
332
373
|
* Get all table names in the database
|
|
333
374
|
*/
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.88.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
package/skills/uql-orm/SKILL.md
CHANGED
|
@@ -154,7 +154,7 @@ transaction. A querier from `pool.getQuerier()` is yours to release: bind it wit
|
|
|
154
154
|
## Migrations
|
|
155
155
|
|
|
156
156
|
`npx uql-migrate` reads `uql.config.ts`. `sync` creates what the entities imply (development only);
|
|
157
|
-
`generate:entities` writes the diff as a migration file to review; `up` applies migrations; `generate:from-db`
|
|
157
|
+
`generate:entities` writes the diff as a migration file to review, renaming a column its field was renamed from, printing `renameTable` for a table that may have been, rebuilding a SQLite table for what it cannot alter, and refusing a required column with no default on a table holding rows; `up` applies migrations; `generate:from-db`
|
|
158
158
|
writes entity classes from an existing database; `drift:check` fails when the database no longer matches.
|
|
159
159
|
Triggers are part of the diff: uql installs its own under `_uql_`-prefixed names and never touches another.
|
|
160
160
|
|