/**
* Native Postgres database handle.
*/
- PGconn *dbh;
+ struct GNUNET_PQ_Context *dbh;
/**
* Number of key-value pairs in the database.
};
plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
- "datacache-postgres");
+ "datacache-postgres",
+ es,
+ ps);
if (NULL == plugin->dbh)
return GNUNET_SYSERR;
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (plugin->dbh,
- es))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- GNUNET_PQ_prepare_statements (plugin->dbh,
- ps))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
return GNUNET_OK;
}
struct GNUNET_DATACACHE_PluginFunctions *api = cls;
struct Plugin *plugin = api->cls;
- PQfinish (plugin->dbh);
+ GNUNET_PQ_disconnect (plugin->dbh);
GNUNET_free (plugin);
GNUNET_free (api);
return NULL;
/**
* Native Postgres database handle.
*/
- PGconn *dbh;
+ struct GNUNET_PQ_Context *dbh;
};
#undef RESULT_COLUMNS
plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->env->cfg,
- "datastore-postgres");
+ "datastore-postgres",
+ es,
+ ps);
if (NULL == plugin->dbh)
return GNUNET_SYSERR;
-
- if ((GNUNET_OK !=
- GNUNET_PQ_exec_statements (plugin->dbh,
- es)) ||
- (GNUNET_OK !=
- GNUNET_PQ_prepare_statements (plugin->dbh,
- ps)))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
return GNUNET_OK;
}
struct GNUNET_DATASTORE_PluginFunctions *api = cls;
struct Plugin *plugin = api->cls;
- PQfinish (plugin->dbh);
+ GNUNET_PQ_disconnect (plugin->dbh);
GNUNET_free (plugin);
GNUNET_free (api);
return NULL;
* @param scratch_length number of entries left in @a scratch
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
-typedef int (*GNUNET_PQ_QueryConverter) (void *cls,
- const void *data,
- size_t data_len,
- void *param_values[],
- int param_lengths[],
- int param_formats[],
- unsigned int param_length,
- void *scratch[],
- unsigned int scratch_length);
+typedef int
+(*GNUNET_PQ_QueryConverter) (void *cls,
+ const void *data,
+ size_t data_len,
+ void *param_values[],
+ int param_lengths[],
+ int param_formats[],
+ unsigned int param_length,
+ void *scratch[],
+ unsigned int scratch_length);
/**
* #GNUNET_YES if all results could be extracted
* #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
*/
-typedef int (*GNUNET_PQ_ResultConverter) (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst);
+typedef int
+(*GNUNET_PQ_ResultConverter) (void *cls,
+ PGresult *result,
+ int row,
+ const char *fname,
+ size_t *dst_size,
+ void *dst);
/**
* @param cls closure
* @param rd result data to clean up
*/
-typedef void (*GNUNET_PQ_ResultCleanup) (void *cls, void *rd);
+typedef void
+(*GNUNET_PQ_ResultCleanup) (void *cls,
+ void *rd);
/**
/* ************************* pq.c functions ************************ */
+/**
+ * Postgres context.
+ */
+struct GNUNET_PQ_Context;
+
+
/**
* Execute a prepared statement.
*
- * @param db_conn database connection
+ * @param db database context
* @param name name of the prepared statement
* @param params parameters to the statement
* @return postgres result
* @deprecated (should become an internal API)
*/
PGresult *
-GNUNET_PQ_exec_prepared (PGconn *db_conn,
+GNUNET_PQ_exec_prepared (struct GNUNET_PQ_Context *db,
const char *name,
const struct GNUNET_PQ_QueryParam *params);
* Check the @a result's error code to see what happened.
* Also logs errors.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement in
* @param statement_name name of the statement that created @a result
* @param result result to check
* @return status code from the result, mapping PQ status
* @deprecated (low level, let's see if we can do with just the high-level functions)
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_result (PGconn *connection,
+GNUNET_PQ_eval_result (struct GNUNET_PQ_Context *db,
const char *statement_name,
PGresult *result);
* statement in @a connnection using the given @a params. Returns the
* resulting session state.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @return status code from the result, mapping PQ status
* zero; if INSERT was successful, we return one.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_non_select (PGconn *connection,
+GNUNET_PQ_eval_prepared_non_select (struct GNUNET_PQ_Context *db,
const char *statement_name,
const struct GNUNET_PQ_QueryParam *params);
* @param result the postgres result
* @param num_result the number of results in @a result
*/
-typedef void (*GNUNET_PQ_PostgresResultHandler) (void *cls,
- PGresult *result,
- unsigned int num_results);
+typedef void
+(*GNUNET_PQ_PostgresResultHandler) (void *cls,
+ PGresult *result,
+ unsigned int num_results);
/**
* status including the number of results given to @a rh (possibly zero).
* @a rh will not have been called if the return value is negative.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @param rh function to call with the result set, NULL to ignore
* codes to `enum GNUNET_DB_QueryStatus`.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_multi_select (PGconn *connection,
+GNUNET_PQ_eval_prepared_multi_select (struct GNUNET_PQ_Context *db,
const char *statement_name,
const struct GNUNET_PQ_QueryParam *params,
GNUNET_PQ_PostgresResultHandler rh,
* value was #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT. Returns the
* resulting session status.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @param[in,out] rs result specification to use for storing the result of the query
* codes to `enum GNUNET_DB_QueryStatus`.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_singleton_select (
- PGconn *connection,
- const char *statement_name,
- const struct GNUNET_PQ_QueryParam *params,
- struct GNUNET_PQ_ResultSpec *rs);
+GNUNET_PQ_eval_prepared_singleton_select (struct GNUNET_PQ_Context *db,
+ const char *statement_name,
+ const struct
+ GNUNET_PQ_QueryParam *params,
+ struct GNUNET_PQ_ResultSpec *rs);
/* ******************** pq_prepare.c functions ************** */
* Number of arguments included in @e sql.
*/
unsigned int num_arguments;
+
};
/**
* Request creation of prepared statements @a ps from Postgres.
*
- * @param connection connection to prepare the statements for
+ * @param db database to prepare the statements for
* @param ps #GNUNET_PQ_PREPARED_STATEMENT_END-terminated array of prepared
* statements.
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on error
*/
int
-GNUNET_PQ_prepare_statements (PGconn *connection,
+GNUNET_PQ_prepare_statements (struct GNUNET_PQ_Context *db,
const struct GNUNET_PQ_PreparedStatement *ps);
/**
* Request execution of an array of statements @a es from Postgres.
*
- * @param connection connection to execute the statements over
+ * @param pq database to execute the statements in
* @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated array of prepared
* statements.
* @return #GNUNET_OK on success (modulo statements where errors can be ignored)
* #GNUNET_SYSERR on error
*/
int
-GNUNET_PQ_exec_statements (PGconn *connection,
+GNUNET_PQ_exec_statements (struct GNUNET_PQ_Context *db,
const struct GNUNET_PQ_ExecuteStatement *es);
/**
* Create a connection to the Postgres database using @a config_str
* for the configuration. Initialize logging via GNUnet's log
- * routines and disable Postgres's logger.
+ * routines and disable Postgres's logger. Also ensures that the
+ * statements in @a es are executed whenever we (re)connect to the
+ * database, and that the prepared statements in @a ps are "ready".
+ * If statements in @es fail that were created with
+ * #GNUNET_PQ_make_execute(), then the entire operation fails.
*
* @param config_str configuration to use
+ * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
+ * array of statements to execute upon EACH connection, can be NULL
+ * @param ps array of prepared statements to prepare, can be NULL
* @return NULL on error
*/
-PGconn *
-GNUNET_PQ_connect (const char *config_str);
+struct GNUNET_PQ_Context *
+GNUNET_PQ_connect (const char *config_str,
+ const struct GNUNET_PQ_ExecuteStatement *es,
+ const struct GNUNET_PQ_PreparedStatement *ps);
/**
* Connect to a postgres database using the configuration
- * option "CONFIG" in @a section.
+ * option "CONFIG" in @a section. Also ensures that the
+ * statements in @a es are executed whenever we (re)connect to the
+ * database, and that the prepared statements in @a ps are "ready".
*
* @param cfg configuration
* @param section configuration section to use to get Postgres configuration options
+ * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
+ * array of statements to execute upon EACH connection, can be NULL
+ * @param ps array of prepared statements to prepare, can be NULL
* @return the postgres handle, NULL on error
*/
-PGconn *
+struct GNUNET_PQ_Context *
GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *section);
+ const char *section,
+ const struct GNUNET_PQ_ExecuteStatement *es,
+ const struct GNUNET_PQ_PreparedStatement *ps);
+
+
+/**
+ * Reinitialize the database @a db.
+ *
+ * @param db database connection to reinitialize
+ */
+void
+GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db);
+
+
+/**
+ * Disconnect from the database, destroying the prepared statements
+ * and releasing other associated resources.
+ *
+ * @param db database handle to disconnect (will be free'd)
+ */
+void
+GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db);
#endif /* GNUNET_PQ_LIB_H_ */
const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
- * Native Postgres database handle.
+ * Postgres database handle.
*/
- PGconn *dbh;
+ struct GNUNET_PQ_Context *dbh;
};
"WITH OIDS");
const struct GNUNET_PQ_ExecuteStatement *cr;
- plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
- "namecache-postgres");
- if (NULL == plugin->dbh)
- return GNUNET_SYSERR;
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
"namecache-postgres",
{
cr = &es_default;
}
-
{
struct GNUNET_PQ_ExecuteStatement es[] = {
*cr,
"CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
-
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (plugin->dbh,
- es))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
- }
-
- {
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("cache_block",
"INSERT INTO ns096blocks (query, block, expiration_time) VALUES "
GNUNET_PQ_PREPARED_STATEMENT_END
};
- if (GNUNET_OK !=
- GNUNET_PQ_prepare_statements (plugin->dbh,
- ps))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
+ plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
+ "namecache-postgres",
+ es,
+ ps);
}
-
+ if (NULL == plugin->dbh)
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
static void
database_shutdown (struct Plugin *plugin)
{
- PQfinish (plugin->dbh);
+ GNUNET_PQ_disconnect (plugin->dbh);
plugin->dbh = NULL;
}
const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
- * Native Postgres database handle.
+ * Postgres database handle.
*/
- PGconn *dbh;
+ struct GNUNET_PQ_Context *dbh;
};
")"
"WITH OIDS");
const struct GNUNET_PQ_ExecuteStatement *cr;
+ struct GNUNET_PQ_ExecuteStatement sc = GNUNET_PQ_EXECUTE_STATEMENT_END;
- plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
- "namestore-postgres");
- if (NULL == plugin->dbh)
- return GNUNET_SYSERR;
- if (GNUNET_YES ==
- GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
- "namestore-postgres",
- "ASYNC_COMMIT"))
- {
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (plugin->dbh,
- es))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
- }
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
"namestore-postgres",
cr = &es_default;
}
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
+ "namestore-postgres",
+ "ASYNC_COMMIT"))
+ sc = GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off");
+
{
struct GNUNET_PQ_ExecuteStatement es[] = {
*cr,
"ON ns098records (label)"),
GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS zone_label "
"ON ns098records (zone_private_key,label)"),
+ sc,
GNUNET_PQ_EXECUTE_STATEMENT_END
};
-
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (plugin->dbh,
- es))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
- }
-
- {
struct GNUNET_PQ_PreparedStatement ps[] = {
GNUNET_PQ_make_prepare ("store_records",
"INSERT INTO ns098records"
GNUNET_PQ_PREPARED_STATEMENT_END
};
- if (GNUNET_OK !=
- GNUNET_PQ_prepare_statements (plugin->dbh,
- ps))
- {
- PQfinish (plugin->dbh);
- plugin->dbh = NULL;
- return GNUNET_SYSERR;
- }
+ plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
+ "namestore-postgres",
+ es,
+ ps);
}
-
+ if (NULL == plugin->dbh)
+ return GNUNET_SYSERR;
return GNUNET_OK;
}
static void
database_shutdown (struct Plugin *plugin)
{
- PQfinish (plugin->dbh);
+ GNUNET_PQ_disconnect (plugin->dbh);
plugin->dbh = NULL;
}
libgnunetpq_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
$(GN_LIB_LDFLAGS) \
- -version-info 0:0:0
+ -version-info 1:0:0
if ENABLE_TEST_RUN
TESTS = \
/*
This file is part of GNUnet
- Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+ Copyright (C) 2014, 2015, 2016, 2017, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
-
+#include "pq.h"
/**
* Execute a prepared statement.
*
- * @param db_conn database connection
+ * @param db database handle
* @param name name of the prepared statement
* @param params parameters to the statement
* @return postgres result
*/
PGresult *
-GNUNET_PQ_exec_prepared (PGconn *db_conn,
+GNUNET_PQ_exec_prepared (struct GNUNET_PQ_Context *db,
const char *name,
const struct GNUNET_PQ_QueryParam *params)
{
unsigned int len;
- unsigned int i;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running prepared statement `%s' on %p\n",
name,
- db_conn);
+ db);
/* count the number of parameters */
len = 0;
- for (i = 0; 0 != params[i].num_params; i++)
+ for (unsigned int i = 0; 0 != params[i].num_params; i++)
len += params[i].num_params;
/* new scope to allow stack allocation without alloca */
unsigned int soff;
PGresult *res;
int ret;
+ ConnStatusType status;
off = 0;
soff = 0;
- for (i = 0; 0 != params[i].num_params; i++)
+ for (unsigned int i = 0; 0 != params[i].num_params; i++)
{
const struct GNUNET_PQ_QueryParam *x = ¶ms[i];
"pq",
"Executing prepared SQL statement `%s'\n",
name);
- res = PQexecPrepared (db_conn,
+ res = PQexecPrepared (db->conn,
name,
len,
(const char **) param_values,
param_lengths,
param_formats,
1);
+ if ( (PGRES_COMMAND_OK != PQresultStatus (res)) &&
+ (CONNECTION_OK != (status = PQstatus (db->conn))) )
+ {
+ GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
+ "pq",
+ "Database disconnected on SQL statement `%s' (reconnecting)\n",
+ name);
+ GNUNET_PQ_reconnect (db);
+ res = NULL;
+ }
+
for (off = 0; off < soff; off++)
GNUNET_free (scratch[off]);
return res;
void
GNUNET_PQ_cleanup_result (struct GNUNET_PQ_ResultSpec *rs)
{
- unsigned int i;
-
- for (i = 0; NULL != rs[i].conv; i++)
+ for (unsigned int i = 0; NULL != rs[i].conv; i++)
if (NULL != rs[i].cleaner)
rs[i].cleaner (rs[i].cls,
rs[i].dst);
struct GNUNET_PQ_ResultSpec *rs,
int row)
{
- unsigned int i;
- int ret;
-
- for (i = 0; NULL != rs[i].conv; i++)
+ if (NULL == result)
+ return GNUNET_SYSERR;
+ for (unsigned int i = 0; NULL != rs[i].conv; i++)
{
struct GNUNET_PQ_ResultSpec *spec;
+ int ret;
spec = &rs[i];
ret = spec->conv (spec->cls,
--- /dev/null
+/*
+ This file is part of GNUnet
+ Copyright (C) 2017, 2019 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/**
+ * @file pq/pq.h
+ * @brief shared internal data structures of libgnunetpq
+ * @author Christian Grothoff
+ */
+#ifndef PQ_H
+#define PQ_H
+
+#include "gnunet_util_lib.h"
+#include "gnunet_pq_lib.h"
+
+/**
+ * Handle to Postgres database.
+ */
+struct GNUNET_PQ_Context
+{
+ /**
+ * Actual connection.
+ */
+ PGconn *conn;
+
+ /**
+ * Statements to execute upon connection.
+ */
+ struct GNUNET_PQ_ExecuteStatement *es;
+
+ /**
+ * Prepared statements.
+ */
+ struct GNUNET_PQ_PreparedStatement *ps;
+
+ /**
+ * Configuration to use to connect to the DB.
+ */
+ char *config_str;
+};
+
+#endif
/*
This file is part of GNUnet
- Copyright (C) 2017 GNUnet e.V.
+ Copyright (C) 2017, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
+#include "pq.h"
/**
const PGresult *res)
{
/* do nothing, intentionally */
+ (void) arg;
+ (void) res;
}
/**
* Function called by libpq whenever it wants to log something.
- * We log those using the Taler logger.
+ * We log those using the GNUnet logger.
*
* @param arg the SQL connection that was used
* @param message information about some libpq event
pq_notice_processor_cb (void *arg,
const char *message)
{
+ (void) arg;
GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
"pq",
"%s",
/**
* Create a connection to the Postgres database using @a config_str
* for the configuration. Initialize logging via GNUnet's log
- * routines and disable Postgres's logger.
+ * routines and disable Postgres's logger. Also ensures that the
+ * statements in @a es are executed whenever we (re)connect to the
+ * database, and that the prepared statements in @a ps are "ready".
+ * If statements in @es fail that were created with
+ * #GNUNET_PQ_make_execute(), then the entire operation fails.
+ *
+ * The caller MUST ensure that @a es and @a ps remain allocated and
+ * initialized in memory until #GNUNET_PQ_disconnect() is called,
+ * as they may be needed repeatedly and no copy will be made.
*
* @param config_str configuration to use
+ * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
+ * array of statements to execute upon EACH connection, can be NULL
+ * @param ps array of prepared statements to prepare, can be NULL
* @return NULL on error
*/
-PGconn *
-GNUNET_PQ_connect (const char *config_str)
+struct GNUNET_PQ_Context *
+GNUNET_PQ_connect (const char *config_str,
+ const struct GNUNET_PQ_ExecuteStatement *es,
+ const struct GNUNET_PQ_PreparedStatement *ps)
{
- PGconn *conn;
+ struct GNUNET_PQ_Context *db;
+ unsigned int elen = 0;
+ unsigned int plen = 0;
+
+ if (NULL != es)
+ while (NULL != es[elen].sql)
+ elen++;
+ if (NULL != ps)
+ while (NULL != ps[plen].name)
+ plen++;
+
+ db = GNUNET_new (struct GNUNET_PQ_Context);
+ db->config_str = GNUNET_strdup (config_str);
+ if (0 != elen)
+ {
+ db->es = GNUNET_new_array (elen + 1,
+ struct GNUNET_PQ_ExecuteStatement);
+ memcpy (db->es,
+ es,
+ elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
+ }
+ if (0 != plen)
+ {
+ db->ps = GNUNET_new_array (plen + 1,
+ struct GNUNET_PQ_PreparedStatement);
+ memcpy (db->ps,
+ ps,
+ plen * sizeof (struct GNUNET_PQ_PreparedStatement));
+ }
+ GNUNET_PQ_reconnect (db);
+ if (NULL == db->conn)
+ {
+ GNUNET_free (db->config_str);
+ GNUNET_free (db);
+ return NULL;
+ }
+ return db;
+}
+
- conn = PQconnectdb (config_str);
- if ((NULL == conn) ||
- (CONNECTION_OK !=
- PQstatus (conn)))
+/**
+ * Reinitialize the database @a db.
+ *
+ * @param db database connection to reinitialize
+ */
+void
+GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
+{
+ if (NULL != db->conn)
+ PQfinish (db->conn);
+ db->conn = PQconnectdb (db->config_str);
+ if ((NULL == db->conn) ||
+ (CONNECTION_OK != PQstatus (db->conn)))
{
GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
"pq",
"Database connection to '%s' failed: %s\n",
- config_str,
- (NULL != conn) ?
- PQerrorMessage (conn)
+ db->config_str,
+ (NULL != db->conn) ?
+ PQerrorMessage (db->conn)
: "PQconnectdb returned NULL");
- if (NULL != conn)
- PQfinish (conn);
- return NULL;
+ if (NULL != db->conn)
+ {
+ PQfinish (db->conn);
+ db->conn = NULL;
+ }
+ return;
}
- PQsetNoticeReceiver (conn,
+ PQsetNoticeReceiver (db->conn,
&pq_notice_receiver_cb,
- conn);
- PQsetNoticeProcessor (conn,
+ db);
+ PQsetNoticeProcessor (db->conn,
&pq_notice_processor_cb,
- conn);
- return conn;
+ db);
+ if ( (NULL != db->es) &&
+ (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (db,
+ db->es)) )
+ {
+ PQfinish (db->conn);
+ db->conn = NULL;
+ return;
+ }
+ if ( (NULL != db->ps) &&
+ (GNUNET_OK !=
+ GNUNET_PQ_prepare_statements (db,
+ db->ps)) )
+ {
+ PQfinish (db->conn);
+ db->conn = NULL;
+ return;
+ }
}
/**
* Connect to a postgres database using the configuration
- * option "CONFIG" in @a section.
+ * option "CONFIG" in @a section. Also ensures that the
+ * statements in @a es are executed whenever we (re)connect to the
+ * database, and that the prepared statements in @a ps are "ready".
+ *
+ * The caller MUST ensure that @a es and @a ps remain allocated and
+ * initialized in memory until #GNUNET_PQ_disconnect() is called,
+ * as they may be needed repeatedly and no copy will be made.
*
* @param cfg configuration
* @param section configuration section to use to get Postgres configuration options
+ * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
+ * array of statements to execute upon EACH connection, can be NULL
+ * @param ps array of prepared statements to prepare, can be NULL
* @return the postgres handle, NULL on error
*/
-PGconn *
+struct GNUNET_PQ_Context *
GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *section)
+ const char *section,
+ const struct GNUNET_PQ_ExecuteStatement *es,
+ const struct GNUNET_PQ_PreparedStatement *ps)
{
- PGconn *dbh;
+ struct GNUNET_PQ_Context *db;
char *conninfo;
- /* Open database and precompile statements */
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
"CONFIG",
&conninfo))
conninfo = NULL;
- dbh = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo);
+ db = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo,
+ es,
+ ps);
GNUNET_free_non_null (conninfo);
- return dbh;
+ return db;
}
+/**
+ * Disconnect from the database, destroying the prepared statements
+ * and releasing other associated resources.
+ *
+ * @param db database handle to disconnect (will be free'd)
+ */
+void
+GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
+{
+ GNUNET_free_non_null (db->es);
+ GNUNET_free_non_null (db->ps);
+ PQfinish (db->conn);
+ GNUNET_free (db);
+}
+
/* end of pq/pq_connect.c */
/*
This file is part of GNUnet
- Copyright (C) 2017 GNUnet e.V.
+ Copyright (C) 2017, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
+#include "pq.h"
/**
* Check the @a result's error code to see what happened.
* Also logs errors.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement that created @a result
* @param result result to check
* @return status code from the result, mapping PQ status
* @deprecated (low level, let's see if we can do with just the high-level functions)
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_result (PGconn *connection,
+GNUNET_PQ_eval_result (struct GNUNET_PQ_Context *db,
const char *statement_name,
PGresult *result)
{
ExecStatusType est;
+ if (NULL == result)
+ return GNUNET_DB_STATUS_SOFT_ERROR;
est = PQresultStatus (result);
if ((PGRES_COMMAND_OK != est) &&
(PGRES_TUPLES_OK != est))
{
const char *sqlstate;
+ ConnStatusType status;
+
+ if (CONNECTION_OK != (status = PQstatus (db->conn)))
+ {
+ GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
+ "pq",
+ "Database connection failed during query `%s': %d (reconnecting)\n",
+ statement_name,
+ status);
+ GNUNET_PQ_reconnect (db);
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ }
sqlstate = PQresultErrorField (result,
PG_DIAG_SQLSTATE);
PG_DIAG_MESSAGE_DETAIL),
PQresultErrorMessage (result),
PQresStatus (PQresultStatus (result)),
- PQerrorMessage (connection));
+ PQerrorMessage (db->conn));
return GNUNET_DB_STATUS_SOFT_ERROR;
}
if (0 == strcmp (sqlstate,
PG_DIAG_MESSAGE_DETAIL),
PQresultErrorMessage (result),
PQresStatus (PQresultStatus (result)),
- PQerrorMessage (connection));
+ PQerrorMessage (db->conn));
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
PG_DIAG_MESSAGE_DETAIL),
PQresultErrorMessage (result),
PQresStatus (PQresultStatus (result)),
- PQerrorMessage (connection));
+ PQerrorMessage (db->conn));
return GNUNET_DB_STATUS_HARD_ERROR;
}
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
* statement in @a connnection using the given @a params. Returns the
* resulting session state.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @return status code from the result, mapping PQ status
* zero; if INSERT was successful, we return one.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_non_select (PGconn *connection,
+GNUNET_PQ_eval_prepared_non_select (struct GNUNET_PQ_Context *db,
const char *statement_name,
const struct GNUNET_PQ_QueryParam *params)
{
PGresult *result;
enum GNUNET_DB_QueryStatus qs;
- result = GNUNET_PQ_exec_prepared (connection,
+ result = GNUNET_PQ_exec_prepared (db,
statement_name,
params);
- qs = GNUNET_PQ_eval_result (connection,
+ if (NULL == result)
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ qs = GNUNET_PQ_eval_result (db,
statement_name,
result);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
* status including the number of results given to @a rh (possibly zero).
* @a rh will not have been called if the return value is negative.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @param rh function to call with the result set, NULL to ignore
* codes to `enum GNUNET_DB_QueryStatus`.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_multi_select (PGconn *connection,
+GNUNET_PQ_eval_prepared_multi_select (struct GNUNET_PQ_Context *db,
const char *statement_name,
const struct GNUNET_PQ_QueryParam *params,
GNUNET_PQ_PostgresResultHandler rh,
enum GNUNET_DB_QueryStatus qs;
unsigned int ret;
- result = GNUNET_PQ_exec_prepared (connection,
+ result = GNUNET_PQ_exec_prepared (db,
statement_name,
params);
- qs = GNUNET_PQ_eval_result (connection,
+ if (NULL == result)
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ qs = GNUNET_PQ_eval_result (db,
statement_name,
result);
if (qs < 0)
* value was #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT. Returns the
* resulting session status.
*
- * @param connection connection to execute the statement in
+ * @param db database to execute the statement with
* @param statement_name name of the statement
* @param params parameters to give to the statement (#GNUNET_PQ_query_param_end-terminated)
* @param[in,out] rs result specification to use for storing the result of the query
* codes to `enum GNUNET_DB_QueryStatus`.
*/
enum GNUNET_DB_QueryStatus
-GNUNET_PQ_eval_prepared_singleton_select (PGconn *connection,
+GNUNET_PQ_eval_prepared_singleton_select (struct GNUNET_PQ_Context *db,
const char *statement_name,
const struct
GNUNET_PQ_QueryParam *params,
PGresult *result;
enum GNUNET_DB_QueryStatus qs;
- result = GNUNET_PQ_exec_prepared (connection,
+ result = GNUNET_PQ_exec_prepared (db,
statement_name,
params);
- qs = GNUNET_PQ_eval_result (connection,
+ if (NULL == result)
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ qs = GNUNET_PQ_eval_result (db,
statement_name,
result);
if (qs < 0)
/*
This file is part of GNUnet
- Copyright (C) 2017 GNUnet e.V.
+ Copyright (C) 2017, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
+#include "pq.h"
/**
/**
* Request execution of an array of statements @a es from Postgres.
*
- * @param connection connection to execute the statements over
+ * @param db database to execute the statements with
* @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated array of prepared
* statements.
* @return #GNUNET_OK on success (modulo statements where errors can be ignored)
* #GNUNET_SYSERR on error
*/
int
-GNUNET_PQ_exec_statements (PGconn *connection,
+GNUNET_PQ_exec_statements (struct GNUNET_PQ_Context *db,
const struct GNUNET_PQ_ExecuteStatement *es)
{
for (unsigned int i = 0; NULL != es[i].sql; i++)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running statement `%s' on %p\n",
es[i].sql,
- connection);
- result = PQexec (connection,
+ db);
+ result = PQexec (db->conn,
es[i].sql);
if ((GNUNET_NO == es[i].ignore_errors) &&
(PGRES_COMMAND_OK != PQresultStatus (result)))
PG_DIAG_MESSAGE_DETAIL),
PQresultErrorMessage (result),
PQresStatus (PQresultStatus (result)),
- PQerrorMessage (connection));
+ PQerrorMessage (db->conn));
PQclear (result);
return GNUNET_SYSERR;
}
/*
This file is part of GNUnet
- Copyright (C) 2017 GNUnet e.V.
+ Copyright (C) 2017, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
+#include "pq.h"
/**
/**
* Request creation of prepared statements @a ps from Postgres.
*
- * @param connection connection to prepare the statements for
+ * @param db database to prepare the statements for
* @param ps #GNUNET_PQ_PREPARED_STATEMENT_END-terminated array of prepared
* statements.
* @return #GNUNET_OK on success,
* #GNUNET_SYSERR on error
*/
int
-GNUNET_PQ_prepare_statements (PGconn *connection,
+GNUNET_PQ_prepare_statements (struct GNUNET_PQ_Context *db,
const struct GNUNET_PQ_PreparedStatement *ps)
{
+ if (db->ps != ps)
+ {
+ /* add 'ps' to list db->ps of prepared statements to run on reconnect! */
+ unsigned int olen = 0; /* length of existing 'db->ps' array */
+ unsigned int nlen = 0; /* length of 'ps' array */
+ struct GNUNET_PQ_PreparedStatement *rps; /* combined array */
+
+ if (NULL != db->ps)
+ while (NULL != db->ps[olen].name)
+ olen++;
+ while (NULL != ps[nlen].name)
+ nlen++;
+ rps = GNUNET_new_array (olen + nlen + 1,
+ struct GNUNET_PQ_PreparedStatement);
+ if (NULL != db->ps)
+ memcpy (rps,
+ db->ps,
+ olen * sizeof (struct GNUNET_PQ_PreparedStatement));
+ memcpy (&rps[olen],
+ ps,
+ nlen * sizeof (struct GNUNET_PQ_PreparedStatement));
+ GNUNET_free_non_null (db->ps);
+ db->ps = rps;
+ }
+
+ /* actually prepare statements */
for (unsigned int i = 0; NULL != ps[i].name; i++)
{
PGresult *ret;
"Preparing SQL statement `%s' as `%s'\n",
ps[i].sql,
ps[i].name);
- ret = PQprepare (connection,
+ ret = PQprepare (db->conn,
ps[i].name,
ps[i].sql,
ps[i].num_arguments,
_ ("PQprepare (`%s' as `%s') failed with error: %s\n"),
ps[i].sql,
ps[i].name,
- PQerrorMessage (connection));
+ PQerrorMessage (db->conn));
PQclear (ret);
return GNUNET_SYSERR;
}
res = (int64_t *) PQgetvalue (result,
row,
fnum);
- if (INT64_MAX == *res)
+ if (INT64_MAX == GNUNET_ntohll ((uint64_t) *res))
*udst = GNUNET_TIME_UNIT_FOREVER_ABS;
else
udst->abs_value_us = GNUNET_ntohll ((uint64_t) *res);
/*
This file is part of GNUnet
- (C) 2015, 2016 GNUnet e.V.
+ (C) 2015, 2016, 2019 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
* @author Christian Grothoff <christian@grothoff.org>
*/
#include "platform.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_pq_lib.h"
+#include "pq.h"
/**
* Setup prepared statements.
*
- * @param db_conn connection handle to initialize
+ * @param db database handle to initialize
* @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
*/
static int
-postgres_prepare (PGconn *db_conn)
+postgres_prepare (struct GNUNET_PQ_Context *db)
{
- PGresult *result;
-
-#define PREPARE(name, sql, ...) \
- do { \
- result = PQprepare (db_conn, name, sql, __VA_ARGS__); \
- if (PGRES_COMMAND_OK != PQresultStatus (result)) \
- { \
- GNUNET_break (0); \
- PQclear (result); result = NULL; \
- return GNUNET_SYSERR; \
- } \
- PQclear (result); result = NULL; \
- } while (0);
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("test_insert",
+ "INSERT INTO test_pq ("
+ " pub"
+ ",sig"
+ ",abs_time"
+ ",forever"
+ ",hash"
+ ",vsize"
+ ",u16"
+ ",u32"
+ ",u64"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6,"
+ "$7, $8, $9);",
+ 9),
+ GNUNET_PQ_make_prepare ("test_select",
+ "SELECT"
+ " pub"
+ ",sig"
+ ",abs_time"
+ ",forever"
+ ",hash"
+ ",vsize"
+ ",u16"
+ ",u32"
+ ",u64"
+ " FROM test_pq"
+ " ORDER BY abs_time DESC "
+ " LIMIT 1;",
+ 0),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
- PREPARE ("test_insert",
- "INSERT INTO test_pq ("
- " pub"
- ",sig"
- ",abs_time"
- ",forever"
- ",hash"
- ",vsize"
- ",u16"
- ",u32"
- ",u64"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6,"
- "$7, $8, $9);",
- 9, NULL);
- PREPARE ("test_select",
- "SELECT"
- " pub"
- ",sig"
- ",abs_time"
- ",forever"
- ",hash"
- ",vsize"
- ",u16"
- ",u32"
- ",u64"
- " FROM test_pq"
- " ORDER BY abs_time DESC "
- " LIMIT 1;",
- 0, NULL);
- return GNUNET_OK;
-#undef PREPARE
+ return GNUNET_PQ_prepare_statements (db,
+ ps);
}
/**
* Run actual test queries.
*
+ * @param db database handle
* @return 0 on success
*/
static int
-run_queries (PGconn *conn)
+run_queries (struct GNUNET_PQ_Context *db)
{
struct GNUNET_CRYPTO_RsaPublicKey *pub;
struct GNUNET_CRYPTO_RsaPublicKey *pub2 = NULL;
GNUNET_PQ_result_spec_end
};
- result = GNUNET_PQ_exec_prepared (conn,
+ result = GNUNET_PQ_exec_prepared (db,
"test_insert",
params_insert);
if (PGRES_COMMAND_OK != PQresultStatus (result))
}
PQclear (result);
- result = GNUNET_PQ_exec_prepared (conn,
+ result = GNUNET_PQ_exec_prepared (db,
"test_select",
params_select);
if (1 !=
main (int argc,
const char *const argv[])
{
- PGconn *conn;
- PGresult *result;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
+ " pub BYTEA NOT NULL"
+ ",sig BYTEA NOT NULL"
+ ",abs_time INT8 NOT NULL"
+ ",forever INT8 NOT NULL"
+ ",hash BYTEA NOT NULL CHECK(LENGTH(hash)=64)"
+ ",vsize VARCHAR NOT NULL"
+ ",u16 INT2 NOT NULL"
+ ",u32 INT4 NOT NULL"
+ ",u64 INT8 NOT NULL"
+ ")"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+ struct GNUNET_PQ_Context *db;
int ret;
GNUNET_log_setup ("test-pq",
"WARNING",
NULL);
- conn = PQconnectdb ("postgres:///gnunetcheck");
- if (CONNECTION_OK != PQstatus (conn))
+ db = GNUNET_PQ_connect ("postgres:///gnunetcheck",
+ es,
+ NULL);
+ if (CONNECTION_OK != PQstatus (db->conn))
{
fprintf (stderr,
"Cannot run test, database connection failed: %s\n",
- PQerrorMessage (conn));
+ PQerrorMessage (db->conn));
GNUNET_break (0);
- PQfinish (conn);
+ GNUNET_PQ_disconnect (db);
return 77; /* signal test was skipped */
}
-
- result = PQexec (conn,
- "CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
- " pub BYTEA NOT NULL"
- ",sig BYTEA NOT NULL"
- ",abs_time INT8 NOT NULL"
- ",forever INT8 NOT NULL"
- ",hash BYTEA NOT NULL CHECK(LENGTH(hash)=64)"
- ",vsize VARCHAR NOT NULL"
- ",u16 INT2 NOT NULL"
- ",u32 INT4 NOT NULL"
- ",u64 INT8 NOT NULL"
- ")");
- if (PGRES_COMMAND_OK != PQresultStatus (result))
- {
- fprintf (stderr,
- "Failed to create table: %s\n",
- PQerrorMessage (conn));
- PQclear (result);
- PQfinish (conn);
- return 1;
- }
- PQclear (result);
if (GNUNET_OK !=
- postgres_prepare (conn))
+ postgres_prepare (db))
{
GNUNET_break (0);
- PQfinish (conn);
+ GNUNET_PQ_disconnect (db);
return 1;
}
- ret = run_queries (conn);
- result = PQexec (conn,
- "DROP TABLE test_pq");
- if (PGRES_COMMAND_OK != PQresultStatus (result))
+ ret = run_queries (db);
+#if TEST_RESTART
+ fprintf (stderr, "Please restart Postgres database now!\n");
+ sleep (60);
+ ret = run_queries (db);
+ fprintf (stderr, "Result: %d (expect: 1 -- if you restarted the DB)\n", ret);
+ ret = run_queries (db);
+ fprintf (stderr, "Result: %d (expect: 0)\n", ret);
+#endif
{
- fprintf (stderr,
- "Failed to create table: %s\n",
- PQerrorMessage (conn));
- PQclear (result);
- PQfinish (conn);
- return 1;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("DROP TABLE test_pq"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (db,
+ es))
+ {
+ fprintf (stderr,
+ "Failed to drop table\n");
+ GNUNET_PQ_disconnect (db);
+ return 1;
+ }
}
- PQclear (result);
- PQfinish (conn);
+ GNUNET_PQ_disconnect (db);
return ret;
}