Skip to content

fix(user-create): use throwing QueryUtils variants so rollback fires#37

Merged
kojiromike merged 1 commit into
mainfrom
fix/8-rollback-on-sql-errors
May 12, 2026
Merged

fix(user-create): use throwing QueryUtils variants so rollback fires#37
kojiromike merged 1 commit into
mainfrom
fix/8-rollback-on-sql-errors

Conversation

@kojiromike
Copy link
Copy Markdown
Contributor

Summary

  • UserManager::create() wrapped its inserts in try/catch (\Throwable) to roll back on failure, but called the global sqlInsert / sqlStatement helpers — which route SQL errors through HelpfulDie() and exit() on most builds. The catch never fired and a failure mid-create could leave a users row without a matching users_secure row.
  • Switch the in-transaction writes (and the optional UUID backfill) to QueryUtils::sqlInsert / QueryUtils::sqlStatementThrowException, which raise SqlQueryException instead of exiting. The existing catch then runs sqlRollbackTrans() and rethrows.
  • Adds a regression test that arms the next throwing-variant call to raise SqlQueryException and asserts beginCount == 1, commitCount == 0, rollbackCount == 1.

Closes #8.

Test plan

  • composer phpunit — 78 tests, 236 assertions, including the new createRollsBackWhenQueryUtilsThrowsSqlQueryException
  • composer phpcs
  • composer phpstan (level 9, clean)
  • composer rector (dry run, clean)

The transaction in `UserManager::create()` wrapped its inserts in
`try/catch (\Throwable)` to roll back on failure, but routed every write
through OpenEMR's `sqlInsert` / `sqlStatement` globals. Those helpers
funnel SQL errors through `HelpfulDie()`, which calls `exit()` on most
builds — so the catch never fires and the transaction is left in a half
state, with a `users` row but no matching `users_secure` row (the bad
state described in #8).

Switch the writes inside the transaction to
`QueryUtils::sqlInsert` / `QueryUtils::sqlStatementThrowException`,
which raise `SqlQueryException` instead of exiting. The existing
`catch (\Throwable)` then runs `sqlRollbackTrans()` and rethrows.

`assignUuidIfPossible()` gets the same treatment so a SQL error during
the optional UUID backfill doesn't bypass the outer rollback either —
its existing `catch (\Throwable)` keeps that step non-fatal.

Closes #8.
@kojiromike kojiromike merged commit f41a439 into main May 12, 2026
5 checks passed
@kojiromike kojiromike deleted the fix/8-rollback-on-sql-errors branch May 12, 2026 17:48
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.

create() transaction rollback never fires on SQL errors

1 participant