feat: expose PostgreSQL SQLSTATE and diagnostic fields on errors #581
feat: expose PostgreSQL SQLSTATE and diagnostic fields on errors #581schochastics wants to merge 4 commits into
Conversation
…stic fields Errors raised by libpq now signal structured R conditions (class `pq_error`) carrying the SQLSTATE code and other diagnostic fields (hint, detail, constraint, schema, etc.), enabling programmatic error handling via `tryCatch`/`withCallingHandlers`.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
krlmlr
left a comment
There was a problem hiding this comment.
Thanks.
- Could we also throw rlang errors with this approach?
- I'd like to include snapshot tests, needs
local_edition(3)perhaps - The approach differs from what duckdb is doing. I don't remember how I landed on the duckdb approach, perhaps just because I wanted rlang errors everywhere. I'm curious how and why this even works: R -> C++ -> R -> throwing and the resulting object survives. What does the captured call stack look like?
|
Needs pkgdown config: https://github.com/r-dbi/RPostgres/actions/runs/27250549490/job/80473863145?pr=581#step:19:606 . |
|
Ah sorry, missed the part about the rlang errors. I have another look |
…ntext Server/result errors are now raised with rlang::abort() (via signal_pq_error), so they are nicely formatted rlang conditions while keeping the RPostgres_error_<SQLSTATE> / RPostgres_error classes and the libpq diagnostic fields. Because cpp11 evaluates the R error helper in the global environment, the condition is created detached from the R call stack and its call is empty. A small rethrow() layer (R/rethrow.R) wraps the four cpp11 entry points that can raise these errors (result_create, result_fetch, result_bind, connection_copy_data) and re-attaches the user-facing call, skipping the S4 .local dispatch wrapper. This mirrors duckdb-r's rethrow approach. - Move rlang from Suggests to Imports. - Index RPostgres-conditions in _pkgdown.yml so the docs build. - Add snapshot tests (edition 3) plus rlang-class and calling-context checks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Red CI/CD. |
|
Claude says: The two failing job types are unrelated to these changes:
|
|
Yes, of course. Apologies, thanks for checking. |
fix #579
When a server-side error occurs, RPostgres currently surfaces only libpq's error message string and discards the SQLSTATE code and the other diagnostic fields, which forces users to regex-match localized message text to classify errors.
This PR captures those fields from the failing
PGresultand raises a classed R condition that carries them: the condition gains the class vectorc("RPostgres_error_<SQLSTATE>", "RPostgres_error", "error", "condition")and fieldssqlstate,hint,detail,context,table,column,datatype,constraint, andschema(eachNULLwhen libpq doesn't supply it).Users can now dispatch on a specific code (
tryCatch(..., RPostgres_error_FS002 = ...)) or readcnd$sqlstatedirectly, including for custom codes set viaRAISE EXCEPTION ... USING ERRCODE. The original error message is preserved unchanged, and all errors—including connection-level ones with no result—gain theRPostgres_errorparent class for uniform handling.The condition is built in a small internal R helper invoked from C++; error sites that have a
PGresult(step_run,prepare, and the COPY completion check) now route through it, while result-less paths keep their existing behavior aside from the new parent class. Includes tests covering undefined-table (42P01), syntax (42601), customERRCODE(FS002), and unique-violation (23505) cases.