mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(metadata): resolve visible schema for query DDL and loads
This commit is contained in:
@@ -2738,6 +2738,14 @@ func (s *server) getTableDDL(schema, table string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
if visibleColumns, fallbackErr := s.getColumns("", table); fallbackErr == nil && len(visibleColumns) > 0 {
|
||||
columns = visibleColumns
|
||||
if columns[0].ResolvedSchema != nil && *columns[0].ResolvedSchema != "" {
|
||||
effective = *columns[0].ResolvedSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
tableComment, _ := s.getTableComment(effective, table)
|
||||
ddl := s.renderTableDDL(effective, table, columns, tableComment)
|
||||
ddl, err = s.appendTableIndexDDL(effective, table, ddl)
|
||||
|
||||
@@ -3917,6 +3917,14 @@ func TestTableDDLIncludesIdentityIndexesTriggersAndComments(t *testing.T) {
|
||||
t.Fatalf("table DDL missing %q:\n%s", expected, ddl)
|
||||
}
|
||||
}
|
||||
|
||||
ddlUnqualified, err := server.getTableDDL("", "orders")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(ddlUnqualified, `"id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL`) {
|
||||
t.Fatalf("identity clause missing from unqualified table DDL: %s", ddlUnqualified)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTableDDLIncludesEscapedComments(t *testing.T) {
|
||||
|
||||
@@ -1868,6 +1868,14 @@ func (s *server) getTableDDL(schema, table string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
if visibleColumns, fallbackErr := s.getColumns("", table); fallbackErr == nil && len(visibleColumns) > 0 {
|
||||
columns = visibleColumns
|
||||
if columns[0].ResolvedSchema != nil && *columns[0].ResolvedSchema != "" {
|
||||
effective = *columns[0].ResolvedSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
tableComment, _ := s.getTableComment(effective, table)
|
||||
ddl := renderTableDDL(effective, table, columns, tableComment)
|
||||
ddl, err = s.appendTableIndexDDL(effective, table, ddl)
|
||||
|
||||
@@ -397,7 +397,7 @@ import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableI
|
||||
import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp";
|
||||
import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
|
||||
import { buildColumnIndexMap, columnIndexColorClass, columnIndexNameKey, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
|
||||
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
|
||||
import { isSchemaAware, supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
|
||||
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
|
||||
import { restoreDataGridLocalColumnFilters, type DataGridLocalFilterOption } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
import { effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint } from "@/lib/database/jdbcDialect";
|
||||
@@ -12016,7 +12016,7 @@ const canShowTableIndexes = computed(() => tableMetadataCapabilities.value.index
|
||||
async function probeTablePartitionStatus() {
|
||||
const connectionId = props.connectionId;
|
||||
const database = props.database;
|
||||
const schema = props.tableMeta?.schema || props.database || "";
|
||||
const schema = props.tableMeta?.schema || props.schema || (isSchemaAware(resolvedDatabaseType.value) ? "" : props.database) || "";
|
||||
const tableName = props.tableMeta?.tableName;
|
||||
const identity = currentIndexTableIdentity.value;
|
||||
if (!tableMetadataCapabilities.value.partitions || !connectionId || !database || !tableName || !identity) {
|
||||
@@ -12366,7 +12366,7 @@ async function loadForeignKeyDisplayLabels() {
|
||||
configuredColumns.map(async (columnIndex) => {
|
||||
const config = savedForeignKeyDisplayConfig(columnIndex);
|
||||
if (!config) return;
|
||||
const schema = config.refSchema || props.tableMeta?.schema || props.schema || props.database || "";
|
||||
const schema = config.refSchema || props.tableMeta?.schema || props.schema || (isSchemaAware(resolvedDatabaseType.value) ? "" : props.database) || "";
|
||||
try {
|
||||
const columns = await foreignKeyDisplayRequests.request(requestGeneration, JSON.stringify(["columns", props.connectionId, props.database || "", props.tableMeta?.catalog || "", schema, config.refTable]), () =>
|
||||
api.getColumns(props.connectionId!, props.database || "", schema, config.refTable, props.tableMeta?.catalog),
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { computed, ref } from "vue";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { useDataGridTableMetadataLoaders } from "@/composables/useDataGridTableMetadataLoaders";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
loadObjectDdl: vi.fn(),
|
||||
getColumns: vi.fn(),
|
||||
listIndexes: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/metadata/objectDdlCache", () => ({
|
||||
loadObjectDdl: mocks.loadObjectDdl,
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/backend/api", () => ({
|
||||
getColumns: mocks.getColumns,
|
||||
listIndexes: mocks.listIndexes,
|
||||
getTableOwner: vi.fn(),
|
||||
listForeignKeys: vi.fn(),
|
||||
listTriggers: vi.fn(),
|
||||
listConstraints: vi.fn(),
|
||||
listPartitions: vi.fn(),
|
||||
}));
|
||||
|
||||
function createTestState() {
|
||||
return {
|
||||
ddlContent: ref(""),
|
||||
ddlLoading: ref(false),
|
||||
tableInfoColumns: ref([]),
|
||||
tableInfoColumnsLoading: ref(false),
|
||||
tableOwner: ref<string | null>(null),
|
||||
tableOwnerLoading: ref(false),
|
||||
tableOwnerError: ref(""),
|
||||
indexes: ref([]),
|
||||
indexesLoaded: ref(false),
|
||||
indexesLoading: ref(false),
|
||||
indexesError: ref(""),
|
||||
foreignKeys: ref([]),
|
||||
foreignKeysLoaded: ref(false),
|
||||
foreignKeysLoading: ref(false),
|
||||
foreignKeysError: ref(""),
|
||||
triggers: ref([]),
|
||||
triggersLoaded: ref(false),
|
||||
triggersLoading: ref(false),
|
||||
triggersError: ref(""),
|
||||
constraints: ref([]),
|
||||
constraintsLoaded: ref(false),
|
||||
constraintsLoading: ref(false),
|
||||
constraintsError: ref(""),
|
||||
partitioning: ref(null),
|
||||
partitioningLoaded: ref(false),
|
||||
partitioningLoading: ref(false),
|
||||
partitioningError: ref(""),
|
||||
partitioningRequestGeneration: ref(0),
|
||||
tableInfoColumnsRequestGeneration: ref(0),
|
||||
tableOwnerRequestGeneration: ref(0),
|
||||
indexesRequestGeneration: ref(0),
|
||||
foreignKeysRequestGeneration: ref(0),
|
||||
constraintsRequestGeneration: ref(0),
|
||||
};
|
||||
}
|
||||
|
||||
describe("useDataGridTableMetadataLoaders", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.loadObjectDdl.mockResolvedValue({ ddl: "CREATE TABLE test (id int);" });
|
||||
mocks.getColumns.mockResolvedValue([{ name: "id", data_type: "int" }]);
|
||||
mocks.listIndexes.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it("does not fall back to database as schema for schema-aware database Kingbase (#11347)", async () => {
|
||||
const state = createTestState();
|
||||
const resolvedDatabaseType = ref<DatabaseType>("kingbase");
|
||||
const showTableInfo = ref(false);
|
||||
|
||||
const loaders = useDataGridTableMetadataLoaders({
|
||||
props: {
|
||||
connectionId: "conn-1",
|
||||
database: "TESTDB",
|
||||
schema: undefined,
|
||||
tableMeta: {
|
||||
tableName: "CTRC_REG_PRO_FLOW",
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
},
|
||||
},
|
||||
state,
|
||||
settingsStore: {
|
||||
editorSettings: {
|
||||
refreshDdlOnOpen: false,
|
||||
generateSqlIncludeDatabaseName: false,
|
||||
generateSqlQuoteIdentifiers: true,
|
||||
},
|
||||
} as any,
|
||||
connectionStore: {} as any,
|
||||
resolvedDatabaseType: computed(() => resolvedDatabaseType.value),
|
||||
canShowTableIndexes: computed(() => true),
|
||||
showTableInfo,
|
||||
toast: vi.fn(),
|
||||
formatBackendError: (e) => String(e),
|
||||
toastMongoIndexRefreshError: vi.fn(),
|
||||
});
|
||||
|
||||
await loaders.fetchDdl();
|
||||
|
||||
expect(mocks.loadObjectDdl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
connectionId: "conn-1",
|
||||
database: "TESTDB",
|
||||
schema: "",
|
||||
tableName: "CTRC_REG_PRO_FLOW",
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves explicit schema for schema-aware database Kingbase", async () => {
|
||||
const state = createTestState();
|
||||
const resolvedDatabaseType = ref<DatabaseType>("kingbase");
|
||||
const showTableInfo = ref(false);
|
||||
|
||||
const loaders = useDataGridTableMetadataLoaders({
|
||||
props: {
|
||||
connectionId: "conn-1",
|
||||
database: "TESTDB",
|
||||
schema: "public",
|
||||
tableMeta: {
|
||||
tableName: "CTRC_REG_PRO_FLOW",
|
||||
schema: "public",
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
},
|
||||
},
|
||||
state,
|
||||
settingsStore: {
|
||||
editorSettings: {
|
||||
refreshDdlOnOpen: false,
|
||||
generateSqlIncludeDatabaseName: false,
|
||||
generateSqlQuoteIdentifiers: true,
|
||||
},
|
||||
} as any,
|
||||
connectionStore: {} as any,
|
||||
resolvedDatabaseType: computed(() => resolvedDatabaseType.value),
|
||||
canShowTableIndexes: computed(() => true),
|
||||
showTableInfo,
|
||||
toast: vi.fn(),
|
||||
formatBackendError: (e) => String(e),
|
||||
toastMongoIndexRefreshError: vi.fn(),
|
||||
});
|
||||
|
||||
await loaders.fetchDdl();
|
||||
|
||||
expect(mocks.loadObjectDdl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
connectionId: "conn-1",
|
||||
database: "TESTDB",
|
||||
schema: "public",
|
||||
tableName: "CTRC_REG_PRO_FLOW",
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to database as schema for non-schema-aware database MySQL", async () => {
|
||||
const state = createTestState();
|
||||
const resolvedDatabaseType = ref<DatabaseType>("mysql");
|
||||
const showTableInfo = ref(false);
|
||||
|
||||
const loaders = useDataGridTableMetadataLoaders({
|
||||
props: {
|
||||
connectionId: "conn-mysql",
|
||||
database: "shop_db",
|
||||
schema: undefined,
|
||||
tableMeta: {
|
||||
tableName: "orders",
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
},
|
||||
},
|
||||
state,
|
||||
settingsStore: {
|
||||
editorSettings: {
|
||||
refreshDdlOnOpen: false,
|
||||
generateSqlIncludeDatabaseName: false,
|
||||
generateSqlQuoteIdentifiers: true,
|
||||
},
|
||||
} as any,
|
||||
connectionStore: {} as any,
|
||||
resolvedDatabaseType: computed(() => resolvedDatabaseType.value),
|
||||
canShowTableIndexes: computed(() => true),
|
||||
showTableInfo,
|
||||
toast: vi.fn(),
|
||||
formatBackendError: (e) => String(e),
|
||||
toastMongoIndexRefreshError: vi.fn(),
|
||||
});
|
||||
|
||||
await loaders.fetchDdl();
|
||||
|
||||
expect(mocks.loadObjectDdl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
connectionId: "conn-mysql",
|
||||
database: "shop_db",
|
||||
schema: "shop_db",
|
||||
tableName: "orders",
|
||||
}),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { computed, type ComputedRef, type Ref } from "vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { isSchemaAware } from "@/lib/database/databaseCapabilities";
|
||||
import { applyDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
|
||||
import { sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
|
||||
import { loadObjectDdl } from "@/lib/metadata/objectDdlCache";
|
||||
@@ -84,20 +85,27 @@ export interface DataGridTableMetadataLoaderOptions {
|
||||
export function useDataGridTableMetadataLoaders(options: DataGridTableMetadataLoaderOptions) {
|
||||
const { props, state } = options;
|
||||
|
||||
const defaultSchema = () => {
|
||||
if (isSchemaAware(options.resolvedDatabaseType.value)) {
|
||||
return props.tableMeta?.schema || props.schema || "";
|
||||
}
|
||||
return props.tableMeta?.schema || props.schema || props.database || "";
|
||||
};
|
||||
|
||||
const currentTableIdentity = (kind: "columns" | "owner" | "indexes" | "foreignKeys" | "constraints") =>
|
||||
kind === "foreignKeys"
|
||||
? foreignKeyTableIdentity({
|
||||
connectionId: props.connectionId,
|
||||
database: props.database,
|
||||
catalog: props.tableMeta?.catalog,
|
||||
schema: props.tableMeta?.schema,
|
||||
schema: defaultSchema(),
|
||||
tableName: props.tableMeta?.tableName,
|
||||
})
|
||||
: columnIndexTableIdentity({
|
||||
connectionId: props.connectionId,
|
||||
database: props.database,
|
||||
catalog: props.tableMeta?.catalog,
|
||||
schema: props.tableMeta?.schema,
|
||||
schema: defaultSchema(),
|
||||
tableName: props.tableMeta?.tableName,
|
||||
});
|
||||
|
||||
@@ -106,7 +114,7 @@ export function useDataGridTableMetadataLoaders(options: DataGridTableMetadataLo
|
||||
return {
|
||||
connectionId: props.connectionId,
|
||||
database: props.database || "",
|
||||
schema: props.tableMeta.schema || props.database || "",
|
||||
schema: defaultSchema(),
|
||||
tableName: props.tableMeta.tableName,
|
||||
catalog: props.tableMeta.catalog,
|
||||
objectType: tableObjectSourceKind(props.tableMeta.tableType),
|
||||
@@ -123,7 +131,7 @@ export function useDataGridTableMetadataLoaders(options: DataGridTableMetadataLo
|
||||
// Query execution and source-table metadata can belong to different
|
||||
// databases (for example SQL Server three-part table references).
|
||||
request.database = props.tableMeta?.database || request.database;
|
||||
request.schema = props.tableMeta?.schema || props.schema || props.database || "";
|
||||
request.schema = defaultSchema();
|
||||
options.showTableInfo.value = true;
|
||||
state.ddlLoading.value = true;
|
||||
try {
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface TableColumnsMetadata {
|
||||
}
|
||||
|
||||
export interface TableColumnsLoadResult {
|
||||
schema?: string;
|
||||
columns: ColumnInfo[];
|
||||
tableType?: string;
|
||||
cacheStatus: MetadataLoadCacheStatus;
|
||||
@@ -248,8 +249,9 @@ export async function loadTableColumns(request: TableMetadataRequest): Promise<T
|
||||
async () => {
|
||||
// Display-only loader: columns only, never index discovery.
|
||||
const columns = await api.getColumns(request.connectionId, request.database, request.schema ?? "", request.tableName, request.catalog);
|
||||
const resolvedSchema = (request.databaseType === "vastbase" || request.databaseType === "kingbase") && !request.schema ? columns.find((column) => column.resolved_schema)?.resolved_schema : request.schema;
|
||||
return {
|
||||
schema: request.schema || undefined,
|
||||
schema: resolvedSchema || undefined,
|
||||
tableName: request.tableName,
|
||||
tableType: request.tableType,
|
||||
catalog: request.catalog,
|
||||
@@ -272,7 +274,7 @@ export async function loadTableColumns(request: TableMetadataRequest): Promise<T
|
||||
resultCount: metadata.columns.length,
|
||||
force: request.force === true,
|
||||
});
|
||||
return { columns: metadata.columns, tableType: metadata.tableType, cacheStatus: request.force ? "refresh" : "miss", ageMs: 0, cachedAt: metadata.cachedAt };
|
||||
return { schema: metadata.schema, columns: metadata.columns, tableType: metadata.tableType, cacheStatus: request.force ? "refresh" : "miss", ageMs: 0, cachedAt: metadata.cachedAt };
|
||||
}
|
||||
|
||||
export function tableMetadataToDataTabMeta(metadata: TableMetadata, overrides?: { schema?: string }): NonNullable<QueryTab["tableMeta"]> {
|
||||
|
||||
@@ -6283,13 +6283,15 @@ export const useQueryStore = defineStore("query", () => {
|
||||
}
|
||||
|
||||
function loadedEditableSourceFromColumns(target: EditableSourceMetadataTarget, loadedColumns: Awaited<ReturnType<typeof loadTableColumns>>): LoadedEditableSource {
|
||||
const usesReportedSchema = target.request.databaseType === "vastbase" || target.request.databaseType === "kingbase";
|
||||
const writeSchema = usesReportedSchema && !target.writeSchema ? loadedColumns.schema : target.writeSchema;
|
||||
return {
|
||||
source: target.source,
|
||||
analysis: target.analysis,
|
||||
tableMeta: {
|
||||
catalog: target.request.catalog,
|
||||
database: target.request.database,
|
||||
schema: target.writeSchema,
|
||||
schema: writeSchema,
|
||||
tableName: target.request.tableName,
|
||||
tableType: loadedColumns.tableType,
|
||||
columns: loadedColumns.columns,
|
||||
|
||||
+13
-1
@@ -84,7 +84,7 @@ test("uses agent-reported visible schemas for Vastbase and Kingbase index lookup
|
||||
const listIndexes = vi.fn(async (): Promise<IndexInfo[]> => []);
|
||||
vi.doMock("@/lib/backend/api", () => ({ getColumns, listIndexes }));
|
||||
|
||||
const { clearTableMetadataCache, loadTableMetadata } = await import("../../apps/desktop/src/lib/metadata/tableMetadataCache.ts");
|
||||
const { clearTableMetadataCache, loadTableColumns, loadTableMetadata } = await import("../../apps/desktop/src/lib/metadata/tableMetadataCache.ts");
|
||||
clearTableMetadataCache();
|
||||
|
||||
const testCases = [
|
||||
@@ -104,6 +104,18 @@ test("uses agent-reported visible schemas for Vastbase and Kingbase index lookup
|
||||
|
||||
assert.equal(result.metadata.schema, testCase.resolvedSchema);
|
||||
assert.equal(listIndexes.mock.calls[index]?.[2], testCase.resolvedSchema);
|
||||
|
||||
const columnsResult = await loadTableColumns({
|
||||
connectionId: testCase.connectionId,
|
||||
database: "app",
|
||||
schema: "",
|
||||
tableName: "users",
|
||||
tableType: "TABLE",
|
||||
databaseType: testCase.databaseType,
|
||||
driverProfile: testCase.databaseType,
|
||||
force: true,
|
||||
});
|
||||
assert.equal(columnsResult.schema, testCase.resolvedSchema);
|
||||
}
|
||||
assert.equal(listIndexes.mock.calls.length, 2);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user