Skip to content

(MOT-4235) feat(database): add row-change events and error hints - #614

Merged
andersonleal merged 1 commit into
mainfrom
feat/database-row-changed-and-error-hints
Jul 28, 2026
Merged

(MOT-4235) feat(database): add row-change events and error hints#614
andersonleal merged 1 commit into
mainfrom
feat/database-row-changed-and-error-hints

Conversation

@andersonleal

@andersonleal andersonleal commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replace the unsupported PostgreSQL-only database::row-change trigger with database::row-changed for mutations issued through the database worker on SQLite, PostgreSQL, and MySQL.
  • Deliver typed events filterable by table and operation only after commit, preserve requested RETURNING rows, and suppress rolled-back or zero-row writes.
  • Improve database error hints and reject returning requests when SQLite statements cannot produce rows.
  • Add transaction-race regressions and real trigger-delivery E2E coverage across all three drivers.

Why

The previous trigger depended on PostgreSQL logical replication support that was unavailable, leaving SQLite and MySQL without row-change notifications. Since worker-issued writes already pass through the database handlers, emitting after successful commit provides consistent cross-driver behavior without claiming to be external change-data capture.

Impact

Consumers can subscribe to committed worker-issued mutations by database, optional table, and optional operation list. Transaction rollbacks and failed commits remain silent, subscriber failures cannot undo committed writes, and error responses provide more accurate schema guidance.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features
  • SQLite database E2E: 44/44
  • PostgreSQL database E2E: 50/50
  • MySQL database E2E: 46/46

Fixes MOT-4235

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 28, 2026 7:36pm
workers-tech-spec Ready Ready Preview, Comment Jul 28, 2026 7:36pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@andersonleal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: efe96301-be13-4890-a259-802919290af4

📥 Commits

Reviewing files that changed from the base of the PR and between 45ffa82 and 66527f2.

⛔ Files ignored due to path filters (1)
  • database/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (37)
  • .github/workflows/database-e2e.yml
  • database/Cargo.toml
  • database/README.md
  • database/skills/SKILL.md
  • database/src/config.rs
  • database/src/driver/mod.rs
  • database/src/driver/mysql.rs
  • database/src/driver/postgres.rs
  • database/src/driver/sqlite.rs
  • database/src/error.rs
  • database/src/handlers/begin_transaction.rs
  • database/src/handlers/commit_transaction.rs
  • database/src/handlers/execute.rs
  • database/src/handlers/list_databases.rs
  • database/src/handlers/mod.rs
  • database/src/handlers/prepare.rs
  • database/src/handlers/query.rs
  • database/src/handlers/rollback_transaction.rs
  • database/src/handlers/run_statement.rs
  • database/src/handlers/transaction.rs
  • database/src/handlers/transaction_execute.rs
  • database/src/main.rs
  • database/src/transaction.rs
  • database/src/triggers/bus.rs
  • database/src/triggers/handler.rs
  • database/src/triggers/mod.rs
  • database/src/triggers/row_change.rs
  • database/src/triggers/sql.rs
  • database/tests/e2e/README.md
  • database/tests/e2e/docker-compose.yml
  • database/tests/e2e/run-tests.sh
  • database/tests/e2e/workers/harness/src/cases-interactive-tx.ts
  • database/tests/e2e/workers/harness/src/cases-row-change.ts
  • database/tests/e2e/workers/harness/src/cases-row-changed.ts
  • database/tests/e2e/workers/harness/src/database-config.ts
  • database/tests/e2e/workers/harness/src/runner.ts
  • database/tests/integration.rs
📝 Walkthrough

Walkthrough

Replaces the PostgreSQL logical-replication row-change trigger with a driver-agnostic database::row-changed bus. Events are classified from worker SQL, buffered across interactive transactions, emitted after commits, discarded on rollback or timeout, and covered by unit, integration, and E2E tests.

Changes

Row-changed trigger delivery

