4 files changed
+111
-4
No files matched your search
@@ -70,7 +70,7 @@ import type { ColumnInfo, ConnectionConfig, ConstraintInfo, ForeignKeyInfo, Inde
|
||||
import { sortTablesByFkDependency, type TableWithFk } from "@/lib/table/tableDependencySort";
|
||||
import { isSchemaAware, supportsTableVacuum, supportsTransfer } from "@/lib/database/databaseCapabilities";
|
||||
import { supportsSchemaDiagram, supportsTableImport, supportsTableStructureEditing, supportsTableTruncate } from "@/lib/database/databaseFeatureSupport";
|
||||
import { codeMirrorSqlDialect, connectionObjectTreeNodeSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { codeMirrorSqlDialect, connectionObjectTreeNodeSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, objectListSchemaForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { getTableMetadataCapabilities, type TableMetadataCapabilities } from "@/lib/table/tableMetadataCapabilities";
|
||||
import { constraintsForConstraintsTab } from "@/lib/table/constraintPresentation";
|
||||
import { buildTableSelectSql } from "@/lib/table/tableSelectSql";
|
||||
@@ -2817,6 +2817,10 @@ watch([() => props.initialEventName, () => props.initialEventOpenRequestId, () =
|
||||
openInitialEventIfNeeded();
|
||||
});
|
||||
|
||||
// 达梦的对象列表 SQL 固定 `WHERE o.OWNER = ?`(DamengAgent),空 schema 必然
|
||||
// 匹配 0 行;schema 解析失败(loadSchemas 抛错或返回空列表)时标签会整体空白
|
||||
// (#8301)。经 objectListSchemaForConnection 回退到连接用户名(大写),仅限
|
||||
// 达梦;oracle/oceanbase-oracle 维持空 schema 由后端解析当前 schema。
|
||||
async function loadObjects(options?: { allowCached?: boolean; preserveExistingRows?: boolean }) {
|
||||
error.value = "";
|
||||
// A new load supersedes any in-flight one, so reset the transient refresh flags
|
||||
@@ -2830,7 +2834,7 @@ async function loadObjects(options?: { allowCached?: boolean; preserveExistingRo
|
||||
// same-instance refresh) — a failure then keeps the rows and raises a non-blocking
|
||||
// banner instead of replacing the whole list with a full-area error.
|
||||
let scaffoldRefresh = false;
|
||||
const schema = needsSchema.value ? selectedSchema.value || "" : props.database;
|
||||
const schema = needsSchema.value ? objectListSchemaForConnection(props.connection, selectedSchema.value) : props.database;
|
||||
const request = objectBrowserRowsLoadGuard.start(objectBrowserRowsCacheScope(schema));
|
||||
const cacheWriteToken = createObjectBrowserRowsCacheWriteToken(request.scope);
|
||||
|
||||
@@ -2943,7 +2947,15 @@ function normalizeStatisticNumber(value: number | null | undefined): number | nu
|
||||
|
||||
async function reload(options?: { allowCachedObjects?: boolean; contextEpoch?: number; preserveExistingRows?: boolean }) {
|
||||
const epoch = options?.contextEpoch ?? objectBrowserRowsLoadGuard.invalidate();
|
||||
if (!(await loadSchemas(epoch))) return;
|
||||
try {
|
||||
if (!(await loadSchemas(epoch))) return;
|
||||
} catch (e) {
|
||||
// listSchemas 失败(如共享连接上 SET SCHEMA 后的二次元数据请求)时,
|
||||
// loadSchemas 不会触碰 selectedSchema,这里也不清空它:保留 props.schema
|
||||
// 或用户已选值,继续尝试加载对象列表(达梦走用户名回退),避免标签静默
|
||||
// 空白 (#8301)。对象列表若同样失败,loadObjects 自身的 catch 会展示错误。
|
||||
console.warn("[ObjectBrowser] loadSchemas failed, keeping selected schema for", props.connection.id, e);
|
||||
}
|
||||
if (!objectBrowserRowsLoadGuard.isEpochCurrent(epoch)) return;
|
||||
await loadObjects({ allowCached: options?.allowCachedObjects, preserveExistingRows: options?.preserveExistingRows });
|
||||
}
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const objectBrowserSource = readFileSync(new URL("../ObjectBrowser.vue", import.meta.url), "utf8");
|
||||
|
||||
function functionBody(name: string, source: string): string {
|
||||
const signature = new RegExp(`(?:async\\s+)?function\\s+${name}\\s*\\([^)]*\\)\\s*(?::\\s*[^\\{]+)?\\{`, "m").exec(source);
|
||||
if (!signature) throw new Error(`Missing function ${name}`);
|
||||
const bodyStart = signature.index + signature[0].length;
|
||||
let depth = 1;
|
||||
for (let index = bodyStart; index < source.length; index += 1) {
|
||||
if (source[index] === "{") depth += 1;
|
||||
else if (source[index] === "}") depth -= 1;
|
||||
if (depth === 0) return source.slice(bodyStart, index);
|
||||
}
|
||||
throw new Error(`Unclosed function ${name}`);
|
||||
}
|
||||
|
||||
describe("ObjectBrowser object-list schema fallback (#8301)", () => {
|
||||
it("resolves the listObjects schema through the shared Dameng-aware helper", () => {
|
||||
const loadObjects = functionBody("loadObjects", objectBrowserSource);
|
||||
expect(loadObjects).toContain("needsSchema.value ? objectListSchemaForConnection(props.connection, selectedSchema.value) : props.database");
|
||||
expect(objectBrowserSource).toContain('objectListSchemaForConnection, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect"');
|
||||
});
|
||||
|
||||
it("routes the schema through the request scope used by listObjects", () => {
|
||||
const loadSqlObjectBrowserRows = functionBody("loadSqlObjectBrowserRows", objectBrowserSource);
|
||||
expect(loadSqlObjectBrowserRows).toContain("api.listObjects(request.scope.connectionId, request.scope.database, request.scope.schema");
|
||||
const loadObjects = functionBody("loadObjects", objectBrowserSource);
|
||||
expect(loadObjects).toContain("objectBrowserRowsCacheScope(schema)");
|
||||
});
|
||||
|
||||
it("keeps the reload chain alive when loadSchemas rejects", () => {
|
||||
const reload = functionBody("reload", objectBrowserSource);
|
||||
expect(reload).toMatch(/try \{\s*if \(!\(await loadSchemas\(epoch\)\)\) return;\s*\} catch/);
|
||||
const catchClause = reload.slice(reload.indexOf("} catch"));
|
||||
// The failure path must not clear the selection (props.schema / current value)
|
||||
// and must still load objects — Dameng then falls back to the username.
|
||||
expect(catchClause).not.toContain("selectedSchema.value =");
|
||||
expect(catchClause).toContain("console.warn");
|
||||
expect(reload.lastIndexOf("await loadObjects")).toBeGreaterThan(reload.indexOf("} catch"));
|
||||
});
|
||||
|
||||
it("leaves the selected schema untouched when listSchemas itself rejects", () => {
|
||||
const loadSchemas = functionBody("loadSchemas", objectBrowserSource);
|
||||
// Every selectedSchema assignment in the try block sits behind the awaited
|
||||
// listSchemas call, so a rejection keeps the current selection intact.
|
||||
const tryStart = loadSchemas.indexOf("try {");
|
||||
const apiCall = loadSchemas.indexOf("await api.listSchemas", tryStart);
|
||||
const firstAssignment = loadSchemas.indexOf("selectedSchema.value =", apiCall);
|
||||
expect(apiCall).toBeGreaterThan(tryStart);
|
||||
expect(firstAssignment).toBeGreaterThan(apiCall);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user