Skip to content

feat(database): add application errors and safe schema transactions - #739

Merged
logbie merged 13 commits into
mainfrom
codex/orm-transaction-prerequisites
Sep 20, 2026
Merged

logbie merged 13 commits into
mainfrom
codex/orm-transaction-prerequisites

Conversation

@logbie

@logbie logbie commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add catchable application errors and SQLite schema transactions so WFL migration libraries can validate data, fail atomically, and rebuild tables without deleting referencing extension rows.

The reproducer demonstrated that ordinary table rebuilds with foreign keys enabled could delete referencing rows. Libraries also lacked a deliberate error primitive for validation failures that must unwind a transaction.

Changes

  • Add raise_error with safe text validation and normal catchable error propagation.
  • Add in transaction on db for schema changes:: pin the owned SQLite connection, disable foreign keys before BEGIN IMMEDIATE, check foreign keys before commit, and restore the connection on success, failure, and cancellation.
  • Bound schema lock acquisition, reject nested scopes, and preserve ordinary transaction semantics.
  • Add WFL scenarios for rebuilds, rollback, connection restoration, locks, process interruption, canceled HTTP handlers, and contextual fixer protection.
  • Integrate the reviewed HTTP and process prerequisites while retaining both core actions and contextual fixer markers.

Compatibility and risk

Risk class R3: database durability, cancellation, resource ownership, and backward compatibility. Ordinary transactions retain their existing behavior, including normal returns committing. Schema mode is explicit and SQLite-specific. Rebuild code remains responsible for preserving intended schema objects and data; the runtime provides atomicity and foreign-key validation.

Independent review found a canceled-handler ownership leak. The final implementation adds scope cleanup and verifies restoration of the same in-memory connection. A connection whose safe state cannot be restored is discarded; losing the only connection of an in-memory database requires reopening that handle. No production database was changed.

Validation

  • Merged on 2026-09-20 at 10:47:08 UTC as 8d82d785ea59300834de1c48b04a7ba0e187a1cd; GitHub readback confirms merged: true. Post-merge CI35506031173 passed on this merge, including every required job and the automatic version bump. The bump pushed a1249033b0f1cff87ecea8282619808f727d22c3 and tag v26.9.15; version synchronization, both lockfiles, the locked fuzz check, and pre-push hygiene passed. Post-merge program totals remained Linux 181 / Windows 180 with zero failures or timeouts; both integration gates passed 157 programs, documentation 36/36 and web 3/3. Docker35506031179, config lint35506031178, Auto Format35506031176 and CodeQL35506030920 also passed.
  • Red: 5d87d9b7 records the unsafe rebuild and missing application-error capability. Green source: d450dee9; initial evidence: 9defb442.
  • Initial schema branch: 17/17 focused WFL cases; existing Windows WFL gate 151 passed, zero failed, 24 existing skips; Rust workspace 2,414 passed and 27 existing ignored. Strict Clippy, formatting, locked fuzz compilation, hygiene, documentation 36/36, and web integration 2/2 passed.
  • Initial exact-head CI35502181204 passed at 9defb44208fd3fdc10c3758b3dc1f3cc56706251, including actual new WFL suites on Linux and Windows.
  • Final integrated head: 59709aed97d045dbd9a04093fff5c32ae60a95e3. A fresh release build passes all 48/48 new WFL cases (17 schema/error, eight HTTP, 23 process), 147/147 existing Rust compatibility tests, strict Clippy, formatting, locked fuzz compilation, documentation 36/36, and static hygiene. Runtime source and test trees match the earlier combined consumer candidate df6ad9a2.
  • Final remote CI: CI35505568741 passed at the final head. Linux and Windows program logs explicitly show every new schema/error, HTTP, and process suite executed and passed; total programs were 181 and 180 respectively, with zero failures or timeouts. The full Rust run passed 2,429 tests with 27 existing ignored; strict Clippy, formatting, fuzz, database services, release scripts, and both hygiene jobs passed. Both integration gates passed 157 WFL programs with zero failures and 24 existing skips; each also passed documentation 36/36 and web 3/3. Docker35505568718 and config lint35505568777 also passed.
  • Reproduction commands, Red follow-ups, the original local timeout evidence, cleanup review, and binary SHA-256 are in the evidence record.

Checklist

  • WFL regression scenarios and auditable failing-then-passing evidence
  • Documentation and development diary
  • Local checks and independent technical review
  • Remote Linux and Windows CI inspected at the final revision
  • Authorized merge completed and GitHub merge SHA verified
  • Post-merge CI, automatic version bump to 26.9.15 and tag verified
  • Nightly publication verified separately by the coordinating maintainer

