Skip to content

Commit e51c83d

Browse files
committed
Improve CPP & PG Exception handlers
1 parent cbc28da commit e51c83d

File tree

9 files changed

+75
-35
lines changed

9 files changed

+75
-35
lines changed

include/pgduckdb/pg/error_data.hpp

Lines changed: 0 additions & 9 deletions
This file was deleted.

include/pgduckdb/pgduckdb_utils.hpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#include "duckdb/common/exception.hpp"
44
#include "duckdb/common/error_data.hpp"
55
#include "pgduckdb/pgduckdb_duckdb.hpp"
6-
#include "pgduckdb/pg/error_data.hpp"
76
#include "pgduckdb/logger.hpp"
7+
#include "pgduckdb/utility/exception.hpp"
88

99
#include <setjmp.h>
1010

@@ -14,6 +14,7 @@ extern "C" {
1414
// Note: these forward-declarations could live in a header under the `pg/` folder.
1515
// But since they are (hopefully) only used in this file, we keep them here.
1616
struct ErrorContextCallback;
17+
struct ErrorData;
1718
struct MemoryContextData;
1819

1920
typedef struct MemoryContextData *MemoryContext;
@@ -26,6 +27,7 @@ extern ErrorData *CopyErrorData();
2627
extern void FlushErrorState();
2728
extern pg_stack_base_t set_stack_base();
2829
extern void restore_stack_base(pg_stack_base_t base);
30+
extern int errdetail(const char *fmt, ...);
2931
}
3032

