فهرست منبع

- New postgres driver for the new db api in ser
- Support for prepared statements
- Support for oid retrievals from system catalogs
- More flexible data type conversion
- Support for inet data fields

Jan Janak 17 سال پیش
والد
کامیت
7c1f066c89

+ 1 - 0
modules/db_postgres/Makefile

@@ -7,6 +7,7 @@ auto_gen=
 NAME=postgres.so
 NAME=postgres.so
 
 
 # libpq-fe.h locations
 # libpq-fe.h locations
+#DEFS += -DPG_TEST
 DEFS +=-I$(LOCALBASE)/include -I$(LOCALBASE)/pgsql/include  \
 DEFS +=-I$(LOCALBASE)/include -I$(LOCALBASE)/pgsql/include  \
 	-I$(LOCALBASE)/include/postgresql \
 	-I$(LOCALBASE)/include/postgresql \
 	-I/usr/include/postgresql -I/usr/include/postgresql/8.0 \
 	-I/usr/include/postgresql -I/usr/include/postgresql/8.0 \

+ 509 - 0
modules/db_postgres/pg_cmd.c

@@ -0,0 +1,509 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Implementation of functions related to database commands.
+ */
+
+#include "pg_cmd.h"
+#include "pg_sql.h"
+#include "pg_fld.h"
+#include "pg_con.h"
+#include "pg_mod.h"
+#include "pg_uri.h"
+#include "pg_res.h"
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+
+#include <string.h>
+
+/** A global counter used to generate unique command names.
+ * This variable implements a global counter which is used to generate unique
+ * names for server-side commands.
+ */
+static int server_query_no = 0;
+
+static int upload_cmd(db_cmd_t* cmd);
+static void free_pg_params(struct pg_params* cmd);
+
+
+/** Destroys a pg_cmd structure.
+ * This function frees all memory used by pg_cmd structure.
+ * @param cmd A pointer to generic db_cmd command being freed.
+ * @param payload A pointer to pg_cmd structure to be freed.
+ */
+static void pg_cmd_free(db_cmd_t* cmd, struct pg_cmd* payload)
+{
+	db_drv_free(&payload->gen);
+	if (payload->sql_cmd.s) pkg_free(payload->sql_cmd.s);
+	free_pg_params(&payload->params);
+	if (payload->name) pkg_free(payload->name);
+	if (payload->types) PQclear(payload->types);
+	pkg_free(payload);
+}
+
+
+/** Generate a unique name for a server-side PostgreSQL command.
+ * This function generates a unique name for each command that will be used to
+ * identify the prepared statement on the server.
+ *
+ * @param cmd A command whose name is to be generated 
+ * @return A string allocated using pkg_malloc containing the name or NULL on
+ *         error.
+ */
+static int gen_cmd_name(db_cmd_t* cmd)
+{
+	struct pg_cmd* pcmd;
+	char* c;
+	int len;
+
+	pcmd = DB_GET_PAYLOAD(cmd);
+	c = int2str(server_query_no, &len);
+
+	pcmd->name = pkg_malloc(len + 1);
+	if (pcmd->name == NULL) {
+		ERR("postgres: No memory left\n");
+		return -1;
+	}
+	memcpy(pcmd->name, c, len);
+	pcmd->name[len] = '\0';
+	server_query_no++;
+	return 0;
+}
+
+
+/** Creates parameter data structures for PQexecPrepared.
+ * This function creates auxiliary data structures that will be used to pass
+ * parameter value and types to PQexecPrepared.  The function only allocates
+ * memory buffers and determines oids of parameters, actual values will be
+ * assigned by another function at runtime.
+ * @param cmd A command where the data strctures will be created. 
+ * @retval 0 on success.
+ * @retval A negative number on error.
+ */
+static int create_pg_params(db_cmd_t* cmd)
+{
+	int num;
+	struct pg_cmd* pcmd;
+
+	pcmd = DB_GET_PAYLOAD(cmd);
+
+	num = cmd->match_count + cmd->vals_count;
+
+	if (num == 0) return 0;
+	pcmd->params.val = (const char**)pkg_malloc(sizeof(const char*) * num);
+	pcmd->params.len = (int*)pkg_malloc(sizeof(int) * num);
+	pcmd->params.fmt = (int*)pkg_malloc(sizeof(int) * num);
+	
+	if (!pcmd->params.val || !pcmd->params.len || !pcmd->params.fmt) {
+		ERR("postgres: No memory left\n");
+		goto error;
+	}
+	
+	memset(pcmd->params.val, '\0', sizeof(const char*) * num);
+	memset(pcmd->params.len, '\0', sizeof(int) * num);
+	memset(pcmd->params.fmt, '\0', sizeof(int) * num);
+	return 0;
+
+ error:
+	free_pg_params(&pcmd->params);
+	return -1;
+}
+
+
+/**
+ * Free all memory used for PQexecParam parameters. That is
+ * the arrays of Oids, values, lengths, and formats supplied
+ * to PostgreSQL client API functions like PQexecParams.
+ */
+static void free_pg_params(struct pg_params* params)
+{
+	if (params == NULL) return;
+
+	if (params->val) pkg_free(params->val);
+    params->val = NULL;
+
+	if (params->len) pkg_free(params->len);
+	params->len = NULL;
+
+	if (params->fmt) pkg_free(params->fmt);
+	params->fmt = NULL;
+}
+
+
+/** Verify field type compatibility.
+ * This function verifies the types of all parameters of a database command
+ * with the types of corresponding fields on the server to make sure that they
+ * can be converted.
+ * @param cmd A command structure whose parameters are to be checked.
+ * @retval 0 on success.
+ * @retval A negative number if at least one field type does not match.
+ * @todo Store oid and length as part of pg_fld, instead of the arrays used
+ *       as parameters to PQ functions
+ */
+static int check_types(db_cmd_t* cmd) 
+{ 
+	struct pg_cmd* pcmd; 
+	struct pg_con* pcon;
+	
+	pcmd = DB_GET_PAYLOAD(cmd);
+	/* FIXME: The function should take the connection as one of parameters */
+	pcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
+
+	if (pg_check_fld2pg(cmd->match, pcon->oid)) return -1;
+	if (pg_check_fld2pg(cmd->vals, pcon->oid)) return -1;
+	if (pg_check_pg2fld(cmd->result, pcon->oid)) return -1;
+	return 0;
+}
+
+
+static int get_types(db_cmd_t* cmd)
+{
+	struct pg_cmd* pcmd;
+	struct pg_con* pcon;
+
+	pcmd = DB_GET_PAYLOAD(cmd);
+	/* FIXME */
+	pcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
+
+	pcmd->types = PQdescribePrepared(pcon->con, pcmd->name);
+	
+	if (PQresultStatus(pcmd->types) != PGRES_COMMAND_OK) {
+		ERR("postgres: Error while obtaining description of prepared statement\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int pg_cmd(db_cmd_t* cmd)
+{
+	struct pg_cmd* pcmd;
+ 
+	pcmd = (struct pg_cmd*)pkg_malloc(sizeof(struct pg_cmd));
+	if (pcmd == NULL) {
+		ERR("postgres: No memory left\n");
+		goto error;
+	}
+	memset(pcmd, '\0', sizeof(struct pg_cmd));
+	if (db_drv_init(&pcmd->gen, pg_cmd_free) < 0) goto error;
+
+	switch(cmd->type) {
+	case DB_PUT:
+		if (build_insert_sql(&pcmd->sql_cmd, cmd) < 0) goto error;
+		break;
+		
+	case DB_DEL:
+		if (build_delete_sql(&pcmd->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_GET:
+		if (build_select_sql(&pcmd->sql_cmd, cmd) < 0) goto error;
+		break;
+
+	case DB_UPD:
+		if (build_update_sql(&pcmd->sql_cmd, cmd) < 0) goto error;
+		break;
+		
+	case DB_SQL:
+		pcmd->sql_cmd.s = (char*)pkg_malloc(cmd->table.len);
+		if (pcmd->sql_cmd.s == NULL) {
+			ERR("postgres: Out of private memory\n");
+			goto error;
+		}
+		memcpy(pcmd->sql_cmd.s,cmd->table.s, cmd->table.len);
+		pcmd->sql_cmd.len = cmd->table.len;
+        break;
+	}
+
+	DB_SET_PAYLOAD(cmd, pcmd);
+
+	/* Generate a unique name for the command on the server */
+	if (gen_cmd_name(cmd) != 0) goto error; 
+
+	/* Upload the command to the server */
+	if (upload_cmd(cmd) != 0) goto error;
+	
+	/* Obtain the description of the uploaded command, this includes
+	 * information about result and parameter fields */
+	if (get_types(cmd) != 0) goto error;
+
+	/* Update fields based on the information retrieved from the */
+	if (pg_resolve_param_oids(cmd->vals, cmd->match,
+							  cmd->vals_count, cmd->match_count,
+							  pcmd->types)) 
+		goto error;
+	if (pg_resolve_result_oids(cmd->result, cmd->result_count, pcmd->types)) 
+		goto error;
+
+	if (check_types(cmd)) goto error;
+
+	/* Create parameter arrays for PostgreSQL API functions */
+	if (create_pg_params(cmd) < 0) goto error;	
+	return 0;
+
+ error:
+	if (pcmd) {
+		DB_SET_PAYLOAD(cmd, NULL);
+		free_pg_params(&pcmd->params);
+
+		if (pcmd->types) PQclear(pcmd->types);
+		if (pcmd->name) pkg_free(pcmd->name);
+		if (pcmd->sql_cmd.s) pkg_free(pcmd->sql_cmd.s);
+
+		db_drv_free(&pcmd->gen);
+		pkg_free(pcmd);
+	}
+	return -1;
+}
+
+
+int pg_getopt(db_cmd_t* cmd, char* optname, va_list ap)
+{
+	struct pg_cmd* pcmd;
+	long long* id;
+
+	pcmd = (struct pg_cmd*)DB_GET_PAYLOAD(cmd);
+
+	if (!strcasecmp("last_id", optname)) {
+		id = va_arg(ap, long long*);
+		if (id == NULL) {
+			BUG("postgres: NULL pointer passed to 'last_id' option\n");
+			goto error;
+		}
+		return -1;
+	} else {
+		return 1;
+	}
+	return 0;
+
+ error:
+	return -1;
+}
+
+
+int pg_setopt(db_cmd_t* cmd, char* optname, va_list ap)
+{
+	struct pg_cmd* pcmd;
+
+	pcmd = (struct pg_cmd*)DB_GET_PAYLOAD(cmd);
+	return 1;
+}
+
+
+/** Uploads a database command to PostgreSQL server.
+ * This function uploads a pre-compiled database command to PostgreSQL
+ * server using PQprepare.
+ * @param cmd A database command
+ * @retval 0 on success.
+ * @retval A negative number on error.
+ */
+static int upload_cmd(db_cmd_t* cmd)
+{
+	struct pg_cmd* pcmd;
+	struct pg_con* pcon;
+	PGresult* res;
+	int st;
+
+	pcmd = DB_GET_PAYLOAD(cmd);
+	/* FIXME: The function should take the connection as one of parameters */
+	pcon = DB_GET_PAYLOAD(cmd->ctx->con[db_payload_idx]);
+
+	DBG("postgres: Uploading query '%s'='%s'\n", pcmd->name, 
+		pcmd->sql_cmd.s);
+
+	res = PQprepare(pcon->con, pcmd->name, pcmd->sql_cmd.s, 0, NULL);
+	
+	st = PQresultStatus(res);
+	PQclear(res);
+
+	if (st != PGRES_COMMAND_OK && st != PGRES_NONFATAL_ERROR &&
+		st != PGRES_TUPLES_OK) {
+		ERR("postgres: Error while uploading command to server: %d, %s", 
+			st, PQresultErrorMessage(res));
+		ERR("postgres: Command: '%s'\n", pcmd->sql_cmd.s);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+int pg_cmd_exec(db_res_t* res, db_cmd_t* cmd)
+{
+	PGresult* tmp;
+	int i, err, stat;
+	db_con_t* con;
+	struct pg_cmd* pcmd;
+	struct pg_con* pcon;
+	struct pg_uri* puri;
+	struct pg_res* pres;
+
+	/* First things first: retrieve connection info from the currently active
+	 * connection and also mysql payload from the database command
+	 */
+	con = cmd->ctx->con[db_payload_idx];
+	pcmd = DB_GET_PAYLOAD(cmd);
+	pcon = DB_GET_PAYLOAD(con);
+	puri = DB_GET_PAYLOAD(con->uri);
+
+	for(i = 0; i <= pg_retries; i++) {
+		/* Convert parameters from DB-API format to the format accepted
+		 * by PostgreSQL */
+		if (pg_fld2pg(&pcmd->params, 0, pcon->oid, cmd->match, pcon->flags) != 0) 
+			return 1;
+
+		if (pg_fld2pg(&pcmd->params, cmd->match_count,
+					  pcon->oid, cmd->vals, pcon->flags) != 0) return 1;
+		
+		/* Execute the statement */
+		tmp = PQexecPrepared(pcon->con, pcmd->name,
+							 cmd->match_count + cmd->vals_count,
+							 pcmd->params.val, pcmd->params.len,
+							 NULL, 1);
+		if (!tmp) {
+			ERR("postgres: PQexecPrepared returned no result\n");
+			continue;
+		}
+
+		switch(PQresultStatus(tmp)) {
+		case PGRES_COMMAND_OK:
+		case PGRES_NONFATAL_ERROR:
+		case PGRES_TUPLES_OK:
+			if (res) {
+				pres = DB_GET_PAYLOAD(res);
+				pres->res = tmp;
+				pres->rows = PQntuples(tmp);
+			} else {
+				PQclear(tmp);
+			}
+			return 0;
+			
+		default:
+			break;
+		}
+		ERR("postgres: Command on server %s failed: %s: %s\n", 
+			puri->host, PQresStatus(PQresultStatus(tmp)), 
+			PQresultErrorMessage(tmp));
+		PQclear(tmp);
+
+		/* Command failed, first of all determine the status of the connection
+		 * to server */
+		if (PQstatus(pcon->con) != CONNECTION_OK) {
+			INFO("postgres: Connection to server %s disconnected, attempting reconnect\n", 
+				 puri->host);
+			pg_con_disconnect(con);
+			if (pg_con_connect(con)) {
+				INFO("postgres: Failed to reconnect server %s, giving up\n", 
+					 puri->host);
+				return -1;
+			}
+			INFO("postgres: Successfully reconnected server on %s\n", 
+				 puri->host);
+		}
+
+		/* Connection is either connected or has been successfully reconnected, 
+		 * now figure out if the prepared command on the server still exist
+		 */
+		tmp = PQdescribePrepared(pcon->con, pcmd->name);
+		if (tmp == NULL) {
+			ERR("postgres: PQdescribePrepared returned no result\n");
+		    continue;
+		}
+		stat = PQresultStatus(tmp);
+		PQclear(tmp);
+		switch (stat) {
+		case PGRES_COMMAND_OK:
+		case PGRES_NONFATAL_ERROR:
+		case PGRES_TUPLES_OK:
+			INFO("postgres: Command %s on server %s still exists, reusing\n",
+				 pcmd->name, puri->host);
+			/* Command is there, retry */
+			continue;
+		default:
+			break;
+		}
+
+		/* Upload again */
+		INFO("postgres: Command %s on server %s missing, uploading\n",
+			 pcmd->name, puri->host);
+		err = upload_cmd(cmd);
+		if (err < 0) {
+			continue;
+		} else if (err > 0) {
+			/* DB API error, this is a serious problem such
+			 * as memory allocation failure, bail out
+			 */
+			return 1;
+		}
+	}
+
+	INFO("postgres: Failed to execute command %s on server %s, giving up\n",
+		 pcmd->name, puri->host);
+	return -1;
+}
+
+
+int pg_cmd_first(db_res_t* res)
+{
+	struct pg_res* pres;
+
+	pres = DB_GET_PAYLOAD(res);
+
+	if (pres->rows <= 0) return 1; /* Empty table */
+	pres->row = 0;
+	return pg_cmd_next(res);
+}
+
+
+int pg_cmd_next(db_res_t* res)
+{
+	struct pg_res* pres;
+	struct pg_con* pcon;
+
+	pres = DB_GET_PAYLOAD(res);
+	pcon = DB_GET_PAYLOAD(res->cmd->ctx->con[db_payload_idx]);
+
+	if (pres->row >= pres->rows) return 1;
+
+	if (pg_pg2fld(res->cmd->result, pres->res, pres->row, pcon->oid, pcon->flags)) return -1;
+	res->cur_rec->fld = res->cmd->result;
+	pres->row++;
+	return 0;
+}
+
+/** @} */

+ 154 - 0
modules/db_postgres/pg_cmd.h

@@ -0,0 +1,154 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_CMD_H
+#define _PG_CMD_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Declaration of pg_cmd data structure that contains PostgreSQL specific data
+ * stored in db_cmd structures and related functions.
+ */
+
+#include "pg_oid.h"
+
+#include "../../db/db_drv.h"
+#include "../../db/db_cmd.h"
+#include "../../db/db_res.h"
+#include "../../str.h"
+
+#include <stdarg.h>
+#include <libpq-fe.h>
+
+struct pg_params {
+	const char** val;
+	int* len;
+	int* fmt;
+};
+
+
+/** Extension structure of db_cmd adding PostgreSQL specific data.
+ * This data structure extends the generic data structure db_cmd in the
+ * database API with data specific to the postgresql driver.
+ */
+struct pg_cmd {
+	db_drv_t gen; /**< Generic part of the data structure (must be first */
+	char* name;   /**< Name of the prepared query on the server */
+	str sql_cmd;  /**< Database command represented in SQL language */
+
+	struct pg_params params;
+	PGresult* types;
+};
+
+
+/** Creates a new pg_cmd data structure.
+ * This function allocates and initializes memory for a new pg_cmd data
+ * structure. The data structure is then attached to the generic db_cmd
+ * structure in cmd parameter.
+ * @param cmd A generic db_cmd structure to which the newly created pg_cmd
+ *            structure will be attached.
+ */
+int pg_cmd(db_cmd_t* cmd);
+
+
+/** The main execution function in postgres SER driver.
+ * This is the main execution function in this driver. It is executed whenever
+ * a SER module calls db_exec and the target database of the commands is
+ * PostgreSQL. The function contains all the necessary logic to detect reset
+ * or disconnected database connections and uploads commands to the server if
+ * necessary.
+ * @param res A pointer to (optional) result structure if the command returns
+ *            a result.
+ * @retval 0 if executed successfully
+ * @retval A negative number if the database server failed to execute command
+ * @retval A positive number if there was an error on client side (SER)
+ */
+int pg_cmd_exec(db_res_t* res, db_cmd_t* cmd);
+
+
+/** Retrieves the first record from a result set received from PostgreSQL server.
+ * This function is executed whenever a SER module calls db_first to retrieve
+ * the first record from a result. The function retrieves the first record
+ * from a PGresult structure and converts the fields from PostgreSQL to
+ * internal SER representation.
+ * 
+ * @param res A result set retrieved from PostgreSQL server.
+ * @retval 0 If executed successfully.
+ * @retval 1 If the result is empty.
+ * @retival A negative number on error.
+ */
+int pg_cmd_first(db_res_t* res);
+
+
+/** Retrieves the next record from a result set received from PostgreSQL server.
+ * This function is executed whenever a SER module calls db_next to retrieve
+ * the first record from a result. The function advances current cursor
+ * position in the result, retrieves the next record from a PGresult structure
+ * and converts the fields from PostgreSQL to internal SER representation.
+ * 
+ * @param res A result set retrieved from PostgreSQL server.
+ * @retval 0 If executed successfully.
+ * @retval 1 If there are no more records in the result.
+ * @retival A negative number on error.
+ */
+int pg_cmd_next(db_res_t* res);
+
+
+/** Retrieves the value of an db_cmd option.
+ * This function is called when a SER module uses db_getopt to retrieve the
+ * value of db_cmd parameter.
+ * @param cmd A db_cmd structure representing the command.
+ * @param optname Name of the option.
+ * @param ap A pointer the result variable.
+ * @retval 0 on success.
+ * @retval A positive number of the option is not supported/implemented.
+ * @retval A negative number on error.
+ */
+int pg_getopt(db_cmd_t* cmd, char* optname, va_list ap);
+
+
+/** Sets the value of an db_cmd option.
+ * This function is called when a SER module uses db_setopt to set the
+ * value of db_cmd parameter.
+ * @param cmd A db_cmd structure representing the command.
+ * @param optname Name of the option.
+ * @param ap A variable with the value to be set.
+ * @retval 0 on success.
+ * @retval A positive number of the option is not supported/implemented.
+ * @retval A negative number on error.
+ */
+int pg_setopt(db_cmd_t* cmd, char* optname, va_list ap);
+
+/** @} */
+
+#endif /* _PG_CMD_H */

+ 235 - 116
modules/db_postgres/pg_con.c

@@ -1,43 +1,56 @@
 /* 
 /* 
- * $Id$
+ * $Id$ 
  *
  *
- * Portions Copyright (C) 2001-2003  FhG FOKUS
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
  * Copyright (C) 2003 August.Net Services, LLC
  * Copyright (C) 2003 August.Net Services, LLC
- * Portions Copyright (C) 2005 iptelorg GmbH
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
  *
  *
- * This file is part of ser, a free SIP server.
+ * This file is part of SER, a free SIP server.
  *
  *
- * ser 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 2 of the License, or
- * (at your option) any later version
+ * SER 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 2 of the License, or (at your option) any later
+ * version
  *
  *
- * For a license to use the ser software under conditions
- * other than those described here, or to purchase support for this
- * software, please contact iptel.org by e-mail at the following addresses:
- *    [email protected]
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
  *
  *
- * ser 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.
+ * SER 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 this program; if not, write to the Free Software 
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Functions related to connections to PostgreSQL servers.
  */
  */
 
 
 #include "pg_con.h"
 #include "pg_con.h"
+#include "pg_uri.h"
+#include "pg_sql.h"
+
 #include "../../mem/mem.h"
 #include "../../mem/mem.h"
 #include "../../dprint.h"
 #include "../../dprint.h"
 #include "../../ut.h"
 #include "../../ut.h"
+
+#include <malloc.h>
 #include <string.h>
 #include <string.h>
 #include <netinet/in.h>
 #include <netinet/in.h>
 #include <time.h>
 #include <time.h>
 
 
 
 
-/*
- * Override the default notice processor to output the messages 
+/* Override the default notice processor to output the messages 
  * using SER's output subsystem.
  * using SER's output subsystem.
  */
  */
 static void notice_processor(void* arg, const char* message)
 static void notice_processor(void* arg, const char* message)
@@ -46,162 +59,268 @@ static void notice_processor(void* arg, const char* message)
 }
 }
 
 
 
 
-
-/*
- * Determine the format used by the server to store timestamp data type
- * The function returns 1 if the server stores timestamps as int8 and 0
- * if it is stored as double
+/** Determine the format of timestamps used by the server.  
+ * A PostgresSQL server can be configured to store timestamps either as 8-byte
+ * integers or floating point numbers with double precision. This functions
+ * sends a simple SQL query to the server and tries to determine the format of
+ * timestamps from the reply. This function is executed once after connecting
+ * to a PostgreSQL server and the result of the detection is then stored in
+ * form of a flag in pg_con connection structure.
+ * @param con A PostgreSQL connection handle
+ * @retval 0 If the server stores timestamps as floating point numbers.
+ * @retval 1 If the server stores timestamps as 8-byte integers.
+ * @retval A negative number on error.
  */
  */
 static int timestamp_format(PGconn* con)
 static int timestamp_format(PGconn* con)
 {
 {
 	unsigned long long offset;
 	unsigned long long offset;
 	PGresult* res = 0;
 	PGresult* res = 0;
 	char* val;
 	char* val;
+	str sql;
 
 
-	res = PQexecParams(con, "select timestamp '2000-01-01 00:00:00' + time '00:00:01'", 0, 0, 0, 0, 0, 1);	
+	if (build_timestamp_format_sql(&sql) != 0) {
+		ERR("postgres: Error while building SQL query to obtain timestamp format\n");
+		return -1;
+	}
+	res = PQexecParams(con, sql.s, 0, 0, 0, 0, 0, 1);
+	pkg_free(sql.s);
 
 
 	if (PQfformat(res, 0) != 1) {
 	if (PQfformat(res, 0) != 1) {
-		ERR("Binary format expected but server sent text\n");
-		goto err;
+		ERR("postgres: Binary format expected but server sent text\n");
+		goto error;
 	}
 	}
 
 
 	if (PQntuples(res) != 1) {
 	if (PQntuples(res) != 1) {
-		ERR("1 column expected, %d received\n", PQntuples(res));
-		goto err;
+		ERR("postgres: Only one column expected, %d received\n", PQntuples(res));
+		goto error;
 	}
 	}
 
 
 	if (PQnfields(res) != 1) {
 	if (PQnfields(res) != 1) {
-		ERR("1 Row expected, %d received\n", PQnfields(res));
-		goto err;
+		ERR("postgres: Only one row expected, %d received\n", PQnfields(res));
+		goto error;
 	}
 	}
 
 
 	val = PQgetvalue(res, 0, 0);
 	val = PQgetvalue(res, 0, 0);
 	offset = ((unsigned long long)ntohl(((unsigned int*)val)[0]) << 32) 
 	offset = ((unsigned long long)ntohl(((unsigned int*)val)[0]) << 32) 
 		+ ntohl(((unsigned int*)val)[1]);
 		+ ntohl(((unsigned int*)val)[1]);
-
+	
 	PQclear(res);
 	PQclear(res);
 
 
-	     /* Server using int8 timestamps would return 1000000, because it stores
-	      * timestamps in microsecond resolution across the whole range. Server using
-	      * double timestamps would return 1 (encoded as double) here because subsection
-	      * fraction is stored as fractional part in the IEEE representation.
-	      * 1 stored as double would result in 4607182418800017408 when the memory location
-	      * occupied by the variable is read as unsigned long long.
-	      */
+	/* Server using int8 timestamps would return 1000000, because it stores
+	 * timestamps in microsecond resolution across the whole range. Server
+	 * using double timestamps would return 1 (encoded as double) here because
+	 * subsection fraction is stored as fractional part in the IEEE
+	 * representation.  1 stored as double would result in 4607182418800017408
+	 * when the memory location occupied by the variable is read as unsigned
+	 * long long.
+	 */
 	if (offset == 1000000) {
 	if (offset == 1000000) {
-	        DBG("Server uses int8 format for timestamps.\n");
+	        DBG("postgres: Server uses int8 format for timestamps.\n");
 		return 1;
 		return 1;
 	} else {
 	} else {
-		DBG("Server uses double format for timestamps.\n");
+		DBG("postgres: Server uses double format for timestamps.\n");
 		return 0;
 		return 0;
 	}
 	}
 	
 	
- err:
+ error:
 	PQclear(res);
 	PQclear(res);
 	return -1;
 	return -1;
 }
 }
 
 
 
 
-/*
- * Create a new connection structure,
- * open the Postgres connection and set reference count to 1
+/** Retrieves a list of all supported field types from the server.
+ * This function retrieves a list of all supported field types and their Oids
+ * from system catalogs of the server. The list is then stored in pg_con
+ * connection structure and it is used to map field type names, such as int2,
+ * int4, float4, etc. to Oids. Every PostgreSQL server can map field types to
+ * different Oids so we need to store the mapping array in the connection
+ * structure.
+ * @param con A structure representing connection to PostgreSQL server.
+ * @retval 0 If executed successfully.
+ * @retval A negative number on error.
  */
  */
-struct pg_con* pg_new_connection(struct db_id* id)
+static int get_oids(db_con_t* con)
 {
 {
-	struct pg_con* ptr;
-	char* port_str;
-	int ret;
+	struct pg_con* pcon;
+	PGresult* res = NULL;
+	str sql;
+
+	pcon = DB_GET_PAYLOAD(con);
+	if (build_select_oid_sql(&sql) < 0) goto error;
+	res = PQexec(pcon->con, sql.s);
+	pkg_free(sql.s);
+	if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) goto error;
+	pcon->oid = pg_new_oid_table(res);
+	PQclear(res);
+	if (pcon->oid == NULL) goto error;
+	return 0;
+
+ error:
+	if (res) PQclear(res);
+	return -1;
+}
+
+
+/** Free all memory allocated for a pg_con structure.
+ * This function function frees all memory that is in use by
+ * a pg_con structure.
+ * @param con A generic db_con connection structure.
+ * @param payload PostgreSQL specific payload to be freed.
+ */
+static void pg_con_free(db_con_t* con, struct pg_con* payload)
+{
+	if (!payload) return;
 	
 	
-	if (!id) {
-		ERR("Invalid parameter value\n");
-		return 0;
-	}
+	/* Delete the structure only if there are no more references
+	 * to it in the connection pool
+	 */
+	if (db_pool_remove((db_pool_entry_t*)payload) == 0) return;
 	
 	
-	ptr = (struct pg_con*)pkg_malloc(sizeof(struct pg_con));
-	if (!ptr) {
-		ERR("No memory left\n");
-		return 0;
+	db_pool_entry_free(&payload->gen);
+	pg_destroy_oid_table(payload->oid);
+	if (payload->con) PQfinish(payload->con);
+	pkg_free(payload);
+}
+
+
+int pg_con(db_con_t* con)
+{
+	struct pg_con* pcon;
+
+	/* First try to lookup the connection in the connection pool and
+	 * re-use it if a match is found
+	 */
+	pcon = (struct pg_con*)db_pool_get(con->uri);
+	if (pcon) {
+		DBG("postgres: Connection to %.*s:%.*s found in connection pool\n",
+			con->uri->scheme.len, ZSW(con->uri->scheme.s),
+			con->uri->body.len, ZSW(con->uri->body.s));
+		goto found;
+	}
+
+	pcon = (struct pg_con*)pkg_malloc(sizeof(struct pg_con));
+	if (!pcon) {
+		LOG(L_ERR, "postgres: No memory left\n");
+		goto error;
 	}
 	}
-	memset(ptr, 0, sizeof(struct pg_con));
-	ptr->ref = 1;
+	memset(pcon, '\0', sizeof(struct pg_con));
+	if (db_pool_entry_init(&pcon->gen, pg_con_free, con->uri) < 0) goto error;
+
+	DBG("postgres: Preparing new connection to: %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	/* Put the newly created postgres connection into the pool */
+	db_pool_put((struct db_pool_entry*)pcon);
+	DBG("postgres: Connection stored in connection pool\n");
+
+ found:
+	/* Attach driver payload to the db_con structure and set connect and
+	 * disconnect functions
+	 */
+	DB_SET_PAYLOAD(con, pcon);
+	con->connect = pg_con_connect;
+	con->disconnect = pg_con_disconnect;
+	return 0;
+
+ error:
+	if (pcon) {
+		db_pool_entry_free(&pcon->gen);
+		pkg_free(pcon);
+	}
+	return -1;
+}
+
+
+int pg_con_connect(db_con_t* con)
+{
+	struct pg_con* pcon;
+	struct pg_uri* puri;
+	char* port_str;
+	int ret;
+	
+	pcon = DB_GET_PAYLOAD(con);
+	puri = DB_GET_PAYLOAD(con->uri);
 	
 	
-	if (id->port > 0) {
-		port_str = int2str(id->port, 0);
+	/* Do not reconnect already connected connections */
+	if (pcon->flags & PG_CONNECTED) return 0;
+
+	DBG("postgres: Connecting to %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	if (puri->port > 0) {
+		port_str = int2str(puri->port, 0);
 	} else {
 	} else {
 		port_str = NULL;
 		port_str = NULL;
 	}
 	}
 
 
-	if (id->port) {
-		DBG("Opening connection to: %s://%s:%s@%s:%d/%s\n",
-		    ZSW(id->scheme),
-		    ZSW(id->username),
-		    ZSW(id->password),
-		    ZSW(id->host),
-		    id->port,
-		    ZSW(id->database)
-		    );
-	} else {
-		DBG("Opening connection to: %s://%s:%s@%s/%s\n",
-		    ZSW(id->scheme),
-		    ZSW(id->username),
-		    ZSW(id->password),
-		    ZSW(id->host),
-		    ZSW(id->database)
-		    );
+	if (pcon->con) {
+		PQfinish(pcon->con);
+		pcon->con = NULL;
 	}
 	}
+
+	pcon->con = PQsetdbLogin(puri->host, port_str,
+							 NULL, NULL, puri->database,
+							 puri->username, puri->password);
 	
 	
-	ptr->con = PQsetdbLogin(id->host, port_str,
-				NULL, NULL, id->database,
-				id->username, id->password);
-	
-	if (ptr->con == 0) {
-		ERR("PQsetdbLogin ran out of memory\n");
-		goto err;
+	if (pcon->con == NULL) {
+		ERR("postgres: PQsetdbLogin ran out of memory\n");
+		goto error;
 	}
 	}
 	
 	
-	if (PQstatus(ptr->con) != CONNECTION_OK) {
-		ERR("postgres:new_connection: %s\n",
-		    PQerrorMessage(ptr->con));
-		goto err;
+	if (PQstatus(pcon->con) != CONNECTION_OK) {
+		ERR("postgres: %s\n", PQerrorMessage(pcon->con));
+		goto error;
 	}
 	}
-
-	     /* Override default notice processor */
-	PQsetNoticeProcessor(ptr->con, notice_processor, 0);
-
-	DBG("Connected. Protocol version=%d, Server version=%d\n", 
-	    PQprotocolVersion(ptr->con),
+	
+	/* Override default notice processor */
+	PQsetNoticeProcessor(pcon->con, notice_processor, 0);
+	
+	DBG("postgres: Connected. Protocol version=%d, Server version=%d\n", 
+	    PQprotocolVersion(pcon->con),
 #ifdef HAVE_PGSERVERVERSION
 #ifdef HAVE_PGSERVERVERSION
-	    PQserverVersion(ptr->con)
+	    PQserverVersion(pcon->con)
 #else
 #else
 	    0
 	    0
 #endif
 #endif
 	    );
 	    );
 
 
-	ptr->timestamp = time(0);
-	ptr->id = id;
-
-	ret = timestamp_format(ptr->con);
+	ret = timestamp_format(pcon->con);
 	if (ret == 1 || ret == -1) {
 	if (ret == 1 || ret == -1) {
-		     /* Assume INT8 representation if detection fails */
-		ptr->flags |= PG_INT8_TIMESTAMP;
+		/* Assume INT8 representation if detection fails */
+		pcon->flags |= PG_INT8_TIMESTAMP;
+	} else {
+		pcon->flags &= ~PG_INT8_TIMESTAMP;
 	}
 	}
 
 
-	return ptr;
+	if (get_oids(con) < 0) goto error;
 
 
- err:
-	if (ptr && ptr->con) PQfinish(ptr->con);
-	if (ptr) pkg_free(ptr);
+	pcon->flags |= PG_CONNECTED;
 	return 0;
 	return 0;
+
+ error:
+	if (pcon->con) PQfinish(pcon->con);
+	pcon->con = NULL;
+	return -1;
 }
 }
 
 
 
 
-/*
- * Close the connection and release memory
- */
-void pg_free_connection(struct pg_con* con)
+void pg_con_disconnect(db_con_t* con)
 {
 {
-	if (!con) return;
-	if (con->id) free_db_id(con->id);
-	if (con->con) PQfinish(con->con);
-	pkg_free(con);
+	struct pg_con* pcon;
+
+	pcon = DB_GET_PAYLOAD(con);
+	if ((pcon->flags & PG_CONNECTED) == 0) return;
+
+	DBG("postgres: Disconnecting from %.*s:%.*s\n",
+		con->uri->scheme.len, ZSW(con->uri->scheme.s),
+		con->uri->body.len, ZSW(con->uri->body.s));
+
+	PQfinish(pcon->con);
+	pcon->con = NULL;
+	pcon->flags &= ~PG_CONNECTED;
+	pcon->flags &= ~PG_INT8_TIMESTAMP;
 }
 }
+
+/** @} */

+ 71 - 41
modules/db_postgres/pg_con.h

@@ -1,74 +1,104 @@
 /* 
 /* 
- * $Id$
+ * $Id$ 
  *
  *
- * Portions Copyright (C) 2001-2003 FhG Fokus
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
  * Copyright (C) 2003 August.Net Services, LLC
  * Copyright (C) 2003 August.Net Services, LLC
- * Portions Copyright (C) 2005 iptelorg GmbH
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
  *
  *
- * This file is part of ser, a free SIP server.
+ * This file is part of SER, a free SIP server.
  *
  *
- * ser 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 2 of the License, or
- * (at your option) any later version
+ * SER 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 2 of the License, or (at your option) any later
+ * version
  *
  *
- * For a license to use the ser software under conditions
- * other than those described here, or to purchase support for this
- * software, please contact iptel.org by e-mail at the following addresses:
- *    [email protected]
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
  *
  *
- * ser 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.
+ * SER 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 this program; if not, write to the Free Software 
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
  */
 
 
 #ifndef _PG_CON_H
 #ifndef _PG_CON_H
 #define _PG_CON_H
 #define _PG_CON_H
 
 
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Implementation of PostgreSQL connection related data structures and functions.
+ */
+
+#include "pg_oid.h"
+
 #include "../../db/db_pool.h"
 #include "../../db/db_pool.h"
-#include "../../db/db_id.h"
+#include "../../db/db_con.h"
+#include "../../db/db_uri.h"
 
 
 #include <time.h>
 #include <time.h>
 #include <libpq-fe.h>
 #include <libpq-fe.h>
 
 
+/** 
+ * Per-connection flags for PostgreSQL connections.
+ */
 enum pg_con_flags {
 enum pg_con_flags {
-	PG_INT8_TIMESTAMP = 1 << 0
+	PG_CONNECTED      = (1 << 0), /**< The connection has been connected successfully */
+	PG_INT8_TIMESTAMP = (1 << 1)  /**< The server uses 8-byte integer format for timestamps */
 };
 };
 
 
-struct pg_con {
-	struct db_id* id;        /* Connection identifier */
-	unsigned int ref;        /* Reference count */
-	struct pool_con* next;   /* Next connection in the pool */
 
 
-	PGconn* con;             /* Postgres connection handle */
-	unsigned int flags;      /* Flags (currently only binary data format) */
-	time_t timestamp;        /* Timestamp of last query */
-};
+/** A structure representing a connection to PostgreSQL server.
+ * This structure represents connections to PostgreSQL servers. It contains
+ * PostgreSQL specific data, such as PostgreSQL connection handle, connection
+ * flags, and an array with data types supported by the server.
+ */
+typedef struct pg_con {
+	db_pool_entry_t gen;  /**< Generic part of the structure */
+	PGconn* con;          /**< Postgres connection handle */
+	unsigned int flags;   /**< Flags (currently only binary data format) */
+	pg_type_t* oid;       /**< Data types and their Oids obtained from the server */
+} pg_con_t;
 
 
 
 
-/*
- * Some convenience wrappers
+/** Create a new pg_con structure.
+ * This function creates a new pg_con structure and attachs the structure to
+ * the generic db_con structure in the parameter.
+ * @param con A generic db_con structure to be extended with PostgreSQL
+ *            payload
+ * @retval 0 on success
+ * @retval A negative number on error
  */
  */
-#define CON_CONNECTION(db_con) (((struct pg_con*)((db_con)->tail))->con)
-#define CON_FLAGS(db_con)      (((struct pg_con*)((db_con)->tail))->flags)
-#define CON_TIMESTAMP(db_con)  (((struct pg_con*)((db_con)->tail))->timestamp)
+int pg_con(db_con_t* con);
 
 
 
 
-/*
- * Create a new connection structure,
- * open the MySQL connection and set reference count to 1
+/** Establish a new connection to server.  
+ * This function is called when a SER module calls db_connect to establish a
+ * new connection to the database server. After the connection is established
+ * the function sends an SQL query to the server to determine the format of
+ * timestamp fields and also obtains the list of supported field types.
+ * @param con A structure representing database connection.
+ * @retval 0 on success.
+ * @retval A negative number on error.
  */
  */
-struct pg_con* pg_new_connection(struct db_id* id);
+int pg_con_connect(db_con_t* con);
 
 
 
 
-/*
- * Close the connection and release memory
+/** Disconnected from PostgreSQL server.
+ * Disconnects a previously connected connection to PostgreSQL server.
+ * @param con A structure representing the connection to be disconnected.
  */
  */
-void pg_free_connection(struct pg_con* con);
+void pg_con_disconnect(db_con_t* con);
+
+/** @} */
 
 
 #endif /* _PG_CON_H */
 #endif /* _PG_CON_H */

+ 834 - 0
modules/db_postgres/pg_fld.c

@@ -0,0 +1,834 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Data field conversion and type checking functions.
+ */
+
+#include "pg_fld.h"
+#include "pg_con.h" /* flags */
+#include "pg_mod.h"
+
+#include "../../db/db_drv.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+
+#include <stdint.h>
+#include <string.h>
+#include <netinet/in.h>
+
+/**
+ * This is the epoch time in time_t format, this value is used to convert
+ * timestamp values to/from PostgreSQL format.
+ *  2000-01-01 00:00:00 +0000 as the value of time_t in UTC
+ */
+#define PG_EPOCH_TIME ((int64_t)946684800)
+
+
+/** Frees memory used by a pg_fld structure.
+ * This function frees all memory used by a pg_fld structure
+ * @param fld Generic db_fld_t* structure being freed.
+ * @param payload The postgresql extension structure to be freed
+ */
+static void pg_fld_free(db_fld_t* fld, struct pg_fld* payload)
+{
+	db_drv_free(&payload->gen);
+	pkg_free(payload);
+}
+
+
+int pg_fld(db_fld_t* fld, char* table)
+{
+	struct pg_fld* res;
+
+	res = (struct pg_fld*)pkg_malloc(sizeof(struct pg_fld));
+	if (res == NULL) {
+		ERR("postgres: No memory left\n");
+		return -1;
+	}
+	memset(res, '\0', sizeof(struct pg_fld));
+	if (db_drv_init(&res->gen, pg_fld_free) < 0) goto error;
+
+	DB_SET_PAYLOAD(fld, res);
+	return 0;
+
+ error:
+	if (res) pkg_free(res);
+	return -1;
+}
+
+
+static inline uint64_t htonll(uint64_t in)
+{
+	uint32_t* p = (uint32_t*)&in;
+	return ((uint64_t)htonl(p[0]) << 32) + (uint64_t)htonl(p[1]);
+}
+
+
+static inline uint64_t ntohll(uint64_t in)
+{
+	uint32_t* p = (uint32_t*)&in;
+	return ((uint64_t)ntohl(p[0]) << 32) + (uint64_t)ntohl(p[1]);
+}
+
+
+static inline void db_int2pg_int4(struct pg_params* dst, int i, 
+								  db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.int4[0] = htonl(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 4;
+}
+
+
+static inline void db_int2pg_int2(struct pg_params* dst, int i, 
+								  db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.int2[0] = htons(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 2;
+}
+
+
+static inline void db_int2pg_timestamp(struct pg_params* dst, int i, 
+									   db_fld_t* src, unsigned int flags)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	if (flags & PG_INT8_TIMESTAMP) {
+		pfld->v.int8 = ((int64_t)src->v.int4 - PG_EPOCH_TIME) * 1000000;
+	} else {
+		pfld->v.dbl = (double)src->v.int4 - (double)PG_EPOCH_TIME;
+	}
+	pfld->v.int8 = htonll(pfld->v.int8);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 8;
+}
+
+
+static inline void db_int2pg_int8(struct pg_params* dst, int i,
+								  db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.int4[0] = 0;
+	pfld->v.int4[1] = htonl(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 8;
+}
+
+
+static inline void db_int2pg_bool(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	if (src->v.int4) pfld->v.byte[0] = 1;
+	else pfld->v.byte[0] = 0;
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 1;
+}
+
+
+static inline void db_int2pg_inet(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.byte[0] = AF_INET; /* Address family */
+	pfld->v.byte[1] = 32; /* Netmask */
+	pfld->v.byte[2] = 0; /* is CIDR */
+	pfld->v.byte[3] = 4; /* Number of bytes */
+	pfld->v.int4[1] = htonl(src->v.int4); /* Actuall IP address */
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 8;
+}
+
+
+static inline void db_float2pg_float4(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.int4[0] = htonl(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 4;
+}
+
+
+static inline void db_float2pg_float8(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.dbl = src->v.flt;
+	pfld->v.int8 = htonll(pfld->v.int8);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 8;
+}
+
+
+static inline void db_double2pg_float8(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.int8 = htonll(src->v.int8);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 8;
+}
+
+
+static inline void db_double2pg_float4(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+	pfld->v.flt = src->v.dbl;
+	pfld->v.int4[0] = htonl(pfld->v.int4[0]);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+	dst->len[i] = 4;
+}
+
+
+static inline void db_int2pg_bit(struct pg_params* dst, int i, db_fld_t* src)
+{
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+
+	pfld->v.int4[0] = htonl(32);
+	pfld->v.int4[1] = htonl(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+   	dst->len[i] = 8;
+}
+
+/*
+static inline void db_int2pg_varbit(struct pg_params* dst, int i,
+						  db_fld_t* src)
+{
+	unsigned int len = 32;
+	struct pg_fld* pfld = DB_GET_PAYLOAD(src);
+
+	pfld->v.int4[0] = htonl(len);
+	pfld->v.int4[1] = htonl(src->v.int4);
+
+	dst->fmt[i] = 1;
+	dst->val[i] = pfld->v.byte;
+   	dst->len[i] = 4 + len / 8 + (len % 8 ? 1 : 0);
+}
+*/
+
+
+static inline void db_str2pg_string(struct pg_params* dst, int i,
+									db_fld_t* src)
+{
+	dst->fmt[i] = 1;
+	dst->val[i] = src->v.lstr.s;
+	dst->len[i] = src->v.lstr.len;
+}
+
+static inline void db_cstr2pg_string(struct pg_params* dst, int i,
+									 db_fld_t* src)
+{
+	dst->fmt[i] = 0;
+	dst->val[i] = src->v.cstr;
+}
+
+
+int pg_fld2pg(struct pg_params* dst, int off, pg_type_t* types,
+			  db_fld_t* src, unsigned int flags)
+{
+	int i;
+	struct pg_fld* pfld;
+
+	if (src == NULL) return 0;
+
+	for(i = 0; !DB_FLD_EMPTY(src) && !DB_FLD_LAST(src[i]); i++) {
+		pfld = DB_GET_PAYLOAD(src + i);
+
+		/* NULL value */
+		if (src[i].flags & DB_NULL) {
+			dst->val[off + i] = NULL;
+			dst->len[off + i] = 0;
+			continue;
+		}
+
+		switch(src[i].type) {
+		case DB_INT:
+			if (pfld->oid == types[PG_INT2].oid)
+				db_int2pg_int2(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_INT4].oid)
+				db_int2pg_int4(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_TIMESTAMP].oid)
+				db_int2pg_timestamp(dst, off + i, src + i, flags);
+			else if (pfld->oid == types[PG_INT8].oid)
+				db_int2pg_int8(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_INET].oid)
+				db_int2pg_inet(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_BOOL].oid)
+				db_int2pg_bool(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_BIT].oid)
+				db_int2pg_bit(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_VARBIT].oid)
+				db_int2pg_bit(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_BITMAP:
+			if (pfld->oid == types[PG_INT4].oid)
+				db_int2pg_int4(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_INT8].oid)
+				db_int2pg_int8(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_BIT].oid)
+				db_int2pg_bit(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_VARBIT].oid)
+				db_int2pg_bit(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_DATETIME:
+			if (pfld->oid == types[PG_INT4].oid)
+				db_int2pg_int4(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_TIMESTAMP].oid)
+				db_int2pg_timestamp(dst, off + i, src + i, flags);
+			else if (pfld->oid == types[PG_INT8].oid)
+				db_int2pg_int8(dst, off + i, src + i);
+			else goto bug;
+			break;
+			 
+		case DB_FLOAT:
+			if (pfld->oid == types[PG_FLOAT4].oid)
+				db_float2pg_float4(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_FLOAT8].oid)
+				db_float2pg_float8(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_DOUBLE:
+			if (pfld->oid == types[PG_FLOAT4].oid)
+				db_double2pg_float4(dst, off + i, src + i);
+			else if (pfld->oid == types[PG_FLOAT8].oid)
+				db_double2pg_float8(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_STR:
+			if (pfld->oid == types[PG_VARCHAR].oid ||
+				pfld->oid == types[PG_BYTE].oid ||
+				pfld->oid == types[PG_CHAR].oid ||
+				pfld->oid == types[PG_TEXT].oid ||
+				pfld->oid == types[PG_BPCHAR].oid)
+				db_str2pg_string(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_CSTR:
+			if (pfld->oid == types[PG_VARCHAR].oid ||
+				pfld->oid == types[PG_BYTE].oid ||
+				pfld->oid == types[PG_CHAR].oid ||
+				pfld->oid == types[PG_TEXT].oid ||
+				pfld->oid == types[PG_BPCHAR].oid)
+				db_cstr2pg_string(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		case DB_BLOB:
+			if (pfld->oid == types[PG_BYTE].oid)
+				db_str2pg_string(dst, off + i, src + i);
+			else goto bug;
+			break;
+
+		default:
+			BUG("postgres: Unsupported field type %d in field %s\n",
+				src[i].type, src[i].name);
+			return -1;
+		}
+	}
+
+	return 0;
+
+ bug:
+	BUG("postgres: Error while converting DB API type %d to Postgres Oid %d\n",
+		src[i].type, pfld->oid);
+	return -1;
+
+}
+
+
+int pg_check_fld2pg(db_fld_t* fld, pg_type_t* types)
+{
+	int i;
+	const char* name = "UNKNOWN";
+	struct pg_fld* pfld;
+
+	if (fld == NULL) return 0;
+
+	for(i = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
+		pfld = DB_GET_PAYLOAD(fld + i);
+		switch(fld[i].type) {
+		case DB_INT:
+			if (pfld->oid == types[PG_INT2].oid) continue;
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_INT8].oid) continue;
+			if (pfld->oid == types[PG_BOOL].oid) continue;
+			if (pfld->oid == types[PG_INET].oid) continue;
+			if (pfld->oid == types[PG_TIMESTAMP].oid) continue;
+			if (pfld->oid == types[PG_BIT].oid) continue;
+			if (pfld->oid == types[PG_VARBIT].oid) continue;
+			break;
+
+		case DB_BITMAP:
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_INT8].oid) continue;
+			if (pfld->oid == types[PG_BIT].oid) continue;
+			if (pfld->oid == types[PG_VARBIT].oid) continue;
+			break;
+
+		case DB_FLOAT:
+		case DB_DOUBLE:
+			if (pfld->oid == types[PG_FLOAT4].oid) continue;
+			if (pfld->oid == types[PG_FLOAT8].oid) continue;
+			break;
+
+		case DB_CSTR:
+		case DB_STR:
+			if (pfld->oid == types[PG_BYTE].oid) continue;
+			if (pfld->oid == types[PG_CHAR].oid) continue;
+			if (pfld->oid == types[PG_TEXT].oid) continue;
+			if (pfld->oid == types[PG_BPCHAR].oid) continue;
+			if (pfld->oid == types[PG_VARCHAR].oid) continue;
+			break;
+
+		case DB_BLOB:
+			if (pfld->oid == types[PG_BYTE].oid) continue;
+			break;
+
+		case DB_DATETIME:
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_INT8].oid) continue;
+			if (pfld->oid == types[PG_TIMESTAMP].oid) continue;
+			break;
+
+		default:
+			BUG("postgres: Unsupported field type %d, bug in postgres module\n",
+				fld[i].type);
+			return -1;
+		}
+
+		pg_oid2name(&name, types, pfld->oid);
+		ERR("postgres: Cannot convert column '%s' of type %s "
+			"to PostgreSQL column type '%s'\n", 
+			fld[i].name, db_fld_str[fld[i].type], name);
+		return -1;
+	}
+	return 0;
+}
+
+
+int pg_resolve_param_oids(db_fld_t* vals, db_fld_t* match, int n1, int n2, PGresult* types)
+{
+	struct pg_fld* pfld;
+	int i;
+
+	if (n1 + n2 != PQnparams(types)) {
+		ERR("postgres: Number of command parameters do not match\n");
+		return -1;
+	}
+
+	for(i = 0; i < n1; i++) {
+		pfld = DB_GET_PAYLOAD(vals + i);
+		pfld->oid = PQparamtype(types, i);
+	}
+
+	for(i = 0; i < n2; i++) {
+		pfld = DB_GET_PAYLOAD(match + i);
+		pfld->oid = PQparamtype(types, n1 + i);
+	}
+
+	return 0;
+}
+
+
+int pg_resolve_result_oids(db_fld_t* fld, int n, PGresult* types)
+{
+	struct pg_fld* pfld;
+	int i;
+	if (fld == NULL) return 0;
+
+	if (n != PQnfields(types)) {
+		ERR("postgres: Result field numbers do not match\n");
+		return -1;
+	}
+
+	for(i = 0; i < n; i++) {
+		pfld = DB_GET_PAYLOAD(fld + i);
+		pfld->oid = PQftype(types, i);
+	}
+
+	return 0;
+}
+
+
+int pg_check_pg2fld(db_fld_t* fld, pg_type_t* types)
+{
+	int i;
+	const char* name = "UNKNOWN";
+	struct pg_fld* pfld;
+
+	if (fld == NULL) return 0;
+
+	for(i = 0; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
+		pfld = DB_GET_PAYLOAD(fld + i);
+
+		if (pfld->oid == 0) {
+			ERR("postgres: Unknown type fields not supported\n");
+			return -1;
+		}
+
+		switch(fld[i].type) {
+		case DB_INT:
+			if (pfld->oid == types[PG_INT2].oid) continue;
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_INT8].oid) continue;
+			if (pfld->oid == types[PG_BOOL].oid) continue;
+			if (pfld->oid == types[PG_INET].oid) continue;
+			if (pfld->oid == types[PG_TIMESTAMP].oid) continue;
+			if (pfld->oid == types[PG_BIT].oid) continue;
+			if (pfld->oid == types[PG_VARBIT].oid) continue;
+			break;
+
+		case DB_BITMAP:
+			if (pfld->oid == types[PG_INT2].oid) continue;
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_BIT].oid) continue;
+			if (pfld->oid == types[PG_VARBIT].oid) continue;
+			break;
+
+		case DB_FLOAT:
+			if (pfld->oid == types[PG_FLOAT4].oid) continue;
+			break;
+
+		case DB_DOUBLE:
+			if (pfld->oid == types[PG_FLOAT4].oid) continue;
+			if (pfld->oid == types[PG_FLOAT8].oid) continue;
+			break;
+
+		case DB_CSTR:
+			if (pfld->oid == types[PG_CHAR].oid) continue;
+			if (pfld->oid == types[PG_TEXT].oid) continue;
+			if (pfld->oid == types[PG_BPCHAR].oid) continue;
+			if (pfld->oid == types[PG_VARCHAR].oid) continue;
+			break;
+
+		case DB_STR:
+		case DB_BLOB:
+			if (pfld->oid == types[PG_BYTE].oid) continue;
+			if (pfld->oid == types[PG_CHAR].oid) continue;
+			if (pfld->oid == types[PG_TEXT].oid) continue;
+			if (pfld->oid == types[PG_BPCHAR].oid) continue;
+			if (pfld->oid == types[PG_VARCHAR].oid) continue;
+			break;
+
+		case DB_DATETIME:
+			if (pfld->oid == types[PG_INT2].oid) continue;
+			if (pfld->oid == types[PG_INT4].oid) continue;
+			if (pfld->oid == types[PG_TIMESTAMP].oid) continue;
+			break;
+
+		default:
+			BUG("postgres: Unsupported field type %d, bug in postgres module\n",
+				fld[i].type);
+			return -1;
+		}
+
+		pg_oid2name(&name, types, pfld->oid);
+		ERR("postgres: Cannot convert column '%s' of type %s "
+			"to DB API field of type %s\n", 
+			fld[i].name, name, db_fld_str[fld[i].type]);
+		return -1;
+	}
+	return 0;
+}
+
+
+static inline int pg_int2_2_db_int(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int4 = (int16_t)ntohs(*((int16_t*)val));
+	return 0;
+}
+
+
+static inline int pg_int4_2_db_int(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int4 = (int32_t)ntohl(*((int32_t*)val));
+	return 0;
+}
+
+
+static inline int pg_int8_2_db_int(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int8 = (int64_t)ntohll(*((int64_t*)val));
+	return 0;
+}
+
+
+static inline int pg_bool2db_int(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int4 = val[0];
+	return 0;
+}
+
+
+static inline int pg_inet2db_int(db_fld_t* fld, char* val, int len)
+{
+	if (len != 8 || val[2] != 0) {
+		ERR("postgres: Unsupported 'inet' format, column %s\n", fld->name);
+		return -1;
+	}
+
+	if (val[0] != AF_INET) {
+		ERR("postgres: Unsupported address family %d in field %s\n",
+			val[0], fld->name);
+		return -1;
+	}
+
+	if (val[1] != 32) {
+		WARN("postgres: Netmasks shorter than 32-bits not supported, "
+			 "column %s\n", fld->name);
+	}
+
+	if (val[3] != 4) {
+		ERR("postgres: Unsupported IP address size %d in column %s\n",
+			val[3], fld->name);
+		return -1;
+	}
+
+	fld->v.int4 = (int32_t)ntohl(((int32_t*)val)[1]);
+	return 0;
+}
+
+
+static inline int pg_timestamp2db_int(db_fld_t* fld, char* val, int len, 
+									  unsigned int flags)
+{
+	if (flags & PG_INT8_TIMESTAMP) {
+		/* int8 format */
+		fld->v.int4 = (int64_t)ntohll(((int64_t*)val)[0]) / (int64_t)1000000 + PG_EPOCH_TIME;
+	} else {
+		/* double format */
+		fld->v.int4 = PG_EPOCH_TIME + ntohll(((int64_t*)val)[0]);
+	}
+	return 0;
+}
+
+
+static inline int pg_bit2db_int(db_fld_t* fld, char* val, int len)
+{
+	int size;
+
+	size = ntohl(*(uint32_t*)val);
+	if (size != 32) {
+		ERR("postgres: Unsupported bit field size (%d), column %s\n",
+			size, fld->name);
+		return -1;
+	}
+	fld->v.int4 = ntohl(((uint32_t*)val)[1]);
+	return 0;
+}
+
+
+static inline int pg_float42db_float(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int4 = (uint32_t)ntohl(*(uint32_t*)val);
+	return 0;
+}
+
+
+static inline int pg_float42db_double(db_fld_t* fld, char* val, int len)
+{
+	float tmp;
+
+	tmp = ntohl(*(uint32_t*)val);
+	fld->v.dbl = tmp;
+	return 0;
+}
+
+
+static inline int pg_float82db_double(db_fld_t* fld, char* val, int len)
+{
+	fld->v.int8 = ntohll(*(uint64_t*)val);
+	return 0;
+}
+
+
+static inline int pg_string2db_cstr(db_fld_t* fld, char* val, int len)
+{
+	fld->v.cstr = val;
+	return 0;
+}
+
+
+static inline int pg_string2db_str(db_fld_t* fld, char* val, int len)
+{
+	fld->v.lstr.s = val;
+	fld->v.lstr.len = len;
+	return 0;
+}
+
+
+
+int pg_pg2fld(db_fld_t* dst, PGresult* src, int row, 
+			  pg_type_t* types, unsigned int flags)
+{
+	char* val;
+	int i, len, ret;
+	Oid type;
+	
+	if (dst == NULL || src == NULL) return 0;
+	ret = 0;
+
+	for(i = 0; !DB_FLD_EMPTY(dst) && !DB_FLD_LAST(dst[i]); i++) {
+		if (PQgetisnull(src, row, i)) {
+			dst[i].flags |= DB_NULL;
+			continue;
+		} else {
+			dst[i].flags &= ~DB_NULL;
+		}
+
+		type = PQftype(src, i);
+		val = PQgetvalue(src, row, i);
+		len = PQgetlength(src, row, i);		
+
+		switch(dst[i].type) {
+		case DB_INT:
+			if (type == types[PG_INT2].oid)
+				ret |= pg_int2_2_db_int(dst + i, val, len);
+			else if (type == types[PG_INT4].oid)
+				ret |= pg_int4_2_db_int(dst + i, val, len);
+			else if (type == types[PG_INT8].oid)
+				ret |= pg_int8_2_db_int(dst + i, val, len);
+			else if (type == types[PG_BOOL].oid)
+				ret |= pg_bool2db_int(dst + i, val, len);
+			else if (type == types[PG_INET].oid)
+				ret |= pg_inet2db_int(dst + i, val, len);
+			else if (type == types[PG_TIMESTAMP].oid)
+				ret |= pg_timestamp2db_int(dst + i, val, len, flags);
+			else if (type == types[PG_BIT].oid)
+				ret |= pg_bit2db_int(dst + i, val, len);
+			else if (type == types[PG_VARBIT].oid)
+				ret |= pg_bit2db_int(dst + i, val, len);
+			else goto bug;
+			break;
+
+		case DB_FLOAT:
+			if (type == types[PG_FLOAT4].oid)
+				ret |= pg_float42db_float(dst + i, val, len);
+			else goto bug;
+			break;
+
+		case DB_DOUBLE:
+			if (type == types[PG_FLOAT4].oid)
+				ret |= pg_float42db_double(dst + i, val, len);
+			else if (type == types[PG_FLOAT8].oid)
+				ret |= pg_float82db_double(dst + i, val, len);
+			else goto bug;
+			break;
+
+		case DB_DATETIME:
+			if (type == types[PG_INT2].oid)
+				ret |= pg_int2_2_db_int(dst + i, val, len);
+			else if (type == types[PG_INT4].oid)
+				ret |= pg_int4_2_db_int(dst + i, val, len);
+			else if (type == types[PG_TIMESTAMP].oid)
+				ret |= pg_timestamp2db_int(dst + i, val, len, flags);
+			else goto bug;
+			break;
+
+		case DB_CSTR:
+			if ((type == types[PG_CHAR].oid) ||
+				(type == types[PG_TEXT].oid) ||
+				(type == types[PG_BPCHAR].oid) ||
+				(type == types[PG_VARCHAR].oid))
+				ret |= pg_string2db_cstr(dst + i, val, len);
+			else goto bug;
+			break;
+
+		case DB_STR:
+		case DB_BLOB:
+			if ((type == types[PG_BYTE].oid) ||
+				(type == types[PG_CHAR].oid) ||
+				(type == types[PG_TEXT].oid) ||
+				(type == types[PG_BPCHAR].oid) ||
+				(type == types[PG_VARCHAR].oid))
+				ret |= pg_string2db_str(dst + i, val, len);
+			else goto bug;
+			break;
+
+		case DB_BITMAP:
+			if (type == types[PG_INT2].oid)
+				ret |= pg_int2_2_db_int(dst + i, val, len);
+			else if (type == types[PG_INT4].oid)
+				ret |= pg_int4_2_db_int(dst + i, val, len);
+			else if (type == types[PG_BIT].oid)
+				ret |= pg_bit2db_int(dst + i, val, len);
+			else if (type == types[PG_VARBIT].oid)
+				ret |= pg_bit2db_int(dst + i, val, len);
+			else goto bug;
+			break;
+
+		default:
+			BUG("postgres: Unsupported field type %d in field %s\n",
+				dst[i].type, dst[i].name);
+			return -1;			
+		}
+	}
+	return ret;
+
+ bug:
+	BUG("postgres: Error while converting Postgres Oid %d to DB API type %d\n",
+		type, dst[i].type);
+	return -1;
+}
+
+
+/** @} */