@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c50376b2-b0fc-4d25-9c44-fd84113f4325

📥 Commits

Reviewing files that changed from the base of the PR and between eecd658 and 59709ae.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • fuzz/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (69)
  • Cargo.toml
  • Docs/03-language-basics/error-handling.md
  • Docs/04-advanced-features/databases.md
  • Docs/04-advanced-features/subprocess-execution.md
  • Docs/05-standard-library/core-module.md
  • Docs/reference/configuration-reference.md
  • Docs/reference/keyword-reference.md
  • Docs/reference/reserved-keywords.md
  • Engineering/evidence/2026-09-20-orm-prerequisites-red.md
  • Engineering/evidence/2026-09-20-orm-prerequisites.md
  • Engineering/evidence/2026-09-20-process-lifecycle.md
  • History/dev-diary/2026/2026-09-20-application-errors-schema-transactions.md
  • History/dev-diary/2026/2026-09-20-owned-process-results.md
  • TestPrograms/application_errors_test.wfl
  • TestPrograms/database_schema_compatibility_test.wfl
  • TestPrograms/database_schema_lifecycle_test.wfl
  • TestPrograms/database_schema_lock_test.wfl
  • TestPrograms/database_schema_recovery_test.wfl
  • TestPrograms/database_schema_transaction_test.wfl
  • TestPrograms/process/.wflcfg
  • TestPrograms/process/failure-cleanup.test.wfl
  • TestPrograms/process/lifecycle.test.wfl
  • TestPrograms/process/ownership.test.wfl
  • TestPrograms/process/runtime-location.test.wfl
  • TestPrograms/process/timeout-diagnostics.test.wfl
  • TestPrograms/schema_cancellation/.wflcfg
  • TestPrograms/schema_cancellation/cancellation.test.wfl
  • fuzz/seeds/fuzz_parser/seed_schema_transaction.wfl
  • fuzz/seeds/fuzz_parser/seed_schema_transaction_duplicate.wfl
  • fuzz/seeds/fuzz_parser/seed_schema_transaction_incomplete.wfl
  • src/analyzer/mod.rs
  • src/builtins.rs
  • src/fixer/source.rs
  • src/interpreter/database.rs
  • src/interpreter/database/schema.rs
  • src/interpreter/mod.rs
  • src/interpreter/owned_process.rs
  • src/linter/layout.rs
  • src/main.rs
  • src/parser/ast.rs
  • src/parser/mod.rs
  • src/parser/stmt/actions.rs
  • src/parser/stmt/database.rs
  • src/parser/stmt/processes.rs
  • src/stdlib/core.rs
  • src/stdlib/typechecker.rs
  • src/typechecker/mod.rs
  • tests/fixtures/application-errors/library.wfl
  • tests/fixtures/application-errors/uncaught.wfl
  • tests/fixtures/process/.wflcfg
  • tests/fixtures/process/assertion-close.test.wfl
  • tests/fixtures/process/cwd.wfl
  • tests/fixtures/process/error.wfl
  • tests/fixtures/process/exit-alias.wfl
  • tests/fixtures/process/exit-camel.wfl
  • tests/fixtures/process/exit-high.wfl
  • tests/fixtures/process/exit.wfl
  • tests/fixtures/process/flood.wfl
  • tests/fixtures/process/late-write.wfl
  • tests/fixtures/process/nested-driver.wfl
  • tests/fixtures/process/policy/.wflcfg
  • tests/fixtures/process/policy/cwd-policy.wfl
  • tests/fixtures/process/runtime-location.wfl
  • tests/fixtures/process/timeout-diagnostics.wfl
  • tests/fixtures/schema-transactions/cancellation-client.wfl
  • tests/fixtures/schema-transactions/cancellation-server.wfl
  • tests/fixtures/schema-transactions/exit.wfl
  • tests/fixtures/schema-transactions/interrupted.wfl
  • tests/typechecker_statement_operand_contract_test.rs
 _________________________________________________________________
< This is Sparta! And I'm here to kick bugs into the pit of doom. >
 -----------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-20T09:25:57.803953Z 9defb44 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9defb44208

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +34 to +36
sqlx::query("PRAGMA busy_timeout = 5000")
.execute(transaction.connection())
.await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the connection's previous busy timeout

