Route raw workflow queries through the configured storage connection#394
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes two v2/v1 call sites that used DB::table(...) (always the app’s default DB connection) by routing raw query-builder access through the relevant model’s resolved storage connection (workflows.storage.connection). This aligns raw queries with the connection-aware persistence introduced in #391 and prevents “wrong database / missing table” failures when workflow tables live on a dedicated connection.
Changes:
- Route
CommandSequencemaintenance queries forworkflow_commandsthrough the configured command model’s connection/table. - Route
workflow:v1:listqueries throughStoredWorkflow’s resolved connection/table instead of the default connection. - Add a unit regression test that fail-closes when workflow tables exist only on the configured storage connection.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tests/Unit/CommandSequenceStorageConnectionTest.php | Adds a regression test ensuring CommandSequence::reserveNext() executes command-sequence queries on the configured storage connection. |
| src/V2/Support/CommandSequence.php | Replaces DB::table('workflow_commands') with a helper that builds a query on the configured command model’s connection/table. |
| src/Commands/V1ListCommand.php | Builds the v1 listing query via StoredWorkflow’s resolved connection/table instead of the default connection. |
|
Thanks for the PR. This is merged and available in I verified the Packagist package for composer require durable-workflow/workflow:2.0.0-alpha.201 |
Summary
CommandSequence(v2 command-sequence reservation) and theworkflow:v1:listcommand read and wrote workflow tables through
DB::table('...'), which alwaystargets the application's default database connection. This bypasses the
workflows.storage.connectionrouting added in #391, so whenever workflow statelives on a dedicated connection these call sites silently hit the wrong database.
This PR routes those raw queries through the model's resolved connection and adds
a fail-closed regression test.
Root cause
#391 made all workflow persistence connection-aware: every Eloquent model honors
workflows.storage.connectionthrough theResolvesStorageConnectiontrait, andevery migration extends
WorkflowMigration. The routing intentionally lives onthe model class so that both
ConfiguredV2Models::resolve()and hardcoded modelreferences agree on a single connection (no split-brain).
Two code paths reach workflow tables through the raw query builder instead of
a model, so they never picked up that routing:
src/V2/Support/CommandSequence.php—ensureBackfilled()/backfill()useDB::table('workflow_commands')(3 call sites) to read and renumber the commandsequence for a run.
src/Commands/V1ListCommand.php—DB::table('workflows')to list active v1workflows.
DB::table('workflow_commands')resolves the default connection. Whenworkflows.storage.connectionpoints at a separate database (the entire purposeof the key), the
WorkflowRun/WorkflowCommandmodels route to the storageconnection while these raw queries go to the default one. In practice that throws:
because the workflow tables only exist on the storage connection — or, where a
default-connection copy happens to exist, it reads/writes a stale store
(split-brain).
CommandSequence::reserveNext()is on the hot path for starting aworkflow step, so this blocks v2 execution entirely on any dedicated-storage
deployment.
What changed
CommandSequence— a privatecommandsTable()helper returns a querybuilder bound to the configured command model's own connection and table
(
$command->getConnection()->table($command->getTable())). The threeDB::table('workflow_commands')call sites now go through it.V1ListCommand— the listing query is built from the v1 model's resolvedconnection and table (
$storedWorkflow->getConnection()->table($storedWorkflow->getTable())).Both follow the connection-from-the-model pattern already used by
WaterlineEngineSource(DB::connection($model->getConnectionName())), keeping asingle source of truth for where workflow data lives.
Backward compatibility
When
workflows.storage.connectionis unset (the default,null), the modelsresolve to the application's default connection and their existing table names
(
workflow_commands,workflows) — identical to the previousDB::table()behavior. No config or migration change is required, and the default-connection
command-sequence path is covered by the existing v2 unit tests, which continue to
pass unchanged.