#include "bdr.h"
#include "fmgr.h"
+#include "funcapi.h"
#include "libpq-fe.h"
#include "miscadmin.h"
return lsn;
}
-/*
- * Make sure the bdr extension is installed on the other end. If it's a known
- * extension but not present in the current DB error out and tell the user to
- * activate BDR then try again.
- */
static void
-bdr_ensure_ext_installed(PGconn *pgconn, Name bdr_conn_name)
+bdr_get_remote_ext_version(PGconn *pgconn, char **default_version,
+ char **installed_version)
{
PGresult *res;
+
const char *q_bdr_installed =
"SELECT default_version, installed_version "
"FROM pg_catalog.pg_available_extensions WHERE name = 'bdr';";
res = PQexec(pgconn, q_bdr_installed);
+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
elog(ERROR, "Unable to get remote bdr extension version; query %s failed with %s: %s\n",
if (PQntuples(res) == 1)
{
- char *default_version PG_USED_FOR_ASSERTS_ONLY;
/*
* bdr ext is known to Pg, check install state.
- *
- * Right now we don't check the installed version or try to install/upgrade.
*/
- default_version = PQgetvalue(res, 0, 0);
- Assert(default_version != NULL);
- if (PQgetisnull(res, 0, 1))
- {
- ereport(ERROR,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("Remote database for BDR connection %s does not have the bdr extension active",
- NameStr(*bdr_conn_name)),
- errdetail("no entry with name 'bdr' in pg_extensions"),
- errhint("add 'bdr' to shared_preload_libraries in postgresql.conf "
- "on the target server and restart it.")));
- }
+ *default_version = pstrdup(PQgetvalue(res, 0, 0));
+ *installed_version = pstrdup(PQgetvalue(res, 0, 0));
}
else if (PQntuples(res) == 0)
{
/* bdr ext is not known to Pg at all */
- ereport(ERROR,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("Remote PostgreSQL install for bdr connection %s does not have bdr extension installed",
- NameStr(*bdr_conn_name)),
- errdetail("no entry with name 'bdr' in pg_available_extensions; did you install BDR?")));
}
else
{
Assert(false); /* Should not get >1 tuples */
}
+
+ PQclear(res);
+}
+
+/*
+ * Make sure the bdr extension is installed on the other end. If it's a known
+ * extension but not present in the current DB error out and tell the user to
+ * activate BDR then try again.
+ */
+void
+bdr_ensure_ext_installed(PGconn *pgconn)
+{
+ char *default_version = NULL;
+ char *installed_version = NULL;
+
+ bdr_get_remote_ext_version(pgconn, &default_version, &installed_version);
+
+ if (default_version == NULL || strcmp(default_version, "") == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Remote PostgreSQL install for bdr connection does not have bdr extension installed"),
+ errdetail("no entry with name 'bdr' in pg_available_extensions."),
+ errhint("You need to install the BDR extension on the remote end")));
+ }
+
+ if (installed_version == NULL || strcmp(installed_version, "") == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Remote database for BDR connection does not have the bdr extension active"),
+ errdetail("installed_version for entry 'bdr' in pg_available_extensions is blank"),
+ errhint("Run 'CREATE EXTENSION bdr;'")));
+ }
+
+ pfree(default_version);
+ pfree(installed_version);
}
PQfinish(conn);
}
-
/*
* Determine whether we need to initialize the database from a remote
* node and perform the required initialization if so.
PG_ENSURE_ERROR_CLEANUP(bdr_init_replica_conn_close,
PointerGetDatum(&nonrepl_init_conn));
{
- bdr_ensure_ext_installed(nonrepl_init_conn, dbname);
+ bdr_ensure_ext_installed(nonrepl_init_conn);
/* Get the bdr.bdr_nodes status field for our node id from the remote */
status = bdr_get_remote_status(nonrepl_init_conn);
--- /dev/null
+/* -------------------------------------------------------------------------
+ *
+ * bdr_remotecalls.c
+ * Make libpq requests to a remote BDR instance
+ *
+ * Copyright (C) 2012-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * bdr_remotecalls.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "bdr.h"
+
+#include "fmgr.h"
+#include "funcapi.h"
+#include "libpq-fe.h"
+#include "miscadmin.h"
+
+#include "libpq/pqformat.h"
+
+#include "access/heapam.h"
+#include "access/xact.h"
+
+#include "catalog/pg_type.h"
+
+#include "executor/spi.h"
+
+#include "bdr_replication_identifier.h"
+#include "replication/walreceiver.h"
+
+#include "postmaster/bgworker.h"
+#include "postmaster/bgwriter.h"
+
+#include "storage/ipc.h"
+#include "storage/latch.h"
+#include "storage/lwlock.h"
+#include "storage/proc.h"
+#include "storage/shmem.h"
+
+#include "utils/builtins.h"
+#include "utils/pg_lsn.h"
+#include "utils/syscache.h"
+
+PGDLLEXPORT Datum bdr_get_remote_nodeinfo(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(bdr_get_remote_nodeinfo);
+
+/*
+ * Make standard postgres connection, ERROR on failure.
+ */
+PGconn*
+bdr_connect_nonrepl(const char *connstring, const char *appnamesuffix)
+{
+ PGconn *nonrepl_conn;
+ StringInfoData dsn;
+
+ initStringInfo(&dsn);
+ appendStringInfo(&dsn,
+ "%s fallback_application_name='"BDR_LOCALID_FORMAT":%s'",
+ connstring, BDR_LOCALID_FORMAT_ARGS, appnamesuffix);
+
+ /*
+ * Test to see if there's an entry in the remote's bdr.bdr_nodes for our
+ * system identifier. If there is, that'll tell us what stage of startup
+ * we are up to and let us resume an incomplete start.
+ */
+ nonrepl_conn = PQconnectdb(dsn.data);
+ if (PQstatus(nonrepl_conn) != CONNECTION_OK)
+ {
+ ereport(FATAL,
+ (errmsg("could not connect to the server in non-replication mode: %s",
+ PQerrorMessage(nonrepl_conn)),
+ errdetail("dsn was: %s", dsn.data)));
+ }
+
+ return nonrepl_conn;
+}
+
+/*
+ * Close a connection if it exists. The connection passed
+ * is a pointer to a *PGconn; if the target is NULL, it's
+ * presumed not inited or already closed and is ignored.
+ */
+void
+bdr_cleanup_conn_close(int code, Datum connptr)
+{
+ PGconn **conn_p;
+ PGconn *conn;
+
+ conn_p = (PGconn**) DatumGetPointer(connptr);
+ Assert(conn_p != NULL);
+ conn = *conn_p;
+
+ if (conn == NULL)
+ return;
+ if (PQstatus(conn) != CONNECTION_OK)
+ return;
+ PQfinish(conn);
+}
+
+/*
+ * Frees contents of a remote_node_info (but not the struct its self)
+ */
+void
+free_remote_node_info(remote_node_info *ri)
+{
+ pfree(ri->sysid_str);
+ pfree(ri->variant);
+ pfree(ri->version);
+}
+
+/*
+ * The implementation guts of bdr_get_remote_nodeinfo, callable with
+ * a pre-existing connection.
+ */
+void
+bdr_get_remote_nodeinfo_internal(PGconn *conn, struct remote_node_info *ri)
+{
+ PGresult *res, *res2;
+ int i;
+ char *remote_bdr_version_str;
+ int parsed_version_num;
+
+ /* Make sure BDR is actually present and active on the remote */
+ bdr_ensure_ext_installed(conn);
+
+ /* Acquire sysid, timeline, dboid, version and variant */
+ res = PQexec(conn, "SELECT sysid, timeline, dboid, "
+ "bdr.bdr_variant() AS variant, "
+ "bdr.bdr_version() AS version, "
+ "current_setting('is_superuser') AS issuper "
+ "FROM bdr.bdr_get_local_nodeid()");
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ ereport(ERROR,
+ (errmsg("getting remote node id failed"),
+ errdetail("SELECT sysid, timeline, dboid FROM bdr.bdr_get_local_nodeid() failed with: %s",
+ PQerrorMessage(conn))));
+ }
+
+ Assert(PQnfields(res) == 6);
+
+ if (PQntuples(res) != 1)
+ elog(ERROR, "Got %d tuples instead of expected 1", PQntuples(res));
+
+ for (i = 0; i < 6; i++)
+ {
+ if (PQgetisnull(res, 0, i))
+ elog(ERROR, "Unexpectedly null field %s", PQfname(res, i));
+ }
+
+ ri->sysid_str = pstrdup(PQgetvalue(res, 0, 0));
+
+ if (sscanf(ri->sysid_str, UINT64_FORMAT, &ri->sysid) != 1)
+ elog(ERROR, "could not parse remote sysid %s", ri->sysid_str);
+
+ ri->timeline = DatumGetObjectId(
+ DirectFunctionCall1(oidin, CStringGetDatum(PQgetvalue(res, 0, 1))));
+ ri->dboid = DatumGetObjectId(
+ DirectFunctionCall1(oidin, CStringGetDatum(PQgetvalue(res, 0, 2))));
+ ri->variant = pstrdup(PQgetvalue(res, 0, 3));
+ remote_bdr_version_str = PQgetvalue(res, 0, 4);
+ ri->version = pstrdup(remote_bdr_version_str);
+ /* To be filled later: numeric version, min remote version */
+ ri->is_superuser = DatumGetBool(
+ DirectFunctionCall1(boolin, CStringGetDatum(PQgetvalue(res, 0, 5))));
+
+ /*
+ * Even though we should be able to get it from bdr_version_num, always
+ * parse the BDR version so that the parse code gets sanity checked.
+ */
+ parsed_version_num = bdr_parse_version(remote_bdr_version_str, NULL, NULL,
+ NULL, NULL);
+
+ /*
+ * If bdr_version_num() is present then the remote version is greater
+ * than 0.8.0 and we can safely use it and bdr_min_remote_version_num()
+ */
+ res2 = PQexec(conn, "SELECT 1 FROM pg_proc p "
+ "INNER JOIN pg_namespace n ON (p.pronamespace = n.oid) "
+ "WHERE n.nspname = 'bdr' AND p.proname = 'bdr_version_num';");
+
+ if (PQresultStatus(res2) != PGRES_TUPLES_OK)
+ {
+ ereport(ERROR,
+ (errmsg("getting remote available functions failed"),
+ errdetail("Querying remote failed with: %s", PQerrorMessage(conn))));
+ }
+
+ Assert(PQnfields(res2) == 1);
+ Assert(PQntuples(res2) == 0 || PQntuples(res2) == 1);
+
+ if (PQntuples(res2) == 1)
+ {
+ /*
+ * Can safely query for numeric version and min remote version
+ */
+ PQclear(res2);
+
+ res2 = PQexec(conn, "SELECT bdr.bdr_version_num(), "
+ " bdr.bdr_min_remote_version_num();");
+
+ if (PQresultStatus(res2) != PGRES_TUPLES_OK)
+ {
+ ereport(ERROR,
+ (errmsg("getting remote numeric BDR version failed"),
+ errdetail("Querying remote failed with: %s", PQerrorMessage(conn))));
+ }
+
+ Assert(PQnfields(res2) == 2);
+ Assert(PQntuples(res2) == 1);
+
+ ri->version_num = atoi(PQgetvalue(res2, 0, 0));
+ ri->min_remote_version_num = atoi(PQgetvalue(res2, 0, 1));
+
+ if (ri->version_num != parsed_version_num)
+ elog(WARNING, "parsed bdr version %d from string %s != returned bdr version %d",
+ parsed_version_num, remote_bdr_version_str, ri->version_num);
+
+ PQclear(res2);
+ }
+ else
+ {
+ /*
+ * Must be an old version, can't get numeric version.
+ *
+ * All supported versions prior to introduction of bdr_version_num()
+ * have a min_remote_version_num of 000700, we can safely report that.
+ * For the version we have to parse it from the text version.
+ */
+ PQclear(res2);
+
+ /* Shouldn't happen, but as a sanity check: */
+ if (parsed_version_num > 900)
+ elog(ERROR, "Remote BDR version reported as %s (n=%d) but bdr.bdr_version_num() missing",
+ remote_bdr_version_str, parsed_version_num);
+
+ ri->version_num = parsed_version_num;
+ ri->min_remote_version_num = 700;
+ }
+
+ PQclear(res);
+}
+
+Datum
+bdr_get_remote_nodeinfo(PG_FUNCTION_ARGS)
+{
+ const char *remote_node_dsn = text_to_cstring(PG_GETARG_TEXT_P(0));
+ Datum values[8];
+ bool isnull[8] = {false, false, false, false, false, false, false, false};
+ TupleDesc tupleDesc;
+ HeapTuple returnTuple;
+ PGconn *conn;
+
+ if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ conn = bdr_connect_nonrepl(remote_node_dsn, "bdrnodeinfo");
+
+ PG_ENSURE_ERROR_CLEANUP(bdr_cleanup_conn_close,
+ PointerGetDatum(&conn));
+ {
+ struct remote_node_info ri;
+
+ bdr_get_remote_nodeinfo_internal(conn, &ri);
+
+ values[0] = CStringGetTextDatum(ri.sysid_str);
+ values[1] = ObjectIdGetDatum(ri.timeline);
+ values[2] = ObjectIdGetDatum(ri.dboid);
+ values[3] = CStringGetTextDatum(ri.variant);
+ values[4] = CStringGetTextDatum(ri.version);
+ values[5] = Int32GetDatum(ri.version_num);
+ values[6] = Int32GetDatum(ri.min_remote_version_num);
+ values[7] = BoolGetDatum(ri.is_superuser);
+
+ returnTuple = heap_form_tuple(tupleDesc, values, isnull);
+
+ free_remote_node_info(&ri);
+ }
+ PG_END_ENSURE_ERROR_CLEANUP(bdr_cleanup_conn_close,
+ PointerGetDatum(&conn));
+
+ PQfinish(conn);
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(returnTuple));
+}