Skip to content

fix: install MEOS error handler so errors throw instead of exit()#7

Merged
nhungoc1508 merged 1 commit intomainfrom
fix/meos-error-handler
Apr 25, 2026
Merged

fix: install MEOS error handler so errors throw instead of exit()#7
nhungoc1508 merged 1 commit intomainfrom
fix/meos-error-handler

Conversation

@estebanzimanyi
Copy link
Copy Markdown
Member

Summary

MEOS's default error handler writes the message to stderr and calls exit(EXIT_FAILURE) on errlevel == ERROR (PG elog level 21, carried through via postgres.h). That tears down the entire DuckDB process on any invalid user input — a malformed tstzset literal, a negative precision on asText, etc. — which is both user-hostile in production and made every statement error test in PR #6's parity harness kill the test runner before Catch2 could check it.

This PR installs MobilityduckMeosErrorHandler via meos_initialize_error_handler(...) in LoadInternal, right after meos_initialize(). The handler throws DuckDB::InvalidInputException on errlevel >= 21 (ERROR/FATAL/PANIC), so MEOS errors surface as ordinary query failures with the MEOS message attached. Lower levels (WARNING/NOTICE/INFO) are silently ignored for now — can be wired to DuckDB's logger in a follow-up if useful.

Parity file consequences

  • Deletes the top-of-file error-handler gap note (now obsolete).
  • Unwraps the 3 MEOS-error-specific mode skip blocks covering tstzset parse errors and asText negative-digits — they now run as real statement error assertions.
  • Re-wraps one of those (set('{}'::timestamptz[])) in a fresh skip block with a new gap note: that query fails at the DuckDB cast layer with a Conversion Error before MEOS sees it, because '{}' is PG array-literal syntax that DuckDB rejects as a string literal. The equivalent DuckDB form set(ARRAY[]::TIMESTAMPTZ[]) reaches MEOS but the constructor wrapper in src/temporal/set_functions.cpp returns null instead of calling meos_error(...) on empty input — a separate gap, documented for a later follow-up.

Test plan

  • TZ=UTC ./build/release/test/unittest \"<proj>/test/*\" locally: 747 assertions pass across 13 test cases (up from 739 pre-handler), no regressions.
  • CI validates on linux_amd64, linux_arm64, macos, wasm.
  • After merge: the empty-array constructor follow-up (documented in the new skip block) unblocks that one remaining statement error.

Notes

  • Throwing a C++ exception through a C callback is technically UB by the standard, but is well-defined in GCC/Clang with -fexceptions enabled (which DuckDB and its extensions use). This is the same mechanism DuckDB-spatial uses for GDAL error handlers — precedent in the surrounding ecosystem.
  • errcode is currently unused in the handler. Could be mapped to more specific DuckDB exception types in a follow-up (e.g. MEOS_ERR_WKB_INPUTInvalidTypeException), but one-size-fits-all InvalidInputException is fine as a baseline.

🤖 Generated with Claude Code

MEOS's default error handler writes the message to stderr and calls
exit(EXIT_FAILURE) on errlevel == ERROR (PG elog level 21 carried
through via postgres.h). That tears down the whole DuckDB process on
any invalid user input — a parse error on a malformed tstzset literal,
a negative precision on asText, etc. — which (a) is user-hostile in
production and (b) makes every `statement error` test kill the test
runner before Catch2 can check it. PR #6 surfaced this by trying to
exercise MEOS error paths in the parity harness and crashing CI on the
first parse-error query.

This PR installs `MobilityduckMeosErrorHandler` via
`meos_initialize_error_handler(...)` in LoadInternal, right after
`meos_initialize()`. The handler throws a DuckDB::InvalidInputException
on errlevel >= 21 (ERROR/FATAL/PANIC), so MEOS errors surface as
ordinary query failures with the MEOS message attached. Lower levels
(WARNING/NOTICE/INFO) are silently ignored for now — can be wired to
DuckDB's logger in a follow-up if useful.

Parity file (`test/sql/parity/001_set.test`) consequences:

- Deletes the top-of-file error-handler gap note (now obsolete).
- Unwraps the 3 MEOS-error-specific `mode skip` blocks covering tstzset
  parse errors and asText negative-digits — they run as real
  `statement error` assertions.
- Re-wraps ONE of those (`set('{}'::timestamptz[])`) in a skip block
  with a new gap note: that query fails at the DuckDB cast layer with
  a conversion error before MEOS sees it, because `'{}'` is PG
  array-literal syntax that DuckDB rejects as a string literal. The
  equivalent DuckDB form `set(ARRAY[]::TIMESTAMPTZ[])` reaches MEOS
  but the constructor wrapper in src/temporal/set_functions.cpp
  returns null instead of calling meos_error(...) on empty input —
  a separate gap, documented for a later follow-up.

Verified locally (`TZ=UTC ./build/release/test/unittest
"/path/to/test/*"`): 747 assertions pass across 13 test cases (up from
739 pre-handler), no regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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