feat(postgres): support sequence objects

This commit is contained in:
t8y2
2026-06-08 22:54:14 +08:00
parent 178a800ce8
commit befee2fd4c
23 changed files with 175 additions and 32 deletions
@@ -17,6 +17,7 @@ import {
Eraser,
Eye,
FileCode,
ListTree,
Upload,
Loader2,
Network,
@@ -93,7 +94,7 @@ import {
type ObjectBrowserSortKey,
} from "@/lib/objectBrowserRows";
type ObjectFilter = "all" | "tables" | "views" | "procedures" | "functions" | "packages";
type ObjectFilter = "all" | "tables" | "views" | "procedures" | "functions" | "sequences" | "packages";
const props = defineProps<{
connection: ConnectionConfig;
@@ -170,6 +171,7 @@ const tableCount = computed(() => rows.value.filter((row) => row.type === "TABLE
const viewCount = computed(() => rows.value.filter((row) => row.type === "VIEW").length);
const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length);
const functionCount = computed(() => rows.value.filter((row) => row.type === "FUNCTION").length);
const sequenceCount = computed(() => rows.value.filter((row) => row.type === "SEQUENCE").length);
const packageCount = computed(
() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length,
);
@@ -213,6 +215,7 @@ const objectFilters = computed<ObjectFilter[]>(() =>
["views", viewCount.value],
["procedures", procedureCount.value],
["functions", functionCount.value],
["sequences", sequenceCount.value],
["packages", packageCount.value],
] as Array<[ObjectFilter, number]>
)
@@ -258,6 +261,7 @@ function iconFor(row: ObjectBrowserRow) {
if (row.type === "VIEW") return Eye;
if (row.type === "PROCEDURE") return ScrollText;
if (row.type === "FUNCTION") return Braces;
if (row.type === "SEQUENCE") return ListTree;
if (row.type === "PACKAGE" || row.type === "PACKAGE_BODY") return Package;
return Table2;
}
@@ -266,6 +270,7 @@ function typeLabel(type: ObjectBrowserRow["type"]) {
if (type === "VIEW") return t("objects.view");
if (type === "PROCEDURE") return t("objects.procedure");
if (type === "FUNCTION") return t("objects.function");
if (type === "SEQUENCE") return t("objects.sequence");
if (type === "PACKAGE") return t("objects.package");
if (type === "PACKAGE_BODY") return t("objects.packageBody");
return t("objects.table");
@@ -290,6 +295,7 @@ function rowMatchesObjectFilter(row: ObjectBrowserRow) {
if (objectFilter.value === "views") return row.type === "VIEW";
if (objectFilter.value === "procedures") return row.type === "PROCEDURE";
if (objectFilter.value === "functions") return row.type === "FUNCTION";
if (objectFilter.value === "sequences") return row.type === "SEQUENCE";
if (objectFilter.value === "packages") return row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
return true;
}
@@ -330,6 +336,7 @@ function iconClass(type: ObjectBrowserRow["type"]) {
if (type === "VIEW") return "text-purple-500";
if (type === "PROCEDURE") return "text-blue-500";
if (type === "FUNCTION") return "text-amber-500";
if (type === "SEQUENCE") return "text-emerald-500";
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "text-cyan-500";
return "text-green-500";
}
@@ -351,6 +358,7 @@ function canOpenSource(row: ObjectBrowserRow) {
row.type === "VIEW" ||
row.type === "PROCEDURE" ||
row.type === "FUNCTION" ||
row.type === "SEQUENCE" ||
row.type === "PACKAGE" ||
row.type === "PACKAGE_BODY"
);
@@ -405,7 +413,7 @@ async function openSource(row: ObjectBrowserRow) {
);
sourceContent.value = result.source;
sourceDraft.value = result.source;
sourceEditing.value = true;
sourceEditing.value = row.type !== "SEQUENCE";
} catch (e: any) {
sourceError.value = e?.message || String(e);
} finally {
@@ -1145,6 +1153,7 @@ function filterCount(filter: ObjectFilter) {
if (filter === "views") return viewCount.value;
if (filter === "procedures") return procedureCount.value;
if (filter === "functions") return functionCount.value;
if (filter === "sequences") return sequenceCount.value;
if (filter === "packages") return packageCount.value;
return rows.value.length;
}
@@ -1159,9 +1168,11 @@ function filterLabel(filter: ObjectFilter) {
? "objects.procedures"
: filter === "functions"
? "objects.functions"
: filter === "packages"
? "objects.packages"
: "objects.all";
: filter === "sequences"
? "objects.sequences"
: filter === "packages"
? "objects.packages"
: "objects.all";
return `${t(key)} ${filterCount(filter)}`;
}
@@ -1320,6 +1331,7 @@ function getPackageMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
if (item.type === "TABLE") return getTableMenuItems(item);
if (item.type === "VIEW") return getViewMenuItems(item);
if (item.type === "SEQUENCE") return getPackageMenuItems(item);
if (item.type === "PACKAGE" || item.type === "PACKAGE_BODY") return getPackageMenuItems(item);
return getProcFuncMenuItems(item);
}
@@ -270,6 +270,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: ScrollText, colorClass: "text-blue-500" };
case "function":
return { icon: Braces, colorClass: "text-amber-500" };
case "sequence":
return { icon: ListTree, colorClass: "text-emerald-500" };
case "package":
return { icon: Package, colorClass: "text-cyan-500" };
case "package-body":
@@ -282,6 +284,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: ScrollText, colorClass: "text-blue-500" };
case "group-functions":
return { icon: Braces, colorClass: "text-amber-500" };
case "group-sequences":
return { icon: ListTree, colorClass: "text-emerald-500" };
case "group-packages":
return { icon: Package, colorClass: "text-cyan-500" };
case "group-partitions":
@@ -300,6 +304,7 @@ const groupTypes: Set<TreeNodeType> = new Set([
"group-views",
"group-procedures",
"group-functions",
"group-sequences",
"group-packages",
"group-partitions",
"saved-sql-root",
@@ -368,6 +373,7 @@ async function toggle() {
node.type === "group-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;
@@ -473,6 +479,7 @@ function runRowClickAction() {
} else if (
node.type === "procedure" ||
node.type === "function" ||
node.type === "sequence" ||
node.type === "package" ||
node.type === "package-body"
) {
@@ -1225,11 +1232,13 @@ function viewObjectSource() {
.then(async (result) => {
const tabId = queryStore.createTab(node.connectionId!, node.database!, `Source - ${node.label}`);
queryStore.updateSql(tabId, result.source);
queryStore.setObjectSource(tabId, {
schema,
name: node.label,
objectType,
});
if (objectType !== "SEQUENCE") {
queryStore.setObjectSource(tabId, {
schema,
name: node.label,
objectType,
});
}
})
.catch((e: any) => {
toast(e?.message || String(e), 5000);
@@ -3248,6 +3257,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
return items;
}
if (node.type === "sequence") {
items.push({ label: t("contextMenu.viewSource"), action: viewObjectSource, icon: Code2 });
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
return items;
}
if (node.type === "package" || node.type === "package-body") {
items.push({ label: t("contextMenu.viewSource"), action: viewObjectSource, icon: Code2 });
items.push({ label: "", separator: true });
@@ -3410,6 +3426,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
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
+4 -1
View File
@@ -1150,6 +1150,7 @@ export default {
views: "Views",
procedures: "Procedures",
functions: "Functions",
sequences: "Sequences",
packages: "Packages",
partitions: "Partitions",
objectBrowser: "Browse in Object Browser ({count})",
@@ -1207,12 +1208,14 @@ export default {
views: "Views",
procedures: "Procedures",
functions: "Functions",
sequences: "Sequences",
packages: "Packages",
table: "Table",
partitions: "{count} partitions",
view: "View",
procedure: "Procedure",
function: "Function",
sequence: "Sequence",
package: "Package",
packageBody: "Package Body",
name: "Name",
@@ -1228,7 +1231,7 @@ export default {
comment: "Comment",
loadingSchemas: "Loading schemas...",
schema: "Schema",
search: "Search tables, views, functions, or procedures...",
search: "Search tables, views, functions, sequences, or procedures...",
loading: "Loading objects...",
empty: "No objects found",
selectedTables: "{count} tables selected",
+4 -1
View File
@@ -1006,6 +1006,7 @@ export default {
views: "Vistas",
procedures: "Procedimientos",
functions: "Funciones",
sequences: "Secuencias",
packages: "Paquetes",
partitions: "Particiones",
objectBrowser: "Explorar en el navegador de objetos ({count})",
@@ -1016,12 +1017,14 @@ export default {
views: "Vistas",
procedures: "Procedimientos",
functions: "Funciones",
sequences: "Secuencias",
packages: "Paquetes",
table: "Tabla",
partitions: "{count} particiones",
view: "Vista",
procedure: "Procedimiento",
function: "Función",
sequence: "Secuencia",
package: "Paquete",
packageBody: "Cuerpo del paquete",
name: "Nombre",
@@ -1037,7 +1040,7 @@ export default {
comment: "Comentario",
loadingSchemas: "Cargando esquemas...",
schema: "Esquema",
search: "Buscar tablas, vistas, funciones o procedimientos...",
search: "Buscar tablas, vistas, funciones, secuencias o procedimientos...",
loading: "Cargando objetos...",
empty: "No se encontraron objetos",
selectedTables: "{count} tablas seleccionadas",
+4 -1
View File
@@ -1139,6 +1139,7 @@ export default {
views: "Viste",
procedures: "Procedure",
functions: "Funzioni",
sequences: "Sequenze",
packages: "Pacchetti",
partitions: "Partizioni",
objectBrowser: "Sfoglia in Esplora Oggetti ({count})",
@@ -1149,12 +1150,14 @@ export default {
views: "Viste",
procedures: "Procedure",
functions: "Funzioni",
sequences: "Sequenze",
packages: "Pacchetti",
table: "Tabella",
partitions: "{count} partizioni",
view: "Vista",
procedure: "Procedura",
function: "Funzione",
sequence: "Sequenza",
package: "Pacchetto",
packageBody: "Corpo del Pacchetto",
name: "Nome",
@@ -1170,7 +1173,7 @@ export default {
comment: "Commento",
loadingSchemas: "Caricamento schemi...",
schema: "Schema",
search: "Cerca tabelle, viste, funzioni o procedure...",
search: "Cerca tabelle, viste, funzioni, sequenze o procedure...",
loading: "Caricamento oggetti...",
empty: "Nessun oggetto trovato",
selectedTables: "{count} tabelle selezionate",
+4 -1
View File
@@ -1134,6 +1134,7 @@ export default {
views: "Visões",
procedures: "Procedimentos",
functions: "Funções",
sequences: "Sequências",
packages: "Pacotes",
partitions: "Partições",
objectBrowser: "Navegar no Navegador de Objetos ({count})",
@@ -1144,12 +1145,14 @@ export default {
views: "Visões",
procedures: "Procedimentos",
functions: "Funções",
sequences: "Sequências",
packages: "Pacotes",
table: "Tabela",
partitions: "{count} partições",
view: "Visão",
procedure: "Procedimento",
function: "Função",
sequence: "Sequência",
package: "Pacote",
packageBody: "Corpo do pacote",
name: "Nome",
@@ -1165,7 +1168,7 @@ export default {
comment: "Comentário",
loadingSchemas: "Carregando schemas...",
schema: "Schema",
search: "Pesquisar tabelas, visões, funções ou procedimentos...",
search: "Pesquisar tabelas, visões, funções, sequências ou procedimentos...",
loading: "Carregando objetos...",
empty: "Nenhum objeto encontrado",
selectedTables: "{count} tabelas selecionadas",
+4 -1
View File
@@ -1125,6 +1125,7 @@ export default {
views: "视图",
procedures: "存储过程",
functions: "函数",
sequences: "序列",
packages: "包",
partitions: "分区",
objectBrowser: "在对象浏览器中查看 ({count})",
@@ -1180,12 +1181,14 @@ export default {
views: "视图",
procedures: "存储过程",
functions: "函数",
sequences: "序列",
packages: "包",
table: "表",
partitions: "{count} 个分区",
view: "视图",
procedure: "存储过程",
function: "函数",
sequence: "序列",
package: "包",
packageBody: "包体",
name: "名称",
@@ -1201,7 +1204,7 @@ export default {
comment: "注释",
loadingSchemas: "加载 Schema...",
schema: "Schema",
search: "搜索表、视图、函数或存储过程...",
search: "搜索表、视图、函数、序列或存储过程...",
loading: "正在加载对象...",
empty: "没有找到对象",
selectedTables: "已选择 {count} 张表",
+4 -1
View File
@@ -1071,6 +1071,7 @@ export default {
views: "檢視",
procedures: "預存程序",
functions: "函式",
sequences: "序列",
packages: "套件",
partitions: "分割區",
objectBrowser: "在物件瀏覽器中檢視 ({count})",
@@ -1081,12 +1082,14 @@ export default {
views: "檢視",
procedures: "預存程序",
functions: "函式",
sequences: "序列",
packages: "套件",
table: "資料表",
partitions: "{count} 個分割區",
view: "檢視",
procedure: "預存程序",
function: "函式",
sequence: "序列",
package: "套件",
packageBody: "套件主體",
name: "名稱",
@@ -1102,7 +1105,7 @@ export default {
comment: "註解",
loadingSchemas: "載入 Schema……",
schema: "Schema",
search: "搜尋資料表、檢視、函式或預存程序……",
search: "搜尋資料表、檢視、函式、序列或預存程序……",
loading: "正在載入物件……",
empty: "沒有找到物件",
selectedTables: "已選擇 {count} 張資料表",
@@ -1,6 +1,6 @@
import type { DatabaseType } from "@/types/database";
export type SidebarObjectKind = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "PACKAGE" | "PACKAGE_BODY";
export type SidebarObjectKind = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface DatabaseObjectCapabilities {
sidebarObjects: SidebarObjectKind[];
@@ -10,6 +10,7 @@ export interface DatabaseObjectCapabilities {
const TABLE_VIEW_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW"];
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 TABLE_VIEW_ONLY_TYPES = new Set<DatabaseType>([
@@ -31,6 +32,7 @@ const TABLE_VIEW_ONLY_TYPES = new Set<DatabaseType>([
]);
const ORACLE_PACKAGE_TYPES = new Set<DatabaseType>(["oracle", "oceanbase-oracle"]);
const POSTGRES_SEQUENCE_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "kwdb", "opengauss"]);
export function databaseObjectCapabilities(dbType?: DatabaseType): DatabaseObjectCapabilities {
const sidebarObjects = sidebarObjectKindsForDatabase(dbType);
@@ -45,6 +47,7 @@ export function sidebarObjectKindsForDatabase(dbType?: DatabaseType): SidebarObj
if (!dbType) return [...TABLE_VIEW_OBJECTS];
if (ORACLE_PACKAGE_TYPES.has(dbType)) return [...ORACLE_OBJECTS];
if (TABLE_VIEW_ONLY_TYPES.has(dbType)) return [...TABLE_VIEW_OBJECTS];
if (POSTGRES_SEQUENCE_TYPES.has(dbType)) return [...POSTGRES_OBJECTS];
return [...ROUTINE_OBJECTS];
}
@@ -53,6 +56,7 @@ export function normalizeSidebarObjectKind(type: string): SidebarObjectKind {
if (value.includes("PACKAGE BODY") || value.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
if (value.includes("PACKAGE")) return "PACKAGE";
if (value.includes("VIEW")) return "VIEW";
if (value.includes("SEQ")) return "SEQUENCE";
if (value.includes("PROC")) return "PROCEDURE";
if (value.includes("FUNC")) return "FUNCTION";
return "TABLE";
+2 -1
View File
@@ -6,7 +6,7 @@ export type ObjectBrowserRow = {
id: string;
name: string;
schema?: string;
type: "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "PACKAGE" | "PACKAGE_BODY";
type: "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
comment?: string | null;
created_at?: string | null;
updated_at?: string | null;
@@ -24,6 +24,7 @@ export function normalizeObjectBrowserType(type: string): ObjectBrowserRow["type
if (value.includes("PACKAGE BODY") || value.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
if (value.includes("PACKAGE")) return "PACKAGE";
if (value.includes("VIEW")) return "VIEW";
if (value.includes("SEQ")) return "SEQUENCE";
if (value.includes("PROC")) return "PROCEDURE";
if (value.includes("FUNC")) return "FUNCTION";
return "TABLE";
+12 -1
View File
@@ -374,7 +374,9 @@ export function buildSimpleObjectTreeNodes({
for (const obj of objects) {
const objectType = normalizeObjectType(obj.object_type);
if (!["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) continue;
if (!["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) {
continue;
}
const name = normalizeDatabaseObjectName(obj.name);
if (!name) continue;
@@ -426,6 +428,7 @@ function simpleObjectNodeType(objectType: DatabaseObjectTreeKind): TreeNodeType
if (objectType === "VIEW") return "view";
if (objectType === "PROCEDURE") return "procedure";
if (objectType === "FUNCTION") return "function";
if (objectType === "SEQUENCE") return "sequence";
if (objectType === "PACKAGE_BODY") return "package-body";
if (objectType === "PACKAGE") return "package";
return "table";
@@ -458,6 +461,13 @@ const groupDefs: Array<{
nodeType: "group-functions",
childType: "function",
},
{
key: "__sequences",
label: "tree.sequences",
objectTypes: ["SEQUENCE"],
nodeType: "group-sequences",
childType: "sequence",
},
{
key: "__packages",
label: "tree.packages",
@@ -472,6 +482,7 @@ const objectGroupNodeTypes = new Set<TreeNodeType>([
"group-views",
"group-procedures",
"group-functions",
"group-sequences",
"group-packages",
]);
+3 -1
View File
@@ -16,7 +16,7 @@ export type SidebarActivation = "single" | "double";
const dataNodeTypes = new Set<TreeNodeType>(["table", "view"]);
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection", "user-admin"]);
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
const sourceNodeTypes = new Set<TreeNodeType>(["procedure", "function", "package", "package-body"]);
const sourceNodeTypes = new Set<TreeNodeType>(["procedure", "function", "sequence", "package", "package-body"]);
const tableChildGroupNodeTypes = new Set<TreeNodeType>([
"group-columns",
"group-indexes",
@@ -29,6 +29,7 @@ const databaseChildGroupNodeTypes = new Set<TreeNodeType>([
"group-views",
"group-procedures",
"group-functions",
"group-sequences",
"group-packages",
]);
@@ -36,6 +37,7 @@ export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKin
if (type === "view") return "VIEW";
if (type === "procedure") return "PROCEDURE";
if (type === "function") return "FUNCTION";
if (type === "sequence") return "SEQUENCE";
if (type === "package") return "PACKAGE";
if (type === "package-body") return "PACKAGE_BODY";
return null;
@@ -1593,6 +1593,7 @@ export const useConnectionStore = defineStore("connection", () => {
node.type === "group-views" ||
node.type === "group-procedures" ||
node.type === "group-functions" ||
node.type === "group-sequences" ||
node.type === "group-packages"
) {
await loadObjectGroupChildren(node, options);
+4 -2
View File
@@ -178,7 +178,7 @@ export interface TableInfo {
parent_name?: string | null;
}
export type DatabaseObjectType = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "PACKAGE" | "PACKAGE_BODY";
export type DatabaseObjectType = "TABLE" | "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface ObjectInfo {
name: string;
@@ -191,7 +191,7 @@ export interface ObjectInfo {
parent_name?: string | null;
}
export type ObjectSourceKind = "VIEW" | "PROCEDURE" | "FUNCTION" | "PACKAGE" | "PACKAGE_BODY";
export type ObjectSourceKind = "VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
export interface ObjectSource {
name: string;
@@ -288,6 +288,7 @@ export type TreeNodeType =
| "view"
| "procedure"
| "function"
| "sequence"
| "package"
| "package-body"
| "group-columns"
@@ -298,6 +299,7 @@ export type TreeNodeType =
| "group-views"
| "group-procedures"
| "group-functions"
| "group-sequences"
| "group-packages"
| "group-partitions"
| "object-browser"