mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
fix: refresh metadata after schema changes
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
generateSyncSql,
|
||||
type TableDiff,
|
||||
} from "@/lib/schemaDiff";
|
||||
import { sqlMetadataRefreshScope } from "@/lib/sqlMetadataRefresh";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { Loader2, Copy, Play, GitCompareArrows, ArrowLeftRight } from "lucide-vue-next";
|
||||
|
||||
@@ -285,6 +286,12 @@ async function executeSql() {
|
||||
}
|
||||
const failed = syncErrors.value.length;
|
||||
if (failed === 0) {
|
||||
const scope = sqlMetadataRefreshScope(sql);
|
||||
if (scope === "connection") {
|
||||
await store.loadDatabases(targetConnectionId.value, { force: true });
|
||||
} else if (scope === "database") {
|
||||
await store.refreshObjectListTreeNode(targetConnectionId.value, targetDatabase.value, targetSchema.value);
|
||||
}
|
||||
toast(t("diff.syncSuccess"), 2000);
|
||||
open.value = false;
|
||||
} else {
|
||||
|
||||
@@ -34,6 +34,7 @@ import { isSchemaAware } from "@/lib/databaseCapabilities";
|
||||
import { buildTableSelectSql, qualifiedTableName } from "@/lib/tableSelectSql";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { buildExecutableObjectSourceSql, objectSourceSaveExecutionMode } from "@/lib/objectSourceEditor";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import QueryEditor from "@/components/editor/QueryEditor.vue";
|
||||
import type { SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
@@ -62,6 +63,7 @@ const emit = defineEmits<{
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const connectionStore = useConnectionStore();
|
||||
const queryStore = useQueryStore();
|
||||
|
||||
const schemas = ref<string[]>([]);
|
||||
@@ -263,6 +265,11 @@ async function confirmDrop() {
|
||||
: "contextMenu.dropTableSuccess";
|
||||
toast(t(successKey, { name: row.name }));
|
||||
await reload();
|
||||
await connectionStore.refreshObjectListTreeNode(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
row.schema || selectedSchema.value,
|
||||
);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
|
||||
@@ -249,6 +249,9 @@ async function startTransfer() {
|
||||
overallCancelled.value = nextState.cancelled;
|
||||
overallError.value = nextState.error;
|
||||
});
|
||||
if (createTable.value && !overallError.value && !overallCancelled.value) {
|
||||
await store.refreshObjectListTreeNode(targetConnectionId.value, targetDatabase.value, targetSchema.value);
|
||||
}
|
||||
} catch (e: any) {
|
||||
overallError.value = true;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ export function useNavigationTargets(dialogs: {
|
||||
const schema = dialogs.structurePrefillSchema?.value;
|
||||
if (connId && db) {
|
||||
try {
|
||||
await connectionStore.loadTables(connId, db, schema || undefined);
|
||||
await connectionStore.refreshObjectListTreeNode(connId, db, schema || undefined);
|
||||
} catch {}
|
||||
}
|
||||
return;
|
||||
@@ -146,6 +146,11 @@ export function useNavigationTargets(dialogs: {
|
||||
columns,
|
||||
primaryKeys: editablePrimaryKeys(connectionStore.getConfig(activeTab.connectionId)?.db_type, columns),
|
||||
});
|
||||
await connectionStore.refreshObjectListTreeNode(
|
||||
activeTab.connectionId,
|
||||
activeTab.database,
|
||||
activeTab.tableMeta.schema,
|
||||
);
|
||||
await reloadData();
|
||||
} catch (e: any) {
|
||||
toast(e?.message || String(e), 5000);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useHistoryStore } from "@/stores/historyStore";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { classifySqlActivityKind } from "@/lib/historyActivityKind";
|
||||
import { sqlMetadataRefreshScope } from "@/lib/sqlMetadataRefresh";
|
||||
import type { ConnectionConfig, QueryTab } from "@/types/database";
|
||||
|
||||
const DANGER_RE = /^\s*(DROP|DELETE|TRUNCATE|ALTER|UPDATE|MERGE|REPLACE)\b/i;
|
||||
@@ -80,6 +81,14 @@ export function useSqlExecution(deps: {
|
||||
operation: primarySqlOperation(sql),
|
||||
affected_rows: success ? tab.result?.affected_rows : undefined,
|
||||
});
|
||||
if (success) {
|
||||
const scope = sqlMetadataRefreshScope(sql);
|
||||
if (scope === "connection") {
|
||||
await connectionStore.loadDatabases(tab.connectionId, { force: true });
|
||||
} else if (scope === "database") {
|
||||
await connectionStore.refreshObjectListTreeNode(tab.connectionId, tab.database, tab.schema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cancelActiveExecution() {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export type SqlMetadataRefreshScope = "none" | "connection" | "database";
|
||||
|
||||
const DATABASE_DDL_RE = /\b(CREATE|DROP)\s+(DATABASE|SCHEMA)\b/i;
|
||||
const OBJECT_DDL_RE =
|
||||
/\b(CREATE|ALTER|DROP|RENAME)\s+(OR\s+REPLACE\s+)?(((GLOBAL|LOCAL)\s+)?TEMP(ORARY)?\s+)?(MATERIALIZED\s+)?(TABLE|VIEW|INDEX|SEQUENCE|PROCEDURE|FUNCTION|TRIGGER|TYPE)\b/i;
|
||||
|
||||
function stripSqlMetadataComments(sql: string): string {
|
||||
return sql
|
||||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||||
.replace(/--.*$/gm, " ")
|
||||
.replace(/#.*$/gm, " ");
|
||||
}
|
||||
|
||||
export function sqlMetadataRefreshScope(sql: string): SqlMetadataRefreshScope {
|
||||
const cleaned = stripSqlMetadataComments(sql);
|
||||
if (DATABASE_DDL_RE.test(cleaned)) return "connection";
|
||||
if (OBJECT_DDL_RE.test(cleaned)) return "database";
|
||||
return "none";
|
||||
}
|
||||
@@ -1157,6 +1157,17 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await loadDatabases(connectionId, { force: true });
|
||||
}
|
||||
|
||||
async function refreshObjectListTreeNode(connectionId: string, database: string, schema?: string) {
|
||||
const config = getConfig(connectionId);
|
||||
const shouldRefreshSchemaNode = schema && !(config?.db_type === "sqlserver" && schema.toLowerCase() === "dbo");
|
||||
const node = shouldRefreshSchemaNode ? findNode(treeNodes.value, `${connectionId}:${database}:${schema}`) : null;
|
||||
if (node) {
|
||||
await refreshTreeNode(node);
|
||||
return;
|
||||
}
|
||||
await refreshDatabaseTreeNode(connectionId, database);
|
||||
}
|
||||
|
||||
function isSchemaAwareDatabase(connectionId: string): boolean {
|
||||
return isSchemaAware(getConfig(connectionId)?.db_type);
|
||||
}
|
||||
@@ -1587,6 +1598,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
refreshSavedSqlTree,
|
||||
refreshTreeNode,
|
||||
refreshDatabaseTreeNode,
|
||||
refreshObjectListTreeNode,
|
||||
connectedIds,
|
||||
connectionErrors,
|
||||
setConnectionError,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { sqlMetadataRefreshScope } from "../src/lib/sqlMetadataRefresh.ts";
|
||||
|
||||
test("database DDL refreshes the connection database list", () => {
|
||||
assert.equal(sqlMetadataRefreshScope("CREATE DATABASE app;"), "connection");
|
||||
assert.equal(sqlMetadataRefreshScope("drop schema public cascade;"), "connection");
|
||||
});
|
||||
|
||||
test("object DDL refreshes the selected database tree", () => {
|
||||
assert.equal(sqlMetadataRefreshScope("CREATE TABLE users (id int);"), "database");
|
||||
assert.equal(sqlMetadataRefreshScope("CREATE TEMP TABLE scratch (id int);"), "database");
|
||||
assert.equal(sqlMetadataRefreshScope("CREATE MATERIALIZED VIEW daily_users AS SELECT 1;"), "database");
|
||||
assert.equal(sqlMetadataRefreshScope("ALTER TABLE users ADD COLUMN name text;"), "database");
|
||||
assert.equal(sqlMetadataRefreshScope("DROP VIEW active_users;"), "database");
|
||||
assert.equal(
|
||||
sqlMetadataRefreshScope("CREATE OR REPLACE FUNCTION f() RETURNS int AS $$ SELECT 1 $$ LANGUAGE SQL;"),
|
||||
"database",
|
||||
);
|
||||
});
|
||||
|
||||
test("data-only SQL does not refresh metadata trees", () => {
|
||||
assert.equal(sqlMetadataRefreshScope("SELECT * FROM users;"), "none");
|
||||
assert.equal(sqlMetadataRefreshScope("INSERT INTO users VALUES (1);"), "none");
|
||||
assert.equal(sqlMetadataRefreshScope("TRUNCATE TABLE users;"), "none");
|
||||
});
|
||||
Reference in New Issue
Block a user