Skip to content

Commit c17c7e6

Browse files
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 966e029 commit c17c7e6

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
@@ -1502,11 +1502,20 @@ func (e *DuckDBEngine) Search(ctx context.Context, q *search.Query, limit, offse
15021502
return results, nil
15031503
}
15041504

1505-
// GetGmailIDsByFilter returns Gmail IDs matching a filter from Parquet files.
1506-
// Uses EXISTS subqueries for efficient semi-join filtering.
1505+
// GetGmailIDsByFilter returns Gmail IDs matching a filter.
1506+
// This method delegates to SQLite for authoritative deletion status.
1507+
// The Parquet cache may be stale if messages were deleted after the last cache build,
1508+
// so we use SQLite directly to ensure deleted messages are properly excluded.
15071509
func (e *DuckDBEngine) GetGmailIDsByFilter(ctx context.Context, filter MessageFilter) ([]string, error) {
1510+
// Delegate to SQLite for authoritative deletion status.
1511+
// Parquet cache may be stale if deletions occurred after the last build.
1512+
if e.sqliteEngine != nil {
1513+
return e.sqliteEngine.GetGmailIDsByFilter(ctx, filter)
1514+
}
1515+
1516+
// Fall back to Parquet if no SQLite engine available (shouldn't happen in practice)
15081517
if e.analyticsDir == "" {
1509-
return nil, fmt.Errorf("GetGmailIDsByFilter requires Parquet data: pass analyticsDir to NewDuckDBEngine")
1518+
return nil, fmt.Errorf("GetGmailIDsByFilter requires SQLite or Parquet data")
15101519
}
15111520

15121521
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)