This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 37f54717f fix(c/driver/postgresql): improve SQLSTATE error mapping
(#4587)
37f54717f is described below
commit 37f54717f3541956e7a26863eb0fc275094653da
Author: Jeremy Tan <[email protected]>
AuthorDate: Mon Jul 27 14:33:47 2026 +1000
fix(c/driver/postgresql): improve SQLSTATE error mapping (#4587)
Fixes #4579
---
c/driver/postgresql/CMakeLists.txt | 2 +
c/driver/postgresql/error.cc | 57 ++++++++++++++++++++++++++++
c/driver/postgresql/error.h | 15 ++------
c/driver/postgresql/error_test.cc | 69 ++++++++++++++++++++++++++++++++++
c/driver/postgresql/postgresql_test.cc | 6 +--
5 files changed, 135 insertions(+), 14 deletions(-)
diff --git a/c/driver/postgresql/CMakeLists.txt
b/c/driver/postgresql/CMakeLists.txt
index 71dcbdc4a..b18dab6d4 100644
--- a/c/driver/postgresql/CMakeLists.txt
+++ b/c/driver/postgresql/CMakeLists.txt
@@ -81,6 +81,8 @@ if(ADBC_BUILD_TESTS)
EXTRA_LABELS
driver-postgresql
SOURCES
+ error.cc
+ error_test.cc
postgres_type_test.cc
postgresql_test.cc
EXTRA_LINK_LIBS
diff --git a/c/driver/postgresql/error.cc b/c/driver/postgresql/error.cc
index d21edb68f..2bd78d89e 100644
--- a/c/driver/postgresql/error.cc
+++ b/c/driver/postgresql/error.cc
@@ -21,6 +21,7 @@
#include <cstdio>
#include <cstring>
#include <string>
+#include <string_view>
#include <vector>
#include <libpq-fe.h>
@@ -29,6 +30,62 @@
namespace adbcpq {
+AdbcStatusCode ClassifySqlState(const char* sqlstate) {
+ if (!sqlstate) {
+ return ADBC_STATUS_IO;
+ }
+
+ const std::string_view state(sqlstate);
+
+ // https://www.postgresql.org/docs/current/errcodes-appendix.html
+ // Exact matches must precede class matches since PostgreSQL's classes
contain
+ // errors with distinct ADBC meanings.
+ if (state == "57014") {
+ return ADBC_STATUS_CANCELLED;
+ }
+ if (state == "0A000") {
+ return ADBC_STATUS_NOT_IMPLEMENTED;
+ }
+ if (state == "42501") {
+ return ADBC_STATUS_UNAUTHORIZED;
+ }
+
+ if (state == "42P01" || state == "42P02" || state == "42703" || state ==
"42883" ||
+ state == "42704" || state == "42602" || state == "34000" || state ==
"3D000" ||
+ state == "3F000" || state == "26000" || state == "58P01") {
+ return ADBC_STATUS_NOT_FOUND;
+ }
+
+ if (state == "42701" || state == "42P03" || state == "42P04" || state ==
"42P05" ||
+ state == "42P06" || state == "42P07" || state == "42712" || state ==
"42723" ||
+ state == "42710" || state == "58P02") {
+ return ADBC_STATUS_ALREADY_EXISTS;
+ }
+
+ const auto class_code = state.substr(0, 2);
+ if (class_code == "22") {
+ return ADBC_STATUS_INVALID_DATA;
+ }
+ if (class_code == "23") {
+ return ADBC_STATUS_INTEGRITY;
+ }
+ if (class_code == "28") {
+ return ADBC_STATUS_UNAUTHENTICATED;
+ }
+ if (class_code == "25" || class_code == "2D" || class_code == "3B" ||
+ class_code == "55") {
+ return ADBC_STATUS_INVALID_STATE;
+ }
+ if (class_code == "XX") {
+ return ADBC_STATUS_INTERNAL;
+ }
+ if (class_code == "42") {
+ return ADBC_STATUS_INVALID_ARGUMENT;
+ }
+
+ return ADBC_STATUS_IO;
+}
+
AdbcStatusCode SetError(struct AdbcError* error, PGresult* result, const char*
format,
...) {
if (error && error->release) {
diff --git a/c/driver/postgresql/error.h b/c/driver/postgresql/error.h
index bf8a4b2e4..78bad124b 100644
--- a/c/driver/postgresql/error.h
+++ b/c/driver/postgresql/error.h
@@ -19,6 +19,7 @@
#pragma once
+#include <cstring>
#include <string>
#include <vector>
@@ -71,6 +72,8 @@ AdbcStatusCode SetError(struct AdbcError* error, PGresult*
result, const char* f
#undef ADBC_CHECK_PRINTF_ATTRIBUTE
+AdbcStatusCode ClassifySqlState(const char* sqlstate);
+
template <typename... Args>
Status MakeStatus(PGresult* result, const char* format_string, Args&&... args)
{
auto message = ::fmt::vformat(format_string,
::fmt::make_format_args(args...));
@@ -85,17 +88,7 @@ Status MakeStatus(PGresult* result, const char*
format_string, Args&&... args) {
const char* sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE);
if (sqlstate) {
- // https://www.postgresql.org/docs/current/errcodes-appendix.html
- // This can be extended in the future
- if (std::strcmp(sqlstate, "57014") == 0) {
- code = ADBC_STATUS_CANCELLED;
- } else if (std::strcmp(sqlstate, "42P01") == 0 ||
- std::strcmp(sqlstate, "42602") == 0) {
- code = ADBC_STATUS_NOT_FOUND;
- } else if (std::strncmp(sqlstate, "42", 0) == 0) {
- // Class 42 — Syntax Error or Access Rule Violation
- code = ADBC_STATUS_INVALID_ARGUMENT;
- }
+ code = ClassifySqlState(sqlstate);
}
Status status(code, message);
diff --git a/c/driver/postgresql/error_test.cc
b/c/driver/postgresql/error_test.cc
new file mode 100644
index 000000000..75f3bbb2a
--- /dev/null
+++ b/c/driver/postgresql/error_test.cc
@@ -0,0 +1,69 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include "error.h"
+
+namespace adbcpq {
+
+TEST(PostgresErrorTest, ClassifySqlState) {
+ struct Case {
+ const char* sqlstate;
+ AdbcStatusCode status;
+ };
+
+ const Case kCases[] = {
+ {nullptr, ADBC_STATUS_IO},
+ {"57014", ADBC_STATUS_CANCELLED},
+ {"0A000", ADBC_STATUS_NOT_IMPLEMENTED},
+ {"22012", ADBC_STATUS_INVALID_DATA},
+ {"22P02", ADBC_STATUS_INVALID_DATA},
+ {"23505", ADBC_STATUS_INTEGRITY},
+ {"23503", ADBC_STATUS_INTEGRITY},
+ {"28P01", ADBC_STATUS_UNAUTHENTICATED},
+ {"28000", ADBC_STATUS_UNAUTHENTICATED},
+ {"42501", ADBC_STATUS_UNAUTHORIZED},
+ {"42P01", ADBC_STATUS_NOT_FOUND},
+ {"42P02", ADBC_STATUS_NOT_FOUND},
+ {"42703", ADBC_STATUS_NOT_FOUND},
+ {"42602", ADBC_STATUS_NOT_FOUND},
+ {"3D000", ADBC_STATUS_NOT_FOUND},
+ {"58P01", ADBC_STATUS_NOT_FOUND},
+ {"42701", ADBC_STATUS_ALREADY_EXISTS},
+ {"42P03", ADBC_STATUS_ALREADY_EXISTS},
+ {"42P07", ADBC_STATUS_ALREADY_EXISTS},
+ {"42P05", ADBC_STATUS_ALREADY_EXISTS},
+ {"42712", ADBC_STATUS_ALREADY_EXISTS},
+ {"42710", ADBC_STATUS_ALREADY_EXISTS},
+ {"58P02", ADBC_STATUS_ALREADY_EXISTS},
+ {"25P02", ADBC_STATUS_INVALID_STATE},
+ {"55P03", ADBC_STATUS_INVALID_STATE},
+ {"XX000", ADBC_STATUS_INTERNAL},
+ {"42601", ADBC_STATUS_INVALID_ARGUMENT},
+ {"08006", ADBC_STATUS_IO},
+ {"53000", ADBC_STATUS_IO},
+ {"ZZ999", ADBC_STATUS_IO},
+ };
+
+ for (const auto& test_case : kCases) {
+ EXPECT_EQ(test_case.status, ClassifySqlState(test_case.sqlstate))
+ << (test_case.sqlstate ? test_case.sqlstate : "null");
+ }
+}
+
+} // namespace adbcpq
diff --git a/c/driver/postgresql/postgresql_test.cc
b/c/driver/postgresql/postgresql_test.cc
index d66ffd4ad..5bb9d3061 100644
--- a/c/driver/postgresql/postgresql_test.cc
+++ b/c/driver/postgresql/postgresql_test.cc
@@ -1750,7 +1750,7 @@ TEST_F(PostgresStatementTest, SqlIngestJsonInvalid) {
ASSERT_THAT(AdbcStatementBind(&statement, &batch.value, &schema.value,
&error),
IsOkStatus(&error));
ASSERT_THAT(AdbcStatementExecuteQuery(&statement, nullptr, nullptr, &error),
- IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ IsStatus(ADBC_STATUS_INVALID_DATA, &error));
ASSERT_THAT(error.message, ::testing::HasSubstr("invalid input syntax for
type json"));
}
@@ -2327,13 +2327,13 @@ TEST_F(PostgresStatementTest,
SqlExecuteCopyZeroRowOutputError) {
&reader.rows_affected, &error),
IsOkStatus());
ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
- ASSERT_EQ(reader.MaybeNext(), EINVAL);
+ ASSERT_EQ(reader.MaybeNext(), EIO);
AdbcStatusCode status = ADBC_STATUS_OK;
const struct AdbcError* detail =
AdbcErrorFromArrayStream(&reader.stream.value, &status);
ASSERT_NE(nullptr, detail);
- ASSERT_EQ(ADBC_STATUS_INVALID_ARGUMENT, status);
+ ASSERT_EQ(ADBC_STATUS_INVALID_DATA, status);
ASSERT_EQ("22023", std::string_view(detail->sqlstate, 5));
}
}