Skip to content

Commit c582565

Browse files
robelkin-rational-partnersclaude
authored andcommitted
Use SQLite for GetGmailIDsByFilter to ensure accurate deletion status
DuckDBEngine.GetGmailIDsByFilter now delegates to SQLiteEngine instead of querying the Parquet cache. This ensures deleted messages are properly excluded even when the Parquet cache is stale. The Parquet cache only captures deleted_from_source_at at build time. Messages deleted after the cache was built would still appear in staging queries. By using SQLite (the source of truth), we guarantee accurate results for deletion staging in both TUI and MCP. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent de917bd commit c582565

File tree

2 files changed

+18
-9
lines changed

2 files changed

+18
-9
lines changed

internal/query/duckdb.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,11 +1536,20 @@ func (e *DuckDBEngine) Search(ctx context.Context, q *search.Query, limit, offse
15361536
return results, nil
15371537
}
15381538

1539-
// GetGmailIDsByFilter returns Gmail IDs matching a filter from Parquet files.
1540-
// Uses EXISTS subqueries for efficient semi-join filtering.
1539+
// GetGmailIDsByFilter returns Gmail IDs matching a filter.
1540+
// This method delegates to SQLite for authoritative deletion status.
1541+
// The Parquet cache may be stale if messages were deleted after the last cache build,
1542+
// so we use SQLite directly to ensure deleted messages are properly excluded.
15411543
func (e *DuckDBEngine) GetGmailIDsByFilter(ctx context.Context, filter MessageFilter) ([]string, error) {
1544+
// Delegate to SQLite for authoritative deletion status.
1545+
// Parquet cache may be stale if deletions occurred after the last build.
1546+
if e.sqliteEngine != nil {
1547+
return e.sqliteEngine.GetGmailIDsByFilter(ctx, filter)
1548+
}
1549+
1550+
// Fall back to Parquet if no SQLite engine available (shouldn't happen in practice)
15421551
if e.analyticsDir == "" {
1543-
return nil, fmt.Errorf("GetGmailIDsByFilter requires Parquet data: pass analyticsDir to NewDuckDBEngine")
1552+
return nil, fmt.Errorf("GetGmailIDsByFilter requires SQLite or Parquet data")
15441553
}
15451554

15461555
var conditions []string

internal/query/duckdb_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,9 +1665,9 @@ func TestDuckDBEngine_SubAggregate_MultipleEmptyTargets(t *testing.T) {
16651665
}
16661666
}
16671667

1668-
// TestDuckDBEngine_GetGmailIDsByFilter_NoParquet verifies error when analyticsDir is empty.
1669-
func TestDuckDBEngine_GetGmailIDsByFilter_NoParquet(t *testing.T) {
1670-
// Create engine without Parquet
1668+
// TestDuckDBEngine_GetGmailIDsByFilter_NoDataSource verifies error when no SQLite or Parquet available.
1669+
func TestDuckDBEngine_GetGmailIDsByFilter_NoDataSource(t *testing.T) {
1670+
// Create engine without SQLite or Parquet
16711671
engine, err := NewDuckDBEngine("", "", nil)
16721672
if err != nil {
16731673
t.Fatalf("NewDuckDBEngine: %v", err)
@@ -1677,10 +1677,10 @@ func TestDuckDBEngine_GetGmailIDsByFilter_NoParquet(t *testing.T) {
16771677
ctx := context.Background()
16781678
_, err = engine.GetGmailIDsByFilter(ctx, MessageFilter{Sender: "test@example.com"})
16791679
if err == nil {
1680-
t.Fatal("expected error when calling GetGmailIDsByFilter without Parquet")
1680+
t.Fatal("expected error when calling GetGmailIDsByFilter without SQLite or Parquet")
16811681
}
1682-
if !strings.Contains(err.Error(), "requires Parquet") {
1683-
t.Errorf("expected 'requires Parquet' error, got: %v", err)
1682+
if !strings.Contains(err.Error(), "requires SQLite or Parquet") {
1683+
t.Errorf("expected 'requires SQLite or Parquet' error, got: %v", err)
16841684
}
16851685
}
16861686

0 commit comments

Comments
 (0)