+ 146 - 0
modules/db_postgres/pg_fld.h

@@ -0,0 +1,146 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_FLD_H
+#define _PG_FLD_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Implementation of pg_fld data structure representing PostgreSQL fields and
+ * related functions.
+ */
+
+#include "pg_oid.h"
+#include "pg_cmd.h"
+#include "../../db/db_gen.h"
+#include "../../db/db_fld.h"
+#include <libpq-fe.h>
+
+struct pg_fld {
+	db_drv_t gen;
+
+	/**
+	 * A union of varius data types from db_fld, postgres expects binary
+	 * data in network byte order so we use these variables as temporary
+	 * buffer to store values after the conversion.
+	 */
+	union {
+		int          int4[2]; /**< Integer value in network byte order */
+		short        int2[4];
+		float        flt;     /**< Float value in network byte order */
+		double       dbl;     /**< Double value in network byte order */
+		time_t       time;    /**< Unix timestamp in network byte order */
+		unsigned int bitmap;  /**< Bitmap value in network byte order */ 
+		long long    int8;    /**< 8-byte integer value in network byte order */
+		char         byte[8];
+	} v;
+	Oid oid;                 /**< Type of the field on the server */
+};
+
+
+/** Creates a new PostgreSQL specific payload.
+ * This function creates a new PostgreSQL specific payload structure and
+ * attaches the structure to the generic db_fld structure.
+ * @param fld A generic db_fld structure to be exended.
+ * @param table Name of the table on the server.
+ * @retval 0 on success.
+ * @retval A negative number on error.
+ */
+int pg_fld(db_fld_t* fld, char* table);
+
+int pg_resolve_param_oids(db_fld_t* vals, db_fld_t* match, 
+						  int n1, int n2, PGresult* res);
+
+int pg_resolve_result_oids(db_fld_t* fld, int n, PGresult* res);
+
+
+/** Converts arrays of db_fld fields to PostgreSQL parameters.
+ * The function converts fields in SER db_fld format to parameters suitable
+ * for PostgreSQL API functions.
+ * @param values An array of pointers to values in PostgreSQL format. The
+ *               function will store pointers to converted values there.
+ * @param lenghts An array of integers that will be filled with lenghts
+ *                of values.
+ * @param formats An array of value formats, see PostgreSQL API client
+ *                library documentation for more detail.
+ * @param oids Types of corresponding columns on the server.
+ * @param types A type conversion table.
+ * @param fld An array of db_fld fields to be converted.
+ * @param flags Connection flags controlling how values are converted.
+ * @todo Implement support for bit fields with size bigger than 32 
+ * @todo Implement support for varbit properly to remove leading zeroes
+ * @todo Check if timezones are handled properly
+ * @todo Support for DB_NONE in pg_pg2fld and pg_check_pg2fld
+ * @todo local->UTC conversion (also check the SQL command in ser-oob)
+ */
+int pg_fld2pg(struct pg_params* dst, int off, pg_type_t* types, 
+			  db_fld_t* src, unsigned int flags);
+
+
+/** Converts fields from result in PGresult format into SER format.
+ * The function converts fields from PostgreSQL result (PGresult structure)
+ * into the internal format used in SER. The function converts one row at a
+ * time.
+ * @param fld The destination array of db_fld fields to be filled with converted
+ *            values.
+ * @param pres A PostgreSQL result structure to be converted into SER format.
+ * @param row Number of the row to be converted.
+ * @param flags Connection flags controlling how values are converted.
+ * @retval 0 on success
+ * @retval A negative number on error.
+ * @todo UTC->local conversion
+ */
+int pg_pg2fld(db_fld_t* dst, PGresult* src, int row, pg_type_t* types, 
+			  unsigned int flags);
+
+
+/** Checks if all db_fld fields have types compatible with corresponding field 
+ * types on the server.
+ * The functions checks whether all db_fld fields in the last parameter are
+ * compatible with column types on the server.
+ * @param oids An array of Oids of corresponding columns on the server.
+ * @param lenghts An array of sizes of corresponding columns on the server.
+ * @param types An array used to map internal field types to Oids.
+ * @param fld An array of db_fld fields to be checked.
+ * @retval 0 on success
+ * @retval A negative number on error.
+ */
+int pg_check_fld2pg(db_fld_t* fld, pg_type_t* types);
+
+
+int pg_check_pg2fld(db_fld_t* fld, pg_type_t* types);
+
+
+/** @} */
+
+#endif /* _PG_FLD_H */

