mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
@@ -138,6 +138,12 @@ SELECT o.OBJECT_NAME,
|
||||
FROM ALL_OBJECTS o
|
||||
WHERE o.OWNER = :2
|
||||
AND o.OBJECT_TYPE IN ('VIEW', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY')
|
||||
UNION ALL
|
||||
SELECT s.SYNONYM_NAME AS OBJECT_NAME,
|
||||
'SYNONYM' AS OBJECT_TYPE,
|
||||
CAST(NULL AS VARCHAR2(4000)) AS COMMENTS
|
||||
FROM ALL_SYNONYMS s
|
||||
WHERE s.OWNER = :3
|
||||
)`
|
||||
const oracleListObjectsSessionUserBaseSQL = `
|
||||
SELECT OBJECT_NAME, OBJECT_TYPE, COMMENTS
|
||||
@@ -153,14 +159,20 @@ SELECT o.OBJECT_NAME,
|
||||
CAST(NULL AS VARCHAR2(4000)) AS COMMENTS
|
||||
FROM USER_OBJECTS o
|
||||
WHERE o.OBJECT_TYPE IN ('VIEW', 'PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY')
|
||||
UNION ALL
|
||||
SELECT s.SYNONYM_NAME AS OBJECT_NAME,
|
||||
'SYNONYM' AS OBJECT_TYPE,
|
||||
CAST(NULL AS VARCHAR2(4000)) AS COMMENTS
|
||||
FROM USER_SYNONYMS s
|
||||
)`
|
||||
const oracleListObjectsOrderSQL = `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
|
||||
WHEN 'SYNONYM' THEN 4
|
||||
WHEN 'PACKAGE' THEN 5
|
||||
ELSE 6
|
||||
END, OBJECT_NAME`
|
||||
const oracleListObjectsSQL = oracleListObjectsBaseSQL + "\n" + oracleListObjectsOrderSQL
|
||||
const oracleListTriggersSQL = `
|
||||
@@ -1511,7 +1523,7 @@ func oracleListObjectsQuery(schema string, constraints metadataListConstraints)
|
||||
"OBJECT_NAME, OBJECT_TYPE, COMMENTS",
|
||||
"OBJECT_TYPE",
|
||||
oracleListObjectsOrderSQL,
|
||||
[]any{schema, schema},
|
||||
[]any{schema, schema, schema},
|
||||
constraints,
|
||||
)
|
||||
}
|
||||
@@ -2597,6 +2609,9 @@ 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" {
|
||||
return s.getMetadataObjectSource(schema, name, upperType)
|
||||
}
|
||||
|
||||
// Unquoted Oracle identifiers are stored uppercase; quoted mixed-case names must stay exact.
|
||||
// Try caller-provided identity first, then uppercase fallback (same pattern as column metadata).
|
||||
@@ -2612,6 +2627,22 @@ func (s *server) getObjectSource(schema, name, objectType string) (map[string]an
|
||||
return map[string]any{"name": name, "object_type": objectType, "schema": schema, "source": ""}, nil
|
||||
}
|
||||
|
||||
func (s *server) getMetadataObjectSource(schema, name, objectType string) (map[string]any, error) {
|
||||
db, err := s.requireDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var lastErr error
|
||||
for _, candidate := range oracleObjectIdentityNameCandidates(name) {
|
||||
var source string
|
||||
lastErr = db.QueryRow("SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", objectType, candidate, schema).Scan(&source)
|
||||
if lastErr == nil {
|
||||
return map[string]any{"name": candidate, "object_type": objectType, "schema": schema, "source": source}, nil
|
||||
}
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func (s *server) loadObjectSourceText(schema, name, objectType string) (string, bool, error) {
|
||||
rows, err := s.queryRows(`
|
||||
SELECT TEXT
|
||||
|
||||
@@ -1600,6 +1600,9 @@ 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, "ALL_SYNONYMS") || !strings.Contains(sqlText, "'SYNONYM' AS OBJECT_TYPE") {
|
||||
t.Fatalf("object listing should include schema synonyms, got: %s", oracleListObjectsSQL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObjectsQueryAppliesMetadataConstraints(t *testing.T) {
|
||||
@@ -1610,19 +1613,19 @@ func TestListObjectsQueryAppliesMetadataConstraints(t *testing.T) {
|
||||
})
|
||||
sqlText := strings.ToUpper(query.SQL)
|
||||
|
||||
if !strings.Contains(sqlText, "UPPER(OBJECT_NAME) LIKE :3 ESCAPE '\\'") {
|
||||
if !strings.Contains(sqlText, "UPPER(OBJECT_NAME) LIKE :4 ESCAPE '\\'") {
|
||||
t.Fatalf("object listing should push filter predicate, got: %s", query.SQL)
|
||||
}
|
||||
if !strings.Contains(sqlText, "OBJECT_TYPE IN (:4,:5)") {
|
||||
if !strings.Contains(sqlText, "OBJECT_TYPE IN (:5,:6)") {
|
||||
t.Fatalf("object listing should push object type predicate, got: %s", query.SQL)
|
||||
}
|
||||
if !strings.Contains(sqlText, "ROWNUM <= :6") || !strings.Contains(sqlText, "DBX_RN > :7") {
|
||||
if !strings.Contains(sqlText, "ROWNUM <= :7") || !strings.Contains(sqlText, "DBX_RN > :8") {
|
||||
t.Fatalf("object listing should use rownum pagination, got: %s", query.SQL)
|
||||
}
|
||||
if len(query.Args) != 7 {
|
||||
if len(query.Args) != 8 {
|
||||
t.Fatalf("unexpected args: %#v", query.Args)
|
||||
}
|
||||
if query.Args[2] != "%P%K%G%\\%%" || query.Args[3] != "FUNCTION" || query.Args[4] != "PACKAGE" || query.Args[5] != 25 || query.Args[6] != 0 {
|
||||
if query.Args[0] != "APP" || query.Args[1] != "APP" || query.Args[2] != "APP" || query.Args[3] != "%P%K%G%\\%%" || query.Args[4] != "FUNCTION" || query.Args[5] != "PACKAGE" || query.Args[6] != 25 || query.Args[7] != 0 {
|
||||
t.Fatalf("object constraints args were not normalized: %#v", query.Args)
|
||||
}
|
||||
}
|
||||
@@ -1641,6 +1644,9 @@ func TestListSessionUserObjectsQueryUsesUserDictionary(t *testing.T) {
|
||||
if strings.Contains(sqlText, "ALL_TABLES") || strings.Contains(sqlText, "ALL_OBJECTS") {
|
||||
t.Fatalf("session-user object listing should avoid ALL_* dictionaries, got: %s", query.SQL)
|
||||
}
|
||||
if !strings.Contains(sqlText, "USER_SYNONYMS") {
|
||||
t.Fatalf("session-user object listing should include owned synonyms, got: %s", query.SQL)
|
||||
}
|
||||
if strings.Contains(sqlText, "OWNER =") {
|
||||
t.Fatalf("session-user object listing should not add owner predicates, got: %s", query.SQL)
|
||||
}
|
||||
@@ -1661,6 +1667,26 @@ func TestListSessionUserObjectsQueryUsesUserDictionary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObjectsQuerySupportsSynonymOnlyFiltering(t *testing.T) {
|
||||
query := oracleListObjectsQuery("AP", metadataListConstraints{
|
||||
Filter: "spec",
|
||||
Limit: 10,
|
||||
Offset: 20,
|
||||
ObjectTypes: []string{"synonym"},
|
||||
})
|
||||
sqlText := strings.ToUpper(query.SQL)
|
||||
|
||||
if !strings.Contains(sqlText, "FROM ALL_SYNONYMS") || !strings.Contains(sqlText, "S.OWNER = :3") {
|
||||
t.Fatalf("cross-schema synonym listing should use the selected owner, got: %s", query.SQL)
|
||||
}
|
||||
if !strings.Contains(sqlText, "OBJECT_TYPE IN (:5)") {
|
||||
t.Fatalf("synonym filtering should be pushed into the constrained query, got: %s", query.SQL)
|
||||
}
|
||||
if len(query.Args) != 7 || query.Args[2] != "AP" || query.Args[3] != "%S%P%E%C%" || query.Args[4] != "SYNONYM" || query.Args[5] != 30 || query.Args[6] != 20 {
|
||||
t.Fatalf("unexpected synonym query args: %#v", query.Args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleListTriggersSQLLoadsSourceWithoutLongColumns(t *testing.T) {
|
||||
sqlText := strings.ToUpper(oracleListTriggersSQL)
|
||||
|
||||
@@ -2022,6 +2048,55 @@ func TestGetObjectSourceUsesOriginalViewNameWithDBMSMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectSourceUsesDBMSMetadataForSynonym(t *testing.T) {
|
||||
const ddl = `CREATE OR REPLACE EDITIONABLE SYNONYM "AP"."S_SPECS" FOR "ADM"."S_SPECS"`
|
||||
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
|
||||
{
|
||||
queryContains: "DBMS_METADATA.GET_DDL(:1, :2, :3)",
|
||||
args: []driver.Value{"SYNONYM", "S_SPECS", "AP"},
|
||||
rows: [][]driver.Value{{ddl}},
|
||||
},
|
||||
})
|
||||
s := newServer()
|
||||
s.db = db
|
||||
|
||||
result, err := s.getObjectSource("AP", "S_SPECS", "SYNONYM")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result["source"] != ddl || result["object_type"] != "SYNONYM" {
|
||||
t.Fatalf("unexpected synonym 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"`
|
||||
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
|
||||
{
|
||||
queryContains: "DBMS_METADATA.GET_DDL(:1, :2, :3)",
|
||||
args: []driver.Value{"SYNONYM", synonym, "AP"},
|
||||
rows: [][]driver.Value{{ddl}},
|
||||
},
|
||||
})
|
||||
s := newServer()
|
||||
s.db = db
|
||||
|
||||
result, err := s.getObjectSource("AP", synonym, "SYNONYM")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result["name"] != synonym || result["source"] != ddl {
|
||||
t.Fatalf("quoted synonym identity was not preserved: %#v", result)
|
||||
}
|
||||
if scripted.next != len(scripted.steps) {
|
||||
t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjectSourceFallsBackToAllViewsWithOriginalName(t *testing.T) {
|
||||
const viewName = "vEnginWJZ"
|
||||
const source = `SELECT source_id FROM "ZTZS_ERP2"."SOURCE_TABLE"`
|
||||
|
||||
@@ -6,8 +6,10 @@ describe("databaseObjectCapabilities", () => {
|
||||
expect(sidebarObjectKindsForDatabase("dameng")).toEqual(expect.arrayContaining(["MATERIALIZED_VIEW", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"]));
|
||||
});
|
||||
|
||||
it("exposes synonyms for Xugu only", () => {
|
||||
it("exposes synonyms only for database paths with synonym metadata", () => {
|
||||
expect(sidebarObjectKindsForDatabase("oracle")).toContain("SYNONYM");
|
||||
expect(sidebarObjectKindsForDatabase("xugu")).toContain("SYNONYM");
|
||||
expect(sidebarObjectKindsForDatabase("oceanbase-oracle")).not.toContain("SYNONYM");
|
||||
expect(sidebarObjectKindsForDatabase("postgres")).not.toContain("SYNONYM");
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ const POSTGRES_NO_TYPE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIA
|
||||
const KINGBASE_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", "PACKAGE", "PACKAGE_BODY"];
|
||||
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SYNONYM", "PACKAGE", "PACKAGE_BODY"];
|
||||
const OCEANBASE_ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "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"];
|
||||
const PACKAGE_MEMBER_EXPANSION_DATABASES = new Set<DatabaseType>(["oracle", "xugu"]);
|
||||
@@ -49,7 +50,7 @@ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
||||
// oracle
|
||||
["oracle", ORACLE_OBJECTS],
|
||||
["dameng", DAMENG_OBJECTS],
|
||||
["oceanbase-oracle", ORACLE_OBJECTS],
|
||||
["oceanbase-oracle", OCEANBASE_ORACLE_OBJECTS],
|
||||
["xugu", XUGU_OBJECTS],
|
||||
// table and view
|
||||
["sqlite", TABLE_VIEW_OBJECTS],
|
||||
|
||||
Reference in New Issue
Block a user