feat: consolidate database management work
Add catalog-owned logical relationships and runtime snapshots, extend the database-management UI and validation coverage, and document the updated operational workflow. Keep active sensitive-generation status in a tooltip and indicator, and update the layout E2E to follow the history action in its new database-scoped location.
This commit is contained in:
@@ -12,6 +12,7 @@ import { up as upRuntimeSequencePrivileges } from "../src/catalog/migrations/004
|
||||
import { up as upDescriptionGeneration } from "../src/catalog/migrations/005_description_generation_runs.js";
|
||||
import { up as upSensitiveDataFlag } from "../src/catalog/migrations/006_sensitive_data_flag.js";
|
||||
import { up as upSensitiveSuggestionRuns } from "../src/catalog/migrations/007_sensitive_data_suggestion_runs.js";
|
||||
import { up as upLogicalRelationships } from "../src/catalog/migrations/008_catalog_logical_relationships.js";
|
||||
|
||||
const dockerAvailable = spawnSync("docker", ["info"], { stdio: "ignore" }).status === 0;
|
||||
|
||||
@@ -26,6 +27,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL migration enforces one database per wo
|
||||
await upTables(db);
|
||||
await upSchemaSync(db);
|
||||
await upSensitiveDataFlag(db);
|
||||
await upLogicalRelationships(db);
|
||||
await sql`CREATE ROLE thothii_catalog_runtime`.execute(db);
|
||||
await upRuntimeSequencePrivileges(db);
|
||||
const sequencePrivilege = await sql<{ allowed: boolean }>`
|
||||
@@ -202,6 +204,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository performs scoped metadata cl
|
||||
await upTables(db);
|
||||
await upSchemaSync(db);
|
||||
await upSensitiveDataFlag(db);
|
||||
await upLogicalRelationships(db);
|
||||
const repository = new KyselyCatalogRepository(db);
|
||||
const database = await repository.create({
|
||||
workspaceId: "cleanup-test",
|
||||
@@ -280,6 +283,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository atomically consolidates sel
|
||||
await upTables(db);
|
||||
await upSchemaSync(db);
|
||||
await upSensitiveDataFlag(db);
|
||||
await upLogicalRelationships(db);
|
||||
const repository = new KyselyCatalogRepository(db);
|
||||
const database = await repository.create({
|
||||
workspaceId: "consolidation-test",
|
||||
@@ -379,6 +383,7 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository persists description and se
|
||||
await upTables(db);
|
||||
await upSchemaSync(db);
|
||||
await upSensitiveDataFlag(db);
|
||||
await upLogicalRelationships(db);
|
||||
await upDescriptionGeneration(db);
|
||||
await upSensitiveSuggestionRuns(db);
|
||||
const repository = new KyselyCatalogRepository(db);
|
||||
@@ -610,3 +615,63 @@ test.skipIf(!dockerAvailable)("PostgreSQL repository persists description and se
|
||||
await container.stop();
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
test.skipIf(!dockerAvailable)("PostgreSQL repository persists logical relationship lifecycle and tombstones", async () => {
|
||||
const container = await new PostgreSqlContainer("postgres:17.6-bookworm").start();
|
||||
const db = new Kysely<CatalogDatabase>({
|
||||
dialect: new PostgresDialect({ pool: new Pool({ connectionString: container.getConnectionUri() }) }),
|
||||
plugins: [new CamelCasePlugin()],
|
||||
});
|
||||
try {
|
||||
await upDatabases(db);
|
||||
await upTables(db);
|
||||
await upSchemaSync(db);
|
||||
await upSensitiveDataFlag(db);
|
||||
await upLogicalRelationships(db);
|
||||
const repository = new KyselyCatalogRepository(db);
|
||||
const database = await repository.create({
|
||||
workspaceId: "logical-relationships",
|
||||
engine: "postgres",
|
||||
databaseName: "warehouse",
|
||||
schema: "public",
|
||||
binding: { transport: "postgres_direct", host: "db.internal", port: 5432, username: "reader" },
|
||||
});
|
||||
await repository.applySchemaSync(database.id, database.version, "all", [], {
|
||||
schemaVersion: 1,
|
||||
capabilities: { tables: "available", columns: "available", relationships: "available" },
|
||||
tables: [{ name: "users", sourceComment: null }, { name: "orders", sourceComment: null }],
|
||||
columns: [
|
||||
{ tableName: "users", name: "id", ordinalPosition: 1, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: 1, sourceComment: null },
|
||||
{ tableName: "orders", name: "id", ordinalPosition: 1, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: 1, sourceComment: null },
|
||||
{ tableName: "orders", name: "user_id", ordinalPosition: 2, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: null, sourceComment: null },
|
||||
],
|
||||
relationships: [],
|
||||
});
|
||||
const context = (await repository.getLogicalRelationshipContext(database.id))!;
|
||||
const source = context.endpoints.find((item) => item.tableName === "orders" && item.columnName === "user_id")!;
|
||||
const target = context.endpoints.find((item) => item.tableName === "users" && item.columnName === "id")!;
|
||||
const created = await repository.insertLogicalRelationship(database.id, source.columnId, target.columnId, false);
|
||||
expect(created).toMatchObject({ origin: "manual", status: "active" });
|
||||
await expect(repository.insertLogicalRelationship(database.id, source.columnId, target.columnId, true))
|
||||
.resolves.toBeUndefined();
|
||||
|
||||
expect(await repository.setLogicalRelationshipStatus(database.id, created!.id, "excluded"))
|
||||
.toMatchObject({ status: "excluded" });
|
||||
await expect(repository.insertGeneratedLogicalRelationships(database.id, [{
|
||||
sourceColumnId: source.columnId, targetColumnId: target.columnId,
|
||||
}])).resolves.toBe(0);
|
||||
|
||||
await expect(repository.deleteLogicalRelationship(database.id, created!.id)).resolves.toBe(true);
|
||||
await expect(repository.insertGeneratedLogicalRelationships(database.id, [{
|
||||
sourceColumnId: source.columnId, targetColumnId: target.columnId,
|
||||
}])).resolves.toBe(1);
|
||||
expect(await repository.listLogicalRelationships(database.id))
|
||||
.toMatchObject([{ origin: "generated", status: "active" }]);
|
||||
|
||||
await db.deleteFrom("catalogColumns").where("id", "=", source.columnId).execute();
|
||||
expect(await repository.listLogicalRelationships(database.id)).toEqual([]);
|
||||
} finally {
|
||||
await db.destroy();
|
||||
await container.stop();
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
Reference in New Issue
Block a user