Executing a schema transaction permanently overwrites the acquired connection's busy_timeout with 5000 ms because cleanup restores only foreign_keys. This is directly observable with a single-connection in-memory database, and it also affects whichever pooled file connection is reused: a program that configured a shorter or longer timeout before the migration silently gets different lock-wait behavior afterward, including after failed begins or rollbacks. Capture the previous pragma value and restore it before returning the connection to the pool.

Useful? React with 👍 / 👎.

)),
};
}
restore_connection(self.connection.take().expect("owned connection")).await

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish post-commit cleanup failures from failed commits

If COMMIT succeeds but restoring foreign_keys fails, this returns an ordinary error whose cleanup message tells the caller to retry even though the schema and migration-ledger writes are already durable. A caller that retries the migration rather than re-reading its ledger can repeat non-idempotent work or report a false rollback. Preserve the committed outcome explicitly in this error path and direct callers to inspect the committed state instead of treating it as an aborted transaction.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Devin Review

Comment on lines +34 to +36
sqlx::query("PRAGMA busy_timeout = 5000")
.execute(transaction.connection())
.await?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Schema transactions overwrite busy timeout

After a schema transaction, busy_timeout remains 5000 instead of retaining the connection's prior setting. In-memory handles reuse that connection, so later ordinary statements can unexpectedly wait five seconds.

Learn more

PRAGMA busy_timeout is connection-local and remains active until changed. Schema mode changes it only to bound BEGIN IMMEDIATE, but successful commit, rollback, failed setup, and cancellation all return or close the connection without restoring the previous value. This is deterministic for sqlite::memory:, whose pool contains exactly one connection.

Example: A program sets PRAGMA busy_timeout = 100, runs one schema transaction, then reads PRAGMA busy_timeout. It gets 5000, and a later ordinary write can wait five seconds instead of 100 milliseconds.

Recommended fix: Read and retain the connection's existing busy_timeout before changing it. Restore that value alongside foreign_keys on every success, rollback, setup-failure, and cancellation cleanup path; discard the connection if restoration fails.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/stdlib/core.rs
Comment on lines +40 to +42
Value::Text(message) if !message.trim().is_empty() => {
Err(RuntimeError::new(message.to_string(), 0, 0))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Expression-form errors expose runtime wrapper

With raise_error of message, error_message includes an internal native-function and line-zero wrapper. The FunctionCall path stringifies native errors, unlike ActionCall, so the documented forms produce different diagnostics.

Learn more

The parser represents raise_error of message as Expression::FunctionCall, while call raise_error with message becomes Expression::ActionCall. The native returns a RuntimeError containing only the application message. The expression evaluator converts that error to text and wraps it in a new RuntimeError, adding internal location and function text; the action-call evaluator only updates the original location.

Example: raise_error of "Save failed" is caught with an error_message resembling Error in native function: Runtime error at line 0, column 0: Save failed. call raise_error with "Save failed" yields Save failed.

Recommended fix: Make native invocation through Expression::FunctionCall preserve the original RuntimeError and replace only its call-site line and column, matching the Expression::ActionCall path. Check existing native-call diagnostics before applying this behavior globally.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +28 to +45
let begin = async {
let connection = pool.acquire().await?;
// Install the cancellation guard before touching connection state.
let mut transaction = Self {
connection: Some(connection),
};
sqlx::query("PRAGMA busy_timeout = 5000")
.execute(transaction.connection())
.await?;
sqlx::query("PRAGMA foreign_keys = OFF")
.execute(transaction.connection())
.await?;
sqlx::query("BEGIN IMMEDIATE")
.execute(transaction.connection())
.await?;
Ok::<_, sqlx::Error>(transaction)
};
match tokio::time::timeout(LOCK_WAIT, begin).await {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Lock diagnostic conflates pool exhaustion

The five-second bound includes pool.acquire(), so pool exhaustion reports a competing database writer. Review whether acquisition needs a distinct diagnostic or timeout.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

…-prerequisites

# Conflicts:
#	Docs/reference/keyword-reference.md
#	Docs/reference/reserved-keywords.md
#	src/fixer/source.rs
# Conflicts:
#	Docs/05-standard-library/core-module.md
#	src/builtins.rs
#	src/stdlib/core.rs
@logbie
logbie merged commit 8d82d78 into main Sep 20, 2026
18 of 19 checks passed
@logbie
logbie deleted the codex/orm-transaction-prerequisites branch September 20, 2026 10:47
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.

1 participant