+ 535 - 0
modules/db_postgres/pg_mod.c

@@ -0,0 +1,535 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * Postgres module interface.
+ */
+
+#include "pg_mod.h"
+#include "pg_uri.h"
+#include "pg_con.h"
+#include "pg_cmd.h"
+#include "pg_res.h"
+#include "pg_fld.h"
+
+#include "../../sr_module.h"
+
+#ifdef PG_TEST
+#include <limits.h>
+#include <float.h>
+#endif
+
+static int pg_mod_init(void);
+
+MODULE_VERSION
+
+int pg_connect_timeout = 0;  /* Default is unlimited */
+int pg_retries = 2;  /* How many times should the module try re-execute failed commands.
+					  * 0 disables reconnecting */
+
+/*
+ * Postgres module interface
+ */
+static cmd_export_t cmds[] = {
+	{"db_ctx",    (cmd_function)NULL, 0, 0, 0},
+	{"db_con",    (cmd_function)pg_con, 0, 0, 0},
+	{"db_uri",    (cmd_function)pg_uri, 0, 0, 0},
+	{"db_cmd",    (cmd_function)pg_cmd, 0, 0, 0},
+	{"db_put",    (cmd_function)pg_cmd_exec, 0, 0, 0},
+	{"db_del",    (cmd_function)pg_cmd_exec, 0, 0, 0},
+	{"db_get",    (cmd_function)pg_cmd_exec, 0, 0, 0},
+	{"db_upd",    (cmd_function)pg_cmd_exec, 0, 0, 0},
+	{"db_sql",    (cmd_function)pg_cmd_exec, 0, 0, 0},
+	{"db_res",    (cmd_function)pg_res, 0, 0, 0},
+	{"db_fld",    (cmd_function)pg_fld, 0, 0, 0},
+	{"db_first",  (cmd_function)pg_cmd_first, 0, 0, 0},
+	{"db_next",   (cmd_function)pg_cmd_next, 0, 0, 0},
+	{"db_setopt", (cmd_function)pg_setopt, 0, 0, 0},
+	{"db_getopt", (cmd_function)pg_getopt, 0, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"retries",         PARAM_INT, &pg_retries },
+	{0, 0, 0}
+};
+
+
+struct module_exports exports = {
+	"postgres",
+	cmds,
+	0,            /* RPC method */
+	params,       /* module parameters */
+	pg_mod_init,  /* module initialization function */
+	0,            /* response function*/
+	0,            /* destroy function */
+	0,            /* oncancel function */
+	0             /* per-child init function */
+};
+
+/*
+CREATE TABLE test (
+    col_bool BOOL,
+    col_bytea BYTEA,
+    col_char CHAR,
+    col_int8 INT8,
+    col_int4 INT4,
+    col_int2 INT2,
+    col_text TEXT,
+    col_float4 FLOAT4,
+    col_float8 FLOAT8,
+    col_inet INET,
+    col_bpchar BPCHAR,
+    col_varchar VARCHAR,
+    col_timestamp TIMESTAMP,
+    col_timestamptz TIMESTAMPTZ,
+    col_bit BIT(32),
+    col_varbit VARBIT
+);
+*/
+
+
+#ifdef PG_TEST
+int pg_test(void)
+{
+	int i, row;
+	db_ctx_t* db;
+	db_cmd_t* put, *del, *get;
+	db_res_t* result;
+	db_rec_t* rec;
+	char* times;
+
+	db_fld_t int_vals[] = {
+		{.name = "col_bool",      .type = DB_INT},
+		{.name = "col_int8",      .type = DB_INT},
+		{.name = "col_int4",      .type = DB_INT},
+		{.name = "col_inet",      .type = DB_INT},
+		{.name = "col_timestamp", .type = DB_INT},
+		{.name = "col_bit",       .type = DB_INT},
+		{.name = "col_varbit",    .type = DB_INT},
+		{.name = NULL}
+	};
+
+	db_fld_t datetime_vals[] = {
+		{.name = "col_int8",      .type = DB_INT},
+		{.name = "col_int4",      .type = DB_INT},
+		{.name = "col_timestamp", .type = DB_INT},
+		{.name = NULL}
+	};
+
+
+	db_fld_t bitmap_vals[] = {
+		{.name = "col_int8",      .type = DB_INT},
+		{.name = "col_int4",      .type = DB_INT},
+		{.name = "col_bit",       .type = DB_INT},
+		{.name = "col_varbit",    .type = DB_INT},
+		{.name = NULL}
+	};
+
+	db_fld_t float_vals[] = {
+		{.name = "col_float4", .type = DB_FLOAT},
+		{.name = "col_float8", .type = DB_FLOAT},
+		{.name = NULL}
+	};
+
+	db_fld_t double_vals[] = {
+		{.name = "col_float8", .type = DB_DOUBLE},
+		{.name = NULL}
+	};
+
+	db_fld_t str_vals[] = {
+		{.name = "col_varchar", .type = DB_STR},
+		{.name = "col_bytea",   .type = DB_STR},
+		{.name = "col_text",    .type = DB_STR},
+		{.name = "col_bpchar",  .type = DB_STR},
+		{.name = "col_char",    .type = DB_STR},
+		{.name = NULL}
+	};
+
+	db_fld_t cstr_vals[] = {
+		{.name = "col_varchar", .type = DB_CSTR},
+		{.name = "col_bytea",   .type = DB_CSTR},
+		{.name = "col_text",    .type = DB_CSTR},
+		{.name = "col_bpchar",  .type = DB_CSTR},
+		{.name = "col_char",    .type = DB_CSTR},
+		{.name = NULL}
+	};
+
+	db_fld_t blob_vals[] = {
+		{.name = "col_bytea",   .type = DB_BLOB},
+		{.name = NULL}
+	};
+
+
+	db_fld_t res[] = {
+		{.name = "col_bool",      .type = DB_INT},
+		{.name = "col_bytea",     .type = DB_BLOB},
+		{.name = "col_char",      .type = DB_STR},
+		{.name = "col_int8",      .type = DB_INT},
+		{.name = "col_int4",      .type = DB_INT},
+		{.name = "col_int2",      .type = DB_INT},
+		{.name = "col_text",      .type = DB_STR},
+		{.name = "col_float4",    .type = DB_FLOAT},
+		{.name = "col_float8",    .type = DB_DOUBLE},
+		{.name = "col_inet",      .type = DB_INT},
+		{.name = "col_bpchar",    .type = DB_STR},
+		{.name = "col_varchar",   .type = DB_STR},
+		{.name = "col_timestamp", .type = DB_DATETIME},
+		{.name = "col_bit",       .type = DB_BITMAP},
+		{.name = "col_varbit",    .type = DB_BITMAP},
+		{.name = NULL}
+	};
+
+
+	db = db_ctx("postgres");
+	if (db == NULL) {
+		ERR("Error while initializing database layer\n");
+		goto error;
+	}
+	if (db_add_db(db, "postgres://janakj:honzacvut@localhost/ser") < 0) goto error;
+	if (db_connect(db) < 0) goto error;
+	
+	del = db_cmd(DB_DEL, db, "test", NULL, NULL, NULL);
+	if (del == NULL) {
+		ERR("Error while building delete * query\n");
+		goto error;
+	}
+
+    put = db_cmd(DB_PUT, db, "test", NULL, NULL, int_vals);
+	if (put == NULL) {
+		ERR("Error while building test query\n");
+		goto error;
+	}
+
+	if (db_exec(NULL, del)) {
+		ERR("Error while deleting rows from test table\n");
+		goto error;
+	}
+
+	put->vals[0].v.int4 = 0xffffffff;
+	put->vals[1].v.int4 = 0xffffffff;
+	put->vals[2].v.int4 = 0xffffffff;
+	put->vals[3].v.int4 = 0xffffffff;
+	put->vals[4].v.int4 = 0xffffffff;
+	put->vals[5].v.int4 = 0xffffffff;
+	put->vals[6].v.int4 = 0xffffffff;
+
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.int4 = 0;
+	put->vals[1].v.int4 = 0;
+	put->vals[2].v.int4 = 0;
+	put->vals[3].v.int4 = 0;
+	put->vals[4].v.int4 = 0;
+	put->vals[5].v.int4 = 0;
+	put->vals[6].v.int4 = 0;
+
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, bitmap_vals);
+	if (put == NULL) {
+		ERR("Error while building bitmap test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.int4 = 0xffffffff;
+	put->vals[1].v.int4 = 0xffffffff;
+	put->vals[2].v.int4 = 0xffffffff;
+	put->vals[3].v.int4 = 0xffffffff;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.int4 = 0;
+	put->vals[1].v.int4 = 0;
+	put->vals[2].v.int4 = 0;
+	put->vals[3].v.int4 = 0;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, float_vals);
+	if (put == NULL) {
+		ERR("Error while building float test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.flt = FLT_MAX;
+	put->vals[1].v.flt = FLT_MAX;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.flt = FLT_MIN;
+	put->vals[1].v.flt = FLT_MIN;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, double_vals);
+	if (put == NULL) {
+		ERR("Error while building double test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.dbl = DBL_MAX;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.dbl = DBL_MIN;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, str_vals);
+	if (put == NULL) {
+		ERR("Error while building str test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.lstr.s = "";
+	put->vals[0].v.lstr.len = 0;
+	put->vals[1].v.lstr.s = "";
+	put->vals[1].v.lstr.len = 0;
+	put->vals[2].v.lstr.s = "";
+	put->vals[2].v.lstr.len = 0;
+	put->vals[3].v.lstr.s = "";
+	put->vals[3].v.lstr.len = 0;
+	put->vals[4].v.lstr.s = "";
+	put->vals[4].v.lstr.len = 0;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.lstr.s = "abc";
+	put->vals[0].v.lstr.len = 3;
+	put->vals[1].v.lstr.s = "abc";
+	put->vals[1].v.lstr.len = 3;
+	put->vals[2].v.lstr.s = "abc";
+	put->vals[2].v.lstr.len = 3;
+	put->vals[3].v.lstr.s = "abc";
+	put->vals[3].v.lstr.len = 3;
+	put->vals[4].v.lstr.s = "a";
+	put->vals[4].v.lstr.len = 1;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, cstr_vals);
+	if (put == NULL) {
+		ERR("Error while building cstr test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.cstr = "";
+	put->vals[1].v.cstr = "";
+	put->vals[2].v.cstr = "";
+	put->vals[3].v.cstr = "";
+	put->vals[4].v.cstr = "";
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.cstr = "def";
+	put->vals[1].v.cstr = "def";
+	put->vals[2].v.cstr = "def";
+	put->vals[3].v.cstr = "def";
+	put->vals[4].v.cstr = "d";
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, blob_vals);
+	if (put == NULL) {
+		ERR("Error while building blob test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.blob.s = "\0\0\0\0";
+	put->vals[0].v.blob.len = 4;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+
+	db_cmd_free(put);
+
+	put = db_cmd(DB_PUT, db, "test", NULL, NULL, datetime_vals);
+	if (put == NULL) {
+		ERR("Error while building datetime test query\n");
+		goto error;
+	}
+
+	put->vals[0].v.time = 0xffffffff;
+	put->vals[1].v.time = 0xffffffff;
+	put->vals[2].v.time = 0xffffffff;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	put->vals[0].v.time = 0;
+	put->vals[1].v.time = 0;
+	put->vals[2].v.time = 0;
+	if (db_exec(NULL, put)) {
+		ERR("Error while executing database command\n");
+		goto error;
+	}
+
+	if (put) db_cmd_free(put);
+	if (del) db_cmd_free(del);
+	put = NULL;
+	del = NULL;
+
+
+	get = db_cmd(DB_GET, db, "test", res, NULL, NULL);
+	if (get == NULL) {
+		ERR("Error while building select query\n");
+		goto error;
+	}
+
+	if (db_exec(&result, get)) {
+		ERR("Error while executing select query\n");
+		goto error;
+	}
+
+	rec = db_first(result);
+	row = 1;
+	while(rec) {
+		ERR("row: %d\n", row);
+		for(i = 0; !DB_FLD_LAST(rec->fld[i]); i++) {
+			if (rec->fld[i].flags & DB_NULL) {
+				ERR("%s: NULL\n", rec->fld[i].name);
+			} else {
+				switch(rec->fld[i].type) {
+				case DB_INT:
+				case DB_BITMAP:
+					ERR("%s: %d\n", rec->fld[i].name, rec->fld[i].v.int4);
+					break;
+					
+				case DB_DATETIME:
+					times = ctime(&rec->fld[i].v.time);
+					ERR("%s: %d:%.*s\n", rec->fld[i].name, rec->fld[i].v.time, strlen(times) - 1, times);
+					break;
+					
+				case DB_DOUBLE:
+					ERR("%s: %f\n", rec->fld[i].name, rec->fld[i].v.dbl);
+					break;
+					
+				case DB_FLOAT:
+					ERR("%s: %f\n", rec->fld[i].name, rec->fld[i].v.flt);
+					break;
+					
+				case DB_STR:
+				case DB_BLOB:
+					ERR("%s: %.*s\n", rec->fld[i].name, rec->fld[i].v.lstr.len, rec->fld[i].v.lstr.s);
+					break;
+					
+				case DB_CSTR:
+					ERR("%s: %s\n", rec->fld[i].name, rec->fld[i].v.cstr);
+					break;
+				}
+			}
+		}
+		ERR("\n");
+		rec = db_next(result);
+		row++;
+	}
+
+	db_res_free(result);
+
+	db_cmd_free(get);
+	db_disconnect(db);
+	db_ctx_free(db);
+	return 0;
+
+ error:
+	if (get) db_cmd_free(get);
+	if (put) db_cmd_free(put);
+	if (del) db_cmd_free(del);
+	db_disconnect(db);
+	db_ctx_free(db);
+	return -1;
+}
+#endif /* PG_TEST */
+
+static int pg_mod_init(void)
+{
+#ifdef PG_TEST
+	if (pg_test() == 0) {
+		ERR("postgres: Testing successful\n");
+	} else {
+		ERR("postgres: Testing failed\n");
+	}
+	return -1;
+#endif /* PG_TEST */
+	return 0;
+}
+
+/** @} */

+ 47 - 0
modules/db_postgres/pg_mod.h

@@ -0,0 +1,47 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_MOD_H
+#define _PG_MOD_H
+
+/** @defgroup postgres PostgreSQL Database Driver
+ * @ingroup DB_API 
+ */
+/** @{ */
+
+/** \file 
+ * Postgres module interface.
+ */
+
+extern int pg_retries;
+
+/** @} */
+
+#endif /* _PG_MOD_H */

+ 184 - 0
modules/db_postgres/pg_oid.c

@@ -0,0 +1,184 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Implementation of functions related to PostgreSQL Oid identifiers.
+ */
+
+
+#include "pg_oid.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include <strings.h>
+#include <malloc.h>
+#include <string.h>
+
+/** An array of supported PostgreSQL field types. */
+static char* pg_type_id_name[] = {
+	"bool",
+	"bytea",
+	"char",
+	"int8",
+	"int2",
+	"int4",
+	"text",
+	"float4",
+	"float8",
+	"inet",
+	"bpchar",
+	"varchar",
+	"timestamp",
+	"timestamptz",
+	"bit",
+	"varbit",
+};
+
+
+static int get_index(char* name)
+{
+	int i;
+	
+	for(i = 0; i < PG_ID_MAX; i++) {
+		if (strcasecmp(name, pg_type_id_name[i]) == 0) return i; 
+	}
+	return -1;
+}
+
+
+pg_type_t* pg_new_oid_table(PGresult* res)
+ {
+	pg_type_t* table = NULL;
+	int row, n, end, idx, fields;
+	str s;
+	
+	if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) goto error;
+
+	n = PQntuples(res);
+	if (n <= 0) goto error;
+	fields = PQnfields(res);
+	if (fields != 2) goto error;
+
+	table = (pg_type_t*)malloc(sizeof(pg_type_t) * (n + 1));
+	if (table == NULL) goto error;
+	memset(table, '\0', sizeof(pg_type_t) * (n + 1));
+	
+	end = n - 1;
+	for(row = 0; row < n; row++) {
+
+		/* Get name */
+		s.s = PQgetvalue(res, row, 0);
+		if (s.s == NULL) goto error;
+
+		/* Find index where the record is to be stored */
+		idx = get_index(s.s);
+		if (idx == -1) idx = end--;
+
+		/* Store the name */
+		table[idx].name = strdup(s.s);
+		if (table[idx].name == NULL) goto error;
+
+		/* Oid */
+		s.s = PQgetvalue(res, row, 1);
+		if (s.s == NULL) goto error;
+		s.len = strlen(s.s);
+		if (str2int(&s, &table[idx].oid) < 0) goto error;
+
+		DBG("postgres: Type %s maps to Oid %d\n", table[idx].name, table[idx].oid);
+	}
+	return table;
+	
+ error:
+	ERR("postgres: Error while obtaining field/data type description from server\n");
+	if (table) {
+		for(idx = 0; idx < n; idx++) {
+			if (table[idx].name) free(table[idx].name);
+		}
+		free(table);
+	}
+	return NULL;
+}
+
+
+void pg_destroy_oid_table(pg_type_t* table)
+{
+	int i;
+	if (table) {
+		for(i = 0; table[i].name; i++) {
+			free(table[i].name);
+		}
+		free(table);
+	}
+}
+
+
+int pg_name2oid(Oid* oid, pg_type_t* table, const char* name)
+{
+	int i;
+
+	if (!oid || !table) {
+		BUG("postgres: Invalid parameters to pg_name2oid\n");
+		return -1;
+	}
+
+	if (name == NULL || name[0] == '\0') return 1;
+
+	for(i = 0; table[i].name; i++) {
+		if (strcasecmp(table[i].name, name) == 0) {
+			*oid = table[i].oid;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+
+int pg_oid2name(const char** name, pg_type_t* table, Oid oid)
+{
+	int i;
+
+	if (!table || !name) {
+		BUG("postgres: Invalid parameters to pg_oid2name\n");
+		return -1;
+	}
+
+	for(i = 0; table[i].name; i++) {
+		if (oid == table[i].oid) {
+			*name = table[i].name;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+/** @} */

+ 128 - 0
modules/db_postgres/pg_oid.h

@@ -0,0 +1,128 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_OID_H
+#define _PG_OID_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Data structures and functions implementing support for PostgreSQL Oid
+ * identifiers.
+ */
+
+#include <libpq-fe.h>
+
+/** Structure mapping field names to Oids.
+ * This structure is used to map field names or data type names to their
+ * Oids/field types.
+ */
+typedef struct pg_type {
+	Oid oid;    /**< PostgreSQL Oid (object identifier) */
+	char* name; /**< Field name */
+} pg_type_t;
+
+
+/** Enumeration of supported PostreSQL types.
+ * This is the enumeration of all PostgreSQL types supported
+ * by this driver, that means this driver will be able to convert
+ * to and from these types.
+ *
+ * This enum is primarilly used as index to arrays of pg_type_t that are
+ * stored in pg_con structures. Upon connecting to a PostgreSQL server the
+ * driver retrieves the list of supported data types and oids from the server
+ * and stores then in an array. Different PostgreSQL servers can have
+ * different Oids for various data types so we have to have one array that
+ * maps symbolic names below to Oids per connection/server.
+ */
+enum pg_type_id {
+	PG_BOOL = 0,    /**< Boolean, true/false */
+	PG_BYTE,        /**< Binary data */
+	PG_CHAR,        /**< Single character */
+	PG_INT8,        /**< Integer with 8-byte storage */
+	PG_INT2,        /**< Integer with 2-byte storage */
+	PG_INT4,        /**< Integer with 4-byte storage */
+	PG_TEXT,        /**< Variable-length string, no limit specified */
+	PG_FLOAT4,      /**< Single-precision floating point number, 4-byte storage */
+	PG_FLOAT8,      /**< Double-precision floating point number, 8-byte storage */
+	PG_INET,        /**< IP address/netmask, host address */
+	PG_BPCHAR,      /**< Blank-padded string, fixed storage length */
+	PG_VARCHAR,     /**< Non-blank padded string, variable storage length */
+	PG_TIMESTAMP,   /**< Date and time */
+	PG_TIMESTAMPTZ, /**< Date and time with time zone */
+	PG_BIT,         /**< Fixed-length bit string */
+	PG_VARBIT,      /**< Variable-length bit string */
+	PG_ID_MAX       /**< Bumper, this must be the last element of the enum */
+};
+
+
+/** Creates a new Oid mapping table.
+ * The function creates a new Oid mapping table and initalizes the contents
+ * of the table with values obtained from the PostgreSQL result in parameter.
+ * Each element of the table maps field type name to oid and vice versa.
+ * @param res A PostgreSQL result structure used to initialize the array.
+ * @retval A pointer to the resulting array.
+ * @retval NULL on error.
+ */
+pg_type_t* pg_new_oid_table(PGresult* res);
+
+
+/** Frees all memory used by the table
+ * @param table A pointer to table to be freed
+ */
+void pg_destroy_oid_table(pg_type_t* table);
+
+
+/** Maps a field type name to Oid.
+ * @param oid The resulting oid
+ * @param table The mapping table
+ * @param name Field type name
+ * @retval 0 on success
+ * @retval 1 if the type name is unknown
+ * @retval -1 on error.
+ */
+int pg_name2oid(Oid* oid, pg_type_t* table, const char* name);
+
+
+/** Maps a field type name to Oid.
+ * @param oid The resulting oid
+ * @param table The mapping table
+ * @param name Field type name
+ * @retval 0 on success
+ * @retval 1 if the type name is unknown
+ * @retval -1 on error.
+ */
+int pg_oid2name(const char** name, pg_type_t* table, Oid oid);
+
+/** @} */
+
+#endif /* _PG_OID_H */

+ 76 - 0
modules/db_postgres/pg_res.c

@@ -0,0 +1,76 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Functions working with result structures received from PostgreSQL servers.
+ */
+
+#include "pg_res.h"
+#include "pg_cmd.h"
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../db/db_gen.h"
+
+
+static void pg_res_free(db_res_t* res, struct pg_res* payload)
+{
+	db_drv_free(&payload->gen);
+	if (payload->res) PQclear(payload->res);
+	pkg_free(payload);
+}
+
+
+int pg_res(db_res_t* res)
+{
+	struct pg_res* pres;
+
+	pres = (struct pg_res*)pkg_malloc(sizeof(struct pg_res));
+	if (pres == NULL) {
+		ERR("postgres: No memory left\n");
+		return -1;
+	}
+	if (db_drv_init(&pres->gen, pg_res_free) < 0) goto error;
+	DB_SET_PAYLOAD(res, pres);
+	return 0;
+	
+ error:
+	if (pres) {
+		db_drv_free(&pres->gen);
+		pkg_free(pres);
+	}
+	return -1;
+}
+
+/** @} */

+ 58 - 0
modules/db_postgres/pg_res.h

@@ -0,0 +1,58 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_RES_H
+#define _PG_RES_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Data structures and functions to convert results obtained from PostgreSQL
+ * servers.
+ */
+
+#include "../../db/db_drv.h"
+#include "../../db/db_res.h"
+
+#include <libpq-fe.h>
+
+struct pg_res {
+	db_drv_t gen;
+	PGresult* res;
+    int row, rows;
+};
+
+int pg_res(db_res_t* cmd);
+
+/** @} */
+
+#endif /* _PG_RES_H */

+ 424 - 0
modules/db_postgres/pg_sql.c

@@ -0,0 +1,424 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Implementation of various functions that assemble SQL query strings for
+ * PostgreSQL.
+ */
+
+#include "pg_sql.h"
+
+#include "../../db/db_cmd.h"
+#include "../../db/db_fld.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+
+#include <string.h>
+
+
+enum {
+	STR_DELETE,
+	STR_INSERT,
+	STR_UPDATE,
+	STR_SELECT,
+	STR_REPLACE,
+	STR_SET,
+	STR_WHERE,
+	STR_IS,
+	STR_AND,
+	STR_OR,
+	STR_ESC,
+	STR_OP_EQ,
+	STR_OP_NE,
+	STR_OP_LT,
+	STR_OP_GT,
+	STR_OP_LEQ,
+	STR_OP_GEQ,
+	STR_VALUES,
+	STR_FROM,
+	STR_OID,
+	STR_TIMESTAMP,
+	STR_ZT
+};
+
+
+static str strings[] = {
+	STR_STATIC_INIT("delete from "),
+	STR_STATIC_INIT("insert into "),
+	STR_STATIC_INIT("update "),
+	STR_STATIC_INIT("select "),
+	STR_STATIC_INIT("replace "),
+	STR_STATIC_INIT(" set "),
+	STR_STATIC_INIT(" where "),
+	STR_STATIC_INIT(" is "),
+	STR_STATIC_INIT(" and "),
+	STR_STATIC_INIT(" or "),
+	STR_STATIC_INIT("?"),
+	STR_STATIC_INIT("="),
+	STR_STATIC_INIT("!="),
+	STR_STATIC_INIT("<"),
+	STR_STATIC_INIT(">"),
+	STR_STATIC_INIT("<="),
+	STR_STATIC_INIT(">="),
+	STR_STATIC_INIT(") values ("),
+	STR_STATIC_INIT(" from "),
+	STR_STATIC_INIT("select typname,pg_type.oid from pg_type"),
+	STR_STATIC_INIT("select timestamp '2000-01-01 00:00:00' + time '00:00:01'"), 
+	STR_STATIC_INIT("\0")
+};
+
+
+/**
+ * Reallocatable string buffer.
+ */
+struct string_buffer {
+	char *s;			/**< allocated memory itself */
+	int   len;			/**< used memory */
+	int   size;			/**< total size of allocated memory */
+	int   increment;	/**< increment when realloc is necessary */ 
+};
+
+
+/** Appends string to string buffer.
+ * This function appends string to dynamically created string buffer,
+ * the buffer is automatically extended if there is not enough room
+ * in the buffer. The buffer is allocated using pkg_malloc.
+ * @param sb    string buffer
+ * @param nstr  string to add
+ * @return      0 if OK, -1 if failed
+ */
+static inline int sb_add(struct string_buffer *sb, str *nstr)
+{
+	int new_size = 0;
+	int rsize = sb->len + nstr->len;
+	int asize;
+	char *newp;
+	
+	if (rsize > sb->size) {
+		asize = rsize - sb->size;
+		new_size = sb->size + (asize / sb->increment  + 
+							   (asize % sb->increment > 0)) * sb->increment;
+		newp = pkg_malloc(new_size);
+		if (!newp) {
+			ERR("postgres: No memory left\n");
+			return -1;
+		}
+		if (sb->s) {
+			memcpy(newp, sb->s, sb->len);
+			pkg_free(sb->s);
+		}
+		sb->s = newp;
+		sb->size = new_size;
+	}
+	memcpy(sb->s + sb->len, nstr->s, nstr->len);
+	sb->len += nstr->len;
+	return 0;
+}
+
+
+/** Creates str string from zero terminated string without copying.
+ * This function initializes members of a temporary str structure
+ * with the pointer and lenght of the string from s parameter.
+ *
+ * @param str A pointer to temporary str structure.
+ * @param s   A zero terminated string.
+ * @return Pointer to the str structure.
+ */
+static inline str* set_str(str *str, const char *s)
+{
+	str->s = (char *)s;
+	str->len = strlen(s);
+	return str;
+}
+
+
+/** Returns a parameter marker for PostgreSQL with number i
+ * The function builds a parameter marker for use in
+ * PostgreSQL SQL queries, such as $1, $2, etc.
+ * @param i Number of the parameter
+ * @retval A pointer to static string with the marker
+ */
+static str* get_marker(unsigned int i)
+{
+	static char buf[INT2STR_MAX_LEN + 1];
+	static str res;
+	const char* c;
+
+	buf[0] = '$';
+	res.s = buf;
+
+	c = int2str(i, &res.len);
+	memcpy(res.s + 1, c, res.len);
+	res.len++;
+	return &res;
+}
+
+
+int build_update_sql(str* sql_cmd, db_cmd_t* cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+							  .size = 0, .increment = 128};
+	db_fld_t* fld;
+	int i, rv = 0;
+	str tmpstr;
+
+	rv = sb_add(&sql_buf, &strings[STR_UPDATE]); /* "UPDATE " */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+	rv |= sb_add(&sql_buf, &cmd->table);		 /* table name */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+	rv |= sb_add(&sql_buf, &strings[STR_SET]);	 /* " SET " */
+
+	/* column name-value pairs */
+	for(i = 0, fld = cmd->vals; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, "="));
+		rv |= sb_add(&sql_buf, &strings[STR_ESC]);
+		if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ","));
+	}
+	if (rv) goto error;
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+
+			switch(fld[i].op) {
+			case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
+			case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
+			case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
+			case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
+			case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
+			case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
+			}
+			
+			rv |= sb_add(&sql_buf, get_marker(i + 1));
+			if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
+		}
+	}
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+int build_insert_sql(str* sql_cmd, db_cmd_t* cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+									.size = 0, .increment = 128};
+	db_fld_t* fld;
+	int i, rv = 0;
+	str tmpstr;
+
+	rv = sb_add(&sql_buf, &strings[STR_INSERT]); /* "INSERT INTO " */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+	rv |= sb_add(&sql_buf, &cmd->table);		 /* table name */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\" ("));
+
+	/* column names */
+	for(i = 0, fld = cmd->vals; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+		if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ","));
+	}
+	if (rv) goto error;
+
+	rv |= sb_add(&sql_buf, &strings[STR_VALUES]);
+
+	for(i = 0, fld = cmd->vals; !DB_FLD_EMPTY(fld) && !DB_FLD_LAST(fld[i]); i++) {
+		rv |= sb_add(&sql_buf, get_marker(i + 1));
+		if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ","));
+	}
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, ")"));
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+				 
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+int build_delete_sql(str* sql_cmd, db_cmd_t* cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+									.size = 0, .increment = 128};
+	db_fld_t* fld;
+	int i, rv = 0;
+	str tmpstr;
+
+	rv = sb_add(&sql_buf, &strings[STR_DELETE]); /* "DELETE FROM " */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+	rv |= sb_add(&sql_buf, &cmd->table);		 /* table name */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+
+			switch(fld[i].op) {
+			case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
+			case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
+			case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
+			case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
+			case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
+			case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
+			}
+			
+			rv |= sb_add(&sql_buf, get_marker(i + 1));
+			if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
+		}
+	}
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+int build_select_sql(str* sql_cmd, db_cmd_t* cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+									.size = 0, .increment = 128};
+	db_fld_t* fld;
+	int i, rv = 0;
+	str tmpstr;
+
+	rv = sb_add(&sql_buf, &strings[STR_SELECT]); /* "SELECT " */
+
+	if (DB_FLD_EMPTY(cmd->result)) {
+		rv |= sb_add(&sql_buf, set_str(&tmpstr, "*"));
+	} else {
+		for(i = 0, fld = cmd->result; !DB_FLD_LAST(fld[i]); i++) {
+			rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+			if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, set_str(&tmpstr, ","));
+		}
+	}
+
+	rv |= sb_add(&sql_buf, &strings[STR_FROM]);  /* " FROM " */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+	rv |= sb_add(&sql_buf, &cmd->table);		 /* table name */
+	rv |= sb_add(&sql_buf, set_str(&tmpstr, "\""));
+
+	if (!DB_FLD_EMPTY(cmd->match)) {
+		rv |= sb_add(&sql_buf, &strings[STR_WHERE]);
+
+		for(i = 0, fld = cmd->match; !DB_FLD_LAST(fld[i]); i++) {
+			rv |= sb_add(&sql_buf, set_str(&tmpstr, fld[i].name));
+
+			switch(fld[i].op) {
+			case DB_EQ:  rv |= sb_add(&sql_buf, &strings[STR_OP_EQ]);  break;
+			case DB_NE:  rv |= sb_add(&sql_buf, &strings[STR_OP_NE]);  break;
+			case DB_LT:  rv |= sb_add(&sql_buf, &strings[STR_OP_LT]);  break;
+			case DB_GT:  rv |= sb_add(&sql_buf, &strings[STR_OP_GT]);  break;
+			case DB_LEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_LEQ]); break;
+			case DB_GEQ: rv |= sb_add(&sql_buf, &strings[STR_OP_GEQ]); break;
+			}
+			
+			rv |= sb_add(&sql_buf, get_marker(i + 1));
+			if (!DB_FLD_LAST(fld[i + 1])) rv |= sb_add(&sql_buf, &strings[STR_AND]);
+		}
+	}
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+int build_select_oid_sql(str* sql_cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+									.size = 0, .increment = 128};
+	int rv = 0;
+	
+	rv = sb_add(&sql_buf, &strings[STR_OID]);
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+ error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+
+int build_timestamp_format_sql(str* sql_cmd)
+{
+	struct string_buffer sql_buf = {.s = NULL, .len = 0, 
+									.size = 0, .increment = 128};
+	int rv = 0;
+	
+	rv = sb_add(&sql_buf, &strings[STR_TIMESTAMP]);
+	rv |= sb_add(&sql_buf, &strings[STR_ZT]);
+	if (rv) goto error;
+
+	sql_cmd->s = sql_buf.s;
+	sql_cmd->len = sql_buf.len;
+	return 0;
+
+ error:
+	if (sql_buf.s) pkg_free(sql_buf.s);
+	return -1;
+}
+
+/** @} */