Layer / File(s) Summary
Event contracts, classification, and dispatch
database/src/triggers/*
Adds mutation classification, event payloads, subscriber filtering, buffering, dispatch, and live database validation for database::row-changed.
Handler and transaction lifecycle integration
database/src/handlers/*, database/src/main.rs, database/src/transaction.rs
Publishes committed writes, stages interactive-transaction changes, commits or drops them on transaction completion, and clears timed-out changes.
SQLite validation and schema diagnostics
database/src/driver/sqlite.rs
Adds schema-aware errors, explicit RETURNING_MISMATCH validation, dialect hints, and regression tests.
PostgreSQL aborted transaction handling
database/src/driver/postgres.rs
Probes transaction state before commit and tests rejected commits after an aborted interactive transaction.
Documentation, harness, and cleanup updates
database/README.md, database/skills/*, database/tests/e2e/*, database/Cargo.toml
Documents the new trigger contract, removes replication-era setup and errors, updates E2E cases, and supports database URL overrides.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DatabaseWorker
  participant RowChangeBus
  participant TriggerFunction
  Client->>DatabaseWorker: execute mutation
  DatabaseWorker->>RowChangeBus: emit or stage row change
  DatabaseWorker->>RowChangeBus: commit staged changes
  RowChangeBus->>TriggerFunction: dispatch event
Loading

Possibly related PRs

  • iii-hq/workers#585: Updates database resolution in handlers that provide the db value used by row-change emission.

Suggested reviewers: sergiofilhowz, guibeira

Poem

I’m a rabbit with events in my pack,
Commit sends them forward; rollback sends them back.
SQL names tables, buses make them fly,
Replication slots now hop goodbye.
Tests thump softly: the rows arrive!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: new database row-change events plus error hint improvements.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/database-row-changed-and-error-hints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 49 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@andersonleal
andersonleal marked this pull request as ready for review July 28, 2026 18:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
database/src/driver/sqlite.rs (1)

316-331: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

returning arm of statement_returns_rows is now dead at every validate_returning call site.

Once validate_returning has passed, !returning.is_empty() implies column_count() > 0, so the first disjunct in statement_returns_rows can never decide routing in execute/tx_execute. Consider dropping the parameter and routing purely on column_count() to keep one source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/src/driver/sqlite.rs` around lines 316 - 331, Update
statement_returns_rows and its execute/tx_execute call sites to route solely
from the statement’s column_count(), removing the now-redundant returning-based
condition and parameter if unused. Preserve validate_returning as the single
validation for requested RETURNING clauses.
database/tests/e2e/README.md (1)

105-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Layout table omits the new case file. Sibling case files are listed; add workers/harness/src/cases-row-changed.ts for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/tests/e2e/README.md` around lines 105 - 118, Add
workers/harness/src/cases-row-changed.ts to the Layout table in
database/tests/e2e/README.md, listing it as the corresponding row-change case
file alongside the other workers/harness/src case files.
database/src/triggers/sql.rs (1)

216-224: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc claims quoted references match, but bare never strips quotes. A binding configured with table: "\"Orders\"" (or `orders`) won't match the classifier's unquoted orders. Either trim "/`/[] in bare, or soften the doc comment to unquoted references only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/src/triggers/sql.rs` around lines 216 - 224, Update the nested bare
helper in same_table to normalize quoted table identifiers by stripping
surrounding double quotes, backticks, or square brackets before lowercasing and
comparing. Preserve schema-qualifier removal and the documented matching
behavior for quoted and unquoted references.
database/src/triggers/bus.rs (1)

265-320: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Dispatch is awaited, so it isn't really fire-and-forget. commit/emit block the calling handler for up to dispatch_timeout_ms per subscriber per event; a commit with several staged statements and a couple of slow subscribers adds seconds to commitTransaction. Consider draining the buffer and spawning one task that fans out in order, keeping statement ordering intact while returning to the caller immediately.

♻️ Sketch (requires `RowChangeBus` in an `Arc` at the call sites)
-    pub async fn commit(&self, transaction_id: &str) {
-        let staged = self.lock().take_pending(transaction_id);
-        for p in staged {
-            self.fan_out(RowChangedEvent { .. }).await;
-        }
-    }
+    pub fn commit(self: &Arc<Self>, transaction_id: &str) {
+        let staged = self.lock().take_pending(transaction_id);
+        if staged.is_empty() {
+            return;
+        }
+        let bus = Arc::clone(self);
+        tokio::spawn(async move {
+            for p in staged {
+                bus.fan_out(RowChangedEvent { /* … */ }).await;
+            }
+        });
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/src/triggers/bus.rs` around lines 265 - 320, Update
RowChangeBus::commit and its emit/dispatch flow so draining pending events
schedules fan_out work in a background task and returns without awaiting
subscriber dispatch. Preserve staged statement ordering by having the spawned
task process events sequentially, and adjust call sites or ownership as needed
to provide RowChangeBus through Arc while retaining rollback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/src/handlers/commit_transaction.rs`:
- Around line 70-73: Update the commit flow around commit_row_changes to release
the transaction’s pinned connection guard before awaiting subscriber/event
delivery. Ensure the guard is explicitly dropped after the durable commit and
before dispatching staged events, while preserving the existing statement-order
flush and successful CommitTxResp behavior.

