/*
This file is part of GNUnet
(C) 2009, 2010, 2011 Christian Grothoff (and other contributing authors)
GNUnet is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3, 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNUnet; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
/**
* @file datastore/plugin_datastore_postgres.c
* @brief postgres-based datastore backend
* @author Christian Grothoff
*/
#include "platform.h"
#include "gnunet_datastore_plugin.h"
#include <postgresql/libpq-fe.h>
#define DEBUG_POSTGRES GNUNET_EXTRA_LOGGING
/**
* After how many ms "busy" should a DB operation fail for good?
* A low value makes sure that we are more responsive to requests
* (especially PUTs). A high value guarantees a higher success
* rate (SELECTs in iterate can take several seconds despite LIMIT=1).
*
* The default value of 1s should ensure that users do not experience
* huge latencies while at the same time allowing operations to succeed
* with reasonable probability.
*/
#define BUSY_TIMEOUT GNUNET_TIME_UNIT_SECONDS
/**
* Context for all functions in this plugin.
*/
struct Plugin
{
/**
* Our execution environment.
*/
struct GNUNET_DATASTORE_PluginEnvironment *env;
/**
* Native Postgres database handle.
*/
PGconn *dbh;
};
/**
* Check if the result obtained from Postgres has
* the desired status code. If not, log an error, clear the
* result and return GNUNET_SYSERR.
*
* @param plugin global context
* @param ret result to check
* @param expected_status expected return value
* @param command name of SQL command that was run
* @param args arguments to SQL command
* @param line line number for error reporting
* @return GNUNET_OK if the result is acceptable
*/
static int
check_result (struct Plugin *plugin, PGresult * ret, int expected_status,
const char *command, const char *args, int line)
{
if (ret == NULL)
{
GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
"datastore-postgres",
"Postgres failed to allocate result for `%s:%s' at %d\n",
command, args, line);
return GNUNET_SYSERR;
}
if (PQresultStatus (ret) != expected_status)
{
GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
"datastore-postgres",
_("`%s:%s' failed at %s:%d with error: %s"), command, args,
__FILE__, line, PQerrorMessage (plugin->dbh));
PQclear (ret);
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Run simple SQL statement (without results).
*
* @param plugin global context
* @param sql statement to run
* @param line code line for error reporting
*/
static int
pq_exec (struct Plugin *plugin, const char *sql, int line)
{
PGresult *ret;
ret = PQexec (plugin->dbh, sql);
if (GNUNET_OK !=
check_result (plugin, ret, PGRES_COMMAND_OK, "PQexec", sql, line))
return GNUNET_SYSERR;
PQclear (ret);
return GNUNET_OK;
}
/**
* Prepare SQL statement.
*
* @param plugin global context
* @param name name for the prepared SQL statement
* @param sql SQL code to prepare
* @param nparams number of parameters in sql
* @param line code line for error reporting
* @return GNUNET_OK on success
*/
static int
pq_prepare (struct Plugin *plugin, const char *name, const char *sql,
int nparams, int line)
{
PGresult *ret;
ret = PQprepare (plugin->dbh, name, sql, nparams, NULL);
if (GNUNET_OK !=
check_result (plugin, ret, PGRES_COMMAND_OK, "PQprepare", sql, line))
return GNUNET_SYSERR;
PQclear (ret);
return GNUNET_OK;
}
/**
* @brief Get a database handle
*
* @param plugin global context
* @return GNUNET_OK on success, GNUNET_SYSERR on error
*/
static int
init_connection (struct Plugin *plugin)
{
char *conninfo;
PGresult *ret;
/* Open database and precompile statements */
conninfo = NULL;
(void) GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
"datastore-postgres", "CONFIG",
&conninfo);
plugin->dbh = PQconnectdb (conninfo == NULL ? "" : conninfo);
if (NULL == plugin->dbh)
{
/* FIXME: warn about out-of-memory? */
GNUNET_free_non_null (conninfo);
return GNUNET_SYSERR;
}
if (PQstatus (plugin->dbh) != CONNECTION_OK)
{
GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "datastore-postgres",
_
("Unable to initialize Postgres with configuration `%s': %s"),
conninfo, PQerrorMessage (plugin->dbh));
PQfinish (plugin->dbh);
plugin->dbh = NULL;
GNUNET_free_non_null (conninfo);
return GNUNET_SYSERR;
}
GNUNET_free_non_null (conninfo);
ret =
PQexec (plugin->dbh,
"CREATE TABLE gn090 (" " repl INTEGER NOT NULL DEFAULT 0,"
" type INTEGER NOT NULL DEFAULT 0,"
" prio INTEGER NOT NULL DEFAULT 0,"
" anonLevel INTEGER NOT NULL DEFAULT 0,"