+ 118 - 0
modules/db_postgres/pg_sql.h

@@ -0,0 +1,118 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_SQL_H
+#define _PG_SQL_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file
+ * Implementation of various functions that assemble SQL query strings for
+ * PostgreSQL.
+ */
+
+#include "../../db/db_cmd.h"
+#include "../../str.h"
+
+
+/** Builds an UPDATE SQL statement.
+ * This function creates an UPDATE SQL statement where column values are
+ * replaced with special markers. The resulting SQL statement is suitable
+ * for submitting as prepared statement. The result string is zero terminated.
+ * @param sql_cmd Pointer to a str variable where the resulting SQL statement
+ *                will be stored. The buffer in sql_cmd->s is allocated using
+ *                pkg_malloc and the caller of the function is responsible for
+ *                freeing it.
+ * @param cmd The command whose data will be used to generate the query.
+ * @return 0 on success, negative number on error
+ */
+int build_update_sql(str* sql_cmd, db_cmd_t* cmd);
+
+
+/** Builds an INSERT SQL statement.
+ * This function creates an INSERT SQL statement where column values are
+ * replaced with special markers. The resulting SQL statement is suitable
+ * for submitting as prepared statement. The result string is zero terminated.
+ * @param sql_cmd Pointer to a str variable where the resulting SQL statement
+ *                will be stored. The buffer in sql_cmd->s is allocated using
+ *                pkg_malloc and the caller of the function is responsible for *                freeing it.
+ * @param cmd The command whose data will be used to generate the query.
+ * @return 0 on success, negative number on error
+ */
+int build_insert_sql(str* sql_cmd, db_cmd_t* cmd);
+
+
+/** Builds a DELETE SQL statement.
+ * This function creates a DELETE SQL statement where column values are
+ * replaced with special markers. The resulting SQL statement is suitable
+ * for submitting as prepared statement. The result string is zero terminated.
+ * @param sql_cmd Pointer to a str variable where the resulting SQL statement
+ *                will be stored. The buffer in sql_cmd->s is allocated using
+ *                pkg_malloc and the caller of the function is responsible for
+ *                freeing it.
+ * @param cmd The command whose data will be used to generate the query.
+ * @return 0 on success, negative number on error
+ */
+int build_delete_sql(str* sql_cmd, db_cmd_t* cmd);
+
+
+/** Builds a SELECT SQL statement.
+ * This function creates a SELECT SQL statement where column values are
+ * replaced with special markers. The resulting SQL statement is suitable
+ * for submitting as prepared statement. The result string is zero terminated.
+ * @param sql_cmd Pointer to a str variable where the resulting SQL statement
+ *                will be stored. The buffer in sql_cmd->s is allocated using
+ *                pkg_malloc and the caller of the function is responsible for
+ *                freeing it.
+ * @param cmd The command whose data will be used to generate the query.
+ * @return 0 on success, negative number on error
+ */
+int build_select_sql(str* sql_cmd, db_cmd_t* cmd);
+
+
+/* Builds SQL query used to obtain the list of supported field types.
+ * This function builds a special SQL query that is used to obtain the list
+ * of supported field type from the server's system catalogs.
+ */
+int build_select_oid_sql(str* sql_cmd);
+
+
+/** Builds the SQL query used to determine the format of timestamp fields.
+ * This function builds a special SQL query that is sent to the server
+ * immediately after establishing a connection to determine the format of
+ * timestamp fields used on the server.
+ */
+int build_timestamp_format_sql(str* sql_cmd);
+
+/** @} */
+
+#endif /* _PG_SQL_H */