In `@database/src/handlers/transaction.rs`:
- Around line 119-122: Update the transaction batch loop over sql_texts and
steps to preserve step.rows when emitting events: extend the batch step result
with the returned column names, convert each row into a JSON object, and pass
the resulting data instead of None to emit_row_change. Keep None only when no
RETURNING rows are available.

In `@database/src/triggers/sql.rs`:
- Around line 151-159: Update strip_conflict_clause to parse the optional “OR
<action>” prefix while treating consecutive whitespace, including newlines, as a
single separator. Ensure the returned SQL begins at the actual table name for
inputs such as repeated spaces, and preserve the existing behavior for
statements without an OR prefix.

---

Nitpick comments:
In `@database/src/driver/sqlite.rs`:
- Around line 316-331: Update statement_returns_rows and its execute/tx_execute
call sites to route solely from the statement’s column_count(), removing the
now-redundant returning-based condition and parameter if unused. Preserve
validate_returning as the single validation for requested RETURNING clauses.

In `@database/src/triggers/bus.rs`:
- Around line 265-320: Update RowChangeBus::commit and its emit/dispatch flow so
draining pending events schedules fan_out work in a background task and returns
without awaiting subscriber dispatch. Preserve staged statement ordering by
having the spawned task process events sequentially, and adjust call sites or
ownership as needed to provide RowChangeBus through Arc while retaining rollback
behavior.

In `@database/src/triggers/sql.rs`:
- Around line 216-224: Update the nested bare helper in same_table to normalize
quoted table identifiers by stripping surrounding double quotes, backticks, or
square brackets before lowercasing and comparing. Preserve schema-qualifier
removal and the documented matching behavior for quoted and unquoted references.

In `@database/tests/e2e/README.md`:
- Around line 105-118: Add workers/harness/src/cases-row-changed.ts to the
Layout table in database/tests/e2e/README.md, listing it as the corresponding
row-change case file alongside the other workers/harness/src case files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6df332f-18dc-4a53-858b-ae8acb8781a0

📥 Commits

Reviewing files that changed from the base of the PR and between 43487cb and 45ffa82.

⛔ Files ignored due to path filters (1)
  • database/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .github/workflows/database-e2e.yml
  • database/Cargo.toml
  • database/README.md
  • database/skills/SKILL.md
  • database/src/config.rs
  • database/src/driver/postgres.rs
  • database/src/driver/sqlite.rs
  • database/src/error.rs
  • database/src/handlers/begin_transaction.rs
  • database/src/handlers/commit_transaction.rs
  • database/src/handlers/execute.rs
  • database/src/handlers/list_databases.rs
  • database/src/handlers/mod.rs
  • database/src/handlers/prepare.rs
  • database/src/handlers/query.rs
  • database/src/handlers/rollback_transaction.rs
  • database/src/handlers/run_statement.rs
  • database/src/handlers/transaction.rs
  • database/src/handlers/transaction_execute.rs
  • database/src/main.rs
  • database/src/transaction.rs
  • database/src/triggers/bus.rs
  • database/src/triggers/handler.rs
  • database/src/triggers/mod.rs
  • database/src/triggers/row_change.rs
  • database/src/triggers/sql.rs
  • database/tests/e2e/README.md
  • database/tests/e2e/docker-compose.yml
  • database/tests/e2e/run-tests.sh
  • database/tests/e2e/workers/harness/src/cases-interactive-tx.ts
  • database/tests/e2e/workers/harness/src/cases-row-change.ts
  • database/tests/e2e/workers/harness/src/cases-row-changed.ts
  • database/tests/e2e/workers/harness/src/database-config.ts
  • database/tests/e2e/workers/harness/src/runner.ts
  • database/tests/integration.rs
💤 Files with no reviewable changes (5)
  • database/Cargo.toml
  • database/tests/e2e/workers/harness/src/cases-row-change.ts
  • database/tests/e2e/docker-compose.yml
  • database/src/triggers/row_change.rs
  • database/src/error.rs

Comment thread database/src/handlers/commit_transaction.rs
Comment thread database/src/handlers/transaction.rs
Comment thread database/src/triggers/sql.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants