From 88d05a0a19fae86641f370481dd867610225fbf0 Mon Sep 17 00:00:00 2001 From: zipg Date: Tue, 14 Jul 2026 12:35:13 +0800 Subject: [PATCH] feat(oracle): improve multi-schema completion --- agents/drivers/oracle-go/main.go | 438 ++++++++++++++++++ agents/drivers/oracle-go/main_test.go | 96 ++++ .../src/components/editor/QueryEditor.vue | 115 +++-- apps/desktop/src/lib/sql/sqlCompletion.ts | 64 ++- .../connectionStore.completion.spec.ts | 72 +++ apps/desktop/src/stores/connectionStore.ts | 123 ++++- ...eenshot-oracle-multi-schema-completion.png | Bin 0 -> 63490 bytes docs/screenshot-oracle-routine-completion.png | Bin 0 -> 46957 bytes packages/app-tests/sqlCompletion.test.ts | 96 ++++ 9 files changed, 934 insertions(+), 70 deletions(-) create mode 100644 docs/screenshot-oracle-multi-schema-completion.png create mode 100644 docs/screenshot-oracle-routine-completion.png diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index 476facc981..cdc2bc4bc4 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -168,6 +168,37 @@ type connectParams struct { ConnectionString string `json:"connection_string"` } +type completionAssistantRequest struct { + ConnectionID string `json:"connection_id"` + Database string `json:"database"` + Schema string `json:"schema"` + ObjectKinds []string `json:"object_kinds"` + Mask string `json:"mask"` + CaseSensitive bool `json:"case_sensitive"` + GlobalSearch bool `json:"global_search"` + MaxResults int `json:"max_results"` + ParentSchema string `json:"parent_schema"` + ParentName string `json:"parent_name"` + MatchMode string `json:"match_mode"` +} + +type completionAssistantCandidate struct { + Name string `json:"name"` + Kind string `json:"kind"` + Database *string `json:"database"` + Schema *string `json:"schema"` + ParentSchema *string `json:"parent_schema"` + ParentName *string `json:"parent_name"` + Comment *string `json:"comment"` + DataType *string `json:"data_type"` +} + +type completionAssistantResponse struct { + Candidates []completionAssistantCandidate `json:"candidates"` + Incomplete bool `json:"incomplete"` + FallbackUsed bool `json:"fallback_used"` +} + type queryOptions struct { SQL string `json:"sql"` Database string `json:"database"` @@ -637,6 +668,13 @@ func (s *server) dispatch(method string, params map[string]json.RawMessage) (any schema := stringParam(params, "schema") result, err := s.listObjects(schema, metadataListConstraintsFromParams(params)) return result, false, err + case "completion_assistant_search_v1": + var request completionAssistantRequest + if err := decodeParams(params, &request); err != nil { + return nil, false, err + } + result, err := s.completionAssistantSearch(request) + return result, false, err case "get_columns": schema := stringParam(params, "schema") table := stringParam(params, "table") @@ -1329,6 +1367,406 @@ func (s *server) listObjects(schema string, constraints metadataListConstraints) return emptyIfNil(result), rows.Err() } +func (s *server) completionAssistantSearch(request completionAssistantRequest) (completionAssistantResponse, error) { + limit := request.MaxResults + if limit <= 0 { + limit = 100 + } + if limit > 1000 { + limit = 1000 + } + preferredSchema := strings.ToUpper(strings.TrimSpace(request.Schema)) + if preferredSchema == "" { + var err error + preferredSchema, err = s.currentSchema() + if err != nil { + return completionAssistantResponse{}, err + } + } + + if completionRequestHasTableLikeKind(request.ObjectKinds) { + return s.completionAssistantTables(request, preferredSchema, limit) + } + if completionRequestHasRoutineKind(request.ObjectKinds) { + return s.completionAssistantRoutines(request, preferredSchema, limit) + } + return completionAssistantResponse{Candidates: []completionAssistantCandidate{}}, nil +} + +func completionRequestHasTableLikeKind(kinds []string) bool { + if len(kinds) == 0 { + return true + } + for _, kind := range kinds { + switch strings.ToLower(strings.TrimSpace(kind)) { + case "table", "view": + return true + } + } + return false +} + +func completionRequestHasRoutineKind(kinds []string) bool { + for _, kind := range kinds { + switch strings.ToLower(strings.TrimSpace(kind)) { + case "routine", "procedure", "function": + return true + } + } + return false +} + +func (s *server) completionAssistantTables(request completionAssistantRequest, preferredSchema string, limit int) (completionAssistantResponse, error) { + scanLimit := limit * 3 + if scanLimit < limit+1 { + scanLimit = limit + 1 + } + if scanLimit > 1000 { + scanLimit = 1000 + } + query := oracleCompletionTablesQuery(request, preferredSchema, scanLimit+1) + rows, err := s.queryRows(query.SQL, query.Args) + if err != nil { + return completionAssistantResponse{}, err + } + + type tableRow struct { + owner, name, objectType string + targetOwner, targetName sql.NullString + } + rawRows := make([]tableRow, 0, scanLimit+1) + targets := make([]oracleCompletionSynonymTarget, 0) + for rows.Next() { + var row tableRow + if err := rows.Scan(&row.owner, &row.name, &row.objectType, &row.targetOwner, &row.targetName); err != nil { + s.closeRows(rows) + return completionAssistantResponse{}, err + } + rawRows = append(rawRows, row) + if strings.EqualFold(row.objectType, "SYNONYM") && row.targetOwner.Valid && row.targetName.Valid { + targets = append(targets, oracleCompletionSynonymTarget{Owner: row.targetOwner.String, Name: row.targetName.String}) + } + } + rowsErr := rows.Err() + closeErr := s.closeRows(rows) + if rowsErr != nil { + return completionAssistantResponse{}, rowsErr + } + if closeErr != nil { + return completionAssistantResponse{}, closeErr + } + + validTargets, err := s.oracleCompletionValidSynonymTargets(targets, oracleCompletionTableObjectTypes(request.ObjectKinds)) + if err != nil { + return completionAssistantResponse{}, err + } + candidates := make([]completionAssistantCandidate, 0, limit+1) + for _, row := range rawRows { + if strings.EqualFold(row.objectType, "SYNONYM") { + if !row.targetOwner.Valid || !row.targetName.Valid { + continue + } + if _, ok := validTargets[oracleCompletionSynonymTargetKey(row.targetOwner.String, row.targetName.String)]; !ok { + continue + } + } + kind := "table" + if strings.EqualFold(row.objectType, "VIEW") { + kind = "view" + } + candidates = append(candidates, completionAssistantCandidate{ + Name: row.name, + Kind: kind, + Database: stringPointer(request.Database), + Schema: stringPointer(row.owner), + DataType: stringPointer(row.objectType), + }) + } + incomplete := len(candidates) > limit || len(rawRows) > scanLimit + if incomplete { + if len(candidates) > limit { + candidates = candidates[:limit] + } + } + return completionAssistantResponse{Candidates: candidates, Incomplete: incomplete}, nil +} + +type oracleCompletionSynonymTarget struct { + Owner string + Name string +} + +func oracleCompletionSynonymTargetKey(owner, name string) string { + return owner + "\x00" + name +} + +func (s *server) oracleCompletionValidSynonymTargets(targets []oracleCompletionSynonymTarget, objectTypes []string) (map[string]struct{}, error) { + unique := make([]oracleCompletionSynonymTarget, 0, len(targets)) + seen := make(map[string]struct{}, len(targets)) + for _, target := range targets { + key := oracleCompletionSynonymTargetKey(target.Owner, target.Name) + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + unique = append(unique, target) + } + + valid := make(map[string]struct{}, len(unique)) + const batchSize = 100 + for start := 0; start < len(unique); start += batchSize { + end := start + batchSize + if end > len(unique) { + end = len(unique) + } + query := oracleCompletionSynonymTargetsQuery(unique[start:end], objectTypes) + rows, err := s.queryRows(query.SQL, query.Args) + if err != nil { + return nil, err + } + for rows.Next() { + var owner, name string + if err := rows.Scan(&owner, &name); err != nil { + s.closeRows(rows) + return nil, err + } + valid[oracleCompletionSynonymTargetKey(owner, name)] = struct{}{} + } + rowsErr := rows.Err() + closeErr := s.closeRows(rows) + if rowsErr != nil { + return nil, rowsErr + } + if closeErr != nil { + return nil, closeErr + } + } + return valid, nil +} + +func (s *server) completionAssistantRoutines(request completionAssistantRequest, preferredSchema string, limit int) (completionAssistantResponse, error) { + query := oracleCompletionRoutinesQuery(request, preferredSchema, limit+1) + rows, err := s.queryRows(query.SQL, query.Args) + if err != nil { + return completionAssistantResponse{}, err + } + defer s.closeRows(rows) + + candidates := make([]completionAssistantCandidate, 0, limit+1) + for rows.Next() { + var owner, name, objectType string + var parentName sql.NullString + if err := rows.Scan(&owner, &name, &objectType, &parentName); err != nil { + return completionAssistantResponse{}, err + } + kind := strings.ToLower(objectType) + if objectType == "PACKAGE" { + kind = "object" + } + candidate := completionAssistantCandidate{ + Name: name, + Kind: kind, + Database: stringPointer(request.Database), + Schema: stringPointer(owner), + DataType: stringPointer(objectType), + } + if parentName.Valid { + candidate.ParentSchema = stringPointer(owner) + candidate.ParentName = stringPointer(parentName.String) + } + candidates = append(candidates, candidate) + } + if err := rows.Err(); err != nil { + return completionAssistantResponse{}, err + } + incomplete := len(candidates) > limit + if incomplete { + candidates = candidates[:limit] + } + return completionAssistantResponse{Candidates: candidates, Incomplete: incomplete}, nil +} + +func stringPointer(value string) *string { + if value == "" { + return nil + } + result := value + return &result +} + +func oracleCompletionTableObjectTypes(kinds []string) []string { + objectTypes := make([]string, 0, 2) + for _, kind := range kinds { + switch strings.ToLower(strings.TrimSpace(kind)) { + case "table": + objectTypes = append(objectTypes, "'TABLE'") + case "view": + objectTypes = append(objectTypes, "'VIEW'") + } + } + if len(objectTypes) == 0 { + objectTypes = []string{"'TABLE'", "'VIEW'"} + } + return objectTypes +} + +func oracleCompletionTablesQuery(request completionAssistantRequest, preferredSchema string, limit int) oracleMetadataListQuery { + objectTypes := oracleCompletionTableObjectTypes(request.ObjectKinds) + pattern := oracleCompletionLikePattern(request.Mask, request.MatchMode) + args := make([]any, 0, 7) + args = append(args, pattern) + objectNamePredicate := oracleCompletionNamePredicate("o.OBJECT_NAME", len(args), request.CaseSensitive) + objectOwnerPredicate := "" + synonymOwnerPredicate := "" + owner := "" + if !request.GlobalSearch { + owner = strings.ToUpper(strings.TrimSpace(request.ParentSchema)) + if owner == "" { + owner = strings.ToUpper(strings.TrimSpace(request.Schema)) + } + if owner == "" { + owner = preferredSchema + } + args = append(args, owner) + objectOwnerPredicate = fmt.Sprintf(" AND o.OWNER = :%d", len(args)) + } + args = append(args, pattern) + synonymNamePredicate := oracleCompletionNamePredicate("s.SYNONYM_NAME", len(args), request.CaseSensitive) + if owner != "" { + args = append(args, owner) + synonymOwnerPredicate = fmt.Sprintf(" AND s.OWNER = :%d", len(args)) + } + args = append(args, preferredSchema) + preferredParam := len(args) + args = append(args, strings.TrimSpace(request.Mask)) + exactParam := len(args) + args = append(args, limit) + limitParam := len(args) + + baseSQL := fmt.Sprintf(`SELECT o.OWNER, + o.OBJECT_NAME, + o.OBJECT_TYPE, + CAST(NULL AS VARCHAR2(128)) AS TARGET_OWNER, + CAST(NULL AS VARCHAR2(128)) AS TARGET_NAME + FROM ALL_OBJECTS o + WHERE o.OBJECT_TYPE IN (%s) + AND %s%s + UNION ALL + SELECT s.OWNER, + s.SYNONYM_NAME AS OBJECT_NAME, + 'SYNONYM' AS OBJECT_TYPE, + s.TABLE_OWNER AS TARGET_OWNER, + s.TABLE_NAME AS TARGET_NAME + FROM ALL_SYNONYMS s + WHERE s.DB_LINK IS NULL + AND %s%s`, strings.Join(objectTypes, ","), objectNamePredicate, objectOwnerPredicate, synonymNamePredicate, synonymOwnerPredicate) + unionSQL := fmt.Sprintf("SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, TARGET_OWNER, TARGET_NAME\nFROM (\n%s\n)", baseSQL) + orderedSQL := oracleCompletionOrderedSQL(unionSQL, "OBJECT_NAME", "OBJECT_TYPE", preferredParam, exactParam) + return oracleMetadataListQuery{ + SQL: fmt.Sprintf("SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, TARGET_OWNER, TARGET_NAME FROM (\n%s\n) WHERE ROWNUM <= :%d", orderedSQL, limitParam), + Args: args, + } +} + +func oracleCompletionSynonymTargetsQuery(targets []oracleCompletionSynonymTarget, objectTypes []string) oracleMetadataListQuery { + args := make([]any, 0, len(targets)*2) + predicates := make([]string, 0, len(targets)) + for _, target := range targets { + args = append(args, target.Owner, target.Name) + predicates = append(predicates, fmt.Sprintf("(o.OWNER = :%d AND o.OBJECT_NAME = :%d)", len(args)-1, len(args))) + } + return oracleMetadataListQuery{ + SQL: fmt.Sprintf("SELECT DISTINCT o.OWNER, o.OBJECT_NAME\nFROM ALL_OBJECTS o\nWHERE o.OBJECT_TYPE IN (%s)\n AND (%s)", strings.Join(objectTypes, ","), strings.Join(predicates, " OR ")), + Args: args, + } +} + +func oracleCompletionRoutinesQuery(request completionAssistantRequest, preferredSchema string, limit int) oracleMetadataListQuery { + pattern := oracleCompletionLikePattern(request.Mask, request.MatchMode) + args := make([]any, 0, 6) + baseSQL := ` +SELECT o.OWNER, o.OBJECT_NAME, o.OBJECT_TYPE, CAST(NULL AS VARCHAR2(128)) AS PARENT_NAME +FROM ALL_OBJECTS o +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) + nameParam := len(args) + + ownerPredicate := "" + if !request.GlobalSearch { + owner := strings.ToUpper(strings.TrimSpace(request.ParentSchema)) + if owner == "" { + owner = strings.ToUpper(strings.TrimSpace(request.Schema)) + } + if owner == "" { + owner = preferredSchema + } + args = append(args, owner) + ownerPredicate = fmt.Sprintf(" AND OWNER = :%d", len(args)) + } + args = append(args, preferredSchema) + preferredParam := len(args) + args = append(args, strings.TrimSpace(request.Mask)) + exactParam := len(args) + args = append(args, limit) + limitParam := len(args) + + filteredSQL := fmt.Sprintf("SELECT DISTINCT OWNER, OBJECT_NAME, OBJECT_TYPE, PARENT_NAME\nFROM (\n%s\n)\nWHERE %s%s", baseSQL, oracleCompletionNamePredicate("OBJECT_NAME", nameParam, request.CaseSensitive), ownerPredicate) + orderedSQL := oracleCompletionOrderedSQL(filteredSQL, "OBJECT_NAME", "OBJECT_TYPE", preferredParam, exactParam) + return oracleMetadataListQuery{ + SQL: fmt.Sprintf("SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, PARENT_NAME FROM (\n%s\n) WHERE ROWNUM <= :%d", orderedSQL, limitParam), + Args: args, + } +} + +func oracleCompletionNamePredicate(column string, parameter int, caseSensitive bool) string { + if caseSensitive { + return fmt.Sprintf("%s LIKE :%d ESCAPE '\\'", column, parameter) + } + return fmt.Sprintf("UPPER(%s) LIKE UPPER(:%d) ESCAPE '\\'", column, parameter) +} + +func oracleCompletionLikePattern(mask, matchMode string) string { + escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(strings.TrimSpace(mask)) + if strings.EqualFold(strings.TrimSpace(matchMode), "contains") { + return "%" + escaped + "%" + } + return escaped + "%" +} + +func oracleCompletionOrderedSQL(baseSQL, nameColumn, typeColumn string, preferredParam, exactParam int) string { + return fmt.Sprintf(`%s +ORDER BY CASE + WHEN OWNER = :%d THEN 0 + WHEN OWNER = 'PUBLIC' THEN 1 + WHEN OWNER IN ('SYS','SYSTEM','SYSMAN','DBSNMP','OUTLN','XDB','MDSYS','CTXSYS','WMSYS') THEN 3 + ELSE 2 + END, + CASE WHEN UPPER(%s) = UPPER(:%d) THEN 0 ELSE 1 END, + CASE %s WHEN 'TABLE' THEN 0 WHEN 'VIEW' THEN 1 WHEN 'FUNCTION' THEN 0 WHEN 'PROCEDURE' THEN 1 WHEN 'PACKAGE' THEN 2 WHEN 'SYNONYM' THEN 3 ELSE 4 END, + %s, + OWNER`, baseSQL, preferredParam, nameColumn, exactParam, typeColumn, nameColumn) +} + func (s *server) getColumns(schema, table string) ([]columnInfo, error) { schema, err := s.normalizeSchema(schema) if err != nil { diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index 70f7dc9cf7..edfa68ad1a 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -828,6 +828,102 @@ func TestOracleFuzzyLikePatternEscapesSpecialCharacters(t *testing.T) { } } +func TestOracleCompletionTablesQuerySearchesAcrossSchemasWithPriority(t *testing.T) { + query := oracleCompletionTablesQuery(completionAssistantRequest{ + Database: "ORCL", + Schema: "APP", + ObjectKinds: []string{"table", "view"}, + Mask: "dept_d", + GlobalSearch: true, + MatchMode: "prefix", + }, "APP", 201) + sqlText := strings.ToUpper(query.SQL) + + if !strings.Contains(sqlText, "ALL_OBJECTS") || !strings.Contains(sqlText, "ALL_SYNONYMS") { + t.Fatalf("global completion should include objects and synonyms: %s", query.SQL) + } + if !strings.Contains(sqlText, "S.TABLE_OWNER AS TARGET_OWNER") || !strings.Contains(sqlText, "S.TABLE_NAME AS TARGET_NAME") || !strings.Contains(sqlText, "S.DB_LINK IS NULL") { + t.Fatalf("table completion should return local synonym targets for bounded validation: %s", query.SQL) + } + if strings.Contains(sqlText, "JOIN ALL_OBJECTS TARGET") { + t.Fatalf("Oracle 11g completion must not join full dictionary views before applying the result limit: %s", query.SQL) + } + if !strings.Contains(sqlText, "SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, TARGET_OWNER, TARGET_NAME\nFROM (\nSELECT O.OWNER") { + t.Fatalf("Oracle 11g requires the union to be wrapped before expression-based ordering: %s", query.SQL) + } + if strings.Contains(sqlText, "WHERE UPPER(OBJECT_NAME) LIKE UPPER(:1) ESCAPE '\\' AND OWNER =") { + t.Fatalf("global completion must not restrict results to one owner: %s", query.SQL) + } + if !strings.Contains(sqlText, "WHEN OWNER = :3 THEN 0") || !strings.Contains(sqlText, "WHERE ROWNUM <= :5") { + t.Fatalf("completion should prioritize the current schema and use Oracle 11g rownum limiting: %s", query.SQL) + } + if len(query.Args) != 5 || query.Args[0] != `dept\_d%` || query.Args[1] != `dept\_d%` || query.Args[2] != "APP" || query.Args[3] != "dept_d" || query.Args[4] != 201 { + t.Fatalf("unexpected completion args: %#v", query.Args) + } +} + +func TestOracleCompletionSynonymTargetsQueryIsBoundedToCandidates(t *testing.T) { + query := oracleCompletionSynonymTargetsQuery([]oracleCompletionSynonymTarget{{Owner: "DBX_TEST", Name: "DEPT_DICT"}, {Owner: "HR", Name: "EMP_VIEW"}}, []string{"'TABLE'", "'VIEW'"}) + sqlText := strings.ToUpper(query.SQL) + + if !strings.Contains(sqlText, "O.OBJECT_TYPE IN ('TABLE','VIEW')") || !strings.Contains(sqlText, "(O.OWNER = :1 AND O.OBJECT_NAME = :2)") || !strings.Contains(sqlText, "(O.OWNER = :3 AND O.OBJECT_NAME = :4)") { + t.Fatalf("synonym target validation should query only returned targets: %s", query.SQL) + } + wantArgs := []any{"DBX_TEST", "DEPT_DICT", "HR", "EMP_VIEW"} + if !reflect.DeepEqual(query.Args, wantArgs) { + t.Fatalf("unexpected synonym target args: %#v", query.Args) + } +} + +func TestOracleCompletionTablesQueryScopesExplicitSchema(t *testing.T) { + query := oracleCompletionTablesQuery(completionAssistantRequest{ + Schema: "APP", + ParentSchema: "HR", + ObjectKinds: []string{"table"}, + Mask: "EMP", + }, "APP", 50) + + if !strings.Contains(strings.ToUpper(query.SQL), "AND O.OWNER = :2") || !strings.Contains(strings.ToUpper(query.SQL), "AND S.OWNER = :4") { + t.Fatalf("explicit schema completion should restrict owner: %s", query.SQL) + } + if len(query.Args) != 7 || query.Args[1] != "HR" || query.Args[3] != "HR" || query.Args[4] != "APP" { + t.Fatalf("unexpected scoped completion args: %#v", query.Args) + } +} + +func TestOracleCompletionRoutinesQueryUsesPublicPackageMetadata(t *testing.T) { + query := oracleCompletionRoutinesQuery(completionAssistantRequest{ + Schema: "HR", + ParentSchema: "HR", + ParentName: "PAYROLL", + ObjectKinds: []string{"routine"}, + Mask: "CALC", + }, "HR", 200) + sqlText := strings.ToUpper(query.SQL) + + if !strings.Contains(sqlText, "ALL_PROCEDURES") || !strings.Contains(sqlText, "ALL_ARGUMENTS") { + t.Fatalf("package completion should use callable procedure metadata: %s", query.SQL) + } + 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) + } + if !strings.Contains(sqlText, "P.OBJECT_NAME = :1") || !strings.Contains(sqlText, "UPPER(OBJECT_NAME) LIKE UPPER(:2)") || !strings.Contains(sqlText, "AND OWNER = :3") { + 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" { + t.Fatalf("unexpected package completion args: %#v", query.Args) + } +} + +func TestOracleCompletionLikePatternSupportsPrefixAndContains(t *testing.T) { + if got := oracleCompletionLikePattern(`A_%`, "prefix"); got != `A\_\%%` { + t.Fatalf("prefix pattern = %q", got) + } + if got := oracleCompletionLikePattern("DEPT", "contains"); got != "%DEPT%" { + t.Fatalf("contains pattern = %q", got) + } +} + func TestIsOraclePGALimitError(t *testing.T) { if !isOraclePGALimitError(errors.New("ORA-04036: PGA memory used by the instance exceeds PGA_AGGREGATE_LIMIT")) { t.Fatal("expected ORA-04036 to be detected") diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 6f90d2f26f..65c0a972b5 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -1233,7 +1233,7 @@ async function findExactSemanticDiagnosticTable(table: SqlTableReference): Promi const localExact = localMatches.find((item) => completionTablesMatch(item, table)); if (localExact) return localExact; - const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema); + const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema, false, props.schema); cachedTables = mergeCompletionTables(cachedTables, remoteMatches); return remoteMatches.find((item) => completionTablesMatch(item, table)) ?? null; } @@ -1362,12 +1362,12 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number) if (cachedTables.length === 0) { cachedTables = usesLocalOnlyCompletionMetadata() ? connectionStore.lookupLocalCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema) - : await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema); + : await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema, false, props.schema); } let table = matchTable(qualifiedTableLookup, cachedTables) ?? matchTable(tableLookupName, cachedTables) ?? matchTable(identifier, cachedTables) ?? matchTable(name, cachedTables); if (!table && !usesLocalOnlyCompletionMetadata()) { - const hoverTables = await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, semanticTarget?.schema ?? props.schema); + const hoverTables = await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, semanticTarget?.schema ?? props.schema, false, props.schema); cachedTables = [...cachedTables, ...hoverTables]; table = matchTable(qualifiedTableLookup, hoverTables) ?? matchTable(tableLookupName, hoverTables) ?? matchTable(identifier, hoverTables) ?? matchTable(name, hoverTables); } @@ -2019,6 +2019,7 @@ async function provideSqlCompletions(context: CompletionContext) { snippets: settingsStore.editorSettings.snippets, dialect: props.dialect, databaseType: props.databaseType, + currentSchema: props.schema, keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase, autoAliasTables: settingsStore.editorSettings.autoAliasTables, }); @@ -2038,6 +2039,7 @@ async function provideSqlCompletions(context: CompletionContext) { snippets: settingsStore.editorSettings.snippets, dialect: props.dialect, databaseType: props.databaseType, + currentSchema: props.schema, keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase, autoAliasTables: settingsStore.editorSettings.autoAliasTables, }); @@ -2150,12 +2152,11 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType { cachedTables = mergeCompletionTables(cachedTables, tables); if (completionContext.suggestTables && completionContext.referencedTables.length > 0) { @@ -2257,9 +2260,8 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType {}); } - if (!localOnlyMetadata && (completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions))) { - void connectionStore - .refreshCompletionObjects(connectionId, database, completionContext.prefix, MAX_COMPLETION_TABLES, props.schema) + if (!localOnlyMetadata && shouldLoadCompletionObjects(completionContext)) { + void listCompletionObjectsForContext(completionContext) .then((objects) => { cachedCompletionObjects = mergeCompletionObjects(cachedCompletionObjects, objects); }) @@ -2315,14 +2317,18 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType, incoming: Array<{ name: string; schema?: string; type?: "table" | "view" }>) { +function mergeCompletionTables(existing: SqlCompletionTable[], incoming: SqlCompletionTable[]): SqlCompletionTable[] { const merged = [...existing]; - const seen = new Set(existing.map((table) => `${table.schema ?? ""}.${table.name}`.toLowerCase())); + const indexes = new Map(existing.map((table, index) => [`${table.schema ?? ""}.${table.name}`.toLowerCase(), index])); for (const table of incoming) { const key = `${table.schema ?? ""}.${table.name}`.toLowerCase(); - if (seen.has(key)) continue; - seen.add(key); - merged.push(table); + const index = indexes.get(key); + if (index == null) { + indexes.set(key, merged.length); + merged.push(table); + } else { + merged[index] = { ...merged[index], ...table }; + } } return merged; } @@ -2331,9 +2337,9 @@ function withCompletionLatencyBudget(remote: Promise, local: T): Promise((resolve) => setTimeout(() => resolve(local), COMPLETION_REMOTE_LATENCY_BUDGET_MS))]); } -function listCompletionTablesWithLatencyBudget(connectionId: string, database: string, filter: string, limit: number, schema?: string): Promise { - const local = connectionStore.lookupLocalCompletionTables(connectionId, database, filter, limit, schema); - const remote = connectionStore.listCompletionTables(connectionId, database, filter, limit, schema).then((tables) => { +function listCompletionTablesWithLatencyBudget(connectionId: string, database: string, filter: string, limit: number, schema?: string, globalSearch = false): Promise { + const local = connectionStore.lookupLocalCompletionTables(connectionId, database, filter, limit, globalSearch ? undefined : schema); + const remote = connectionStore.listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, props.schema).then((tables) => { cachedTables = mergeCompletionTables(cachedTables, tables); return tables; }); @@ -2341,6 +2347,43 @@ function listCompletionTablesWithLatencyBudget(connectionId: string, database: s return withCompletionLatencyBudget(remote, local); } +interface RoutineCompletionTarget { + schema?: string; + parentName?: string; + globalSearch?: boolean; +} + +function shouldLoadCompletionObjects(completionContext: ReturnType): boolean { + const routineContext = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions); + return routineContext && !isReferencedTableQualifier(completionContext); +} + +function oracleRoutineCompletionTargets(completionContext: ReturnType): RoutineCompletionTarget[] { + const parts = (completionContext.qualifierParts?.length ? completionContext.qualifierParts : completionContext.qualifier?.split("."))?.filter(Boolean) ?? []; + if (parts.length === 0) return [{ schema: props.schema, globalSearch: true }]; + if (parts.length === 1) { + return [{ schema: props.schema, parentName: parts[0] }, { schema: parts[0] }]; + } + return [{ schema: parts[parts.length - 2], parentName: parts[parts.length - 1] }]; +} + +function lookupLocalCompletionObjectsForContext(completionContext: ReturnType): SqlCompletionObject[] { + if (!props.connectionId || props.database == null) return []; + if (props.databaseType === "oracle") { + return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.prefix, MAX_COMPLETION_TABLES); + } + return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier && !completionContext.exclusiveColumnSuggestions ? completionContext.qualifier : props.schema); +} + +async function listCompletionObjectsForContext(completionContext: ReturnType): Promise { + if (!props.connectionId || props.database == null) return []; + if (props.databaseType !== "oracle") { + return connectionStore.listCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema); + } + const groups = await Promise.all(oracleRoutineCompletionTargets(completionContext).map((target) => connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, target.schema, target.parentName, target.globalSearch, props.schema))); + return groups.reduce((objects, group) => mergeCompletionObjects(objects, group), [] as SqlCompletionObject[]); +} + async function performAsyncCompletionWithResult(epoch: number, completionContext: ReturnType, fullDoc: string, position: number) { const localOnlyMetadata = usesLocalOnlyCompletionMetadata(); const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns(); @@ -2377,22 +2420,19 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext completionContext, knownDatabases: databaseNames, }); + const globalOracleTableSearch = props.databaseType === "oracle" && completionContext.suggestTables && !completionContext.qualifier; let tables = shouldLoadTables ? localOnlyMetadata - ? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema) - : await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema) + ? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, globalOracleTableSearch ? undefined : tableLookupTarget.schema) + : await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema, globalOracleTableSearch) : cachedTables; if (epoch !== completionEpoch) return null; - const shouldLoadObjects = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions); - let completionObjects = shouldLoadObjects - ? localOnlyMetadata - ? connectionStore.lookupLocalCompletionObjects(props.connectionId!, props.database!, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema) - : await connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema) - : cachedCompletionObjects; + const shouldLoadObjects = shouldLoadCompletionObjects(completionContext); + let completionObjects = shouldLoadObjects ? (localOnlyMetadata ? lookupLocalCompletionObjectsForContext(completionContext) : await listCompletionObjectsForContext(completionContext)) : cachedCompletionObjects; if (epoch !== completionEpoch) return null; - if (!localOnlyMetadata && completionContext.qualifier && completionObjects.length === 0) { + if (props.databaseType !== "oracle" && !localOnlyMetadata && completionContext.qualifier && completionObjects.length === 0) { const schemaObjects = await connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier); if (schemaObjects.length > 0) { completionObjects = schemaObjects; @@ -2445,7 +2485,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext }); const unresolvedRefs = refs.filter((rt) => !rt.schema && !rt.columns); if (!localOnlyMetadata && unresolvedRefs.length > 0) { - const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema))); + const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema, false, props.schema))); if (epoch !== completionEpoch) return null; const lookupTables = lookupGroups.flat(); refs = refs.map((rt) => { @@ -2564,6 +2604,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext snippets: settingsStore.editorSettings.snippets, dialect: props.dialect, databaseType: props.databaseType, + currentSchema: props.schema, keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase, autoAliasTables: settingsStore.editorSettings.autoAliasTables, }); @@ -2584,12 +2625,16 @@ function isTableNameCompletionContext(completionContext: ReturnType `${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase())); + const indexes = new Map(existing.map((object, index) => [`${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase(), index])); for (const object of incoming) { const key = `${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase(); - if (seen.has(key)) continue; - seen.add(key); - merged.push(object); + const index = indexes.get(key); + if (index == null) { + indexes.set(key, merged.length); + merged.push(object); + } else { + merged[index] = { ...merged[index], ...object }; + } } return merged; } @@ -3098,7 +3143,7 @@ onMounted(async () => { // Some metadata providers only accept table-name masks, not schema.table masks. cachedTables = usesLocalOnlyCompletionMetadata() ? connectionStore.lookupLocalCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema) - : await connectionStore.listCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema); + : await connectionStore.listCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema, false, props.schema); } // 1. Check if it's a table name diff --git a/apps/desktop/src/lib/sql/sqlCompletion.ts b/apps/desktop/src/lib/sql/sqlCompletion.ts index 1583806b5e..7ea3815ecf 100644 --- a/apps/desktop/src/lib/sql/sqlCompletion.ts +++ b/apps/desktop/src/lib/sql/sqlCompletion.ts @@ -1157,6 +1157,9 @@ export interface SqlCompletionTable { name: string; schema?: string; type?: "table" | "view"; + detail?: string; + applyName?: string; + boost?: number; } export interface SqlCompletionObject { @@ -1165,6 +1168,8 @@ export interface SqlCompletionObject { type: "procedure" | "function" | "trigger" | "package"; parentSchema?: string; parentName?: string; + applyName?: string; + boost?: number; } export interface SqlCompletionColumn { @@ -1192,6 +1197,7 @@ export interface SqlCompletionItem { apply?: string; boost: number; exactMatch?: boolean; + dedupeKey?: string; } export function shouldChainSqlCompletionAfterAccept(item: { type?: string; apply?: string }): boolean { @@ -1272,6 +1278,7 @@ export interface SqlCompletionProviderInput { snippets?: SqlSnippet[]; dialect?: "mysql" | "postgres" | "sqlserver"; databaseType?: DatabaseType; + currentSchema?: string; keywordCase?: SqlKeywordCase; autoAliasTables?: boolean; } @@ -1288,6 +1295,7 @@ export function buildSqlCompletionItems( translations?: SqlCompletionTranslations; dialect?: "mysql" | "postgres" | "sqlserver"; databaseType?: DatabaseType; + currentSchema?: string; keywordCase?: SqlKeywordCase; autoAliasTables?: boolean; }, @@ -1375,7 +1383,7 @@ class SqlCompletionProvider { if (!context.exclusiveColumnSuggestions && context.suggestTables) { this.items.push(...buildForeignKeyRelatedTableItems(context, this.input.tables, this.input.foreignKeysByTable, this.dialect)); - this.items.push(...buildTableItems(context.prefix, this.input.tables, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, context.referencedTables, this.databaseType)); + this.items.push(...buildTableItems(context, this.input.tables, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, context.referencedTables, this.databaseType, this.input.currentSchema)); if (isOracleLikeDatabase(this.databaseType)) { this.items.push(...buildOracleTableFunctionItems(context.prefix)); } @@ -1385,7 +1393,7 @@ class SqlCompletionProvider { } if (context.suggestRoutines || context.exclusiveRoutineSuggestions || context.oracleTableFunctionContext) { - this.items.push(...buildObjectItems(context, this.input.objects ?? [], this.dialect)); + this.items.push(...buildObjectItems(context, this.input.objects ?? [], this.dialect, this.databaseType, this.input.currentSchema)); } if (context.comparisonLeftColumn && context.suggestKeywords) { @@ -2755,19 +2763,33 @@ function requiresPostgresIdentifierQuote(identifier: string): boolean { const POSTGRES_IDENTIFIER_KEYWORDS = new Set(SQL_KEYWORDS.map((keyword) => keyword.toLowerCase()).concat(["current_user", "session_user", "user"])); -function buildTableItems(prefix: string, tables: SqlCompletionTable[], dialect?: "mysql" | "postgres" | "sqlserver", autoAliasTables = false, referencedTables: SqlCompletionReferencedTable[] = [], databaseType?: DatabaseType): SqlCompletionItem[] { +function buildTableItems( + context: Pick, + tables: SqlCompletionTable[], + dialect?: "mysql" | "postgres" | "sqlserver", + autoAliasTables = false, + referencedTables: SqlCompletionReferencedTable[] = [], + databaseType?: DatabaseType, + currentSchema?: string, +): SqlCompletionItem[] { + const { prefix } = context; + const qualifierSchema = context.qualifier?.split(".").filter(Boolean).pop(); const existingAliases = new Set(referencedTables.map((ref) => ref.alias?.toLowerCase()).filter((alias): alias is string => !!alias)); return tables .filter((table) => matchesPrefix(table.name, prefix)) .map((table) => { - const applyName = quoteSqlIdentifier(table.name, dialect); + const qualifiedByContext = !!qualifierSchema && !!table.schema && normalizeIdentifierPart(qualifierSchema) === normalizeIdentifierPart(table.schema); + const oracleSchemaQualification = databaseType === "oracle" && table.schema && table.schema.toUpperCase() !== "PUBLIC" && (!currentSchema || normalizeIdentifierPart(table.schema) !== normalizeIdentifierPart(currentSchema)); + const defaultApplyName = oracleSchemaQualification ? `${quoteSqlIdentifier(table.schema!, dialect)}.${quoteSqlIdentifier(table.name, dialect)}` : quoteSqlIdentifier(table.name, dialect); + const applyName = qualifiedByContext ? quoteSqlIdentifier(table.name, dialect) : (table.applyName ?? defaultApplyName); const alias = autoAliasTables ? generateTableCompletionAlias(table.name, existingAliases) : ""; return { label: table.name, type: "table" as const, - detail: table.schema ? `${table.schema}.${table.name}` : table.type, + detail: table.detail ?? (table.schema ? `${table.schema}.${table.name}` : table.type), apply: formatTableAliasApply(applyName, alias, databaseType), - boost: computeBoost(table.name, prefix) + 1000, + boost: computeBoost(table.name, prefix) + 1000 + (table.boost ?? 0), + dedupeKey: table.applyName || (databaseType === "oracle" && table.schema) ? applyName : undefined, }; }) .sort(compareCompletionItems) @@ -2837,25 +2859,27 @@ function buildSchemaItems(prefix: string, schemas: string[], dialect?: "mysql" | })); } -function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionObject[], dialect?: "mysql" | "postgres" | "sqlserver"): SqlCompletionItem[] { - const onlyProcedures = context.exclusiveRoutineSuggestions; +function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionObject[], dialect?: "mysql" | "postgres" | "sqlserver", databaseType?: DatabaseType, currentSchema?: string): SqlCompletionItem[] { + if (completionQualifierIsReferencedTable(context)) return []; + const onlyProcedures = context.contextKind === "exec"; + const prioritizeOracleFunctions = databaseType === "oracle" && context.statementKind === "select"; return objects .filter((object) => (!onlyProcedures || object.type === "procedure") && objectMatchesCompletionContext(object, context)) .map((object) => { const qualifiedByContext = objectIsQualifiedByContext(object, context); + const objectInCurrentSchema = !!currentSchema && !!object.schema && normalizeIdentifierPart(object.schema) === normalizeIdentifierPart(currentSchema); const applyName = qualifiedByContext || (context.qualifier && object.schema?.toLowerCase() === context.qualifier.toLowerCase()) ? quoteSqlIdentifier(object.name, dialect) - : object.schema - ? `${quoteSqlIdentifier(object.schema, dialect)}.${quoteSqlIdentifier(object.name, dialect)}` - : quoteSqlIdentifier(object.name, dialect); + : (object.applyName ?? (object.schema && !objectInCurrentSchema ? `${quoteSqlIdentifier(object.schema, dialect)}.${quoteSqlIdentifier(object.name, dialect)}` : quoteSqlIdentifier(object.name, dialect))); const detail = object.type === "trigger" && object.parentName ? `trigger on ${object.parentName}` : object.parentName ? `${object.type} in ${object.parentName}` : object.schema ? `${object.type} in ${object.schema}` : object.type; return { label: object.name, type: "function" as const, detail, apply: object.type === "trigger" || object.type === "package" ? applyName : `${applyName}()`, - boost: computeBoost(object.name, context.prefix) + (object.type === "procedure" ? 1800 : object.type === "package" ? 1600 : 900), + boost: computeBoost(object.name, context.prefix) + routineTypeBoost(object.type, prioritizeOracleFunctions) + (object.boost ?? 0), + dedupeKey: object.applyName || (databaseType === "oracle" && object.schema) ? applyName : undefined, // Preserve exact routine matches before the capped candidate list is truncated. exactMatch: !!context.prefix && object.name.toLowerCase() === context.prefix.toLowerCase(), }; @@ -2864,6 +2888,20 @@ function buildObjectItems(context: SqlCompletionContext, objects: SqlCompletionO .slice(0, MAX_TABLE_COMPLETION_ITEMS); } +function routineTypeBoost(type: SqlCompletionObject["type"], prioritizeFunctions: boolean): number { + if (type === "package") return 1600; + if (type === "function") return prioritizeFunctions ? 1800 : 900; + return prioritizeFunctions ? 900 : 1800; +} + +function completionQualifierIsReferencedTable(context: SqlCompletionContext): boolean { + if (!context.qualifier) return false; + const qualifier = context.qualifier; + const qualifierLower = qualifier.toLowerCase(); + const qualifiedTarget = qualifiedTableTargetFromContext(context); + return context.referencedTables.some((table) => referencedTableMatchesColumnQualifier(table, qualifier, qualifierLower, qualifiedTarget)); +} + function objectIsQualifiedByContext(object: SqlCompletionObject, context: SqlCompletionContext): boolean { if (!context.qualifier || !object.parentName) return false; const qualifier = context.qualifier.toLowerCase(); @@ -4059,7 +4097,7 @@ function getHistoryBoost(label: string, type: string): number { function dedupeAndSort(items: SqlCompletionItem[]): SqlCompletionItem[] { const seen = new Set(); return items.sort(compareCompletionItems).filter((item) => { - const key = `${item.type}:${item.label}`; + const key = `${item.type}:${item.dedupeKey ?? item.label}`; if (seen.has(key)) return false; seen.add(key); return true; diff --git a/apps/desktop/src/stores/__tests__/connectionStore.completion.spec.ts b/apps/desktop/src/stores/__tests__/connectionStore.completion.spec.ts index 14855a39d7..2870dc311b 100644 --- a/apps/desktop/src/stores/__tests__/connectionStore.completion.spec.ts +++ b/apps/desktop/src/stores/__tests__/connectionStore.completion.spec.ts @@ -25,6 +25,18 @@ function postgresConnection(): ConnectionConfig { } as ConnectionConfig; } +function oracleConnection(): ConnectionConfig { + return { + ...postgresConnection(), + id: "oracle-1", + name: "Oracle 11g", + db_type: "oracle", + port: 1521, + username: "APP", + database: "ORCL", + } as ConnectionConfig; +} + function deferred() { let resolve!: (value: T) => void; const promise = new Promise((res) => { @@ -147,6 +159,66 @@ describe("connectionStore completion assistant", () => { expect(tables).toEqual([{ name: "TEST_USERS", schema: "SYSDBA", type: "table" }]); }); + it("maps global Oracle tables with safe qualification and schema priority", async () => { + const completionAssistantSearch = vi.fn().mockResolvedValue({ + candidates: [ + { name: "DEPT_DICT", kind: "table", schema: "APP", data_type: "TABLE" }, + { name: "DEPT_DICT", kind: "view", schema: "COMM", data_type: "VIEW" }, + { name: "V_DEPT_DICT", kind: "view", schema: "SYS", data_type: "VIEW" }, + { name: "DEPT_DICT_ALIAS", kind: "table", schema: "PUBLIC", data_type: "SYNONYM" }, + ], + incomplete: false, + fallback_used: false, + }); + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false })); + vi.doMock("@/lib/backend/api", () => ({ + checkConnectionHealth: vi.fn().mockResolvedValue(undefined), + completionAssistantSearch, + listTables: vi.fn().mockResolvedValue([]), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + store.connections = [oracleConnection()]; + store.connectedIds.add("oracle-1"); + + const tables = await store.listCompletionTables("oracle-1", "ORCL", "DEPT_D", 20, "APP", true); + + expect(completionAssistantSearch).toHaveBeenCalledWith(expect.objectContaining({ schema: "APP", parent_schema: null, global_search: true, mask: "DEPT_D" })); + expect(tables).toEqual([ + expect.objectContaining({ name: "DEPT_DICT", schema: "APP", applyName: "DEPT_DICT", boost: 2400 }), + expect.objectContaining({ name: "DEPT_DICT", schema: "COMM", applyName: "COMM.DEPT_DICT", boost: 0 }), + expect.objectContaining({ name: "V_DEPT_DICT", schema: "SYS", applyName: "SYS.V_DEPT_DICT", boost: -1200 }), + expect.objectContaining({ name: "DEPT_DICT_ALIAS", schema: "PUBLIC", applyName: "DEPT_DICT_ALIAS", detail: "PUBLIC · synonym", boost: 1200 }), + ]); + }); + + it("maps Oracle package members without scanning every schema", async () => { + const completionAssistantSearch = vi.fn().mockResolvedValue({ + candidates: [{ name: "CALCULATE_BONUS", kind: "function", schema: "HR", parent_schema: "HR", parent_name: "PAYROLL", data_type: "FUNCTION" }], + incomplete: false, + fallback_used: false, + }); + + vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false })); + vi.doMock("@/lib/backend/api", () => ({ + checkConnectionHealth: vi.fn().mockResolvedValue(undefined), + completionAssistantSearch, + listObjects: vi.fn().mockResolvedValue([]), + })); + + const { useConnectionStore } = await import("@/stores/connectionStore"); + const store = useConnectionStore(); + store.connections = [oracleConnection()]; + store.connectedIds.add("oracle-1"); + + const objects = await store.listCompletionObjects("oracle-1", "ORCL", "CALC", 20, "HR", "PAYROLL", false, "APP"); + + expect(completionAssistantSearch).toHaveBeenCalledWith(expect.objectContaining({ object_kinds: ["routine"], mask: "CALC", schema: "APP", parent_schema: "HR", parent_name: "PAYROLL", global_search: false })); + expect(objects).toEqual([expect.objectContaining({ name: "CALCULATE_BONUS", schema: "HR", type: "function", parentSchema: "HR", parentName: "PAYROLL", applyName: "HR.CALCULATE_BONUS", boost: 0 })]); + }); + it("limits concurrent completion column metadata requests per connection database", async () => { const gates = [deferred(), deferred(), deferred(), deferred()]; let activeColumns = 0; diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index c646244595..a04111dc62 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -4002,14 +4002,60 @@ export const useConnectionStore = defineStore("connection", () => { }); } - function completionAssistantTables(candidates: CompletionAssistantCandidate[]): SqlCompletionTable[] { + const ORACLE_SYSTEM_COMPLETION_SCHEMAS = new Set(["SYS", "SYSTEM", "SYSMAN", "DBSNMP", "OUTLN", "XDB", "MDSYS", "CTXSYS", "WMSYS"]); + + function completionPreferredSchema(connectionId: string, preferredSchema?: string): string | undefined { + return preferredSchema?.trim() || getConfig(connectionId)?.username?.trim() || undefined; + } + + function completionCandidateSchemaBoost(schema: string | null | undefined, preferredSchema?: string): number { + if (schema && preferredSchema && schema.toLowerCase() === preferredSchema.toLowerCase()) return 2400; + if (schema?.toUpperCase() === "PUBLIC") return 1200; + if (schema && ORACLE_SYSTEM_COMPLETION_SCHEMAS.has(schema.toUpperCase())) return -1200; + return 0; + } + + function completionCandidateApplyName(name: string, schema: string | null | undefined, preferredSchema?: string): string { + if (!schema || schema.toUpperCase() === "PUBLIC" || (preferredSchema && schema.toLowerCase() === preferredSchema.toLowerCase())) return name; + return `${schema}.${name}`; + } + + function completionAssistantTables(candidates: CompletionAssistantCandidate[], preferredSchema?: string, withOracleMetadata = false): SqlCompletionTable[] { return candidates .filter((candidate) => candidate.kind === "table" || candidate.kind === "view") - .map((candidate) => ({ - name: candidate.name, - schema: candidate.schema ?? undefined, - type: candidate.kind === "view" ? ("view" as const) : ("table" as const), - })); + .map((candidate) => { + const table: SqlCompletionTable = { + name: candidate.name, + schema: candidate.schema ?? undefined, + type: candidate.kind === "view" ? "view" : "table", + }; + if (!withOracleMetadata) return table; + return { + ...table, + detail: candidate.schema ? `${candidate.schema} · ${(candidate.data_type || candidate.kind).toLowerCase()}` : candidate.kind, + applyName: completionCandidateApplyName(candidate.name, candidate.schema, preferredSchema), + boost: completionCandidateSchemaBoost(candidate.schema, preferredSchema), + }; + }); + } + + function completionAssistantObjects(candidates: CompletionAssistantCandidate[], preferredSchema?: string): SqlCompletionObject[] { + return candidates + .map((candidate): SqlCompletionObject | null => { + const candidateType = candidate.data_type?.toUpperCase(); + const type = candidate.kind === "procedure" ? "procedure" : candidate.kind === "function" ? "function" : candidate.kind === "object" && candidateType === "PACKAGE" ? "package" : null; + if (!type) return null; + return { + name: candidate.name, + schema: candidate.schema ?? undefined, + type, + parentSchema: candidate.parent_schema ?? undefined, + parentName: candidate.parent_name ?? undefined, + applyName: completionCandidateApplyName(candidate.name, candidate.schema, preferredSchema), + boost: completionCandidateSchemaBoost(candidate.schema, preferredSchema), + }; + }) + .filter((object): object is SqlCompletionObject => object != null); } function completionAssistantColumns(candidates: CompletionAssistantCandidate[], table: string, schema?: string): SqlCompletionColumn[] { @@ -4024,23 +4070,45 @@ export const useConnectionStore = defineStore("connection", () => { })); } - async function listCompletionAssistantTables(connectionId: string, database: string, filter: string, limit?: number, schema?: string): Promise { + async function listCompletionAssistantTables(connectionId: string, database: string, filter: string, limit?: number, schema?: string, globalSearch = false, currentSchema?: string): Promise { + const oracleAssistant = getConfig(connectionId)?.db_type === "oracle"; + const preferredSchema = oracleAssistant ? completionPreferredSchema(connectionId, currentSchema) : schema?.trim() || undefined; const objectKinds: CompletionAssistantObjectKind[] = ["table", "view"]; const response = await completionAssistantSearch({ connection_id: connectionId, database, - schema: schema ?? null, + schema: preferredSchema ?? null, object_kinds: objectKinds, mask: filter.trim(), max_results: limit ?? 200, - parent_schema: schema ?? null, + global_search: globalSearch, + parent_schema: globalSearch ? null : (schema ?? null), match_mode: "prefix", }); - const tables = completionAssistantTables(response.candidates); + const tables = completionAssistantTables(response.candidates, preferredSchema, oracleAssistant); indexCompletionTables(connectionId, database, schema, tables); return tables; } + async function listCompletionAssistantObjects(connectionId: string, database: string, filter: string, limit: number | undefined, schema: string | undefined, parentName: string | undefined, globalSearch: boolean, currentSchema?: string): Promise { + const preferredSchema = completionPreferredSchema(connectionId, currentSchema); + const response = await completionAssistantSearch({ + connection_id: connectionId, + database, + schema: preferredSchema ?? null, + object_kinds: ["routine"], + mask: filter.trim(), + max_results: limit ?? 200, + global_search: globalSearch, + parent_schema: globalSearch ? null : (schema ?? null), + parent_name: parentName ?? null, + match_mode: "prefix", + }); + const objects = completionAssistantObjects(response.candidates, preferredSchema); + indexCompletionObjects(connectionId, database, schema, objects); + return objects; + } + async function listCompletionAssistantColumns(connectionId: string, database: string, table: string, schema?: string): Promise { const response = await completionAssistantSearch({ connection_id: connectionId, @@ -4341,7 +4409,7 @@ export const useConnectionStore = defineStore("connection", () => { }); } - async function listCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string): Promise { + async function listCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string, globalSearch = false, currentSchema?: string): Promise { const trimmedFilter = filter.trim(); const normalizedFilter = trimmedFilter.toLowerCase(); // Remote queries (Dameng/Oracle) are case-sensitive, so the cache key must @@ -4349,7 +4417,7 @@ export const useConnectionStore = defineStore("connection", () => { // second lookup returns the first's stale results. Local lookups below stay // case-insensitive because tableMatchScore normalizes internally. const relaxedFilter = relaxedCompletionTableFilter(trimmedFilter); - const cacheKey = `${connectionId}:${database}:${trimmedFilter}:${limit ?? ""}:${schema ?? ""}`; + const cacheKey = `${connectionId}:${database}:${trimmedFilter}:${limit ?? ""}:${schema ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}`; if (completionTablesCache.value[cacheKey]) { return completionTablesCache.value[cacheKey]; } @@ -4363,7 +4431,7 @@ export const useConnectionStore = defineStore("connection", () => { if (normalizedFilter || limit) { let results: SqlCompletionTable[] = []; try { - results = await listCompletionAssistantTables(connectionId, database, trimmedFilter, limit, schema); + results = await listCompletionAssistantTables(connectionId, database, trimmedFilter, limit, schema, globalSearch, currentSchema); } catch { if (schema) { const tables = await api.listTables(connectionId, database, schema, trimmedFilter, limit); @@ -4377,7 +4445,13 @@ export const useConnectionStore = defineStore("connection", () => { } } if (results.length === 0 && relaxedFilter) { - if (schema) { + if (globalSearch) { + try { + results = await listCompletionAssistantTables(connectionId, database, relaxedFilter, expandedCompletionLimit(limit), schema, true, currentSchema); + } catch { + results = []; + } + } else if (schema) { try { const tables = await api.listTables(connectionId, database, schema, relaxedFilter, expandedCompletionLimit(limit)); results = tables.map((table) => ({ @@ -4453,16 +4527,21 @@ export const useConnectionStore = defineStore("connection", () => { return deduped; } - async function listCompletionObjects(connectionId: string, database: string, filter = "", limit?: number, schema?: string): Promise { + async function listCompletionObjects(connectionId: string, database: string, filter = "", limit?: number, schema?: string, parentName?: string, globalSearch = false, currentSchema?: string): Promise { const normalizedFilter = filter.trim().toLowerCase(); - const cacheKey = `${connectionId}:${database}:${schema ?? ""}`; + const oracleAssistant = getConfig(connectionId)?.db_type === "oracle" && (!!normalizedFilter || typeof limit === "number" || !!parentName || globalSearch); + const cacheKey = oracleAssistant ? `${connectionId}:${database}:${schema ?? ""}:${parentName ?? ""}:${normalizedFilter}:${limit ?? ""}:${globalSearch ? "global" : "scoped"}:${currentSchema ?? ""}` : `${connectionId}:${database}:${schema ?? ""}`; if (!completionObjectsCache.value[cacheKey]) { await withCompletionInFlight( `${cacheKey}:objects`, async () => { await ensureConnected(connectionId); - const objects = isSchemaAwareDatabase(connectionId) ? await listSchemaAwareCompletionObjects(connectionId, database, schema) : await api.listCompletionObjects(connectionId, database, schema || database); - completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(objects.map(toSqlCompletionObject).filter((object): object is SqlCompletionObject => object != null)); + if (oracleAssistant) { + completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(await listCompletionAssistantObjects(connectionId, database, filter, limit, schema, parentName, globalSearch, currentSchema)); + } else { + const objects = isSchemaAwareDatabase(connectionId) ? await listSchemaAwareCompletionObjects(connectionId, database, schema) : await api.listCompletionObjects(connectionId, database, schema || database); + completionObjectsCache.value[cacheKey] = dedupeCompletionObjects(objects.map(toSqlCompletionObject).filter((object): object is SqlCompletionObject => object != null)); + } indexCompletionObjects(connectionId, database, schema, completionObjectsCache.value[cacheKey]); evictOldestCacheEntries(completionObjectsCache.value, COMPLETION_CACHE_MAX); }, @@ -4614,12 +4693,12 @@ export const useConnectionStore = defineStore("connection", () => { return foreignKeys; } - function refreshCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string): Promise { - return listCompletionTables(connectionId, database, filter, limit, schema); + function refreshCompletionTables(connectionId: string, database: string, filter = "", limit?: number, schema?: string, globalSearch = false, currentSchema?: string): Promise { + return listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, currentSchema); } - function refreshCompletionObjects(connectionId: string, database: string, filter = "", limit?: number, schema?: string): Promise { - return listCompletionObjects(connectionId, database, filter, limit, schema); + function refreshCompletionObjects(connectionId: string, database: string, filter = "", limit?: number, schema?: string, parentName?: string, globalSearch = false, currentSchema?: string): Promise { + return listCompletionObjects(connectionId, database, filter, limit, schema, parentName, globalSearch, currentSchema); } function refreshCompletionSchemas(connectionId: string, database: string): Promise { diff --git a/docs/screenshot-oracle-multi-schema-completion.png b/docs/screenshot-oracle-multi-schema-completion.png new file mode 100644 index 0000000000000000000000000000000000000000..f6bc61dd8574d37dd30d430d0e0c72b662cb01f7 GIT binary patch literal 63490 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
)gG=LUq%tTv>}T8HRq;wN?DpqDPl7t-g~_tD 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%0Dpz%ZUTa@L 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 N>%E8eJhk$;@*lXb(cu69 literal 0 HcmV?d00001 diff --git a/packages/app-tests/sqlCompletion.test.ts b/packages/app-tests/sqlCompletion.test.ts index 727383ddab..69d70ae517 100644 --- a/packages/app-tests/sqlCompletion.test.ts +++ b/packages/app-tests/sqlCompletion.test.ts @@ -400,6 +400,20 @@ test("suggests only matching columns for an explicit alias qualifier prefix", () ); }); +test("does not mix routines into an explicit table alias column completion", () => { + const sql = "select u.na from public.users u"; + const cursor = "select u.na".length; + const items = buildSqlCompletionItems(sql, cursor, { + tables, + columnsByTable, + objects: [{ name: "name_formatter", schema: "u", type: "function", applyName: "u.name_formatter" }], + databaseType: "oracle", + }); + + assert.ok(items.some((item) => item.label === "name" && item.type === "column")); + assert.equal(items.some((item) => item.label === "name_formatter"), false); +}); + test("keeps explicit alias column suggestions scoped to the alias table", () => { const sql = "select * from public.users u join public.orders o on u.id = o.user_id where o.st"; const items = buildSqlCompletionItems(sql, sql.length, { @@ -1555,6 +1569,88 @@ test("suggests package members after package qualifier", () => { assert.equal(member.apply, "calculate_bonus()"); }); +test("keeps Oracle functions available in qualified expression routine context", () => { + const sql = "select HGY.FN_"; + const items = buildSqlCompletionItems(sql, sql.length, { + tables, + columnsByTable, + objects: [ + { name: "FN_CHECKIDCARD", schema: "HGY", type: "function" }, + { name: "FN_REFRESH", schema: "HGY", type: "procedure" }, + ], + databaseType: "oracle", + }); + + const fn = items.find((item) => item.label === "FN_CHECKIDCARD"); + assert.ok(fn); + assert.equal(fn.apply, "FN_CHECKIDCARD()"); + assert.equal(items.find((item) => item.type === "function")?.label, "FN_CHECKIDCARD"); +}); + +test("prioritizes current Oracle schema tables and safely qualifies other schemas", () => { + const sql = "select * from dept_d"; + const items = buildSqlCompletionItems(sql, sql.length, { + tables: [ + { name: "DEPT_DICT", schema: "COMM", type: "table", applyName: "COMM.DEPT_DICT", boost: 0 }, + { name: "DEPT_DICT", schema: "APP", type: "table", applyName: "DEPT_DICT", boost: 2400 }, + { name: "DEPT_DICT", schema: "SYS", type: "view", applyName: "SYS.DEPT_DICT", boost: -1200 }, + ], + columnsByTable, + databaseType: "oracle", + }); + const matches = items.filter((item) => item.label === "DEPT_DICT"); + + assert.deepEqual( + matches.map((item) => item.apply), + ["DEPT_DICT", "COMM.DEPT_DICT", "SYS.DEPT_DICT"], + ); +}); + +test("does not duplicate an Oracle schema qualifier when applying a scoped table", () => { + const sql = "select * from COMM.DEPT_D"; + const items = buildSqlCompletionItems(sql, sql.length, { + tables: [{ name: "DEPT_DICT", schema: "COMM", type: "table" }], + columnsByTable, + databaseType: "oracle", + currentSchema: "APP", + }); + + const table = items.find((item) => item.label === "DEPT_DICT"); + assert.ok(table); + assert.equal(table.apply, "DEPT_DICT"); +}); + +test("keeps same-name Oracle routines from different schemas", () => { + const sql = "select FN_CHECK"; + const items = buildSqlCompletionItems(sql, sql.length, { + tables: [], + objects: [ + { name: "FN_CHECK", schema: "APP", type: "function", boost: 2400 }, + { name: "FN_CHECK", schema: "HR", type: "function" }, + ], + columnsByTable: new Map(), + databaseType: "oracle", + currentSchema: "APP", + }); + + assert.deepEqual( + items.filter((item) => item.label === "FN_CHECK").map((item) => item.apply), + ["FN_CHECK()", "HR.FN_CHECK()"], + ); +}); + +test("still deduplicates built-in and database routines outside Oracle metadata search", () => { + const sql = "select DATE_FORMAT"; + const items = buildSqlCompletionItems(sql, sql.length, { + tables: [], + objects: [{ name: "DATE_FORMAT", schema: "app", type: "function" }], + columnsByTable: new Map(), + databaseType: "mysql", + }); + + assert.equal(items.filter((item) => item.type === "function" && item.label === "DATE_FORMAT").length, 1); +}); + test("matches alias qualifier case-insensitively", () => { const sql = "select O. from public.orders o"; const cursor = "select O.".length;