feat(oracle): expand package members in schema tree

This commit is contained in:
t8y2 committed 2026-08-10 18:15:47 +08:00
1 parent ad9075e18f
commit 585e2f3c50
12 files changed
+508 -75

No files matched your search

+222 -21
View File
@@ -232,6 +232,7 @@ type completionAssistantCandidate struct {
ParentName *string `json:"parent_name"` ParentName *string `json:"parent_name"`
Comment *string `json:"comment"` Comment *string `json:"comment"`
DataType *string `json:"data_type"` DataType *string `json:"data_type"`
Signature *string `json:"signature"`
} }
type completionAssistantResponse struct { type completionAssistantResponse struct {
@@ -1845,6 +1846,10 @@ func (s *server) oracleCompletionValidSynonymTargets(targets []oracleCompletionS
} }
func (s *server) completionAssistantRoutines(request completionAssistantRequest, preferredSchema string, limit int) (completionAssistantResponse, error) { func (s *server) completionAssistantRoutines(request completionAssistantRequest, preferredSchema string, limit int) (completionAssistantResponse, error) {
if strings.TrimSpace(request.ParentName) != "" {
return s.completionAssistantPackageRoutines(request, preferredSchema, limit)
}
query := oracleCompletionRoutinesQuery(request, preferredSchema, limit+1) query := oracleCompletionRoutinesQuery(request, preferredSchema, limit+1)
rows, err := s.queryRows(query.SQL, query.Args) rows, err := s.queryRows(query.SQL, query.Args)
if err != nil { if err != nil {
@@ -1886,6 +1891,176 @@ func (s *server) completionAssistantRoutines(request completionAssistantRequest,
return completionAssistantResponse{Candidates: candidates, Incomplete: incomplete}, nil return completionAssistantResponse{Candidates: candidates, Incomplete: incomplete}, nil
} }
type oraclePackageRoutineRow struct {
owner string
parentName string
name string
objectID int64
subprogramID int64
position sql.NullInt64
sequence sql.NullInt64
argumentName sql.NullString
inOut sql.NullString
dataType sql.NullString
typeOwner sql.NullString
typeName sql.NullString
typeSubname sql.NullString
dataLength sql.NullInt64
dataPrecision sql.NullInt64
dataScale sql.NullInt64
}
type oraclePackageRoutine struct {
owner string
parentName string
name string
isFunction bool
returnType string
argumentList []string
}
func (s *server) completionAssistantPackageRoutines(request completionAssistantRequest, preferredSchema string, limit int) (completionAssistantResponse, error) {
query := oracleCompletionPackageRoutinesQuery(request, preferredSchema)
rows, err := s.queryRows(query.SQL, query.Args)
if err != nil {
return completionAssistantResponse{}, err
}
defer s.closeRows(rows)
packageRows := make([]oraclePackageRoutineRow, 0)
for rows.Next() {
var row oraclePackageRoutineRow
if err := rows.Scan(
&row.owner,
&row.parentName,
&row.name,
&row.objectID,
&row.subprogramID,
&row.position,
&row.sequence,
&row.argumentName,
&row.inOut,
&row.dataType,
&row.typeOwner,
&row.typeName,
&row.typeSubname,
&row.dataLength,
&row.dataPrecision,
&row.dataScale,
); err != nil {
return completionAssistantResponse{}, err
}
packageRows = append(packageRows, row)
}
if err := rows.Err(); err != nil {
return completionAssistantResponse{}, err
}
return oracleCompletionPackageCandidates(packageRows, request.Database, limit), nil
}
func oracleCompletionPackageCandidates(rows []oraclePackageRoutineRow, database string, limit int) completionAssistantResponse {
routines := make([]oraclePackageRoutine, 0)
routineIndexes := make(map[string]int)
for _, row := range rows {
key := fmt.Sprintf("%s\x00%d\x00%d", row.owner, row.objectID, row.subprogramID)
index, found := routineIndexes[key]
if !found {
index = len(routines)
routineIndexes[key] = index
routines = append(routines, oraclePackageRoutine{owner: row.owner, parentName: row.parentName, name: row.name})
}
routine := &routines[index]
if !row.position.Valid {
continue
}
dataType := oracleCompletionArgumentDataType(row)
if row.position.Int64 == 0 {
routine.isFunction = true
routine.returnType = dataType
continue
}
argument := oracleCompletionArgumentSignature(row, dataType)
if argument != "" {
routine.argumentList = append(routine.argumentList, argument)
}
}
incomplete := len(routines) > limit
if incomplete {
routines = routines[:limit]
}
candidates := make([]completionAssistantCandidate, 0, len(routines))
for _, routine := range routines {
kind := "procedure"
if routine.isFunction {
kind = "function"
}
candidate := completionAssistantCandidate{
Name: routine.name,
Kind: kind,
Database: stringPointer(database),
Schema: stringPointer(routine.owner),
ParentSchema: stringPointer(routine.owner),
ParentName: stringPointer(routine.parentName),
Signature: stringPointer(strings.Join(routine.argumentList, ", ")),
}
if routine.returnType != "" {
candidate.DataType = stringPointer(routine.returnType)
}
candidates = append(candidates, candidate)
}
return completionAssistantResponse{Candidates: candidates, Incomplete: incomplete}
}
func oracleCompletionArgumentSignature(row oraclePackageRoutineRow, dataType string) string {
parts := make([]string, 0, 3)
if row.argumentName.Valid {
parts = append(parts, strings.TrimSpace(row.argumentName.String))
}
if row.inOut.Valid {
direction := strings.Join(strings.Fields(strings.ReplaceAll(row.inOut.String, "/", " ")), " ")
if direction != "" {
parts = append(parts, direction)
}
}
if dataType != "" {
parts = append(parts, dataType)
}
return strings.Join(parts, " ")
}
func oracleCompletionArgumentDataType(row oraclePackageRoutineRow) string {
typeParts := make([]string, 0, 3)
if row.typeOwner.Valid && strings.TrimSpace(row.typeOwner.String) != "" {
typeParts = append(typeParts, strings.TrimSpace(row.typeOwner.String))
}
if row.typeName.Valid && strings.TrimSpace(row.typeName.String) != "" {
typeParts = append(typeParts, strings.TrimSpace(row.typeName.String))
}
if row.typeSubname.Valid && strings.TrimSpace(row.typeSubname.String) != "" {
typeParts = append(typeParts, strings.TrimSpace(row.typeSubname.String))
}
if len(typeParts) > 0 {
return strings.Join(typeParts, ".")
}
dataType := strings.TrimSpace(row.dataType.String)
switch strings.ToUpper(dataType) {
case "NUMBER", "NUMERIC", "DECIMAL":
if row.dataPrecision.Valid {
if row.dataScale.Valid {
return fmt.Sprintf("%s(%d, %d)", dataType, row.dataPrecision.Int64, row.dataScale.Int64)
}
return fmt.Sprintf("%s(%d)", dataType, row.dataPrecision.Int64)
}
case "CHAR", "VARCHAR", "VARCHAR2", "NCHAR", "NVARCHAR2", "RAW":
if row.dataLength.Valid && row.dataLength.Int64 > 0 {
return fmt.Sprintf("%s(%d)", dataType, row.dataLength.Int64)
}
}
return dataType
}
func stringPointer(value string) *string { func stringPointer(value string) *string {
if value == "" { if value == "" {
return nil return nil
@@ -1983,31 +2158,11 @@ func oracleCompletionSynonymTargetsQuery(targets []oracleCompletionSynonymTarget
func oracleCompletionRoutinesQuery(request completionAssistantRequest, preferredSchema string, limit int) oracleMetadataListQuery { func oracleCompletionRoutinesQuery(request completionAssistantRequest, preferredSchema string, limit int) oracleMetadataListQuery {
pattern := oracleCompletionLikePattern(request.Mask, request.MatchMode) pattern := oracleCompletionLikePattern(request.Mask, request.MatchMode)
args := make([]any, 0, 6) args := make([]any, 0, 5)
baseSQL := ` baseSQL := `
SELECT o.OWNER, o.OBJECT_NAME, o.OBJECT_TYPE, CAST(NULL AS VARCHAR2(128)) AS PARENT_NAME SELECT o.OWNER, o.OBJECT_NAME, o.OBJECT_TYPE, CAST(NULL AS VARCHAR2(128)) AS PARENT_NAME
FROM ALL_OBJECTS o FROM ALL_OBJECTS o
WHERE o.OBJECT_TYPE IN ('FUNCTION', 'PROCEDURE', 'PACKAGE')` WHERE o.OBJECT_TYPE IN ('FUNCTION', 'PROCEDURE', 'PACKAGE')`
if parentName := strings.ToUpper(strings.TrimSpace(request.ParentName)); parentName != "" {
args = append(args, parentName)
parentParam := len(args)
baseSQL = fmt.Sprintf(`
SELECT p.OWNER,
p.PROCEDURE_NAME AS OBJECT_NAME,
CASE WHEN EXISTS (
SELECT 1
FROM ALL_ARGUMENTS a
WHERE a.OWNER = p.OWNER
AND a.OBJECT_ID = p.OBJECT_ID
AND a.SUBPROGRAM_ID = p.SUBPROGRAM_ID
AND a.POSITION = 0
) THEN 'FUNCTION' ELSE 'PROCEDURE' END AS OBJECT_TYPE,
p.OBJECT_NAME AS PARENT_NAME
FROM ALL_PROCEDURES p
WHERE p.OBJECT_TYPE = 'PACKAGE'
AND p.PROCEDURE_NAME IS NOT NULL
AND p.OBJECT_NAME = :%d`, parentParam)
}
args = append(args, pattern) args = append(args, pattern)
nameParam := len(args) nameParam := len(args)
@@ -2038,6 +2193,52 @@ WHERE p.OBJECT_TYPE = 'PACKAGE'
} }
} }
func oracleCompletionPackageRoutinesQuery(request completionAssistantRequest, preferredSchema string) oracleMetadataListQuery {
owner := strings.TrimSpace(request.ParentSchema)
if owner == "" {
owner = strings.TrimSpace(request.Schema)
}
if owner == "" {
owner = preferredSchema
}
parentName := strings.TrimSpace(request.ParentName)
pattern := oracleCompletionLikePattern(request.Mask, request.MatchMode)
args := []any{owner, parentName, pattern}
return oracleMetadataListQuery{
SQL: fmt.Sprintf(`SELECT p.OWNER,
p.OBJECT_NAME AS PARENT_NAME,
p.PROCEDURE_NAME AS OBJECT_NAME,
p.OBJECT_ID,
p.SUBPROGRAM_ID,
a.POSITION,
a.SEQUENCE,
a.ARGUMENT_NAME,
a.IN_OUT,
a.DATA_TYPE,
a.TYPE_OWNER,
a.TYPE_NAME,
a.TYPE_SUBNAME,
a.DATA_LENGTH,
a.DATA_PRECISION,
a.DATA_SCALE
FROM ALL_PROCEDURES p
LEFT JOIN ALL_ARGUMENTS a
ON a.OWNER = p.OWNER
AND a.OBJECT_ID = p.OBJECT_ID
AND a.SUBPROGRAM_ID = p.SUBPROGRAM_ID
AND a.DATA_LEVEL = 0
WHERE p.OBJECT_TYPE = 'PACKAGE'
AND p.PROCEDURE_NAME IS NOT NULL
AND p.OWNER = :1
AND p.OBJECT_NAME = :2
AND %s
ORDER BY p.PROCEDURE_NAME,
p.SUBPROGRAM_ID,
NVL(a.SEQUENCE, 0)`, oracleCompletionNamePredicate("p.PROCEDURE_NAME", 3, request.CaseSensitive)),
Args: args,
}
}
func oracleCompletionNamePredicate(column string, parameter int, caseSensitive bool) string { func oracleCompletionNamePredicate(column string, parameter int, caseSensitive bool) string {
if caseSensitive { if caseSensitive {
return fmt.Sprintf("%s LIKE :%d ESCAPE '\\'", column, parameter) return fmt.Sprintf("%s LIKE :%d ESCAPE '\\'", column, parameter)
+74 -4
View File
@@ -1706,13 +1706,13 @@ func TestOracleCompletionTablesQueryScopesExplicitSchema(t *testing.T) {
} }
func TestOracleCompletionRoutinesQueryUsesPublicPackageMetadata(t *testing.T) { func TestOracleCompletionRoutinesQueryUsesPublicPackageMetadata(t *testing.T) {
query := oracleCompletionRoutinesQuery(completionAssistantRequest{ query := oracleCompletionPackageRoutinesQuery(completionAssistantRequest{
Schema: "HR", Schema: "HR",
ParentSchema: "HR", ParentSchema: "HR",
ParentName: "PAYROLL", ParentName: "PAYROLL",
ObjectKinds: []string{"routine"}, ObjectKinds: []string{"routine"},
Mask: "CALC", Mask: "CALC",
}, "HR", 200) }, "HR")
sqlText := strings.ToUpper(query.SQL) sqlText := strings.ToUpper(query.SQL)
if !strings.Contains(sqlText, "ALL_PROCEDURES") || !strings.Contains(sqlText, "ALL_ARGUMENTS") { if !strings.Contains(sqlText, "ALL_PROCEDURES") || !strings.Contains(sqlText, "ALL_ARGUMENTS") {
@@ -1721,14 +1721,84 @@ func TestOracleCompletionRoutinesQueryUsesPublicPackageMetadata(t *testing.T) {
if strings.Contains(sqlText, "ALL_SOURCE") || strings.Contains(sqlText, "PACKAGE BODY") { if strings.Contains(sqlText, "ALL_SOURCE") || strings.Contains(sqlText, "PACKAGE BODY") {
t.Fatalf("package completion must not expose private package body source: %s", query.SQL) t.Fatalf("package completion must not expose private package body source: %s", query.SQL)
} }
if !strings.Contains(sqlText, "P.OBJECT_NAME = :1") || !strings.Contains(sqlText, "UPPER(OBJECT_NAME) LIKE UPPER(:2)") || !strings.Contains(sqlText, "AND OWNER = :3") { if !strings.Contains(sqlText, "P.OWNER = :1") || !strings.Contains(sqlText, "P.OBJECT_NAME = :2") || !strings.Contains(sqlText, "UPPER(P.PROCEDURE_NAME) LIKE UPPER(:3)") {
t.Fatalf("package completion should scope package and owner: %s", query.SQL) t.Fatalf("package completion should scope package and owner: %s", query.SQL)
} }
if len(query.Args) != 6 || query.Args[0] != "PAYROLL" || query.Args[1] != "CALC%" || query.Args[2] != "HR" { if !strings.Contains(sqlText, "A.OBJECT_ID = P.OBJECT_ID") || !strings.Contains(sqlText, "A.SUBPROGRAM_ID = P.SUBPROGRAM_ID") || !strings.Contains(sqlText, "A.DATA_LEVEL = 0") {
t.Fatalf("package completion should join complete top-level argument metadata: %s", query.SQL)
}
if strings.Contains(sqlText, "ROWNUM") {
t.Fatalf("package completion must not truncate argument rows before overload grouping: %s", query.SQL)
}
wantArgs := []any{"HR", "PAYROLL", "CALC%"}
if !reflect.DeepEqual(query.Args, wantArgs) {
t.Fatalf("unexpected package completion args: %#v", query.Args) t.Fatalf("unexpected package completion args: %#v", query.Args)
} }
} }
func TestOracleCompletionPackageCandidatesPreserveOverloadsAndReturnTypes(t *testing.T) {
rows := []oraclePackageRoutineRow{
{
owner: "HR", parentName: "PAYROLL", name: "CALCULATE", objectID: 42, subprogramID: 1,
position: sql.NullInt64{Int64: 1, Valid: true}, sequence: sql.NullInt64{Int64: 1, Valid: true},
argumentName: sql.NullString{String: "P_VALUE", Valid: true}, inOut: sql.NullString{String: "IN", Valid: true}, dataType: sql.NullString{String: "NUMBER", Valid: true},
},
{
owner: "HR", parentName: "PAYROLL", name: "CALCULATE", objectID: 42, subprogramID: 2,
position: sql.NullInt64{Int64: 1, Valid: true}, sequence: sql.NullInt64{Int64: 1, Valid: true},
argumentName: sql.NullString{String: "P_VALUE", Valid: true}, inOut: sql.NullString{String: "IN", Valid: true}, dataType: sql.NullString{String: "VARCHAR2", Valid: true},
},
{
owner: "HR", parentName: "PAYROLL", name: "ITEM_COUNT", objectID: 42, subprogramID: 3,
position: sql.NullInt64{Int64: 0, Valid: true}, sequence: sql.NullInt64{Int64: 1, Valid: true}, dataType: sql.NullString{String: "NUMBER", Valid: true},
},
{
owner: "HR", parentName: "PAYROLL", name: "ITEM_COUNT", objectID: 42, subprogramID: 3,
position: sql.NullInt64{Int64: 1, Valid: true}, sequence: sql.NullInt64{Int64: 2, Valid: true},
argumentName: sql.NullString{String: "P_ACTIVE", Valid: true}, inOut: sql.NullString{String: "IN/OUT", Valid: true}, dataType: sql.NullString{String: "NUMBER", Valid: true},
},
}
response := oracleCompletionPackageCandidates(rows, "XE", 10)
if response.Incomplete {
t.Fatal("complete package metadata should not be marked incomplete")
}
if len(response.Candidates) != 3 {
t.Fatalf("overloads should remain distinct candidates: %#v", response.Candidates)
}
want := []struct {
name string
kind string
signature string
returnType string
}{
{name: "CALCULATE", kind: "procedure", signature: "P_VALUE IN NUMBER"},
{name: "CALCULATE", kind: "procedure", signature: "P_VALUE IN VARCHAR2"},
{name: "ITEM_COUNT", kind: "function", signature: "P_ACTIVE IN OUT NUMBER", returnType: "NUMBER"},
}
for index, expected := range want {
candidate := response.Candidates[index]
if candidate.Name != expected.name || candidate.Kind != expected.kind || candidate.Signature == nil || *candidate.Signature != expected.signature {
t.Fatalf("candidate %d = %#v, want name=%q kind=%q signature=%q", index, candidate, expected.name, expected.kind, expected.signature)
}
if expected.returnType == "" {
if candidate.DataType != nil {
t.Fatalf("candidate %d return type = %#v, want nil", index, candidate.DataType)
}
} else if candidate.DataType == nil || *candidate.DataType != expected.returnType {
t.Fatalf("candidate %d return type = %#v, want %q", index, candidate.DataType, expected.returnType)
}
if candidate.ParentSchema == nil || *candidate.ParentSchema != "HR" || candidate.ParentName == nil || *candidate.ParentName != "PAYROLL" {
t.Fatalf("candidate %d lost package identity: %#v", index, candidate)
}
}
limited := oracleCompletionPackageCandidates(rows, "XE", 2)
if !limited.Incomplete || len(limited.Candidates) != 2 {
t.Fatalf("member limit should apply after overload grouping: %#v", limited)
}
}
func TestOracleCompletionLikePatternSupportsPrefixAndContains(t *testing.T) { func TestOracleCompletionLikePatternSupportsPrefixAndContains(t *testing.T) {
if got := oracleCompletionLikePattern(`A_%`, "prefix"); got != `A\_\%%` { if got := oracleCompletionLikePattern(`A_%`, "prefix"); got != `A\_\%%` {
t.Fatalf("prefix pattern = %q", got) t.Fatalf("prefix pattern = %q", got)
+45 -29
View File
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8"
) )
const xuguPackageSpecSQL = ` const xuguPackageSpecSQL = `
@@ -201,8 +202,8 @@ func stringPointerOrNil(value string) *string {
if value == "" { if value == "" {
return nil return nil
} }
copy := value result := value
return &copy return &result
} }
func parseXuguPackageMembers(spec string) []xuguPackageMember { func parseXuguPackageMembers(spec string) []xuguPackageMember {
@@ -212,20 +213,21 @@ func parseXuguPackageMembers(spec string) []xuguPackageMember {
index = next index = next
continue continue
} }
switch spec[index] { current, width := utf8.DecodeRuneInString(spec[index:])
switch current {
case '(': case '(':
parenDepth++ parenDepth++
index++ index += width
continue continue
case ')': case ')':
if parenDepth > 0 { if parenDepth > 0 {
parenDepth-- parenDepth--
} }
index++ index += width
continue continue
} }
if parenDepth != 0 || !isXuguIdentifierStart(rune(spec[index])) { if parenDepth != 0 || !isXuguIdentifierStart(current) {
index++ index += width
continue continue
} }
wordStart := index wordStart := index
@@ -277,8 +279,9 @@ func xuguFunctionReturnType(fragment string) string {
index = next index = next
continue continue
} }
if !isXuguIdentifierStart(rune(fragment[index])) { current, width := utf8.DecodeRuneInString(fragment[index:])
index++ if !isXuguIdentifierStart(current) {
index += width
continue continue
} }
start := index start := index
@@ -298,7 +301,8 @@ func findXuguDeclarationEnd(value string, start int) int {
index = next index = next
continue continue
} }
switch value[index] { current, width := utf8.DecodeRuneInString(value[index:])
switch current {
case '(': case '(':
depth++ depth++
case ')': case ')':
@@ -310,7 +314,7 @@ func findXuguDeclarationEnd(value string, start int) int {
return index return index
} }
} }
index++ index += width
} }
return -1 return -1
} }
@@ -322,7 +326,8 @@ func findXuguClosingParen(value string, open int) (int, bool) {
index = next index = next
continue continue
} }
switch value[index] { current, width := utf8.DecodeRuneInString(value[index:])
switch current {
case '(': case '(':
depth++ depth++
case ')': case ')':
@@ -331,7 +336,7 @@ func findXuguClosingParen(value string, open int) (int, bool) {
return index, true return index, true
} }
} }
index++ index += width
} }
return 0, false return 0, false
} }
@@ -342,21 +347,24 @@ func readXuguSQLIdentifier(value string, index int) (string, int, bool) {
} }
if value[index] == '"' { if value[index] == '"' {
var builder strings.Builder var builder strings.Builder
for cursor := index + 1; cursor < len(value); cursor++ { for cursor := index + 1; cursor < len(value); {
if value[cursor] != '"' { current, width := utf8.DecodeRuneInString(value[cursor:])
builder.WriteByte(value[cursor]) if current != '"' {
builder.WriteRune(current)
cursor += width
continue continue
} }
if cursor+1 < len(value) && value[cursor+1] == '"' { if cursor+1 < len(value) && value[cursor+1] == '"' {
builder.WriteByte('"') builder.WriteByte('"')
cursor++ cursor += 2
continue continue
} }
return builder.String(), cursor + 1, true return builder.String(), cursor + 1, true
} }
return "", index, false return "", index, false
} }
if !isXuguIdentifierStart(rune(value[index])) { current, _ := utf8.DecodeRuneInString(value[index:])
if !isXuguIdentifierStart(current) {
return "", index, false return "", index, false
} }
next := scanXuguIdentifier(value, index) next := scanXuguIdentifier(value, index)
@@ -364,8 +372,12 @@ func readXuguSQLIdentifier(value string, index int) (string, int, bool) {
} }
func scanXuguIdentifier(value string, index int) int { func scanXuguIdentifier(value string, index int) int {
for index < len(value) && isXuguIdentifierPart(rune(value[index])) { for index < len(value) {
index++ current, width := utf8.DecodeRuneInString(value[index:])
if !isXuguIdentifierPart(current) {
break
}
index += width
} }
return index return index
} }
@@ -380,8 +392,9 @@ func isXuguIdentifierPart(value rune) bool {
func skipXuguSQLSpaceAndComments(value string, index int) int { func skipXuguSQLSpaceAndComments(value string, index int) int {
for index < len(value) { for index < len(value) {
if unicode.IsSpace(rune(value[index])) { current, width := utf8.DecodeRuneInString(value[index:])
index++ if unicode.IsSpace(current) {
index += width
continue continue
} }
if next, ok := skipXuguSQLComment(value, index); ok { if next, ok := skipXuguSQLComment(value, index); ok {
@@ -401,12 +414,14 @@ func skipXuguSQLLiteralOrComment(value string, index int) (int, bool) {
return index, false return index, false
} }
quote := value[index] quote := value[index]
for cursor := index + 1; cursor < len(value); cursor++ { for cursor := index + 1; cursor < len(value); {
if value[cursor] != quote { current, width := utf8.DecodeRuneInString(value[cursor:])
if current != rune(quote) {
cursor += width
continue continue
} }
if cursor+1 < len(value) && value[cursor+1] == quote { if cursor+1 < len(value) && value[cursor+1] == quote {
cursor++ cursor += 2
continue continue
} }
return cursor + 1, true return cursor + 1, true
@@ -442,9 +457,10 @@ func compactXuguSQLFragment(value string) string {
index = next index = next
continue continue
} }
if unicode.IsSpace(rune(value[index])) { current, width := utf8.DecodeRuneInString(value[index:])
if unicode.IsSpace(current) {
spacePending = builder.Len() > 0 spacePending = builder.Len() > 0
index++ index += width
continue continue
} }
if spacePending && builder.Len() > 0 { if spacePending && builder.Len() > 0 {
@@ -456,8 +472,8 @@ func compactXuguSQLFragment(value string) string {
index = next index = next
continue continue
} }
builder.WriteByte(value[index]) builder.WriteRune(current)
index++ index += width
} }
return strings.TrimSpace(builder.String()) return strings.TrimSpace(builder.String())
} }
@@ -57,6 +57,21 @@ END;`
} }
} }
func TestParseXuguPackageMembersHandlesUnicodeIdentifiers(t *testing.T) {
spec := `CREATE PACKAGE 中文包 AS
PROCEDURE 处理订单(订单号 IN VARCHAR, 数量 IN NUMERIC);
FUNCTION 统计数量(分类 IN VARCHAR) RETURN NUMERIC;
END;`
got := parseXuguPackageMembers(spec)
want := []xuguPackageMember{
{Name: "处理订单", Kind: "PROCEDURE", Signature: "订单号 IN VARCHAR, 数量 IN NUMERIC"},
{Name: "统计数量", Kind: "FUNCTION", Signature: "分类 IN VARCHAR", ReturnType: "NUMERIC"},
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected Unicode package members:\n got: %#v\nwant: %#v", got, want)
}
}
func TestParseXuguPackageMembersIgnoresBodyOnlyDeclarations(t *testing.T) { func TestParseXuguPackageMembersIgnoresBodyOnlyDeclarations(t *testing.T) {
spec := `CREATE PACKAGE pkg AS spec := `CREATE PACKAGE pkg AS
PROCEDURE public_proc(p_value INT); PROCEDURE public_proc(p_value INT);
@@ -91,6 +91,7 @@ import {
supportsTableTruncate, supportsTableTruncate,
supportsTableStructureEditing, supportsTableStructureEditing,
supportsTransfer, supportsTransfer,
supportsPackageMemberExpansion,
usesTreeSchemaMode, usesTreeSchemaMode,
isSingleDatabase, isSingleDatabase,
} from "@/lib/database/databaseCapabilities"; } from "@/lib/database/databaseCapabilities";
@@ -622,8 +623,8 @@ async function toggle() {
return; return;
} }
if (node.type === "package" && node.connectionId && connectionStore.getConfig(node.connectionId)?.db_type === "xugu") { if (node.type === "package" && node.connectionId && supportsPackageMemberExpansion(currentDatabaseType())) {
await connectionStore.loadXuguPackageMembers(node); await connectionStore.loadPackageMembers(node);
emitNodeToggled(node, wasExpanded); emitNodeToggled(node, wasExpanded);
return; return;
} }
@@ -13,6 +13,9 @@ const connectionStore = {
canUseLoadedTreeNodeToggle: vi.fn(() => true), canUseLoadedTreeNodeToggle: vi.fn(() => true),
releaseCollapsedTreeNodeChildren: vi.fn(), releaseCollapsedTreeNodeChildren: vi.fn(),
getConfig: vi.fn(() => ({ db_type: "mysql" })), getConfig: vi.fn(() => ({ db_type: "mysql" })),
loadPackageMembers: vi.fn(async (node: TreeNode) => {
node.isExpanded = true;
}),
}; };
vi.mock("@/stores/connectionStore", () => ({ vi.mock("@/stores/connectionStore", () => ({
@@ -41,6 +44,7 @@ afterEach(() => {
connectionStore.treeNodes = []; connectionStore.treeNodes = [];
connectionStore.sidebarSearchQuery = ""; connectionStore.sidebarSearchQuery = "";
vi.clearAllMocks(); vi.clearAllMocks();
connectionStore.getConfig.mockReturnValue({ db_type: "mysql" });
}); });
describe("SidebarTreeRuntimeHost expansion", () => { describe("SidebarTreeRuntimeHost expansion", () => {
@@ -78,4 +82,38 @@ describe("SidebarTreeRuntimeHost expansion", () => {
expect(toggled).toHaveBeenCalledWith(renderedGroup, false); expect(toggled).toHaveBeenCalledWith(renderedGroup, false);
expect(liveGroup.isExpanded).toBe(false); expect(liveGroup.isExpanded).toBe(false);
}); });
it("loads Oracle package members through the shared expansion path", async () => {
const packageNode: TreeNode = {
id: "oracle:XE:APP:package:BUSINESS_API",
label: "BUSINESS_API",
type: "package",
connectionId: "oracle",
database: "XE",
schema: "APP",
isExpanded: false,
children: [],
};
connectionStore.treeNodes = [packageNode];
connectionStore.getConfig.mockReturnValue({ db_type: "oracle" });
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
const toggled = vi.fn();
const app = createApp(
defineComponent({
setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: packageNode, depth: 0, onNodeToggled: toggled }),
}),
);
mountedApps.push(app);
const container = document.createElement("div");
document.body.append(container);
app.use(i18n);
app.mount(container);
host.value?.toggleNode(packageNode);
await vi.waitFor(() => expect(connectionStore.loadPackageMembers).toHaveBeenCalledWith(packageNode));
expect(packageNode.isExpanded).toBe(true);
expect(toggled).toHaveBeenCalledWith(packageNode, true);
});
}); });
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { normalizeSidebarObjectKind, sidebarObjectKindsForDatabase } from "@/lib/database/databaseObjectCapabilities"; import { normalizeSidebarObjectKind, sidebarObjectKindsForDatabase, supportsPackageMemberExpansion } from "@/lib/database/databaseObjectCapabilities";
describe("databaseObjectCapabilities", () => { describe("databaseObjectCapabilities", () => {
it("exposes supported programmable objects for Dameng", () => { it("exposes supported programmable objects for Dameng", () => {
@@ -11,6 +11,13 @@ describe("databaseObjectCapabilities", () => {
expect(sidebarObjectKindsForDatabase("postgres")).not.toContain("SYNONYM"); expect(sidebarObjectKindsForDatabase("postgres")).not.toContain("SYNONYM");
}); });
it("expands package members only for implemented database paths", () => {
expect(supportsPackageMemberExpansion("oracle")).toBe(true);
expect(supportsPackageMemberExpansion("xugu")).toBe(true);
expect(supportsPackageMemberExpansion("dameng")).toBe(false);
expect(supportsPackageMemberExpansion("opengauss")).toBe(false);
});
it("exposes only tables for HBase namespaces", () => { it("exposes only tables for HBase namespaces", () => {
expect(sidebarObjectKindsForDatabase("hbase")).toEqual(["TABLE"]); expect(sidebarObjectKindsForDatabase("hbase")).toEqual(["TABLE"]);
}); });
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { CompletionAssistantCandidate, TreeNode } from "@/types/database"; import type { CompletionAssistantCandidate, TreeNode } from "@/types/database";
import { buildXuguPackageMemberNodes, markXuguPackageNodesExpandable } from "@/lib/sidebar/xuguPackageMembers"; import { buildPackageMemberNodes, markPackageNodesExpandable } from "@/lib/sidebar/packageMembers";
function packageNode(): TreeNode { function packageNode(): TreeNode {
return { return {
@@ -15,10 +15,10 @@ function packageNode(): TreeNode {
}; };
} }
describe("Xugu package member tree", () => { describe("package member tree", () => {
it("marks package specifications as expandable without changing other objects", () => { it("marks package specifications as expandable without changing other objects", () => {
const nodes: TreeNode[] = [packageNode(), { id: "body", label: "business_api", type: "package-body" }, { id: "proc", label: "standalone", type: "procedure" }]; const nodes: TreeNode[] = [packageNode(), { id: "body", label: "business_api", type: "package-body" }, { id: "proc", label: "standalone", type: "procedure" }];
const result = markXuguPackageNodesExpandable(nodes); const result = markPackageNodesExpandable(nodes);
expect(result[0]?.children).toEqual([]); expect(result[0]?.children).toEqual([]);
expect(result[1]?.children).toBeUndefined(); expect(result[1]?.children).toBeUndefined();
@@ -32,7 +32,7 @@ describe("Xugu package member tree", () => {
{ name: "lookup", kind: "function", signature: null, data_type: "VARCHAR" }, { name: "lookup", kind: "function", signature: null, data_type: "VARCHAR" },
{ name: "ignored_table", kind: "table" }, { name: "ignored_table", kind: "table" },
]; ];
const result = buildXuguPackageMemberNodes(packageNode(), candidates); const result = buildPackageMemberNodes(packageNode(), candidates);
expect(result.map((node) => [node.type, node.label])).toEqual([ expect(result.map((node) => [node.type, node.label])).toEqual([
["procedure", "calculate(p_value IN INT)"], ["procedure", "calculate(p_value IN INT)"],
@@ -50,7 +50,7 @@ describe("Xugu package member tree", () => {
{ name: "MIXEDCASE", kind: "function", signature: "p INT" }, { name: "MIXEDCASE", kind: "function", signature: "p INT" },
{ name: "", kind: "procedure" }, { name: "", kind: "procedure" },
]; ];
const result = buildXuguPackageMemberNodes(packageNode(), candidates); const result = buildPackageMemberNodes(packageNode(), candidates);
expect(result.map((node) => node.objectName)).toEqual(["MixedCase", "MIXEDCASE"]); expect(result.map((node) => node.objectName)).toEqual(["MixedCase", "MIXEDCASE"]);
}); });
@@ -18,6 +18,7 @@ const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZ
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"]; const 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 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 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"]);
const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
// postgres // postgres
@@ -82,6 +83,10 @@ export function sidebarObjectKindsForDatabase(dbType?: DatabaseType): SidebarObj
return DATABASE_TYPE_OBJECTS.get(dbType) ?? [...ROUTINE_OBJECTS]; return DATABASE_TYPE_OBJECTS.get(dbType) ?? [...ROUTINE_OBJECTS];
} }
export function supportsPackageMemberExpansion(dbType?: DatabaseType): boolean {
return !!dbType && PACKAGE_MEMBER_EXPANSION_DATABASES.has(dbType);
}
export function normalizeSidebarObjectKind(type: string): SidebarObjectKind { export function normalizeSidebarObjectKind(type: string): SidebarObjectKind {
const value = type.toUpperCase(); const value = type.toUpperCase();
const normalized = value.replace(/[\s-]+/g, "_"); const normalized = value.replace(/[\s-]+/g, "_");
@@ -1,10 +1,10 @@
import type { CompletionAssistantCandidate, TreeNode } from "@/types/database"; import type { CompletionAssistantCandidate, TreeNode } from "@/types/database";
export function markXuguPackageNodesExpandable(nodes: TreeNode[]): TreeNode[] { export function markPackageNodesExpandable(nodes: TreeNode[]): TreeNode[] {
return nodes.map((node) => (node.type === "package" ? { ...node, children: node.children ?? [] } : node)); return nodes.map((node) => (node.type === "package" ? { ...node, children: node.children ?? [] } : node));
} }
export function buildXuguPackageMemberNodes(packageNode: TreeNode, candidates: readonly CompletionAssistantCandidate[]): TreeNode[] { export function buildPackageMemberNodes(packageNode: TreeNode, candidates: readonly CompletionAssistantCandidate[]): TreeNode[] {
const parentName = packageNode.objectName || packageNode.label; const parentName = packageNode.objectName || packageNode.label;
const parentSchema = packageNode.schema; const parentSchema = packageNode.schema;
const seen = new Set<string>(); const seen = new Set<string>();
@@ -178,7 +178,7 @@ describe("connectionStore metadata loading", () => {
store.connectedIds = new Set([connection.id]); store.connectedIds = new Set([connection.id]);
store.treeNodes = [packageNode]; store.treeNodes = [packageNode];
await store.loadXuguPackageMembers(packageNode); await store.loadPackageMembers(packageNode);
expect(completionAssistantSearch).toHaveBeenCalledWith({ expect(completionAssistantSearch).toHaveBeenCalledWith({
connection_id: connection.id, connection_id: connection.id,
@@ -200,6 +200,84 @@ describe("connectionStore metadata loading", () => {
expect(packageNode.children?.every((child) => child.parentName === "business_api")).toBe(true); expect(packageNode.children?.every((child) => child.parentName === "business_api")).toBe(true);
}, 15000); }, 15000);
it("loads Oracle package overloads through the shared package member path", async () => {
const completionAssistantSearch = vi.fn().mockResolvedValue({
candidates: [
{ name: "CALCULATE", kind: "procedure", signature: "P_VALUE IN NUMBER" },
{ name: "CALCULATE", kind: "procedure", signature: "P_VALUE IN VARCHAR2" },
{ name: "ITEM_COUNT", kind: "function", data_type: "NUMBER" },
],
incomplete: false,
fallback_used: false,
});
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({ checkConnectionHealth: vi.fn().mockResolvedValue(undefined), completionAssistantSearch }));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
const connection = oracleConnection();
const packageNode: TreeNode = {
id: "oracle-1:XE:APP:package:BUSINESS_API",
label: "BUSINESS_API",
type: "package",
connectionId: connection.id,
database: "XE",
schema: "APP",
children: [],
isExpanded: false,
};
store.connections = [connection];
store.connectedIds = new Set([connection.id]);
store.treeNodes = [packageNode];
await store.loadPackageMembers(packageNode);
expect(completionAssistantSearch).toHaveBeenCalledWith({
connection_id: connection.id,
database: "XE",
schema: "APP",
object_kinds: ["routine"],
mask: "",
case_sensitive: true,
global_search: false,
max_results: 1000,
search_in_comments: false,
search_in_definitions: false,
parent_schema: "APP",
parent_name: "BUSINESS_API",
match_mode: "prefix",
});
expect(packageNode.isExpanded).toBe(true);
expect(packageNode.children?.map((child) => child.label)).toEqual(["CALCULATE(P_VALUE IN NUMBER)", "CALCULATE(P_VALUE IN VARCHAR2)", "ITEM_COUNT"]);
}, 15000);
it("does not query package members for unsupported databases", async () => {
const completionAssistantSearch = vi.fn();
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({ checkConnectionHealth: vi.fn().mockResolvedValue(undefined), completionAssistantSearch }));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
const connection = { ...xuguConnection(), id: "dameng-1", db_type: "dameng" } as ConnectionConfig;
const packageNode: TreeNode = {
id: "dameng-1:APP:package:BUSINESS_API",
label: "BUSINESS_API",
type: "package",
connectionId: connection.id,
database: "APP",
schema: "APP",
children: [],
isExpanded: false,
};
store.connections = [connection];
store.connectedIds = new Set([connection.id]);
await store.loadPackageMembers(packageNode);
expect(completionAssistantSearch).not.toHaveBeenCalled();
expect(packageNode.isExpanded).toBe(false);
}, 15000);
it("loads missing database roots only for connected sidebar search targets", async () => { it("loads missing database roots only for connected sidebar search targets", async () => {
const checkConnectionHealth = vi.fn().mockResolvedValue(undefined); const checkConnectionHealth = vi.fn().mockResolvedValue(undefined);
const listDatabases = vi.fn().mockResolvedValue([{ name: "dajia", comment: null }]); const listDatabases = vi.fn().mockResolvedValue([{ name: "dajia", comment: null }]);
+12 -10
View File
@@ -62,7 +62,7 @@ import { mergeSqlObjectNavigationType, sqlObjectNavigationTypeFromTableType } fr
import * as api from "@/lib/backend/api"; import * as api from "@/lib/backend/api";
import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import { useTunnelProfileStore } from "@/stores/tunnelProfileStore";
import { connectionIsDorisFamilyCatalogCapable, isInternalDorisCatalog, isSchemaAware, normalizeSidebarObjectKind, sidebarObjectKindsForDatabase, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities"; import { connectionIsDorisFamilyCatalogCapable, isInternalDorisCatalog, isSchemaAware, normalizeSidebarObjectKind, sidebarObjectKindsForDatabase, supportsPackageMemberExpansion, usesTreeSchemaMode } from "@/lib/database/databaseCapabilities";
import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionShouldDiscoverJdbcSchemas, connectionShouldLoadIdentifierQuote, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbIdentifierQuoteOverride } from "@/lib/database/jdbcDialect"; import { connectionObjectTreeNodeSchema, connectionObjectTreeQuerySchema, connectionShouldDiscoverJdbcSchemas, connectionShouldLoadIdentifierQuote, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbIdentifierQuoteOverride } from "@/lib/database/jdbcDialect";
import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, compareSidebarNames, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree"; import { buildDatabaseTreeNodes, buildDuckDbConnectionTreeNodes, compareSidebarNames, sortSidebarDatabases, sortSidebarNames, shouldIncludeDefaultDatabaseNode } from "@/lib/database/databaseTree";
import { buildSqlServerDatabaseTreeNodes } from "@/lib/database/sqlServerTree"; import { buildSqlServerDatabaseTreeNodes } from "@/lib/database/sqlServerTree";
@@ -111,7 +111,7 @@ import { appendAgentDriverUpdateHint, hasAgentDriverUpdate, hasInstalledAgentVer
import { appendConnectionErrorHints } from "@/lib/connection/connectionErrorHints"; import { appendConnectionErrorHints } from "@/lib/connection/connectionErrorHints";
import { appendVisibleDatabaseSelection } from "@/lib/connection/connectionVisibleDatabases"; import { appendVisibleDatabaseSelection } from "@/lib/connection/connectionVisibleDatabases";
import { filterNacosNamespacesForSidebar, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility"; import { filterNacosNamespacesForSidebar, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility";
import { buildXuguPackageMemberNodes, markXuguPackageNodesExpandable } from "@/lib/sidebar/xuguPackageMembers"; import { buildPackageMemberNodes, markPackageNodesExpandable } from "@/lib/sidebar/packageMembers";
import { configuredDatabaseProductName, connectionConfigFingerprint, normalizeDatabaseConnectionInfo } from "@/lib/connection/connectionDatabaseInfo"; import { configuredDatabaseProductName, connectionConfigFingerprint, normalizeDatabaseConnectionInfo } from "@/lib/connection/connectionDatabaseInfo";
import { createMetadataLoadTrace, logMetadataLoadTrace, MetadataLoadCoordinator, type MetadataLoadTraceLogger } from "@/lib/metadata/metadataLoadCoordinator"; import { createMetadataLoadTrace, logMetadataLoadTrace, MetadataLoadCoordinator, type MetadataLoadTraceLogger } from "@/lib/metadata/metadataLoadCoordinator";
import type { MetadataScopeInput } from "@/lib/metadata/metadataLoadScope"; import type { MetadataScopeInput } from "@/lib/metadata/metadataLoadScope";
@@ -1606,7 +1606,8 @@ export const useConnectionStore = defineStore("connection", () => {
}); });
const refreshedGroup = grouped.find((group) => group.type === options.node.type); const refreshedGroup = grouped.find((group) => group.type === options.node.type);
const children = refreshedGroup?.children ?? []; const children = refreshedGroup?.children ?? [];
return options.node.connectionId && getConfig(options.node.connectionId)?.db_type === "xugu" ? markXuguPackageNodesExpandable(children) : children; const databaseType = options.node.connectionId ? effectiveDatabaseTypeForConnection(getConfig(options.node.connectionId)) : undefined;
return supportsPackageMemberExpansion(databaseType) ? markPackageNodesExpandable(children) : children;
} }
function tableInfosToCompletionTables(tables: readonly TableInfo[], schema?: string): SqlCompletionTable[] { function tableInfosToCompletionTables(tables: readonly TableInfo[], schema?: string): SqlCompletionTable[] {
@@ -1870,8 +1871,8 @@ export const useConnectionStore = defineStore("connection", () => {
schema: options.effectiveSchema, schema: options.effectiveSchema,
objects: supplementalObjects, objects: supplementalObjects,
}); });
if (getConfig(options.connectionId)?.db_type === "xugu") { if (supportsPackageMemberExpansion(effectiveDatabaseTypeForConnection(getConfig(options.connectionId)))) {
supplementalChildren = markXuguPackageNodesExpandable(supplementalChildren); supplementalChildren = markPackageNodesExpandable(supplementalChildren);
} }
if (supplementalChildren.length === 0) return; if (supplementalChildren.length === 0) return;
if (isTreeLoadSearchChanged(searchFilter, options.loadOptions)) return; if (isTreeLoadSearchChanged(searchFilter, options.loadOptions)) return;
@@ -5618,8 +5619,9 @@ export const useConnectionStore = defineStore("connection", () => {
}); });
} }
async function loadXuguPackageMembers(node: TreeNode, options?: LoadTreeOptions): Promise<void> { async function loadPackageMembers(node: TreeNode, options?: LoadTreeOptions): Promise<void> {
if (node.type !== "package" || !node.connectionId || !node.database || getConfig(node.connectionId)?.db_type !== "xugu") return; if (node.type !== "package" || !node.connectionId || !node.database) return;
if (!supportsPackageMemberExpansion(effectiveDatabaseTypeForConnection(getConfig(node.connectionId)))) return;
const connectionId = node.connectionId; const connectionId = node.connectionId;
const database = node.database; const database = node.database;
const schema = node.schema; const schema = node.schema;
@@ -5629,7 +5631,7 @@ export const useConnectionStore = defineStore("connection", () => {
try { try {
await runTreeMetadataLoad( await runTreeMetadataLoad(
{ {
kind: "xugu-package-members", kind: "package-members",
connectionId, connectionId,
database, database,
schema, schema,
@@ -5656,7 +5658,7 @@ export const useConnectionStore = defineStore("connection", () => {
}); });
const targetNode = treeNodeLoadTarget(load); const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return; if (!targetNode) return;
setChildren(targetNode, buildXuguPackageMemberNodes(targetNode, response.candidates)); setChildren(targetNode, buildPackageMemberNodes(targetNode, response.candidates));
targetNode.isExpanded = true; targetNode.isExpanded = true;
}, },
options, options,
@@ -7280,7 +7282,7 @@ export const useConnectionStore = defineStore("connection", () => {
loadTables, loadTables,
loadTableForLocate, loadTableForLocate,
loadObjectGroupChildren, loadObjectGroupChildren,
loadXuguPackageMembers, loadPackageMembers,
loadMoreObjectGroupChildren, loadMoreObjectGroupChildren,
loadAllObjectGroupChildren, loadAllObjectGroupChildren,
loadTableGroups, loadTableGroups,