diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index d2a48113157..18199b06ce9 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1060,6 +1060,90 @@ PostgresPollingStatusType PQresetPoll(PGconn *conn);
+
+ PQpingParamsPQpingParams>>
+
+
+ PQpingParams reports the status of the
+ server. It accepts connection parameters identical to those of
+ PQconnectdbParams>, described above. It is not, however,
+ necessary to supply correct user name, password, or database name
+ values to obtain the server status.
+
+
+PGPing PQpingParams(const char **keywords, const char **values, int expand_dbname);
+
+
+ The function returns one of the following values:
+
+
+
+ PQPING_OK
+
+
+ The server is running and appears to be accepting connections.
+
+
+
+
+
+ PQPING_REJECT
+
+
+ The server is running but is in a state that disallows connections
+ (startup, shutdown, or crash recovery).
+
+
+
+
+
+ PQPING_NO_RESPONSE
+
+
+ The server could not be contacted.
+
+
+
+
+
+ PQPING_NO_ATTEMPT
+
+
+ No attempt was made to contact the server, because the supplied
+ parameters were incorrect or there was some client-side problem
+ (for example, out of memory).
+
+
+
+
+
+
+
+
+
+
+
+ PQpingPQping>>
+
+
+ PQping reports the status of the
+ server. It accepts connection parameters identical to those of
+ PQconnectdb>, described above. It is not, however,
+ necessary to supply correct user name, password, or database name
+ values to obtain the server status.
+
+
+PGPing PQping(const char *conninfo);
+
+
+
+
+ The return values are the same as for PQpingParams>.
+
+
+
+
+
@@ -1511,74 +1595,6 @@ int PQbackendPID(const PGconn *conn);
-
- PQpingParamsPQpingParams>>
-
-
- PQpingParams indicates the status of the
- server. The currently recognized parameter key words are the
- same as PQconnectParams>.
-
-
-PGPing PQpingParams(const char **keywords, const char **values, int expand_dbname);
-
-
- It returns one of the following values:
-
-
-
- PQACCESS
-
-
- The server is running and allows access.
-
-
-
-
-
- PQREJECT
-
-
- The server is running but rejected a connection request.
-
-
-
-
-
- PQNORESPONSE
-
-
- The server did not respond.
-
-
-
-
-
-
-
-
-
-
-
- PQpingPQping>>
-
-
- Returns the status of the server.
-
-
-PGPing PQping(const char *conninfo);
-
-
-
-
- This function uses the same conninfo parameter
- key words as PQconnectdb>. It returns the same
- values as PQpingParams> above.
-
-
-
-
-
PQconnectionNeedsPasswordPQconnectionNeedsPassword>>
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index dc30f5de9f5..3cf2afcc27f 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -397,20 +397,21 @@ start_postmaster(void)
/*
* Find the pgport and try a connection
+ *
* Note that the checkpoint parameter enables a Windows service control
* manager checkpoint, it's got nothing to do with database checkpoints!!
*/
static PGPing
test_postmaster_connection(bool do_checkpoint)
{
- PGPing ret = PQACCESS; /* assume success for zero wait */
+ PGPing ret = PQPING_OK; /* assume success for wait == zero */
int i;
char portstr[32];
char *p;
char *q;
char connstr[128]; /* Should be way more than enough! */
- *portstr = '\0';
+ portstr[0] = '\0';
/*
* Look in post_opts for a -p switch.
@@ -453,7 +454,7 @@ test_postmaster_connection(bool do_checkpoint)
* This parsing code isn't amazingly bright either, but it should be okay
* for valid port settings.
*/
- if (!*portstr)
+ if (!portstr[0])
{
char **optlines;
@@ -491,11 +492,11 @@ test_postmaster_connection(bool do_checkpoint)
}
/* Check environment */
- if (!*portstr && getenv("PGPORT") != NULL)
+ if (!portstr[0] && getenv("PGPORT") != NULL)
strlcpy(portstr, getenv("PGPORT"), sizeof(portstr));
/* Else use compiled-in default */
- if (!*portstr)
+ if (!portstr[0])
snprintf(portstr, sizeof(portstr), "%d", DEF_PGPORT);
/*
@@ -507,34 +508,32 @@ test_postmaster_connection(bool do_checkpoint)
for (i = 0; i < wait_seconds; i++)
{
- if ((ret = PQping(connstr)) != PQNORESPONSE)
- return ret;
- else
- {
+ ret = PQping(connstr);
+ if (ret == PQPING_OK || ret == PQPING_NO_ATTEMPT)
+ break;
+ /* No response, or startup still in process; wait */
#if defined(WIN32)
- if (do_checkpoint)
- {
- /*
- * Increment the wait hint by 6 secs (connection timeout +
- * sleep) We must do this to indicate to the SCM that our
- * startup time is changing, otherwise it'll usually send a
- * stop signal after 20 seconds, despite incrementing the
- * checkpoint counter.
+ if (do_checkpoint)
+ {
+ /*
+ * Increment the wait hint by 6 secs (connection timeout +
+ * sleep) We must do this to indicate to the SCM that our
+ * startup time is changing, otherwise it'll usually send a
+ * stop signal after 20 seconds, despite incrementing the
+ * checkpoint counter.
*/
- status.dwWaitHint += 6000;
- status.dwCheckPoint++;
- SetServiceStatus(hStatus, (LPSERVICE_STATUS) &status);
- }
-
- else
-#endif
- print_msg(".");
-
- pg_usleep(1000000); /* 1 sec */
+ status.dwWaitHint += 6000;
+ status.dwCheckPoint++;
+ SetServiceStatus(hStatus, (LPSERVICE_STATUS) &status);
}
+ else
+#endif
+ print_msg(".");
+
+ pg_usleep(1000000); /* 1 sec */
}
- /* value of last call to PQping */
+ /* return result of last call to PQping */
return ret;
}
@@ -738,24 +737,30 @@ do_start(void)
if (do_wait)
{
- int status;
-
print_msg(_("waiting for server to start..."));
- if ((status = test_postmaster_connection(false)) == PQNORESPONSE)
+ switch (test_postmaster_connection(false))
{
- write_stderr(_("%s: could not start server\n"
- "Examine the log output.\n"),
- progname);
- exit(1);
- }
- else
- {
- print_msg(_(" done\n"));
- print_msg(_("server started\n"));
- if (status == PQREJECT)
- write_stderr(_("warning: could not connect; might be caused by invalid authentication or\n"
- "misconfiguration.\n"));
+ case PQPING_OK:
+ print_msg(_(" done\n"));
+ print_msg(_("server started\n"));
+ break;
+ case PQPING_REJECT:
+ print_msg(_(" stopped waiting\n"));
+ print_msg(_("server is still starting up\n"));
+ break;
+ case PQPING_NO_RESPONSE:
+ print_msg(_(" stopped waiting\n"));
+ write_stderr(_("%s: could not start server\n"
+ "Examine the log output.\n"),
+ progname);
+ exit(1);
+ break;
+ case PQPING_NO_ATTEMPT:
+ print_msg(_(" failed\n"));
+ write_stderr(_("%s: could not wait for server because of misconfiguration\n"),
+ progname);
+ exit(1);
}
}
else
@@ -1289,7 +1294,7 @@ pgwin32_ServiceMain(DWORD argc, LPTSTR *argv)
if (do_wait)
{
write_eventlog(EVENTLOG_INFORMATION_TYPE, _("Waiting for server startup...\n"));
- if (test_postmaster_connection(true) == false)
+ if (test_postmaster_connection(true) != PQPING_OK)
{
write_eventlog(EVENTLOG_INFORMATION_TYPE, _("Timed out waiting for server startup\n"));
pgwin32_SetServiceStatus(SERVICE_STOPPED);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 43969ebbd1b..bff9b478f66 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -99,6 +99,8 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
/* This is part of the protocol so just define it */
#define ERRCODE_INVALID_PASSWORD "28P01"
+/* This too */
+#define ERRCODE_CANNOT_CONNECT_NOW "57P03"
/*
* fall back options if they are not specified by arguments or defined
@@ -376,10 +378,15 @@ PQconnectdbParams(const char **keywords,
}
+/*
+ * PQpingParams
+ *
+ * check server status, accepting parameters identical to PQconnectdbParams
+ */
PGPing
PQpingParams(const char **keywords,
- const char **values,
- int expand_dbname)
+ const char **values,
+ int expand_dbname)
{
PGconn *conn = PQconnectStartParams(keywords, values, expand_dbname);
PGPing ret;
@@ -423,6 +430,11 @@ PQconnectdb(const char *conninfo)
return conn;
}
+/*
+ * PQping
+ *
+ * check server status, accepting parameters identical to PQconnectdb
+ */
PGPing
PQping(const char *conninfo)
{
@@ -1261,6 +1273,7 @@ connectDBStart(PGconn *conn)
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid port number: \"%s\"\n"),
conn->pgport);
+ conn->options_valid = false;
goto connect_errReturn;
}
}
@@ -1309,6 +1322,7 @@ connectDBStart(PGconn *conn)
portstr, gai_strerror(ret));
if (addrs)
pg_freeaddrinfo_all(hint.ai_family, addrs);
+ conn->options_valid = false;
goto connect_errReturn;
}
@@ -2555,27 +2569,61 @@ error_return:
/*
* internal_ping
- * Determine if a server is running and if we can connect to it.
+ * Determine if a server is running and if we can connect to it.
+ *
+ * The argument is a connection that's been started, but not completed.
*/
PGPing
internal_ping(PGconn *conn)
{
- if (conn && conn->status != CONNECTION_BAD)
- {
+ /* Say "no attempt" if we never got to PQconnectPoll */
+ if (!conn || !conn->options_valid)
+ return PQPING_NO_ATTEMPT;
+
+ /* Attempt to complete the connection */
+ if (conn->status != CONNECTION_BAD)
(void) connectDBComplete(conn);
- /*
- * If the connection needs a password, we can consider the
- * server as accepting connections.
- */
- if (conn && (conn->status != CONNECTION_BAD ||
- PQconnectionNeedsPassword(conn)))
- return PQACCESS;
- else
- return PQREJECT;
- }
- else
- return PQNORESPONSE;
+ /* Definitely OK if we succeeded */
+ if (conn->status != CONNECTION_BAD)
+ return PQPING_OK;
+
+ /*
+ * Here is the interesting part of "ping": determine the cause of the
+ * failure in sufficient detail to decide what to return. We do not want
+ * to report that the server is not up just because we didn't have a valid
+ * password, for example.
+ *
+ * If we failed to get any ERROR response from the postmaster, report
+ * PQPING_NO_RESPONSE. This result could be somewhat misleading for a
+ * pre-7.4 server, since it won't send back a SQLSTATE, but those are long
+ * out of support. Another corner case where the server could return a
+ * failure without a SQLSTATE is fork failure, but NO_RESPONSE isn't
+ * totally unreasonable for that anyway. We expect that every other
+ * failure case in a modern server will produce a report with a SQLSTATE.
+ *
+ * NOTE: whenever we get around to making libpq generate SQLSTATEs for
+ * client-side errors, we should either not store those into
+ * last_sqlstate, or add an extra flag so we can tell client-side errors
+ * apart from server-side ones.
+ */
+ if (strlen(conn->last_sqlstate) != 5)
+ return PQPING_NO_RESPONSE;
+
+ /*
+ * Report PQPING_REJECT if server says it's not accepting connections.
+ * (We distinguish this case mainly for the convenience of pg_ctl.)
+ */
+ if (strcmp(conn->last_sqlstate, ERRCODE_CANNOT_CONNECT_NOW) == 0)
+ return PQPING_REJECT;
+
+ /*
+ * Any other SQLSTATE can be taken to indicate that the server is up.
+ * Presumably it didn't like our username, password, or database name; or
+ * perhaps it had some transient failure, but that should not be taken as
+ * meaning "it's down".
+ */
+ return PQPING_OK;
}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index b8e4bee31b1..cf9407de7ae 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -758,15 +758,19 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
/*
* Now build the "overall" error message for PQresultErrorMessage.
+ *
+ * Also, save the SQLSTATE in conn->last_sqlstate.
*/
resetPQExpBuffer(&workBuf);
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
appendPQExpBuffer(&workBuf, "%s: ", val);
- if (conn->verbosity == PQERRORS_VERBOSE)
+ val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
+ if (val)
{
- val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
- if (val)
+ if (strlen(val) < sizeof(conn->last_sqlstate))
+ strcpy(conn->last_sqlstate, val);
+ if (conn->verbosity == PQERRORS_VERBOSE)
appendPQExpBuffer(&workBuf, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index d1a6dd413a5..d9e3067894d 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -109,9 +109,10 @@ typedef enum
typedef enum
{
- PQACCESS, /* connected to server */
- PQREJECT, /* server rejected access */
- PQNORESPONSE /* server did not respond */
+ PQPING_OK, /* server is accepting connections */
+ PQPING_REJECT, /* server is alive but rejecting connections */
+ PQPING_NO_RESPONSE, /* could not establish connection */
+ PQPING_NO_ATTEMPT /* connection not attempted (bad params) */
} PGPing;
/* PGconn encapsulates a connection to the backend.
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 5dc7a122f32..f6a7f39065a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -333,6 +333,7 @@ struct pg_conn
PGTransactionStatusType xactStatus; /* never changes to ACTIVE */
PGQueryClass queryclass;
char *last_query; /* last SQL command, or NULL if unknown */
+ char last_sqlstate[6]; /* last reported SQLSTATE */
bool options_valid; /* true if OK to attempt connection */
bool nonblocking; /* whether this connection is using nonblock
* sending semantics */