fix: distinguish materialized view from regular view

This commit is contained in:
t8y2
2026-06-16 21:23:42 +08:00
parent c619e5da56
commit 8339d23df5
16 changed files with 92 additions and 57 deletions
@@ -393,7 +393,7 @@ async function loadDiagram() {
await store.ensureConnected(connectionId.value);
const querySchema = schema.value || database.value;
const tableInfos = await api.listTables(connectionId.value, database.value, querySchema);
const baseTables = tableInfos.filter((table) => table.table_type !== "VIEW").sort((a, b) => a.name.localeCompare(b.name));
const baseTables = tableInfos.filter((table) => table.table_type !== "VIEW" && table.table_type !== "MATERIALIZED VIEW").sort((a, b) => a.name.localeCompare(b.name));
totalTableCount.value = baseTables.length;
const loadedTables: DiagramTable[] = [];
@@ -357,7 +357,7 @@ async function loadTables(side: "source" | "target") {
const database = side === "source" ? sourceDatabase.value : targetDatabase.value;
if (!connectionId || !database) return;
const schema = side === "source" ? sourceSchema.value || (await resolveSchema(connectionId, database, props.prefillSchema)) : targetSchema.value || (await resolveSchema(connectionId, database));
const tables = (await api.listTables(connectionId, database, schema)).filter((table) => table.table_type !== "VIEW").map((table) => table.name);
const tables = (await api.listTables(connectionId, database, schema)).filter((table) => table.table_type !== "VIEW" && table.table_type !== "MATERIALIZED VIEW").map((table) => table.name);
if (side === "source") {
const preferredSelection = props.prefillTable && tables.includes(props.prefillTable) ? [props.prefillTable] : [...selectedSourceTables.value].filter((table) => tables.includes(table));
@@ -394,7 +394,7 @@ async function ensureTableObjectGroupsLoaded(target: Extract<ActiveTabSidebarTar
function findTableObjectGroupNodes(nodes: TreeNode[], target: Extract<ActiveTabSidebarTarget, { type: "table" }>): TreeNode[] {
const matches: TreeNode[] = [];
for (const node of nodes) {
if ((node.type === "group-tables" || node.type === "group-views") && node.connectionId === target.connectionId && sameTreeName(node.database, target.database) && (!target.schema || sameTreeName(node.schema, target.schema))) {
if ((node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views") && node.connectionId === target.connectionId && sameTreeName(node.database, target.database) && (!target.schema || sameTreeName(node.schema, target.schema))) {
matches.push(node);
}
if (node.children) {
@@ -201,6 +201,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: Table, colorClass: "text-green-500" };
case "view":
return { icon: Eye, colorClass: "text-purple-500" };
case "materialized_view":
return { icon: Eye, colorClass: "text-indigo-500" };
case "column":
if ((node.meta as ColumnInfo).is_primary_key) {
return { icon: Columns3, colorClass: "text-orange-400" };
@@ -249,6 +251,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: Table, colorClass: "text-green-500" };
case "group-views":
return { icon: Eye, colorClass: "text-purple-500" };
case "group-materialized-views":
return { icon: Eye, colorClass: "text-indigo-500" };
case "group-procedures":
return { icon: ScrollText, colorClass: "text-blue-500" };
case "group-functions":
@@ -266,8 +270,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
}
}
const groupTypes: Set<TreeNodeType> = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions"]);
const pinnableTypes: Set<TreeNodeType> = new Set(["connection-group", "database", "schema", "table", "view", "redis-db", "mongo-db", "mongo-collection", "elasticsearch-index"]);
const groupTypes: Set<TreeNodeType> = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions"]);
const pinnableTypes: Set<TreeNodeType> = new Set(["connection-group", "database", "schema", "table", "view", "materialized_view", "redis-db", "mongo-db", "mongo-collection", "elasticsearch-index"]);
function isGroupLabel(node: TreeNode): boolean {
return groupTypes.has(node.type);
@@ -282,7 +286,7 @@ function displayLabel(node: TreeNode): string {
}
function visibleLabel(node: TreeNode): string {
if (node.type === "table" || node.type === "view" || node.type === "mongo-collection" || node.type === "elasticsearch-index") {
if (node.type === "table" || node.type === "view" || node.type === "materialized_view" || node.type === "mongo-collection" || node.type === "elasticsearch-index") {
return sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes);
}
return displayLabel(node);
@@ -326,7 +330,7 @@ async function toggle() {
return;
}
const databaseObjectGroup = node.type === "group-tables" || node.type === "group-views" || node.type === "group-procedures" || node.type === "group-functions" || node.type === "group-sequences" || node.type === "group-packages";
const databaseObjectGroup = node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views" || node.type === "group-procedures" || node.type === "group-functions" || node.type === "group-sequences" || node.type === "group-packages";
if (databaseObjectGroup && connectionStore.isTreeNodeChildrenLoaded(node.id)) {
node.isExpanded = !node.isExpanded;
emit("node-toggled", node, wasExpanded);
@@ -382,7 +386,7 @@ async function toggle() {
}
} else if (node.type === "schema" && node.connectionId && hasTreeNodeDatabaseContext(node) && node.schema) {
await connectionStore.loadTables(node.connectionId, node.database, node.schema);
} else if ((node.type === "table" || node.type === "view") && node.connectionId && hasTreeNodeDatabaseContext(node)) {
} else if ((node.type === "table" || node.type === "view" || node.type === "materialized_view") && node.connectionId && hasTreeNodeDatabaseContext(node)) {
await connectionStore.loadTableGroups(node.connectionId, node.database, node.label, node.schema, node.id);
} else if (node.type === "group-columns" && node.connectionId && hasTreeNodeDatabaseContext(node) && node.tableName) {
await connectionStore.loadColumns(node.connectionId, node.database, node.tableName, node.schema, node.id);
@@ -685,7 +689,7 @@ async function openUserAdmin() {
async function openData() {
const node = props.node;
if (!(node.type === "table" || node.type === "view") || !hasNodeDatabaseContext(node)) return;
if (!(node.type === "table" || node.type === "view" || node.type === "materialized_view") || !hasNodeDatabaseContext(node)) return;
const config = connectionStore.getConfig(node.connectionId);
const traceId = uuid().slice(0, 8);
const startedAt = performance.now();
@@ -730,7 +734,7 @@ async function openData() {
queryStore.setTableMeta(tabId, {
schema: tableSchema,
tableName: node.label,
tableType: node.type === "view" ? "VIEW" : "TABLE",
tableType: node.type === "view" ? "VIEW" : node.type === "materialized_view" ? "MATERIALIZED_VIEW" : "TABLE",
columns: [],
primaryKeys: [],
});
@@ -761,7 +765,7 @@ async function openData() {
});
columns = await api.getColumns(node.connectionId, node.database, querySchema, node.label);
if (!isActive()) return;
primaryKeys = editablePrimaryKeys(effectiveDbType, columns, node.type === "view" ? "VIEW" : "TABLE");
primaryKeys = editablePrimaryKeys(effectiveDbType, columns, node.type === "view" ? "VIEW" : node.type === "materialized_view" ? "MATERIALIZED_VIEW" : "TABLE");
console.info("[DBX][openData:get-columns:done]", {
traceId,
columnCount: columns.length,
@@ -771,7 +775,7 @@ async function openData() {
queryStore.setTableMeta(tabId, {
schema: tableSchema,
tableName: node.label,
tableType: node.type === "view" ? "VIEW" : "TABLE",
tableType: node.type === "view" ? "VIEW" : node.type === "materialized_view" ? "MATERIALIZED_VIEW" : "TABLE",
columns,
primaryKeys,
});
@@ -822,7 +826,7 @@ async function newQuery() {
if (hasTreeNodeDatabaseContext(node)) {
const tabId = queryStore.createTab(node.connectionId, node.database, undefined, "query", node.schema);
// For table/view nodes, generate SELECT * FROM table_name SQL
if (node.type === "table" || node.type === "view") {
if (node.type === "table" || node.type === "view" || node.type === "materialized_view") {
const config = connectionStore.getConfig(node.connectionId);
const dbType = config ? effectiveDatabaseTypeForConnection(config) : undefined;
const tableSchema = node.schema || node.database;
@@ -1234,10 +1238,10 @@ function dropObjectSqlOptions(): DropObjectSqlOptions | null {
}
function dropObjectSqlOptionsForNode(node: TreeNode): DropObjectSqlOptions | null {
if (node.type !== "view" && node.type !== "procedure" && node.type !== "function") return null;
if (node.type !== "view" && node.type !== "materialized_view" && node.type !== "procedure" && node.type !== "function") return null;
return {
databaseType: tableStructureDatabaseTypeForNode(node),
objectType: node.type === "view" ? "VIEW" : node.type === "procedure" ? "PROCEDURE" : "FUNCTION",
objectType: node.type === "view" ? "VIEW" : node.type === "materialized_view" ? "MATERIALIZED_VIEW" : node.type === "procedure" ? "PROCEDURE" : "FUNCTION",
schema: node.schema,
name: node.label,
};
@@ -1292,6 +1296,7 @@ function canDropTableChildObjectNode(node: TreeNode): boolean {
function dropObjectMenuLabel(): string {
if (props.node.type === "view") return t("contextMenu.dropView");
if (props.node.type === "materialized_view") return t("contextMenu.dropView");
if (props.node.type === "procedure") return t("contextMenu.dropProcedure");
if (props.node.type === "function") return t("contextMenu.dropFunction");
return t("contextMenu.dropObject");
@@ -1299,6 +1304,7 @@ function dropObjectMenuLabel(): string {
function dropObjectConfirmTitle(): string {
if (props.node.type === "view") return t("contextMenu.confirmDropViewTitle");
if (props.node.type === "materialized_view") return t("contextMenu.confirmDropViewTitle");
if (props.node.type === "procedure") return t("contextMenu.confirmDropProcedureTitle");
if (props.node.type === "function") return t("contextMenu.confirmDropFunctionTitle");
return t("contextMenu.confirmDropObjectTitle");
@@ -1306,6 +1312,7 @@ function dropObjectConfirmTitle(): string {
function dropObjectConfirmMessage(): string {
if (props.node.type === "view") return t("contextMenu.confirmDropViewMessage", { name: props.node.label });
if (props.node.type === "materialized_view") return t("contextMenu.confirmDropViewMessage", { name: props.node.label });
if (props.node.type === "procedure") return t("contextMenu.confirmDropProcedureMessage", { name: props.node.label });
if (props.node.type === "function") return t("contextMenu.confirmDropFunctionMessage", { name: props.node.label });
return t("contextMenu.confirmDropObjectMessage", { name: props.node.label });
@@ -1376,13 +1383,14 @@ function viewObjectSource() {
function viewObjectDdl() {
const node = props.node;
if (node.type !== "view" || !node.connectionId || !node.database) return;
if ((node.type !== "view" && node.type !== "materialized_view") || !node.connectionId || !node.database) return;
const schema = node.schema || node.database;
const objectType = node.type === "materialized_view" ? "MATERIALIZED_VIEW" : "VIEW";
connectionStore
.ensureConnected(node.connectionId)
.then(() => {
connectionStore.activeConnectionId = node.connectionId!;
return api.getObjectSource(node.connectionId!, node.database!, schema, node.label, "VIEW");
return api.getObjectSource(node.connectionId!, node.database!, schema, node.label, objectType);
})
.then(async (result) => {
const connection = connectionStore.getConfig(node.connectionId!);
@@ -1434,7 +1442,7 @@ function requestDropTableChildObject() {
function canDropTreeNode(node: TreeNode): boolean {
if (node.type === "table") return !!node.connectionId && !!node.database;
if (node.type === "view" || node.type === "procedure" || node.type === "function") {
if (node.type === "view" || node.type === "materialized_view" || node.type === "procedure" || node.type === "function") {
return !!node.connectionId && !!node.database && !!dropObjectSqlOptionsForNode(node);
}
return canDropTableChildObjectNode(node);
@@ -1523,6 +1531,7 @@ function requestDropSelectedNode(): boolean {
function nodeRenameObjectType(): RenameableObjectType | null {
if (props.node.type === "table") return "TABLE";
if (props.node.type === "view") return "VIEW";
if (props.node.type === "materialized_view") return "MATERIALIZED_VIEW";
if (props.node.type === "procedure") return "PROCEDURE";
if (props.node.type === "function") return "FUNCTION";
return null;
@@ -1622,9 +1631,9 @@ async function confirmDropObject() {
await connectionStore.ensureConnected(node.connectionId);
const sql = dropObjectPreviewSql.value || (await buildDropObjectSql(options));
await api.executeQuery(node.connectionId, node.database, sql, node.schema);
const msgKey = node.type === "view" ? "contextMenu.dropViewSuccess" : node.type === "procedure" ? "contextMenu.dropProcedureSuccess" : "contextMenu.dropFunctionSuccess";
const msgKey = node.type === "view" ? "contextMenu.dropViewSuccess" : node.type === "materialized_view" ? "contextMenu.dropViewSuccess" : node.type === "procedure" ? "contextMenu.dropProcedureSuccess" : "contextMenu.dropFunctionSuccess";
toast(t(msgKey, { name: node.label }), 3000);
if (node.type === "view") {
if (node.type === "view" || node.type === "materialized_view") {
connectionStore.removeTreeNode(node.id);
} else {
await refreshTableList(node);
@@ -2528,8 +2537,8 @@ function openDatabaseExport() {
connectionStore.databaseExportSource = {
connectionId: node.connectionId,
database: node.database,
schema: node.type === "schema" || node.type === "table" || node.type === "view" ? node.schema : undefined,
tableName: node.type === "table" || node.type === "view" ? node.label : undefined,
schema: node.type === "schema" || node.type === "table" || node.type === "view" || node.type === "materialized_view" ? node.schema : undefined,
tableName: node.type === "table" || node.type === "view" || node.type === "materialized_view" ? node.label : undefined,
};
}
@@ -2782,7 +2791,7 @@ const TABLE_REFERENCE_DRAG_THRESHOLD = 5;
const TABLE_REFERENCE_DRAGGING_CLASS = "dbx-table-reference-dragging";
const canDragTableReference = computed(() => {
if (props.dragDisabled || !props.node.connectionId || props.node.database == null) return false;
if (props.node.type === "table" || props.node.type === "view") return true;
if (props.node.type === "table" || props.node.type === "view" || props.node.type === "materialized_view") return true;
return props.node.type === "column" && !!props.node.tableName;
});
@@ -3209,8 +3218,8 @@ function treeItemMenuItems(): ContextMenuItem[] {
return items;
}
// 6. Table / View
if (node.type === "table" || node.type === "view") {
// 6. Table / View / Materialized View
if (node.type === "table" || node.type === "view" || node.type === "materialized_view") {
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.viewData"), action: openData, icon: TableProperties });
@@ -3224,7 +3233,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
icon: FileCode,
});
}
if (node.type === "view") {
if (node.type === "view" || node.type === "materialized_view") {
items.push({ label: t("contextMenu.editView"), action: viewObjectSource, icon: Pencil });
items.push({ label: t("contextMenu.viewSource"), action: viewObjectSource, icon: Code2 });
items.push({ label: t("contextMenu.viewDdl"), action: viewObjectDdl, icon: FileCode });
@@ -3240,7 +3249,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
shortcut: shortcutRename,
});
}
if (node.type === "view") {
if (node.type === "view" || node.type === "materialized_view") {
items.push({
label: deleteMenuLabel(t("contextMenu.dropView")),
action: deleteMenuAction(requestDropObject),
@@ -3475,7 +3484,10 @@ function treeItemMenuItems(): ContextMenuItem[] {
/>
<span v-else ref="labelRef" :class="labelWidthClass">{{ visibleLabel(node) }}</span>
<span
v-if="(node.type === 'group-tables' || node.type === 'group-views' || node.type === 'group-procedures' || node.type === 'group-functions' || node.type === 'group-sequences' || node.type === 'group-packages' || node.type === 'group-partitions') && node.objectCount != null"
v-if="
(node.type === 'group-tables' || node.type === 'group-views' || node.type === 'group-materialized-views' || node.type === 'group-procedures' || node.type === 'group-functions' || node.type === 'group-sequences' || node.type === 'group-packages' || node.type === 'group-partitions') &&
node.objectCount != null
"
class="text-muted-foreground text-[10px] shrink-0"
>{{ node.objectCount }}</span
>
+1
View File
@@ -1220,6 +1220,7 @@
triggers: "Triggers",
tables: "Tables",
views: "Views",
materializedViews: "Materialized Views",
procedures: "Procedures",
functions: "Functions",
sequences: "Sequences",
+1
View File
@@ -1219,6 +1219,7 @@
triggers: "触发器",
tables: "表",
views: "视图",
materializedViews: "物化视图",
procedures: "存储过程",
functions: "函数",
sequences: "序列",
@@ -1,6 +1,6 @@
import type { DatabaseType } from "@/types/database";
export type SidebarObjectKind = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export type SidebarObjectKind = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface DatabaseObjectCapabilities {
sidebarObjects: SidebarObjectKind[];
@@ -13,8 +13,9 @@ const TABLE_VIEW_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW"];
const TABLE_VIEW_PROCEDURE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE"];
const TABLE_FUNCTION_OBJECTS: SidebarObjectKind[] = ["TABLE", "FUNCTION"];
const ROUTINE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION"];
const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"];
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"];
const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"];
const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION"];
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"];
const TABLE_ONLY_TYPES = new Set<DatabaseType>(["influxdb"]);
const TABLE_FUNCTION_TYPES = new Set<DatabaseType>(["manticoresearch"]);
@@ -23,6 +24,7 @@ const TABLE_VIEW_ONLY_TYPES = new Set<DatabaseType>(["sqlite", "rqlite", "turso"
const ORACLE_PACKAGE_TYPES = new Set<DatabaseType>(["oracle", "oceanbase-oracle"]);
const POSTGRES_SEQUENCE_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "kwdb", "opengauss"]);
const POSTGRES_LIKE_TYPES = new Set<DatabaseType>(["kingbase", "highgo", "vastbase", "redshift"]);
export function databaseObjectCapabilities(dbType?: DatabaseType): DatabaseObjectCapabilities {
const sidebarObjects = sidebarObjectKindsForDatabase(dbType);
@@ -41,6 +43,7 @@ export function sidebarObjectKindsForDatabase(dbType?: DatabaseType): SidebarObj
if (TABLE_VIEW_PROCEDURE_TYPES.has(dbType)) return [...TABLE_VIEW_PROCEDURE_OBJECTS];
if (TABLE_VIEW_ONLY_TYPES.has(dbType)) return [...TABLE_VIEW_OBJECTS];
if (POSTGRES_SEQUENCE_TYPES.has(dbType)) return [...POSTGRES_OBJECTS];
if (POSTGRES_LIKE_TYPES.has(dbType)) return [...POSTGRES_LIKE_OBJECTS];
return [...ROUTINE_OBJECTS];
}
@@ -48,6 +51,7 @@ export function normalizeSidebarObjectKind(type: string): SidebarObjectKind {
const value = type.toUpperCase();
if (value.includes("PACKAGE BODY") || value.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
if (value.includes("PACKAGE")) return "PACKAGE";
if (value.includes("MATERIALIZED") && value.includes("VIEW")) return "MATERIALIZED_VIEW";
if (value.includes("VIEW")) return "VIEW";
if (value.includes("SEQ")) return "SEQUENCE";
if (value.includes("PROC")) return "PROCEDURE";
+2 -1
View File
@@ -6,7 +6,7 @@ export type ObjectBrowserRow = {
id: string;
name: string;
schema?: string;
type: "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
type: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
comment?: string | null;
created_at?: string | null;
updated_at?: string | null;
@@ -23,6 +23,7 @@ export function normalizeObjectBrowserType(type: string): ObjectBrowserRow["type
const value = type.toUpperCase();
if (value.includes("PACKAGE BODY") || value.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
if (value.includes("PACKAGE")) return "PACKAGE";
if (value.includes("MATERIALIZED") && value.includes("VIEW")) return "MATERIALIZED_VIEW";
if (value.includes("VIEW")) return "VIEW";
if (value.includes("SEQ")) return "SEQUENCE";
if (value.includes("PROC")) return "PROCEDURE";
+2 -2
View File
@@ -23,8 +23,8 @@ export function supportsObjectRename(databaseType: DatabaseType | undefined, obj
}
if (databaseType === "sqlite" || databaseType === "rqlite" || databaseType === "turso") return objectType === "TABLE";
if (databaseType === "mysql" || databaseType === "goldendb") return objectType === "TABLE" || objectType === "VIEW";
if (postgresLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW";
if (oracleLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW";
if (postgresLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW";
if (oracleLikeRenameTypes.has(databaseType)) return objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW";
return false;
}
@@ -105,7 +105,7 @@ export function matchesTarget(node: TreeNode, target: ActiveTabSidebarTarget): b
return node.type === "saved-sql-file" && node.savedSqlId === target.savedSqlId;
}
return (node.type === "table" || node.type === "view") && node.connectionId === target.connectionId && node.database === target.database && schemaMatches(node, target.schema) && node.label === target.tableName;
return (node.type === "table" || node.type === "view" || node.type === "materialized_view") && node.connectionId === target.connectionId && node.database === target.database && schemaMatches(node, target.schema) && node.label === target.tableName;
}
export function findSidebarNodeForActiveTab(tab: QueryTab | undefined | null, flatNodes: readonly FlatTreeNode[]): FlatTreeNode | null {
@@ -2,7 +2,7 @@ import type { TreeNodeType } from "@/types/database";
const leafTypes: Set<TreeNodeType> = new Set(["column", "index", "fkey", "trigger", "procedure", "function", "package", "package-body", "object-browser", "redis-db", "mongo-collection", "elasticsearch-index", "user-admin", "saved-sql-file", "load-more"]);
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "mongo-collection", "elasticsearch-index"]);
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "elasticsearch-index"]);
const emptyContainerTypes: Set<TreeNodeType> = new Set(["saved-sql-root", "saved-sql-folder"]);
+15 -7
View File
@@ -70,7 +70,7 @@ function makeTableTreeEntry({
const node: TreeNode = {
id: `${nodeId}:${nodeIdSchemaSuffix}${name}`,
label: name,
type: objectType === "VIEW" ? ("view" as const) : ("table" as const),
type: objectType === "VIEW" ? ("view" as const) : objectType === "MATERIALIZED_VIEW" ? ("materialized_view" as const) : ("table" as const),
comment,
connectionId,
database,
@@ -213,7 +213,7 @@ export function mergeTableInfosIntoObjects(objects: readonly ObjectInfo[], table
for (const table of tables) {
const objectType = normalizeObjectType(table.table_type);
if (objectType !== "TABLE" && objectType !== "VIEW") continue;
if (objectType !== "TABLE" && objectType !== "VIEW" && objectType !== "MATERIALIZED_VIEW") continue;
const name = normalizeDatabaseObjectName(table.name);
if (!name) continue;
const matchingObject = objects.find((obj) => {
@@ -307,7 +307,7 @@ export function hasTablePartitionGroups(node: TreeNode): boolean {
export type DatabaseObjectTreeKind = SidebarObjectKind;
function buildObjectTreeEntries({ nodeId, connectionId, database, schema, objects, objectType }: { nodeId: string; connectionId: string; database: string; schema?: string; objects: ObjectInfo[]; objectType: "TABLE" | "VIEW" }): TreeNode[] {
function buildObjectTreeEntries({ nodeId, connectionId, database, schema, objects, objectType }: { nodeId: string; connectionId: string; database: string; schema?: string; objects: ObjectInfo[]; objectType: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" }): TreeNode[] {
const entries = objects.flatMap((obj) => {
const name = normalizeDatabaseObjectName(obj.name);
if (!name) return [];
@@ -337,7 +337,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
for (const obj of objects) {
const objectType = normalizeObjectType(obj.object_type);
if (!["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) {
if (!["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) {
continue;
}
@@ -365,7 +365,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
} else {
const simpleNodeType = simpleObjectNodeType(objectType);
objectNodes.push({
id: objectType === "VIEW" ? entry.node.id : `${nodeId}:${childSchema ? `${childSchema}:` : ""}${name}:${objectType}`,
id: objectType === "VIEW" || objectType === "MATERIALIZED_VIEW" ? entry.node.id : `${nodeId}:${childSchema ? `${childSchema}:` : ""}${name}:${objectType}`,
label: name,
type: simpleNodeType,
comment: obj.comment,
@@ -383,6 +383,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
function simpleObjectNodeType(objectType: DatabaseObjectTreeKind): TreeNodeType {
if (objectType === "VIEW") return "view";
if (objectType === "MATERIALIZED_VIEW") return "materialized_view";
if (objectType === "PROCEDURE") return "procedure";
if (objectType === "FUNCTION") return "function";
if (objectType === "SEQUENCE") return "sequence";
@@ -404,6 +405,13 @@ const groupDefs: Array<{
}> = [
{ key: "__tables", label: "tree.tables", objectTypes: ["TABLE"], nodeType: "group-tables", childType: "table" },
{ key: "__views", label: "tree.views", objectTypes: ["VIEW"], nodeType: "group-views", childType: "view" },
{
key: "__materialized_views",
label: "tree.materializedViews",
objectTypes: ["MATERIALIZED_VIEW"],
nodeType: "group-materialized-views",
childType: "materialized_view",
},
{
key: "__procedures",
label: "tree.procedures",
@@ -434,7 +442,7 @@ const groupDefs: Array<{
},
];
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
export function buildObjectGroupPlaceholderNodes({ nodeId, connectionId, database, schema, objectTypes }: { nodeId: string; connectionId: string; database: string; schema?: string; objectTypes: DatabaseObjectTreeKind[] }): TreeNode[] {
const supported = new Set(objectTypes);
@@ -483,7 +491,7 @@ export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, sc
for (const def of groupDefs) {
const items = def.objectTypes.flatMap((objectType) => buckets.get(objectType) ?? []);
if (!items?.length) continue;
const isExpandable = def.nodeType === "group-tables" || def.nodeType === "group-views";
const isExpandable = def.nodeType === "group-tables" || def.nodeType === "group-views" || def.nodeType === "group-materialized-views";
const children = isExpandable
? buildObjectTreeEntries({
nodeId: `${nodeId}:${def.key}`,
+4 -3
View File
@@ -6,16 +6,17 @@ export type TreeNodeRowDoubleClickAction = "open-data" | "open-object-browser" |
export type SidebarSelectionCopyAction = "copy-name" | "none";
export type SidebarActivation = "single" | "double";
const dataNodeTypes = new Set<TreeNodeType>(["table", "view"]);
const dataNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view"]);
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection", "elasticsearch-index", "user-admin"]);
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
const sourceNodeTypes = new Set<TreeNodeType>(["procedure", "function", "sequence", "package", "package-body"]);
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "sequence", "package", "package-body"]);
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-partitions"]);
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
if (type === "view") return "VIEW";
if (type === "materialized_view") return "MATERIALIZED_VIEW";
if (type === "procedure") return "PROCEDURE";
if (type === "function") return "FUNCTION";
if (type === "sequence") return "SEQUENCE";
+8 -8
View File
@@ -1275,7 +1275,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
}
const wantsOnlyTablesOrViews = objectTypes.every((objectType) => objectType === "TABLE" || objectType === "VIEW");
const wantsOnlyTablesOrViews = objectTypes.every((objectType) => objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW");
let children: TreeNode[];
if (wantsOnlyTablesOrViews) {
const page = await loadPagedTableGroupChildren({
@@ -1323,7 +1323,7 @@ export const useConnectionStore = defineStore("connection", () => {
const objectTypes = objectTypesForGroupNode(parent.type);
const parentNodeId = objectGroupRefreshParentId(parent);
if (!objectTypes || !parentNodeId) return;
if (!objectTypes.every((objectType) => objectType === "TABLE" || objectType === "VIEW")) return;
if (!objectTypes.every((objectType) => objectType === "TABLE" || objectType === "VIEW" || objectType === "MATERIALIZED_VIEW")) return;
const config = getConfig(parent.connectionId);
const querySchema = connectionObjectTreeQuerySchema(config, parent.database, parent.schema);
@@ -1605,7 +1605,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
} else if (node.type === "schema" && node.connectionId && hasTreeNodeDatabaseContext(node) && node.schema) {
await loadTables(node.connectionId, node.database, node.schema, options);
} else if ((node.type === "table" || node.type === "view") && node.connectionId && hasTreeNodeDatabaseContext(node)) {
} else if ((node.type === "table" || node.type === "view" || node.type === "materialized_view") && node.connectionId && hasTreeNodeDatabaseContext(node)) {
await loadTableGroups(node.connectionId, node.database, node.label, node.schema, node.id);
} else if (node.type === "group-columns" && node.connectionId && hasTreeNodeDatabaseContext(node) && node.tableName) {
await loadColumns(node.connectionId, node.database, node.tableName, node.schema, node.id);
@@ -1615,7 +1615,7 @@ export const useConnectionStore = defineStore("connection", () => {
await loadForeignKeys(node.connectionId, node.database, node.tableName, node.schema, node.id);
} else if (node.type === "group-triggers" && node.connectionId && hasTreeNodeDatabaseContext(node) && node.tableName) {
await loadTriggers(node.connectionId, node.database, node.tableName, node.schema, node.id);
} else if (node.type === "group-tables" || node.type === "group-views" || node.type === "group-procedures" || node.type === "group-functions" || node.type === "group-sequences" || node.type === "group-packages") {
} else if (node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views" || node.type === "group-procedures" || node.type === "group-functions" || node.type === "group-sequences" || node.type === "group-packages") {
await loadObjectGroupChildren(node, options);
} else if (node.type === "group-partitions") {
node.isExpanded = true;
@@ -1962,7 +1962,7 @@ export const useConnectionStore = defineStore("connection", () => {
return tables.map((table) => ({
name: table.name,
schema: s,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
type: table.table_type === "VIEW" || table.table_type === "MATERIALIZED VIEW" ? ("view" as const) : ("table" as const),
})) as SqlCompletionTable[];
} catch {
return [] as SqlCompletionTable[];
@@ -1984,7 +1984,7 @@ export const useConnectionStore = defineStore("connection", () => {
return tables.map((table) => ({
name: table.name,
schema: s,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
type: table.table_type === "VIEW" || table.table_type === "MATERIALIZED VIEW" ? ("view" as const) : ("table" as const),
})) as SqlCompletionTable[];
} catch {
return [] as SqlCompletionTable[];
@@ -2011,7 +2011,7 @@ export const useConnectionStore = defineStore("connection", () => {
return tables.map((table) => ({
name: table.name,
schema,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
type: table.table_type === "VIEW" || table.table_type === "MATERIALIZED VIEW" ? ("view" as const) : ("table" as const),
}));
} catch {
return [];
@@ -2030,7 +2030,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
completionTablesCache.value[cacheKey] = tables.map((table) => ({
name: table.name,
type: table.table_type === "VIEW" ? ("view" as const) : ("table" as const),
type: table.table_type === "VIEW" || table.table_type === "MATERIALIZED VIEW" ? ("view" as const) : ("table" as const),
}));
completionTablesCache.value[cacheKey] = limit ? completionTablesCache.value[cacheKey].slice(0, limit) : completionTablesCache.value[cacheKey];
indexCompletionTables(connectionId, database, schema, completionTablesCache.value[cacheKey]);
+4 -2
View File
@@ -211,7 +211,7 @@ export interface TableInfo {
parent_name?: string | null;
}
export type DatabaseObjectType = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export type DatabaseObjectType = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface ObjectInfo {
name: string;
@@ -224,7 +224,7 @@ export interface ObjectInfo {
parent_name?: string | null;
}
export type ObjectSourceKind = "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export type ObjectSourceKind = "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface ObjectSource {
name: string;
@@ -390,6 +390,7 @@ export type TreeNodeType =
| "schema"
| "table"
| "view"
| "materialized_view"
| "procedure"
| "function"
| "sequence"
@@ -401,6 +402,7 @@ export type TreeNodeType =
| "group-triggers"
| "group-tables"
| "group-views"
| "group-materialized-views"
| "group-procedures"
| "group-functions"
| "group-sequences"
+6 -1
View File
@@ -8,6 +8,7 @@ use crate::sql_dialect::{is_schema_aware, qualified_table_name, quote_table_iden
pub enum DatabaseObjectType {
Table,
View,
MaterializedView,
Procedure,
Function,
}
@@ -325,7 +326,10 @@ pub fn supports_object_rename(database_type: Option<DatabaseType>, object_type:
return matches!(object_type, DatabaseObjectType::Table | DatabaseObjectType::View);
}
if is_postgres_like_rename(database_type) || is_oracle_like_rename(database_type) {
return matches!(object_type, DatabaseObjectType::Table | DatabaseObjectType::View);
return matches!(
object_type,
DatabaseObjectType::Table | DatabaseObjectType::View | DatabaseObjectType::MaterializedView
);
}
false
}
@@ -462,6 +466,7 @@ fn object_type_keyword(object_type: DatabaseObjectType) -> &'static str {
match object_type {
DatabaseObjectType::Table => "TABLE",
DatabaseObjectType::View => "VIEW",
DatabaseObjectType::MaterializedView => "MATERIALIZED VIEW",
DatabaseObjectType::Procedure => "PROCEDURE",
DatabaseObjectType::Function => "FUNCTION",
}