From f57cd3960b0afe92239021cc29b9e24f5a86b7d5 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 21 Aug 2026 17:22:32 +0000 Subject: [PATCH] fix(oracle): expose sequences in schema tree Closes #6893 --- agents/drivers/oracle-go/main.go | 13 +++---- agents/drivers/oracle-go/main_test.go | 34 ++++++++++++++++++ .../databaseObjectCapabilities.spec.ts | 18 ++++++++-- .../database/databaseObjectCapabilities.ts | 2 +- crates/dbx-core/src/schema.rs | 35 ++++++++++++++++--- 5 files changed, 88 insertions(+), 14 deletions(-) diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index ff8e3e39dc..2901ce8e16 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -167,7 +167,7 @@ SELECT o.OBJECT_NAME, CAST(NULL AS VARCHAR2(4000)) AS COMMENTS FROM ALL_OBJECTS o WHERE o.OWNER = :2 - AND o.OBJECT_TYPE IN ('VIEW', 'MATERIALIZED VIEW', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY') + AND o.OBJECT_TYPE IN ('VIEW', 'MATERIALIZED VIEW', 'PROCEDURE', 'FUNCTION', 'SEQUENCE', 'PACKAGE', 'PACKAGE BODY') UNION ALL SELECT s.SYNONYM_NAME AS OBJECT_NAME, 'SYNONYM' AS OBJECT_TYPE, @@ -198,7 +198,7 @@ SELECT o.OBJECT_NAME, END AS OBJECT_TYPE, CAST(NULL AS VARCHAR2(4000)) AS COMMENTS FROM USER_OBJECTS o -WHERE o.OBJECT_TYPE IN ('VIEW', 'MATERIALIZED VIEW', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY') +WHERE o.OBJECT_TYPE IN ('VIEW', 'MATERIALIZED VIEW', 'PROCEDURE', 'FUNCTION', 'SEQUENCE', 'PACKAGE', 'PACKAGE BODY') UNION ALL SELECT s.SYNONYM_NAME AS OBJECT_NAME, 'SYNONYM' AS OBJECT_TYPE, @@ -211,9 +211,10 @@ const oracleListObjectsOrderSQL = `ORDER BY CASE OBJECT_TYPE WHEN 'MATERIALIZED_VIEW' THEN 2 WHEN 'PROCEDURE' THEN 3 WHEN 'FUNCTION' THEN 4 - WHEN 'SYNONYM' THEN 5 - WHEN 'PACKAGE' THEN 6 - ELSE 7 + WHEN 'SEQUENCE' THEN 5 + WHEN 'SYNONYM' THEN 6 + WHEN 'PACKAGE' THEN 7 + ELSE 8 END, OBJECT_NAME` const oracleListObjectsSQL = oracleListObjectsBaseSQL + "\n" + oracleListObjectsOrderSQL const oracleListTriggersSQL = ` @@ -2914,7 +2915,7 @@ func (s *server) getObjectSource(schema, name, objectType string) (map[string]an } return map[string]any{"name": name, "object_type": objectType, "schema": schema, "source": source}, nil } - if upperType == "SYNONYM" { + if upperType == "SEQUENCE" || upperType == "SYNONYM" { return s.getMetadataObjectSource(schema, name, upperType) } diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index d464eb8083..fdd2c31ac2 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -1876,11 +1876,21 @@ func TestListObjectsSQLUsesSplitDictionaryQuery(t *testing.T) { if !strings.Contains(sqlText, "'PACKAGE BODY'") || !strings.Contains(sqlText, "PACKAGE_BODY") { t.Fatalf("object listing should include package bodies with normalized type, got: %s", oracleListObjectsSQL) } + if !strings.Contains(sqlText, "'SEQUENCE'") { + t.Fatalf("object listing should include sequences, got: %s", oracleListObjectsSQL) + } if !strings.Contains(sqlText, "ALL_SYNONYMS") || !strings.Contains(sqlText, "'SYNONYM' AS OBJECT_TYPE") { t.Fatalf("object listing should include schema synonyms, got: %s", oracleListObjectsSQL) } } +func TestListSessionUserObjectsSQLIncludesSequences(t *testing.T) { + sqlText := strings.ToUpper(oracleListObjectsSessionUserBaseSQL) + if !strings.Contains(sqlText, "FROM USER_OBJECTS") || !strings.Contains(sqlText, "'SEQUENCE'") { + t.Fatalf("session-user object listing should include sequences, got: %s", oracleListObjectsSessionUserBaseSQL) + } +} + func TestListObjectsQueryAppliesMetadataConstraints(t *testing.T) { query := oracleListObjectsQuery("APP", metadataListConstraints{ Filter: "pkg%", @@ -2483,6 +2493,30 @@ func TestGetObjectSourceUsesDBMSMetadataForSynonym(t *testing.T) { } } +func TestGetObjectSourceUsesDBMSMetadataForSequence(t *testing.T) { + const ddl = `CREATE SEQUENCE "HR"."ORDER_SEQ" MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 42 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE GLOBAL` + db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{ + { + queryContains: "DBMS_METADATA.GET_DDL(:1, :2, :3)", + args: []driver.Value{"SEQUENCE", "ORDER_SEQ", "HR"}, + rows: [][]driver.Value{{ddl}}, + }, + }) + s := newServer() + s.db = db + + result, err := s.getObjectSource("HR", "ORDER_SEQ", "SEQUENCE") + if err != nil { + t.Fatal(err) + } + if result["source"] != ddl || result["object_type"] != "SEQUENCE" { + t.Fatalf("unexpected sequence source: %#v", result) + } + if scripted.next != len(scripted.steps) { + t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next) + } +} + func TestGetObjectSourcePreservesQuotedSynonymName(t *testing.T) { const synonym = "MixedSynonym" const ddl = `CREATE OR REPLACE EDITIONABLE SYNONYM "AP"."MixedSynonym" FOR "ADM"."S_SPECS"` diff --git a/apps/desktop/src/lib/__tests__/database/databaseObjectCapabilities.spec.ts b/apps/desktop/src/lib/__tests__/database/databaseObjectCapabilities.spec.ts index 50b1ce4e76..97b2b7094a 100644 --- a/apps/desktop/src/lib/__tests__/database/databaseObjectCapabilities.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/databaseObjectCapabilities.spec.ts @@ -14,7 +14,7 @@ describe("databaseObjectCapabilities", () => { expect(sidebarObjectKindsForDatabase("postgres")).not.toContain("SYNONYM"); }); - it("exposes OceanBase Oracle sequences without widening Oracle or synonym support", () => { + it("exposes OceanBase Oracle sequences without widening synonym support", () => { const oceanBaseObjects = sidebarObjectKindsForDatabase("oceanbase-oracle"); expect(oceanBaseObjects).toEqual(["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"]); @@ -29,8 +29,22 @@ describe("databaseObjectCapabilities", () => { objectTypes: oceanBaseObjects, }).map((node) => node.type), ).toEqual(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]); + }); - expect(sidebarObjectKindsForDatabase("oracle")).toEqual(["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]); + it("exposes Oracle sequences through the existing grouped object path", () => { + const oracleObjects = sidebarObjectKindsForDatabase("oracle"); + + expect(oracleObjects).toEqual(["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]); + expect(databaseObjectCapabilities("oracle").sourceReadable).toContain("SEQUENCE"); + expect( + buildObjectGroupPlaceholderNodes({ + nodeId: "connection:database:HR", + connectionId: "connection", + database: "database", + schema: "HR", + objectTypes: oracleObjects, + }).map((node) => node.type), + ).toContain("group-sequences"); }); it("expands package members only for implemented database paths", () => { diff --git a/apps/desktop/src/lib/database/databaseObjectCapabilities.ts b/apps/desktop/src/lib/database/databaseObjectCapabilities.ts index 302db7a2a8..c8385e6646 100644 --- a/apps/desktop/src/lib/database/databaseObjectCapabilities.ts +++ b/apps/desktop/src/lib/database/databaseObjectCapabilities.ts @@ -31,7 +31,7 @@ const KINGBASE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VI const VASTBASE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "TYPE"]; const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION"]; -const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]; +const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]; const OCEANBASE_ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"]; const DAMENG_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"]; const XUGU_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY"]; diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 5d22ec08d5..801df39533 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -3815,6 +3815,20 @@ for line in sys.stdin: assert!(!super::is_default_oracle_agent_config(&config)); } + #[test] + fn oracle_metadata_object_source_is_limited_to_supported_kinds() { + let oracle = test_connection_config(DatabaseType::Oracle); + let postgres = test_connection_config(DatabaseType::Postgres); + + for object_type in + [db::ObjectSourceKind::Sequence, db::ObjectSourceKind::Package, db::ObjectSourceKind::PackageBody] + { + assert!(super::uses_oracle_metadata_object_source(Some(&oracle), &object_type)); + } + assert!(!super::uses_oracle_metadata_object_source(Some(&postgres), &db::ObjectSourceKind::Sequence,)); + assert!(!super::uses_oracle_metadata_object_source(Some(&oracle), &db::ObjectSourceKind::View,)); + } + #[test] fn agent_table_paging_supports_tdengine_and_default_oracle_only() { assert!(super::supports_agent_table_paging(&test_connection_config(DatabaseType::Tdengine))); @@ -7323,6 +7337,14 @@ fn is_default_oracle_agent_config(config: &ConnectionConfig) -> bool { && !matches!(config.driver_profile.as_deref(), Some("oracle-legacy" | "oracle-10g")) } +fn uses_oracle_metadata_object_source(config: Option<&ConnectionConfig>, object_type: &db::ObjectSourceKind) -> bool { + config.is_some_and(|config| config.db_type == DatabaseType::Oracle) + && matches!( + object_type, + db::ObjectSourceKind::Sequence | db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody + ) +} + fn supports_agent_table_paging(config: &ConnectionConfig) -> bool { // Keep paging opt-in until each legacy agent is known to apply metadata constraints server-side. matches!(config.db_type, DatabaseType::Tdengine) || is_default_oracle_agent_config(config) @@ -8293,9 +8315,7 @@ async fn get_object_source_once( first_string_cell(result?)? } else if let Some(client) = extract_pool!(&connections, &pool_key, Agent) { drop(connections); - if db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle) - && matches!(object_type, db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody) - { + if uses_oracle_metadata_object_source(db_config.as_ref(), &object_type) { oracle_agent_object_source( client, database, @@ -8419,8 +8439,8 @@ pub fn oracle_list_objects_sql(schema: &str) -> String { format!( "SELECT object_name, CASE object_type WHEN 'PACKAGE BODY' THEN 'PACKAGE_BODY' ELSE object_type END AS object_type, owner \ FROM all_objects \ - WHERE owner = {} AND object_type IN ('TABLE', 'VIEW', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY') \ - ORDER BY CASE object_type WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'PROCEDURE' THEN 2 WHEN 'FUNCTION' THEN 3 WHEN 'PACKAGE' THEN 4 ELSE 5 END, object_name", + WHERE owner = {} AND object_type IN ('TABLE', 'VIEW', 'PROCEDURE', 'FUNCTION', 'SEQUENCE', 'PACKAGE', 'PACKAGE BODY') \ + ORDER BY CASE object_type WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'PROCEDURE' THEN 2 WHEN 'FUNCTION' THEN 3 WHEN 'SEQUENCE' THEN 4 WHEN 'PACKAGE' THEN 5 ELSE 6 END, object_name", oracle_owner_filter(schema) ) } @@ -9070,6 +9090,10 @@ mod object_source_tests { oracle_object_source_sql("", "PAYROLL", &ObjectSourceKind::Package), "SELECT DBMS_METADATA.GET_DDL('PACKAGE', 'PAYROLL') FROM DUAL" ); + assert_eq!( + oracle_object_source_sql("HR", "ORDER_SEQ", &ObjectSourceKind::Sequence), + "SELECT DBMS_METADATA.GET_DDL('SEQUENCE', 'ORDER_SEQ', 'HR') FROM DUAL" + ); } #[test] @@ -9078,6 +9102,7 @@ mod object_source_tests { assert!(sql.contains("'PACKAGE'")); assert!(sql.contains("'PACKAGE BODY'")); + assert!(sql.contains("'SEQUENCE'")); assert!(sql.contains("CASE object_type WHEN 'PACKAGE BODY' THEN 'PACKAGE_BODY'")); assert!(sql.contains("owner = 'HR'")); }