diff --git a/include/PgSQLErrorClassifier.h b/include/PgSQLErrorClassifier.h index 1be6b1e6e..471ddce40 100644 --- a/include/PgSQLErrorClassifier.h +++ b/include/PgSQLErrorClassifier.h @@ -27,8 +27,9 @@ enum PgSQLErrorAction { * - "08" (connection exception): retryable * - "40" (transaction rollback, including serialization failure): retryable * - "53" (insufficient resources, e.g. too_many_connections): retryable - * - "57" (operator intervention, e.g. admin_shutdown): fatal - * - "58" (system error, e.g. crash_shutdown): fatal + * - "57" (operator intervention, e.g. admin_shutdown, crash_shutdown): fatal + * Exception: "57014" (query_canceled) is non-fatal + * - "58" (system error, e.g. I/O error): fatal * - All others (syntax, constraint, etc.): report to client * * @param sqlstate 5-character SQLSTATE string (e.g., "08006", "42P01"). diff --git a/lib/PgSQLErrorClassifier.cpp b/lib/PgSQLErrorClassifier.cpp index e6279e369..cd48afcce 100644 --- a/lib/PgSQLErrorClassifier.cpp +++ b/lib/PgSQLErrorClassifier.cpp @@ -26,10 +26,16 @@ PgSQLErrorAction classify_pgsql_error(const char *sqlstate) { // Insufficient resources (too many connections) — retryable if (strcmp(cls, "53") == 0) return PGSQL_ERROR_RETRY; - // Operator intervention (admin shutdown, crash) — fatal - if (strcmp(cls, "57") == 0) return PGSQL_ERROR_FATAL; + // Operator intervention — mostly fatal, except query_canceled + if (strcmp(cls, "57") == 0) { + // 57014 = query_canceled — not fatal, report to client + if (strlen(sqlstate) >= 5 && strncmp(sqlstate, "57014", 5) == 0) { + return PGSQL_ERROR_REPORT_TO_CLIENT; + } + return PGSQL_ERROR_FATAL; // admin_shutdown, crash_shutdown, etc. + } - // System error (I/O error, crash shutdown) — fatal + // System error (I/O error) — fatal if (strcmp(cls, "58") == 0) return PGSQL_ERROR_FATAL; // Everything else (syntax, constraints, data, etc.) — report to client diff --git a/test/tap/tests/unit/pgsql_error_classifier_unit-t.cpp b/test/tap/tests/unit/pgsql_error_classifier_unit-t.cpp index 7d0299096..396492415 100644 --- a/test/tap/tests/unit/pgsql_error_classifier_unit-t.cpp +++ b/test/tap/tests/unit/pgsql_error_classifier_unit-t.cpp @@ -43,6 +43,9 @@ static void test_fatal_errors() { "57P02 (crash shutdown): fatal"); ok(classify_pgsql_error("58000") == PGSQL_ERROR_FATAL, "58000 (system error): fatal"); + // 57014 is an exception — query_canceled is NOT fatal + ok(classify_pgsql_error("57014") == PGSQL_ERROR_REPORT_TO_CLIENT, + "57014 (query canceled): not fatal, report to client"); } static void test_non_retryable_errors() { @@ -81,7 +84,7 @@ static void test_retry_conditions() { } int main() { - plan(25); + plan(26); int rc = test_init_minimal(); ok(rc == 0, "test_init_minimal() succeeds"); @@ -92,7 +95,7 @@ int main() { test_non_retryable_errors(); // 5 test_edge_cases(); // 3 test_retry_conditions(); // 5 - // Total: 1+3+2+2+4+5+3+5 = 25 + // Total: 1+3+2+2+5+5+3+5 = 26 test_cleanup_minimal(); return exit_status();