3133
namespace pgduckdb {
@@ -75,7 +77,7 @@ struct PostgresScopedStackReset {
7577
*/
7678
template <typename Func, Func func, typename... FuncArgs>
7779
typename std::invoke_result<Func, FuncArgs...>::type
78-
__PostgresFunctionGuard__(const char *func_name, FuncArgs... args) {
80+
__PostgresFunctionGuard__(const char *func_name, const char *file_name, int line, FuncArgs... args) {
7981
std::lock_guard<std::recursive_mutex> lock(pgduckdb::GlobalProcessLock::GetLock());
8082
MemoryContext ctx = CurrentMemoryContext;
8183

@@ -102,16 +104,17 @@ __PostgresFunctionGuard__(const char *func_name, FuncArgs... args) {
102104
FlushErrorState();
103105
} else {
104106
// This is a pretty bad situation - we failed to extract the error message.
105-
throw duckdb::Exception(duckdb::ExceptionType::EXECUTOR, "Failed to extract Postgres error message");
107+
pd_log(WARNING, "(PGGuard/%s) Exception in %s:%d: could not extract ErrorData.", func_name, file_name,
108+
line);
109+
throw Exception();
106110
}
107111
} // PG_END_TRY
108112

109-
auto message = duckdb::StringUtil::Format("(PGDuckDB/%s) %s", func_name, pg::GetErrorDataMessage(edata));
110-
throw duckdb::Exception(duckdb::ExceptionType::EXECUTOR, message);
113+
throw pgduckdb::Exception(edata);
111114
}
112115

113116
#define PostgresFunctionGuard(FUNC, ...) \
114-
pgduckdb::__PostgresFunctionGuard__<decltype(&FUNC), &FUNC>(#FUNC, ##__VA_ARGS__)
117+
pgduckdb::__PostgresFunctionGuard__<decltype(&FUNC), &FUNC>(#FUNC, __FILE__, __LINE__, ##__VA_ARGS__)
115118

116119
duckdb::unique_ptr<duckdb::QueryResult> DuckDBQueryOrThrow(duckdb::ClientContext &context, const std::string &query);
117120

include/pgduckdb/utility/cpp_wrapper.hpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <duckdb/common/error_data.hpp>
4+
#include "pgduckdb/utility/exception.hpp"
45

56
extern "C" {
67
#include "postgres.h"
@@ -13,16 +14,33 @@ typename std::invoke_result<Func, FuncArgs &...>::type
1314
__CPPFunctionGuard__(const char *func_name, const char *file_name, int line, FuncArgs &...args) {
1415
const char *error_message = nullptr;
1516
auto pg_es_start = PG_exception_stack;
17+
18+
// We may restore the PG error data from two ways:
19+
// 1. The error data is stored in the exception object and the error type was preserved
20+
// 2. The error pointer was stored in the extra info of the DuckDB exception object
21+
ErrorData *maybe_error_data = nullptr;
1622
try {
1723
return func(args...);
24+
} catch (pgduckdb::Exception &ex) {
25+
if (ex.error_data == nullptr) {
26+
error_message = "Failed to extract Postgres error message";
27+
} else {
28+
maybe_error_data = ex.error_data;
29+
}
1830
} catch (duckdb::Exception &ex) {
1931
duckdb::ErrorData edata(ex.what());
2032
error_message = pstrdup(edata.Message().c_str());
2133
} catch (std::exception &ex) {
2234
const auto msg = ex.what();
2335
if (msg[0] == '{') {
2436
duckdb::ErrorData edata(ex.what());
25-
error_message = pstrdup(edata.Message().c_str());
37+
auto it = edata.ExtraInfo().find("error_data_ptr");
38+
if (it != edata.ExtraInfo().end()) {
39+
// Restore pointer stored in DuckDB exception
40+
maybe_error_data = reinterpret_cast<ErrorData *>(std::stoull(it->second));
41+
} else {
42+
error_message = pstrdup(edata.Message().c_str());
43+
}
2644
} else {
2745
error_message = pstrdup(ex.what());
2846
}
@@ -73,11 +91,14 @@ __CPPFunctionGuard__(const char *func_name, const char *file_name, int line, Fun
7391
PG_exception_stack = pg_es_start;
7492
}
7593

76-
// Simplified version of `elog(ERROR, ...)`, with arguments inlined
77-
if (errstart_cold(ERROR, TEXTDOMAIN)) {
94+
if (maybe_error_data) {
95+
ReThrowError(maybe_error_data);
96+
} else if (errstart_cold(ERROR, TEXTDOMAIN)) {
97+
// Simplified version of `elog(ERROR, ...)`, with arguments inlined
7898
errmsg_internal("(PGDuckDB/%s) %s", func_name, error_message);
7999
errfinish(file_name, line, func_name);
80100
}
101+
81102
pg_unreachable();
82103
}
83104

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#pragma once
2+
3+
#include "duckdb/common/exception.hpp"
4+
5+
extern "C" {
6+
struct ErrorData;
7+
}
8+
9+
namespace pgduckdb {
10+
11+
/*
12+
Custom exception class for PGDuckDB that acts as a shell for ErrorData.
13+
14+
Note: we have to capture the ErrorData pointer in the exception which is
15+
a subclass of duckdb::Exception. This is because in some cases, the exception
16+
is caught by DuckDB and re-thrown. So the original exception is lost.
17+
18+
While it is not ideal to stringify the pointer, it should be valid as long as
19+
it is properly caught by the CPP exception handler in an upper stack.
20+
*/
21+
22+
struct Exception : public duckdb::Exception {
23+
Exception(ErrorData *edata = nullptr)
24+
: duckdb::Exception::Exception(duckdb::ExceptionType::INVALID, "PGDuckDB Exception",
25+
{{"error_data_ptr", std::to_string(reinterpret_cast<uintptr_t>(edata))}}),
26+
error_data(edata) {
27+
}
28+
29+
ErrorData *error_data = nullptr;
30+
};
31+
32+
} // namespace pgduckdb

src/pg/error_data.cpp

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/regression/expected/duckdb_recycle.out

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ EXPLAIN SELECT count(*) FROM ta;
5050
-- Not allowed in a transaction
5151
BEGIN;
5252
CALL duckdb.recycle_ddb();
53-
ERROR: (PGDuckDB/pgduckdb_recycle_ddb_cpp) Executor Error: (PGDuckDB/::PreventInTransactionBlock) duckdb.recycle_ddb() cannot run inside a transaction block
53+
ERROR: duckdb.recycle_ddb() cannot run inside a transaction block
5454
END;
5555
-- Nor in a function
5656
CREATE OR REPLACE FUNCTION f() RETURNS void
@@ -63,9 +63,11 @@ BEGIN
6363
END;
6464
$$;
6565
SET duckdb.force_execution = false;
66+
\set VERBOSITY verbose
6667
SELECT * FROM f();
67-
ERROR: (PGDuckDB/pgduckdb_recycle_ddb_cpp) Executor Error: (PGDuckDB/::PreventInTransactionBlock) duckdb.recycle_ddb() cannot be executed from a function
68+
ERROR: 25001: duckdb.recycle_ddb() cannot be executed from a function
6869
CONTEXT: SQL statement "CALL duckdb.recycle_ddb()"
6970
PL/pgSQL function f() line 3 at CALL
71+
LOCATION: PreventInTransactionBlock, xact.c:3623
7072
DROP TABLE ta;
7173
DROP FUNCTION f;

test/regression/expected/foreign_data_wrapper.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ SELECT 1;
8787
(1 row)
8888

8989
CALL duckdb.enable_motherduck();
90-
ERROR: (PGDuckDB/pgduckdb_enable_motherduck_cpp) Executor Error: (PGDuckDB/::PreventInTransactionBlock) duckdb.enable_motherduck() cannot run inside a transaction block
90+
ERROR: duckdb.enable_motherduck() cannot run inside a transaction block
9191
ROLLBACK;
9292
-- Use helper to enable MD: will fail since there's no token in environment
9393
CALL duckdb.enable_motherduck();
@@ -103,7 +103,7 @@ SELECT 1;
103103
(1 row)
104104

105105
CALL duckdb.enable_motherduck();
106-
ERROR: (PGDuckDB/pgduckdb_enable_motherduck_cpp) Executor Error: (PGDuckDB/::PreventInTransactionBlock) duckdb.enable_motherduck() cannot run inside a transaction block
106+
ERROR: duckdb.enable_motherduck() cannot run inside a transaction block
107107
ROLLBACK;
108108
-- Now MD is enabled again
109109
SELECT * FROM duckdb.is_motherduck_enabled();

test/regression/expected/non_superuser.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ERROR: permission denied for table extensions
2424
-- permissions checked even if it happens straight from DuckDB.
2525
SET duckdb.force_execution = false;
2626
SELECT * FROM duckdb.raw_query($$ SELECT * FROM pgduckdb.public.t $$);
27-
ERROR: (PGDuckDB/pgduckdb_raw_query_cpp) Executor Error: (PGDuckDB/ExecutorStart) permission denied for table t
27+
ERROR: permission denied for table t
2828
SET duckdb.force_execution = true;
2929
-- read_csv from the local filesystem should be disallowed
3030
SELECT count(r['sepal.length']) FROM read_csv('../../data/iris.csv') r;

test/regression/sql/duckdb_recycle.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ BEGIN
1919
CALL duckdb.recycle_ddb();
2020
END;
2121
$$;
22+
2223
SET duckdb.force_execution = false;
24+
25+
\set VERBOSITY verbose
2326
SELECT * FROM f();
2427

2528
DROP TABLE ta;

0 commit comments

Comments
 (0)