From 4098c6d1a27f60027b9e4afa5d3ace98cd969712 Mon Sep 17 00:00:00 2001 From: Aaryan Parik Date: Sat, 23 May 2026 19:10:57 +0000 Subject: [PATCH] psql: Display SQLSTATE macro name in verbose error reports --- src/bin/psql/Makefile | 7 ++- src/bin/psql/command.c | 9 ++++ src/bin/psql/common.c | 28 ++++++++++ src/bin/psql/generate-errcode-names.pl | 71 ++++++++++++++++++++++++++ src/bin/psql/meson.build | 11 ++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/bin/psql/generate-errcode-names.pl diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile index be00326..3b66ec1 100644 --- a/src/bin/psql/Makefile +++ b/src/bin/psql/Makefile @@ -64,6 +64,11 @@ psqlscanslash.c: FLEX_NO_BACKUP=yes tab-complete.c: gen_tabcomplete.pl tab-complete.in.c $(PERL) $^ --outfile $@ +common.o command.o: sqlstate_names.h + +sqlstate_names.h: generate-errcode-names.pl $(top_srcdir)/src/backend/utils/errcodes.txt + $(PERL) $^ $@ + install: all installdirs $(INSTALL_PROGRAM) psql$(X) '$(DESTDIR)$(bindir)/psql$(X)' $(INSTALL_DATA) $(srcdir)/psqlrc.sample '$(DESTDIR)$(datadir)/psqlrc.sample' @@ -77,7 +82,7 @@ uninstall: clean distclean: rm -f psql$(X) $(OBJS) lex.backup rm -rf tmp_check - rm -f sql_help.h sql_help.c psqlscanslash.c tab-complete.c + rm -f sql_help.h sql_help.c psqlscanslash.c tab-complete.c sqlstate_names.h check: $(prove_check) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 01b8f11..7d2731b 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -28,6 +28,7 @@ #include "command.h" #include "common.h" #include "common/logging.h" +#include "sqlstate_names.h" #include "common/string.h" #include "copy.h" #include "describe.h" @@ -1656,6 +1657,14 @@ exec_command_errverbose(PsqlScanState scan_state, bool active_branch) { pg_log_error("%s", msg); PQfreemem(msg); + + const char *sqlstate = PQresultErrorField(pset.last_error_result, PG_DIAG_SQLSTATE); + if (sqlstate) + { + const char *sym = get_sqlstate_symbolic_name(sqlstate); + if (sym) + pg_log_error("SQLSTATE name: %s\n", sym); + } } else puts(_("out of memory")); diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 1a4e2ea..4a54a08 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -22,6 +22,7 @@ #include "command.h" #include "common.h" #include "common/logging.h" +#include "sqlstate_names.h" #include "copy.h" #include "crosstabview.h" #include "fe_utils/cancel.h" @@ -454,8 +455,21 @@ AcceptResult(const PGresult *result, bool show_error) const char *error = PQerrorMessage(pset.db); if (strlen(error)) + { pg_log_info("%s", error); + if (pset.verbosity == PQERRORS_VERBOSE) + { + const char *sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE); + if (sqlstate) + { + const char *sym = get_sqlstate_symbolic_name(sqlstate); + if (sym) + pg_log_info("SQLSTATE name: %s\n", sym); + } + } + } + CheckConnection(); } @@ -1784,8 +1798,22 @@ ExecQueryAndProcessResults(const char *query, const char *error = PQresultErrorMessage(result); if (strlen(error)) + { pg_log_info("%s", error); + if (pset.verbosity == PQERRORS_VERBOSE) + { + const char *sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE); + if (sqlstate) + { + const char *sym = get_sqlstate_symbolic_name(sqlstate); + if (sym) + pg_log_info("SQLSTATE name: %s\n", sym); + } + } + } + + CheckConnection(); if (!is_watch) SetResultVariables(result, false); diff --git a/src/bin/psql/generate-errcode-names.pl b/src/bin/psql/generate-errcode-names.pl new file mode 100644 index 0000000..80d5087 --- /dev/null +++ b/src/bin/psql/generate-errcode-names.pl @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# Generate the sqlstate_names.h header from errcodes.txt +# This maps the 5-character SQLSTATE code to its symbolic macro name for psql's verbose error reports. + +use strict; +use warnings; + +my $infile = shift; +my $outfile = shift; + +open my $in, '<', $infile or die "Could not open $infile: $!"; +open my $out, '>', $outfile or die "Could not open $outfile: $!"; + +print $out "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; +print $out "/* This file maps SQLSTATE codes to their symbolic names. */\n\n"; + +print $out "static const char *\n"; +print $out "get_sqlstate_symbolic_name(const char *sqlstate)\n"; +print $out "{\n"; +print $out " if (sqlstate == NULL)\n"; +print $out " return NULL;\n\n"; + +my %sqlstates; + +while (<$in>) { + chomp; + s/#.*//; # Remove comments + next if /^\s*$/; # Skip empty lines + next if /^Section:/; # Skip section headers + + if (/^([^\s]{5})\s+[EWS]\s+([^\s]+)/) { + my $sqlstate = $1; + my $macro_name = $2; + $sqlstates{$sqlstate} = $macro_name; + } +} + +# We use a simple if-else chain. Since there are around 200-300 codes, a switch statement +# based on strings isn't natively supported in C, but a series of if-strcmp is fast enough +# for error reporting, or we can use a small static array and binary search it. +# A static array and binary search is cleaner and scales better. + +print $out " static const struct {\n"; +print $out " const char sqlstate[6];\n"; +print $out " const char *macro_name;\n"; +print $out " } errcodes[] = {\n"; + +foreach my $sqlstate (sort keys %sqlstates) { + print $out " {\"$sqlstate\", \"$sqlstates{$sqlstate}\"},\n"; +} + +print $out " };\n\n"; +print $out " int low = 0;\n"; +print $out " int high = (sizeof(errcodes) / sizeof(errcodes[0])) - 1;\n\n"; +print $out " while (low <= high) {\n"; +print $out " int mid = low + (high - low) / 2;\n"; +print $out " int cmp = strcmp(errcodes[mid].sqlstate, sqlstate);\n"; +print $out " if (cmp == 0)\n"; +print $out " return errcodes[mid].macro_name;\n"; +print $out " else if (cmp < 0)\n"; +print $out " low = mid + 1;\n"; +print $out " else\n"; +print $out " high = mid - 1;\n"; +print $out " }\n\n"; + +print $out " return NULL;\n"; +print $out "}\n"; + +close $in; +close $out; diff --git a/src/bin/psql/meson.build b/src/bin/psql/meson.build index 922b284..8a11de7 100644 --- a/src/bin/psql/meson.build +++ b/src/bin/psql/meson.build @@ -34,6 +34,17 @@ tabcomplete = custom_target('tabcomplete', generated_sources += tabcomplete psql_sources += tabcomplete +sqlstate_names = custom_target('sqlstate_names', + input: '@SOURCE_ROOT@/src/backend/utils/errcodes.txt', + output: 'sqlstate_names.h', + command: [ + perl, files('generate-errcode-names.pl'), '@INPUT@', '@OUTPUT@', + ], +) +generated_sources += sqlstate_names +psql_sources += sqlstate_names + + sql_help = custom_target('psql_help', output: ['sql_help.c', 'sql_help.h'], depfile: 'sql_help.dep', -- 2.53.0