Contents
Plain-language companion: v0.2.2.md
v0.2.2 — OFFSET, AUTO Mode, ALTER QUERY, Edge Cases & CDC Hardening
Status: Released (2026-03-08).
This milestone shipped paged TopK OFFSET support, AUTO-by-default refresh selection, ALTER QUERY, the remaining upgrade-tooling work, edge-case and WAL CDC hardening, IMMEDIATE-mode parity fixes, and the outstanding documentation sweep.
ORDER BY + LIMIT + OFFSET (Paged TopK) — Finalization ✅
In plain terms: Extends TopK to support OFFSET — so you can define a stream table as “rows 11–20 of the top-20 best-selling products” (page 2 of a ranked list). Useful for paginated leaderboards, ranked feeds, or any use case where you want a specific window into a sorted result.
Core implementation is complete (parser, catalog, refresh path, docs, 9 E2E
tests). The topk_offset catalog column shipped in v0.2.1 and is exercised
by the paged TopK feature here.
| Item | Description | Status | Ref |
|---|---|---|---|
| OS1 | 9 OFFSET E2E tests in e2e_topk_tests.rs |
✅ Done | PLAN_OFFSET_SUPPORT.md §Step 6 |
| OS2 | sql/pg_trickle--0.2.1--0.2.2.sql — function signature updates (no schema DDL needed) |
✅ Done | PLAN_OFFSET_SUPPORT.md §Step 2 |
AUTO Refresh Mode ✅
In plain terms: Changes the default from “always try differential (incremental) refresh” to a smart automatic selection: use differential when the query supports it, fall back to a full re-scan when it doesn’t. New stream tables also get a calculated schedule interval instead of a hardcoded 1-minute default.
| Item | Description | Status | Ref |
|---|---|---|---|
| AM1 | RefreshMode::Auto — uses DIFFERENTIAL when supported, falls back to FULL |
✅ Done | PLAN_REFRESH_MODE_DEFAULT.md |
| AM2 | create_stream_table default changed from 'DIFFERENTIAL' to 'AUTO' |
✅ Done | — |
| AM3 | create_stream_table schedule default changed from '1m' to 'calculated' |
✅ Done | — |
ALTER QUERY ✅
In plain terms: Lets you change the SQL query of an existing stream table without dropping and recreating it. pg_trickle inspects the old and new queries, determines what type of change was made (added a column, dropped a column, or fundamentally incompatible change), and performs the most minimal migration possible — updating in place where it can, rebuilding only when it must.
| Item | Description | Status | Ref |
|---|---|---|---|
| AQ1 | alter_stream_table(query => ...) — validate, classify schema change, migrate storage |
✅ Done | PLAN_ALTER_QUERY.md |
| AQ2 | Schema classification: same, compatible (ADD/DROP COLUMN), incompatible (full rebuild) | ✅ Done | — |
| AQ3 | ALTER-aware cycle detection (check_for_cycles_alter) |
✅ Done | — |
| AQ4 | CDC dependency migration (add/remove triggers for changed sources) | ✅ Done | — |
| AQ5 | SQL Reference & CHANGELOG documentation | ✅ Done | — |
Upgrade Tooling ✅
In plain terms: If the compiled extension library (
.sofile) is a different version than the SQL objects in the database, the scheduler now warns loudly at startup instead of failing in confusing ways later. Also adds FAQ entries and cross-links for common upgrade questions.
| Item | Description | Status | Ref |
|---|---|---|---|
| UG1 | Version mismatch check — scheduler warns if .so version ≠ SQL version |
✅ Done | PLAN_UPGRADE_MIGRATIONS.md §5.2 |
| UG2 | FAQ upgrade section — 3 new entries with UPGRADING.md cross-links | ✅ Done | PLAN_UPGRADE_MIGRATIONS.md §5.4 |
| UG3 | CI and local upgrade automation now target 0.2.2 (upgrade-check, upgrade-image defaults, upgrade E2E env) |
✅ Done | PLAN_UPGRADE_MIGRATIONS.md |
IMMEDIATE Mode Parity ✅
In plain terms: Closes two remaining SQL patterns that worked in DIFFERENTIAL mode but not in IMMEDIATE mode. Recursive CTEs (queries that reference themselves to compute e.g. graph reachability or org-chart hierarchies) now work in IMMEDIATE mode with a configurable depth guard. TopK (ORDER BY + LIMIT) queries also get a dedicated fast micro-refresh path in IMMEDIATE mode.
Close the gap between DIFFERENTIAL and IMMEDIATE mode SQL coverage for the two remaining high-risk patterns — recursive CTEs and TopK queries.
| Item | Description | Effort | Ref |
|---|---|---|---|
| IM1 | Validate recursive CTE semi-naive in IMMEDIATE mode; add stack-depth guard for deeply recursive defining queries | 2–3d | PLAN_EDGE_CASES_TIVM_IMPL_ORDER.md Stage 6 §5.1 | ✅ Done — check_for_delete_changes handles TransitionTable; generate_change_buffer_from uses NEW transition table in IMMEDIATE mode; ivm_recursive_max_depth GUC (default 100) injects __pgt_depth counter into semi-naive SQL |
| IM2 | TopK in IMMEDIATE mode: statement-level micro-refresh + ivm_topk_max_limit GUC |
2–3d | PLAN_EDGE_CASES_TIVM_IMPL_ORDER.md Stage 6 §5.2 | ✅ Done — apply_topk_micro_refresh() in ivm.rs; GUC threshold check in api.rs; 10 E2E tests (basic, insert, delete, update, aggregate, offset, multi-DML, threshold rejection, mode switch) |
IMMEDIATE parity subtotal: ✅ Complete (IM1 + IM2)
Edge Case Hardening ✅
In plain terms: Three targeted fixes for uncommon-but-real scenarios: a cap on CUBE/ROLLUP combinatorial explosion (which can generate thousands of grouping variants from a single query and crash the database); automatic recovery when CDC gets stuck in a “transitioning” state after a database restart; and polling-based change detection for foreign tables (tables in external databases) that can’t use triggers or WAL.
Self-contained items from Stage 7 of the edge-cases/TIVM implementation plan.
| Item | Description | Effort | Ref |
|---|---|---|---|
| EC1 | pg_trickle.max_grouping_set_branches GUC — cap CUBE/ROLLUP branch-count explosion |
4h | PLAN_EDGE_CASES.md EC-02 | ✅ Done — GUC in config.rs (default 64, range 1–65536); parser.rs rejects when branch count exceeds limit; 3 E2E tests (rejection, within-limit, raised limit) |
| EC2 | Post-restart CDC TRANSITIONING health check — detect stuck CDC transitions after crash or restart |
1d | PLAN_EDGE_CASES.md EC-20 | ✅ Done — check_cdc_transition_health() in scheduler.rs; detects missing replication slots; rolls back to TRIGGER mode |
| EC3 | Foreign table support: polling-based change detection via periodic re-execution | 2–3d | PLAN_EDGE_CASES.md EC-05 | ✅ Done — pg_trickle.foreign_table_polling GUC; setup_foreign_table_polling() creates snapshot table; poll_foreign_table_changes() uses EXCEPT ALL deltas; 3 E2E tests (rejection, FULL mode, polling correctness) |
Edge-case hardening subtotal: ✅ Complete (EC1 + EC2 + EC3)
Documentation Sweep
In plain terms: Filled three documentation gaps: what happens to an in-flight refresh if you run DDL (ALTER TABLE, DROP INDEX) at the same time; limitations when using pg_trickle on standby replicas; and a PgBouncer configuration guide explaining the session-mode requirement and incompatible settings.
Remaining documentation gaps identified in Stage 7 of the gap analysis.
| Item | Description | Effort | Status | Ref |
|---|---|---|---|---|
| DS1 | DDL-during-refresh behaviour: document safe patterns and races | 2h | ✅ Done | PLAN_EDGE_CASES.md EC-17 |
| DS2 | Replication/standby limitations: document in FAQ and Architecture | 3h | ✅ Done | PLAN_EDGE_CASES.md EC-21/22/23 |
| DS3 | PgBouncer configuration guide: session-mode requirements and known incompatibilities | 2h | ✅ Done | PLAN_EDGE_CASES.md EC-28 |
Documentation sweep subtotal: ✅ Complete
WAL CDC Hardening
In plain terms: WAL (Write-Ahead Log) mode tracks changes by reading PostgreSQL’s internal replication stream rather than using row-level triggers — which is more efficient and works across concurrent sessions. This work added a complete E2E test suite for WAL mode, hardened the automatic fallback from WAL to trigger mode when WAL isn’t available, and promoted
cdc_mode = 'auto'(try WAL first, fall back to triggers) as the default.WAL decoder F2–F3 fixes (keyless pk_hash,
old_*columns for UPDATE) landed in v0.1.3.
| Item | Description | Effort | Status | Ref |
|---|---|---|---|---|
| W1 | WAL mode E2E test suite (parallel to trigger suite) | 8–12h | ✅ Done | PLAN_HYBRID_CDC.md |
| W2 | WAL→trigger automatic fallback hardening | 4–6h | ✅ Done | PLAN_HYBRID_CDC.md |
| W3 | Promote pg_trickle.cdc_mode = 'auto' to default |
~1h | ✅ Done | PLAN_HYBRID_CDC.md |
WAL CDC subtotal: ~13–19 hours
Exit criteria:
- [x] ORDER BY + LIMIT + OFFSET defining queries accepted, refreshed, and E2E-tested
- [x] sql/pg_trickle--0.2.1--0.2.2.sql exists (column pre-provisioned in 0.2.1; function signature updates)
- [x] Upgrade completeness check passes for 0.2.1→0.2.2
- [x] CI and local upgrade-E2E defaults target 0.2.2
- [x] Version check fires at scheduler startup if .so/SQL versions diverge
- [x] IMMEDIATE mode: recursive CTE semi-naive validated; ivm_recursive_max_depth depth guard added
- [x] IMMEDIATE mode: TopK micro-refresh fully tested end-to-end (10 E2E tests)
- [x] max_grouping_set_branches GUC guards CUBE/ROLLUP explosion (3 E2E tests)
- [x] Post-restart CDC TRANSITIONING health check in place
- [x] Foreign table polling-based CDC implemented (3 E2E tests)
- [x] DDL-during-refresh and standby/replication limitations documented
- [x] WAL CDC mode passes full E2E suite
- [x] E2E tests pass (just build-e2e-image && just test-e2e)