Skip to content

Commit eea88ff

Browse files
authored
feat(pg): add completion scope context (#112)
1 parent e02efb0 commit eea88ff

5 files changed

Lines changed: 1153 additions & 0 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# PostgreSQL Completion Scope Implementation Plan
2+
3+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4+
5+
**Goal:** Add a PostgreSQL completion-only API that returns parser candidates plus the visible FROM scope at the cursor.
6+
7+
**Architecture:** Keep ordinary `Parse` strict and avoid exposing partial ASTs. Add `parser.CollectCompletion(sql, cursorOffset)` as a completion context API that wraps existing `Collect` candidates and computes a best-effort `ScopeSnapshot` from grammar/token facts and parseable prefixes. ByteBase remains responsible for metadata and final candidate formatting.
8+
9+
**Tech Stack:** Go, `github.com/bytebase/omni/pg/parser`, `github.com/bytebase/omni/pg/ast`.
10+
11+
### Task 1: Scope API Tests
12+
13+
**Files:**
14+
- Create: `pg/parser/complete_context_test.go`
15+
16+
**Steps:**
17+
- Write failing tests for `CollectCompletion`.
18+
- Cover incomplete `JOIN ... USING (`, aliases with alias columns, incomplete `JOIN`, subquery aliases, and CTE references.
19+
- Run `go test ./pg/parser -run 'TestCollectCompletion' -count=1`; expected failure is missing API/types.
20+
21+
### Task 2: Minimal API and Scope Extraction
22+
23+
**Files:**
24+
- Create: `pg/parser/complete_context.go`
25+
26+
**Steps:**
27+
- Define `CompletionContext`, `ScopeSnapshot`, `RangeReference`, and `RangeReferenceKind`.
28+
- Implement `CollectCompletion` by reusing `Collect` and extracting visible references for the cursor's SELECT scope.
29+
- Preserve strict `Parse`; completion-only recovery may tolerate incomplete join qualifiers.
30+
- Run focused tests until green.
31+
32+
### Task 3: Public Package Wrapper
33+
34+
**Files:**
35+
- Create: `pg/completion.go`
36+
37+
**Steps:**
38+
- Add a small `pg.CollectCompletion` wrapper if downstream users prefer the top-level pg package.
39+
- Keep the parser package API as the primary implementation.
40+
- Run `go test ./pg ./pg/parser -run 'TestCollectCompletion|TestParse' -count=1`.
41+
42+
### Task 4: Verification
43+
44+
**Steps:**
45+
- Run `go test ./pg/parser ./pg/completion`.
46+
- Confirm ordinary strict parse behavior remains covered by existing parser tests.

pg/completion.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package pg
2+
3+
import "github.com/bytebase/omni/pg/parser"
4+
5+
// CompletionContext is the parser-native context for SQL completion.
6+
type CompletionContext = parser.CompletionContext
7+
8+
// ScopeSnapshot describes the SELECT-level relation scope visible at a cursor.
9+
type ScopeSnapshot = parser.ScopeSnapshot
10+
11+
// RangeReference is a syntax-level FROM/JOIN reference.
12+
type RangeReference = parser.RangeReference
13+
14+
// RangeReferenceKind classifies a range-table entry visible to completion.
15+
type RangeReferenceKind = parser.RangeReferenceKind
16+
17+
const (
18+
RangeReferenceRelation = parser.RangeReferenceRelation
19+
RangeReferenceSubquery = parser.RangeReferenceSubquery
20+
RangeReferenceFunction = parser.RangeReferenceFunction
21+
RangeReferenceJoinAlias = parser.RangeReferenceJoinAlias
22+
RangeReferenceCTE = parser.RangeReferenceCTE
23+
)
24+
25+
// CollectCompletion returns completion candidates plus a best-effort visible
26+
// relation scope at cursorOffset. Ordinary Parse remains strict.
27+
func CollectCompletion(sql string, cursorOffset int) *CompletionContext {
28+
return parser.CollectCompletion(sql, cursorOffset)
29+
}

pg/completion_context_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package pg
2+
3+
import (
4+
"testing"
5+
6+
"github.com/bytebase/omni/pg/parser"
7+
)
8+
9+
func TestCollectCompletionWrapper(t *testing.T) {
10+
sql := "SELECT * FROM t1 JOIN t2 USING ("
11+
ctx := CollectCompletion(sql, len(sql))
12+
if ctx == nil || ctx.Candidates == nil || ctx.Scope == nil {
13+
t.Fatal("expected completion context")
14+
}
15+
if !ctx.Candidates.HasRule("columnref") {
16+
t.Fatal("expected columnref candidate")
17+
}
18+
if len(ctx.Scope.References) != 2 {
19+
t.Fatalf("reference count = %d, want 2", len(ctx.Scope.References))
20+
}
21+
if ctx.Scope.References[0].Kind != parser.RangeReferenceRelation {
22+
t.Fatalf("first reference kind = %v, want relation", ctx.Scope.References[0].Kind)
23+
}
24+
}

0 commit comments

Comments
 (0)