+ 292 - 0
modules/db_postgres/pg_uri.c

@@ -0,0 +1,292 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * The implementation of parser parsing postgres://.. URIs.
+ */
+
+#include "pg_uri.h"
+
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../ut.h"
+#include "../../db/db_gen.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+/** compare s1 & s2  with a function f (which should return 0 if ==);
+ * s1 & s2 can be null
+ * return 0 if match, 1 if not 
+ */
+#define cmpstr(s1, s2, f) \
+	((s1)!=(s2)) && ((s1)==0 || (s2)==0 || (f)((s1), (s2))!=0)
+
+
+/** Compare two connection URIs */
+static unsigned char pg_uri_cmp(db_uri_t* uri1, db_uri_t* uri2)
+{
+	struct pg_uri* puri1, *puri2;
+
+	if (!uri1 || !uri2) return 0;
+
+	puri1 = DB_GET_PAYLOAD(uri1);
+	puri2 = DB_GET_PAYLOAD(uri2);
+	if (puri1->port != puri2->port) return 0;
+
+	if (cmpstr(puri1->username, puri2->username, strcmp)) return 0;
+	if (cmpstr(puri1->password, puri2->password, strcmp)) return 0;
+	if (cmpstr(puri1->host, puri2->host, strcasecmp)) return 0;
+	if (cmpstr(puri1->database, puri2->database, strcmp)) return 0;
+	return 1;
+}
+
+
+/** Duplicate a string
+ */
+static int dupl_string(char** dst, const char* begin, const char* end)
+{
+	if (*dst) pkg_free(*dst);
+
+	*dst = pkg_malloc(end - begin + 1);
+	if ((*dst) == NULL) {
+		return -1;
+	}
+
+	memcpy(*dst, begin, end - begin);
+	(*dst)[end - begin] = '\0';
+	return 0;
+}
+
+
+/** Parses postgres URI of form 
+ * //[username[:password]@]hostname[:port]/database
+ *
+ * Returns 0 if parsing was successful and -1 otherwise
+ */
+static int parse_postgres_uri(struct pg_uri* res, str* uri)
+{
+#define SHORTEST_DB_URL "//a/b"
+#define SHORTEST_DB_URL_LEN (sizeof(SHORTEST_DB_URL) - 1)
+
+	enum state {
+		ST_SLASH1,     /* First slash */
+		ST_SLASH2,     /* Second slash */
+		ST_USER_HOST,  /* Username or hostname */
+		ST_PASS_PORT,  /* Password or port part */
+		ST_HOST,       /* Hostname part */
+		ST_PORT,       /* Port part */
+		ST_DB          /* Database part */
+	};
+
+	enum state st;
+	int  i;
+	const char* begin;
+	char* prev_token;
+
+	prev_token = 0;
+
+	if (!res || !res) {
+		goto err;
+	}
+	
+	if (uri->len < SHORTEST_DB_URL_LEN) {
+		goto err;
+	}
+	
+	st = ST_SLASH1;
+	begin = uri->s;
+
+	for(i = 0; i < uri->len; i++) {
+		switch(st) {
+		case ST_SLASH1:
+			switch(uri->s[i]) {
+			case '/':
+				st = ST_SLASH2;
+				break;
+
+			default:
+				goto err;
+			}
+			break;
+
+		case ST_SLASH2:
+			switch(uri->s[i]) {
+			case '/':
+				st = ST_USER_HOST;
+				begin = uri->s + i + 1;
+				break;
+				
+			default:
+				goto err;
+			}
+			break;
+
+		case ST_USER_HOST:
+			switch(uri->s[i]) {
+			case '@':
+				st = ST_HOST;
+				if (dupl_string(&res->username, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case ':':
+				st = ST_PASS_PORT;
+				if (dupl_string(&prev_token, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) 
+					goto err;
+				return 0;
+			}
+			break;
+
+		case ST_PASS_PORT:
+			switch(uri->s[i]) {
+			case '@':
+				st = ST_HOST;
+				res->username = prev_token;
+				if (dupl_string(&res->password, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				res->host = prev_token;
+				res->port = str2s(begin, uri->s + i - begin, 0);
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) 
+					goto err;
+				return 0;
+			}
+			break;
+
+		case ST_HOST:
+			switch(uri->s[i]) {
+			case ':':
+				st = ST_PORT;
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				begin = uri->s + i + 1;
+				break;
+
+			case '/':
+				if (dupl_string(&res->host, begin, uri->s + i) < 0) goto err;
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) 
+					goto err;
+				return 0;
+			}
+			break;
+
+		case ST_PORT:
+			switch(uri->s[i]) {
+			case '/':
+				res->port = str2s(begin, uri->s + i - begin, 0);
+				if (dupl_string(&res->database, uri->s + i + 1, uri->s + uri->len) < 0) 
+					goto err;
+				return 0;
+			}
+			break;
+			
+		case ST_DB:
+			break;
+		}
+	}
+
+	if (st != ST_DB) goto err;
+	return 0;
+
+ err:
+	if (prev_token) pkg_free(prev_token);
+	if (res == NULL) return -1;
+	if (res->username) {
+		pkg_free(res->username);
+		res->username = NULL;
+	}
+	if (res->password) {
+		pkg_free(res->password);
+		res->password = NULL;
+	}
+	if (res->host) {
+		pkg_free(res->host);
+		res->host = NULL;
+	}
+	if (res->database) {
+		pkg_free(res->database);
+		res->database = NULL;
+	}
+	return -1;
+}
+
+
+
+static void pg_uri_free(db_uri_t* uri, struct pg_uri* payload)
+{
+	if (payload == NULL) return;
+	db_drv_free(&payload->drv);
+	if (payload->username) pkg_free(payload->username);
+	if (payload->password) pkg_free(payload->password);
+	if (payload->host) pkg_free(payload->host);
+	if (payload->database) pkg_free(payload->database);
+	pkg_free(payload);
+}
+
+
+int pg_uri(db_uri_t* uri)
+{
+	struct pg_uri* puri;
+
+	puri = (struct pg_uri*)pkg_malloc(sizeof(struct pg_uri));
+	if (puri == NULL) {
+		ERR("postgres: No memory left\n");
+		goto error;
+	}
+	memset(puri, '\0', sizeof(struct pg_uri));
+	if (db_drv_init(&puri->drv, pg_uri_free) < 0) goto error;
+	if (parse_postgres_uri(puri, &uri->body) < 0) goto error;
+
+	DB_SET_PAYLOAD(uri, puri);
+	uri->cmp = pg_uri_cmp;
+	return 0;
+
+ error:
+	if (puri) {
+		db_drv_free(&puri->drv);
+		if (puri) pkg_free(puri);
+	}
+	return -1;
+}
+
+/** @} */

+ 71 - 0
modules/db_postgres/pg_uri.h

@@ -0,0 +1,71 @@
+/* 
+ * $Id$ 
+ *
+ * PostgreSQL Database Driver for SER
+ *
+ * Portions Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2003 August.Net Services, LLC
+ * Portions Copyright (C) 2005-2008 iptelorg GmbH
+ *
+ * This file is part of SER, a free SIP server.
+ *
+ * SER 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 2 of the License, or (at your option) any later
+ * version
+ *
+ * For a license to use the ser software under conditions other than those
+ * described here, or to purchase support for this software, please contact
+ * iptel.org by e-mail at the following addresses: [email protected]
+ *
+ * SER 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 this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _PG_URI_H
+#define _PG_URI_H
+
+/** \addtogroup postgres
+ * @{ 
+ */
+
+/** \file 
+ * The implementation of parser parsing postgres://.. URIs.
+ */
+
+#include "../../db/db_uri.h"
+#include "../../db/db_drv.h"
+
+/** PostgreSQL driver specific payload to attach to db_uri structures.
+ * This is the PostgreSQL specific structure that will be attached
+ * to generic db_uri structures in the database API in SER. The 
+ * structure contains parsed elements of postgres:// uri.
+ */
+struct pg_uri {
+	db_drv_t drv;
+	char* username;
+	char* password;
+	char* host;
+	unsigned short port;
+	char* database;
+};
+
+/** Create a new pg_uri structure and parse the URI in parameter.
+ * This function builds a new pg_uri structure from the body of
+ * the generic URI given to it in parameter.
+ * @param uri A generic db_uri structure.
+ * @retval 0 on success
+ * @retval A negative number on error.
+ */
+int pg_uri(db_uri_t* uri);
+
+/** @} */
+
+#endif /* _PG_URI_H */
+