/*
 * pua db - presence user agent database support
 *
 * Copyright (C) 2011 Crocodile RCS Ltd
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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
 *
 * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../../core/mem/mem.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_from.h"

#include "pua.h"
#include "pua_db.h"

/* database colums */
static str str_id_col = str_init( "id" );
static str str_pres_uri_col = str_init("pres_uri");
static str str_pres_id_col = str_init("pres_id");
static str str_expires_col= str_init("expires");
static str str_flag_col= str_init("flag");
static str str_etag_col= str_init("etag");
static str str_tuple_id_col= str_init("tuple_id");
static str str_watcher_uri_col= str_init("watcher_uri");
static str str_call_id_col= str_init("call_id");
static str str_to_tag_col= str_init("to_tag");
static str str_from_tag_col= str_init("from_tag");
static str str_cseq_col= str_init("cseq");
static str str_event_col= str_init("event");
static str str_record_route_col= str_init("record_route");
static str str_contact_col= str_init("contact");
static str str_remote_contact_col= str_init("remote_contact");
static str str_extra_headers_col= str_init("extra_headers");
static str str_desired_expires_col= str_init("desired_expires");
static str str_version_col = str_init("version");


/******************************************************************************/

void free_results_puadb( db1_res_t *res )

{
	if (res)
	{
		pua_dbf.free_result(pua_db, res);
		res = NULL;
	}
}

/******************************************************************************/

static void extract_row( db_val_t *values, ua_pres_t *result )

{

	if (result->pres_uri != NULL )
	{
		result->pres_uri->s = (char *)VAL_STRING(values+1);
		result->pres_uri->len = strlen(VAL_STRING(values+1));
	}

	result->id.s = (char *)VAL_STRING(values+2);
	result->id.len = strlen(VAL_STRING(values+2));

	result->event = VAL_INT(values+3);

	result->expires = VAL_INT(values+4);

	result->desired_expires = VAL_INT(values+5);

	result->flag = VAL_INT(values+6);

	/* publish */
	result->etag.s = (char *)VAL_STRING(values+7);
	result->etag.len = strlen(VAL_STRING(values+7));

	result->tuple_id.s = (char *)VAL_STRING(values+8);
	result->tuple_id.len = strlen(VAL_STRING(values+8));

	/* subscribe */
	if (result->watcher_uri != NULL )
	{
		result->watcher_uri->s = (char *)VAL_STRING(values+9);
		result->watcher_uri->len = strlen(VAL_STRING(values+9));
	}

	result->call_id.s = (char *)VAL_STRING(values+10);
	result->call_id.len = strlen(VAL_STRING(values+10));

	result->to_tag.s = (char *)VAL_STRING(values+11);
	result->to_tag.len = strlen(VAL_STRING(values+11));

	result->from_tag.s = (char *)VAL_STRING(values+12);
	result->from_tag.len = strlen(VAL_STRING(values+12));

	result->cseq = VAL_INT(values+13);

	result->record_route.s = (char *)VAL_STRING(values+14);
	result->record_route.len = strlen(VAL_STRING(values+14));

	result->contact.s = (char *)VAL_STRING(values+15);
	result->contact.len = strlen(VAL_STRING(values+15));

	result->remote_contact.s = (char *)VAL_STRING(values+16);
	result->remote_contact.len = strlen(VAL_STRING(values+16));

	result->version = VAL_INT(values+17);

	if (result->extra_headers != NULL )
	{
		result->extra_headers->s = (char *)VAL_STRING(values+18);
		result->extra_headers->len = strlen(VAL_STRING(values+18));
	}
}

/******************************************************************************/

int clean_puadb( int update_period, int min_expires )

{
	int i, nr_rows;
	db_row_t *rows;
	db_val_t *values;
	db_key_t q_cols[1];
	db1_res_t *res= NULL;
	db_val_t q_vals[1];
	db_op_t  q_ops[1];
	int id;
	time_t now;
	ua_pres_t p;
	str pres_uri={0,0}, watcher_uri={0,0}, extra_headers={0,0};

	memset(&p, 0, sizeof(p));
	p.pres_uri = &pres_uri;
	p.watcher_uri = &watcher_uri;
	p.extra_headers = &extra_headers;

	now = time(NULL);

	/* cols and values used for search query */
	q_cols[0] = &str_expires_col;
	q_vals[0].type = DB1_INT;
	q_vals[0].nul = 0;
	q_vals[0].val.int_val = now+update_period;
	q_ops[0] = OP_LT;

	if (pua_dbf.use_table(pua_db, &db_table) < 0) {
	    LM_ERR("error in use_table pua\n");
	    return(-1);
	}

	if(db_fetch_query(&pua_dbf, pua_fetch_rows, pua_db, q_cols, q_ops,
				q_vals, NULL, 1, 0, 0, &res) < 0)
	{
		LM_ERR("DB query error\n");
		return(-1);
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return(-1);
	}

	if (RES_ROW_N(res) == 0)
	{
		/* no match */
		LM_DBG( "No records matched for clean\n");
		pua_dbf.free_result(pua_db, res);
		return(0);
	}

	do {
		nr_rows = RES_ROW_N(res);

		/* get the results and update matching entries */
		rows = RES_ROWS(res);

		for (i=0; i < nr_rows; i++)
		{
			values = ROW_VALUES(rows+i);

			extract_row( values, &p );
			id = VAL_INT(values);

			if((p.desired_expires> p.expires + min_expires) || (p.desired_expires== 0 ))
			{
				if(update_pua(&p)< 0)
				{
					LM_ERR("update_pua failed\n");
				}
				continue;
			}

			if(p.expires < now - 10)
			{
				LM_DBG("Found expired: uri= %.*s\n", p.pres_uri->len, p.pres_uri->s);
				q_cols[0] = &str_id_col;
				q_vals[0].type = DB1_INT;
				q_vals[0].nul = 0;
				q_vals[0].val.int_val = id;

				if ( pua_dbf.delete(pua_db, q_cols, 0, q_vals, 1) < 0 )
				{
					LM_ERR( "Failed to delete from db\n" );
				}
			}

		}
	} while ((db_fetch_next(&pua_dbf, pua_fetch_rows, pua_db, &res)==1)
			&& (RES_ROWS(res)>0));

	pua_dbf.free_result(pua_db, res);
	return(0);
}

/******************************************************************************/

int is_dialog_puadb(ua_pres_t *pres)

{
	int nr_rows;
	db_key_t q_cols[3], res_cols[1];
	db1_res_t *res= NULL;
	db_val_t q_vals[3];
	int n_query_cols= 0, n_res_cols=0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	/* cols and values used for search query */
	q_cols[n_query_cols] = &str_call_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_to_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->to_tag;
	n_query_cols++;

	q_cols[n_query_cols] = &str_from_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	/* return the id column, even though don't actually need */
	res_cols[n_res_cols] = &str_id_col;
	n_res_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if(pua_dbf.query(pua_db, q_cols, 0, q_vals,
				res_cols,n_query_cols,n_res_cols,0,&res) < 0)
	{
		LM_ERR("DB query error\n");
		return(-1);
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return(-1);
	}

	nr_rows = RES_ROW_N(res);
	pua_dbf.free_result(pua_db, res);

	if (nr_rows == 0)
	{
		/* no match */
		LM_DBG("No rows found.\n");
		return(-1);
	}

	if (nr_rows != 1)
	{
		LM_WARN("Too many rows found (%d)\n", nr_rows);
		/* no need to return here - drop thro */
	}

	/* established dialog */
	if(pres->to_tag.len>0)
		return 0;
	/* temporary dialog */
	return 1;
}

/******************************************************************************/

int get_record_id_puadb(ua_pres_t *pres, str **rec_id )

{
	int nr_rows;
	db_row_t *rows;
	db_key_t q_cols[3], res_cols[2];
	db1_res_t *res= NULL;
	db_val_t q_vals[3];
	int n_query_cols=0, n_res_cols=0;
	db_val_t *values;
	str *id;
	str to_tag;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	/* cols and values used for search query */
	q_cols[n_query_cols] = &str_call_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_from_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	q_cols[n_query_cols] = &str_to_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->to_tag;
	n_query_cols++;

	res_cols[n_res_cols] = &str_pres_id_col;
	n_res_cols++;

	*rec_id = NULL;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if(pua_dbf.query(pua_db, q_cols, 0, q_vals,
				res_cols,n_query_cols,n_res_cols,0,&res) < 0)
	{
		LM_ERR("DB query error\n");
		return(-1);
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return(-1);
	}

	nr_rows = RES_ROW_N(res);

	switch (nr_rows)
	{
	case 1:
		rows = RES_ROWS(res);
		values = ROW_VALUES(rows);
		break;

	case 0:
		/* no match */
		LM_DBG("No rows found. Looking for temporary dialog\n");
		pua_dbf.free_result(pua_db, res);

		n_query_cols--;

		res_cols[n_res_cols] = &str_to_tag_col;
		n_res_cols++;

		if(pua_dbf.query(pua_db, q_cols, 0, q_vals,
			res_cols,n_query_cols,n_res_cols,0,&res) < 0)
		{
			LM_ERR("DB query error\n");
			return(-1);
		}

		if (res == NULL)
		{
			LM_ERR("bad result\n");
			return(-1);
		}

		nr_rows = RES_ROW_N(res);

		if (nr_rows == 1)
		{
			rows = RES_ROWS(res);
			values = ROW_VALUES(rows);

			to_tag.s = (char *) VAL_STRING(values + 1);
			to_tag.len = strlen(to_tag.s);

			if (to_tag.len == 0 ||
				(to_tag.len > 0
				 && strncmp(to_tag.s, pres->to_tag.s, pres->to_tag.len) == 0))
			{
				LM_DBG( "Found a (possibly temporary) Dialog\n" );
				break;
			}
			else
				LM_WARN("Failed to find temporary dialog for To-tag: %.*s, found To-tag: %.*s\n",
					pres->to_tag.len, pres->to_tag.s, to_tag.len, to_tag.s);
		}

		if (nr_rows <= 1)
		{
			LM_DBG("Dialog not found\n" );
			pua_dbf.free_result(pua_db, res);
			return(0);
		}

		/* Fall-thru */

	default:
		LM_ERR("Too many rows found (%d)\n", nr_rows);
		pua_dbf.free_result(pua_db, res);
		return(-1);
	}

	id= (str*)pkg_malloc(sizeof(str));

	if(id== NULL)
	{
		PKG_MEM_ERROR;
		pua_dbf.free_result(pua_db, res);
		return(-1);
	}

	id->s= (char*)pkg_malloc( strlen(VAL_STRING(values)) * sizeof(char));

	if(id->s== NULL)
	{
		PKG_MEM_ERROR;
		pkg_free(id);
		pua_dbf.free_result(pua_db, res);
		return(-1);
	}

	memcpy(id->s, VAL_STRING(values), strlen(VAL_STRING(values)) );
	id->len= strlen(VAL_STRING(values));

	*rec_id= id;
	pua_dbf.free_result(pua_db, res);

	LM_DBG("Found id=%.*s\n", id->len, id->s);
	return(0);
}

/******************************************************************************/
int convert_temporary_dialog_puadb(ua_pres_t *pres)
{
	db_key_t query_cols[18];
	db_val_t query_vals[18];
	int n_query_cols = 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	/* The columns I need to query to find the temporary dialog */
        query_cols[n_query_cols] = &str_pres_id_col;
        query_vals[n_query_cols].type = DB1_STR;
        query_vals[n_query_cols].nul = 0;
        query_vals[n_query_cols].val.str_val = pres->id;
        n_query_cols++;

	query_cols[n_query_cols] = &str_pres_uri_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val.s = pres->pres_uri->s;
	query_vals[n_query_cols].val.str_val.len = pres->pres_uri->len;
	n_query_cols++;

	query_cols[n_query_cols] = &str_call_id_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	query_cols[n_query_cols] = &str_from_tag_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	/* The columns I need to fill in to convert a temporary dialog to a dialog */
	query_cols[n_query_cols] = &str_expires_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->expires;
	n_query_cols++;

	query_cols[n_query_cols] = &str_desired_expires_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->desired_expires;
	n_query_cols++;

	query_cols[n_query_cols] = &str_flag_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->flag;
	n_query_cols++;

	query_cols[n_query_cols] = &str_to_tag_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->to_tag;
	n_query_cols++;

	query_cols[n_query_cols] = &str_cseq_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->cseq;
	n_query_cols++;

	query_cols[n_query_cols] = &str_record_route_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->record_route;
	n_query_cols++;

	query_cols[n_query_cols] = &str_contact_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->contact;
	n_query_cols++;

	query_cols[n_query_cols] = &str_remote_contact_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val = pres->remote_contact;
	n_query_cols++;

	query_cols[n_query_cols] = &str_version_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->version;
	n_query_cols++;

	query_cols[n_query_cols] = &str_extra_headers_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	if (pres->extra_headers)
	{
		query_vals[n_query_cols].val.str_val.s = pres->extra_headers->s;
		query_vals[n_query_cols].val.str_val.len = pres->extra_headers->len;
	}
	else
	{
		query_vals[n_query_cols].val.str_val.s = "";
		query_vals[n_query_cols].val.str_val.len = 0;
	}
	n_query_cols++;

	query_cols[n_query_cols] = &str_event_col;
	query_vals[n_query_cols].type = DB1_INT;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.int_val = pres->event;
	n_query_cols++;

	query_cols[n_query_cols] = &str_watcher_uri_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val.s = pres->watcher_uri->s;
	query_vals[n_query_cols].val.str_val.len = pres->watcher_uri->len;
	n_query_cols++;

	query_cols[n_query_cols] = &str_etag_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val.s = 0;
	query_vals[n_query_cols].val.str_val.len = 0;
	n_query_cols++;

	query_cols[n_query_cols] = &str_tuple_id_col;
	query_vals[n_query_cols].type = DB1_STR;
	query_vals[n_query_cols].nul = 0;
	query_vals[n_query_cols].val.str_val.s = 0;
	query_vals[n_query_cols].val.str_val.len = 0;
	n_query_cols++;

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if (pua_dbf.replace != NULL)
	{
		if (pua_dbf.replace(pua_db, query_cols, query_vals, n_query_cols,
					4, 0) < 0)
		{
			LM_ERR("Failed replace db\n");
			return -1;
		}
	}
	else
	{
		if (pua_dbf.update(pua_db, query_cols, 0, query_vals,
				query_cols + 4, query_vals + 4, 4, n_query_cols - 4) < 0)
		{
			LM_ERR("Failed update db\n");
			return -1;
		}

		LM_DBG("affected_rows: %d\n", pua_dbf.affected_rows(pua_db));
		if (pua_dbf.affected_rows(pua_db) == 0)
		{
			if (pua_dbf.insert(pua_db, query_cols, query_vals, n_query_cols) < 0)
			{
				LM_ERR("Failed insert db\n");
				return -1;
			}
		}
	}


	shm_free(pres->remote_contact.s);
	shm_free(pres);

	return 1;
}

/******************************************************************************/

int insert_record_puadb(ua_pres_t* pres)

{
	db_key_t db_cols[18];
	db_val_t db_vals[18];
	int n_cols= 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	db_cols[n_cols] = &str_pres_uri_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->pres_uri->s;
	db_vals[n_cols].val.str_val.len = pres->pres_uri->len;
	n_cols++;

	db_cols[n_cols] = &str_pres_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->id.s;
	db_vals[n_cols].val.str_val.len = pres->id.len;
	n_cols++;

	db_cols[n_cols] = &str_event_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->event;
	n_cols++;

	db_cols[n_cols] = &str_expires_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->expires;
	n_cols++;

	db_cols[n_cols] = &str_desired_expires_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->desired_expires;
	n_cols++;

	db_cols[n_cols] = &str_flag_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->flag;
	n_cols++;

	db_cols[n_cols] = &str_etag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->etag.s;
	db_vals[n_cols].val.str_val.len = pres->etag.len;
	n_cols++;

	db_cols[n_cols] = &str_tuple_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->tuple_id.s;
	db_vals[n_cols].val.str_val.len = pres->tuple_id.len;
	n_cols++;

	db_cols[n_cols] = &str_watcher_uri_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_call_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_to_tag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_from_tag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_cseq_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = 0;
	n_cols++;

	db_cols[n_cols] = &str_record_route_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_contact_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_remote_contact_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_version_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->version;
	n_cols++;

	db_cols[n_cols] = &str_extra_headers_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	if (pres->extra_headers)
	{
		db_vals[n_cols].val.str_val.s = pres->extra_headers->s;
		db_vals[n_cols].val.str_val.len = pres->extra_headers->len;
	}
	else
	{
		db_vals[n_cols].val.str_val.s = "";
		db_vals[n_cols].val.str_val.len = 0;
	}
	n_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if(pua_dbf.insert(pua_db, db_cols, db_vals, n_cols) < 0)
	{
		LM_ERR("DB insert failed\n");
		return(-1);
	}

	return(0);
}

/******************************************************************************/

ua_pres_t *get_record_puadb(str pres_id, str *etag, ua_pres_t *result, db1_res_t **dbres)
{
	db_key_t q_cols[2];
	db_val_t q_vals[2], *values;
	db_row_t *rows;
	db1_res_t *res;
	int n_query_cols = 0, nr_rows;
	db_query_f query_fn = pua_dbf.query_lock ? pua_dbf.query_lock : pua_dbf.query;

	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres_id;
	n_query_cols++;

	if (etag != NULL)
	{
		q_cols[n_query_cols] = &str_etag_col;
		q_vals[n_query_cols].type = DB1_STR;
		q_vals[n_query_cols].nul = 0;
		q_vals[n_query_cols].val.str_val.s = etag->s;
		q_vals[n_query_cols].val.str_val.len = etag->len;
		n_query_cols++;
	}

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(NULL);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(NULL);
	}

	if(query_fn(pua_db, q_cols, 0, q_vals,
				NULL,n_query_cols,0,0,&res) < 0)
	{
		LM_ERR("DB query error\n");
		return(NULL);
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return(NULL);
	}

	nr_rows = RES_ROW_N(res);

	if (nr_rows == 0)
	{
		/* no match */
		LM_DBG("No rows found\n");
		pua_dbf.free_result(pua_db, res);
		return(NULL);
	}

	if (nr_rows != 1)
	{
		LM_ERR("Too many rows found (%d)\n", nr_rows);
		pua_dbf.free_result(pua_db, res);
		return(NULL);
	}

	/* get the results and fill in return data structure */
	rows = RES_ROWS(res);
	values = ROW_VALUES(rows);

	extract_row( values, result );

	*dbres = res;

	return(result);
}

/******************************************************************************/

int delete_record_puadb(ua_pres_t *pres)
{
	db_key_t q_cols[2];
	db_val_t q_vals[2];
	int n_query_cols = 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->id;
	n_query_cols++;

	if (pres->etag.s)
	{
		q_cols[n_query_cols] = &str_etag_col;
		q_vals[n_query_cols].type = DB1_STR;
		q_vals[n_query_cols].nul = 0;
		q_vals[n_query_cols].val.str_val = pres->etag;
		n_query_cols++;
	}

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if (pua_dbf.delete(pua_db, q_cols, 0, q_vals, n_query_cols) < 0)
	{
		LM_ERR("deleting record\n");
		return -1;
	}

	return 1;
}

/******************************************************************************/

int update_record_puadb(ua_pres_t *pres, int expires, str *etag)
{
	db_key_t q_cols[2], u_cols[3];
	db_val_t q_vals[2], u_vals[3];
	int n_query_cols = 0, n_update_cols = 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->id;
	n_query_cols++;

	if (pres->etag.s)
	{
		q_cols[n_query_cols] = &str_etag_col;
		q_vals[n_query_cols].type = DB1_STR;
		q_vals[n_query_cols].nul = 0;
		q_vals[n_query_cols].val.str_val = pres->etag;
		n_query_cols++;
	}

	u_cols[n_update_cols] = &str_desired_expires_col;
	u_vals[n_update_cols].type = DB1_INT;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.int_val = pres->desired_expires;
	n_update_cols++;

	u_cols[n_update_cols] = &str_expires_col;
	u_vals[n_update_cols].type = DB1_INT;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.int_val = expires + (int) time(NULL);
	n_update_cols++;

	if (etag)
	{
		u_cols[n_update_cols] = &str_etag_col;
		u_vals[n_update_cols].type = DB1_STR;
		u_vals[n_update_cols].nul = 0;
		u_vals[n_update_cols].val.str_val.s = etag->s;
		u_vals[n_update_cols].val.str_val.len = etag->len;
		n_update_cols++;
	}

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if (pua_dbf.update(pua_db, q_cols, 0, q_vals, u_cols, u_vals,
			n_query_cols, n_update_cols) < 0)
	{
		LM_ERR("updating dialog\n");
		return -1;
	}

	if (pua_dbf.affected_rows != NULL)
		return pua_dbf.affected_rows(pua_db);

	return 1;
}

/******************************************************************************/

int insert_dialog_puadb(ua_pres_t* pres)

{
	db_key_t db_cols[18];
	db_val_t db_vals[18];
	int n_cols= 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	db_cols[n_cols] = &str_pres_uri_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->pres_uri->s;
	db_vals[n_cols].val.str_val.len = pres->pres_uri->len;
	n_cols++;

	db_cols[n_cols] = &str_pres_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->id.s;
	db_vals[n_cols].val.str_val.len = pres->id.len;
	n_cols++;

	db_cols[n_cols] = &str_event_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->event;
	n_cols++;

	db_cols[n_cols] = &str_expires_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->expires;
	n_cols++;

	db_cols[n_cols] = &str_desired_expires_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->desired_expires;
	n_cols++;

	db_cols[n_cols] = &str_flag_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->flag;
	n_cols++;

	db_cols[n_cols] = &str_etag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_tuple_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = "";
	db_vals[n_cols].val.str_val.len = 0;
	n_cols++;

	db_cols[n_cols] = &str_watcher_uri_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->watcher_uri->s;
	db_vals[n_cols].val.str_val.len = pres->watcher_uri->len;
	n_cols++;

	db_cols[n_cols] = &str_call_id_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->call_id.s;
	db_vals[n_cols].val.str_val.len = pres->call_id.len;
	n_cols++;

	db_cols[n_cols] = &str_to_tag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->to_tag.s;
	db_vals[n_cols].val.str_val.len = pres->to_tag.len;
	n_cols++;

	db_cols[n_cols] = &str_from_tag_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->from_tag.s;
	db_vals[n_cols].val.str_val.len = pres->from_tag.len;
	n_cols++;

	db_cols[n_cols] = &str_cseq_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->cseq;
	n_cols++;

	db_cols[n_cols] = &str_record_route_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->record_route.s;
	db_vals[n_cols].val.str_val.len = pres->record_route.len;
	n_cols++;

	db_cols[n_cols] = &str_contact_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->contact.s;
	db_vals[n_cols].val.str_val.len = pres->contact.len;
	n_cols++;

	db_cols[n_cols] = &str_remote_contact_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.str_val.s = pres->remote_contact.s;
	db_vals[n_cols].val.str_val.len = pres->remote_contact.len;
	n_cols++;

	db_cols[n_cols] = &str_version_col;
	db_vals[n_cols].type = DB1_INT;
	db_vals[n_cols].nul = 0;
	db_vals[n_cols].val.int_val = pres->version;
	n_cols++;

	db_cols[n_cols] = &str_extra_headers_col;
	db_vals[n_cols].type = DB1_STR;
	db_vals[n_cols].nul = 0;
	if (pres->extra_headers)
	{
		db_vals[n_cols].val.str_val.s = pres->extra_headers->s;
		db_vals[n_cols].val.str_val.len = pres->extra_headers->len;
	}
	else
	{
		db_vals[n_cols].val.str_val.s = "";
		db_vals[n_cols].val.str_val.len = 0;
	}
	n_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if(pua_dbf.insert(pua_db, db_cols, db_vals, n_cols) < 0)
	{
		LM_ERR("DB insert failed\n");
		return(-1);
	}

	return(0);
}

/******************************************************************************/

ua_pres_t *get_dialog_puadb(str pres_id, str *pres_uri, ua_pres_t *result, db1_res_t **dbres)
{
	db_key_t q_cols[2];
	db_val_t q_vals[2], *values;
	db_row_t *rows;
	db1_res_t *res;
	int n_query_cols = 0, nr_rows;
	db_query_f query_fn = pua_dbf.query_lock ? pua_dbf.query_lock : pua_dbf.query;

	if (pres_uri == NULL)
	{
		LM_ERR("Attempting to search for a dialog without specifying pres_uri\n");
		return(NULL);
	}

	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_pres_uri_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val.s = pres_uri->s;
	q_vals[n_query_cols].val.str_val.len = pres_uri->len;
	n_query_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(NULL);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(NULL);
	}

	if(query_fn(pua_db, q_cols, 0, q_vals,
				NULL,n_query_cols,0,0,&res) < 0)
	{
		LM_ERR("DB query error\n");
		return(NULL);
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return(NULL);
	}

	nr_rows = RES_ROW_N(res);

	if (nr_rows == 0)
	{
		/* no match */
		LM_DBG("No rows found\n");
		pua_dbf.free_result(pua_db, res);
		return(NULL);
	}
	else if (nr_rows > 1)
	{
		LM_ERR("Too many rows found (%d)... deleting\n", nr_rows);
		pua_dbf.free_result(pua_db, res);

		if (pua_dbf.delete(pua_db, q_cols, 0, q_vals, n_query_cols) < 0)
			LM_ERR("deleting record(s)\n");

		return(NULL);
	}

	/* get the results and fill in return data structure */
	rows = RES_ROWS(res);
	values = ROW_VALUES(rows);

	extract_row( values, result );

	/*pua_dbf.free_result(pua_db, res);*/
	*dbres = res;

	return(result);
}

/******************************************************************************/

int delete_dialog_puadb(ua_pres_t *pres)
{
	db_key_t q_cols[3];
	db_val_t q_vals[3];
	int n_query_cols = 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	q_cols[n_query_cols] = &str_call_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_from_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	if (pres->to_tag.len > 0 && pres->to_tag.s != NULL)
	{
		q_cols[n_query_cols] = &str_to_tag_col;
		q_vals[n_query_cols].type = DB1_STR;
		q_vals[n_query_cols].nul = 0;
		q_vals[n_query_cols].val.str_val = pres->to_tag;
		n_query_cols++;
	}

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if (pua_dbf.delete(pua_db, q_cols, 0, q_vals, n_query_cols) < 0)
	{
		LM_ERR("deleting record\n");
		return -1;
	}

	return 1;
}

/******************************************************************************/

int update_dialog_puadb(ua_pres_t *pres, int expires, str *contact)
{
	db_key_t q_cols[3], u_cols[4];
	db_val_t q_vals[3], u_vals[4];
	int n_query_cols = 0, n_update_cols = 0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	q_cols[n_query_cols] = &str_call_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_from_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	q_cols[n_query_cols] = &str_to_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->to_tag;
	n_query_cols++;

	u_cols[n_update_cols] = &str_desired_expires_col;
	u_vals[n_update_cols].type = DB1_INT;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.int_val = pres->desired_expires;
	n_update_cols++;

	u_cols[n_update_cols] = &str_expires_col;
	u_vals[n_update_cols].type = DB1_INT;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.int_val = expires + (int) time(NULL);
	n_update_cols++;

	u_cols[n_update_cols] = &str_cseq_col;
	u_vals[n_update_cols].type = DB1_INT;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.int_val = pres->cseq;
	n_update_cols++;

	u_cols[n_update_cols] = &str_remote_contact_col;
	u_vals[n_update_cols].type = DB1_STR;
	u_vals[n_update_cols].nul = 0;
	u_vals[n_update_cols].val.str_val.s = contact->s;
	u_vals[n_update_cols].val.str_val.len = contact->len;
	n_update_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if (pua_dbf.update(pua_db, q_cols, 0, q_vals, u_cols, u_vals,
			n_query_cols, n_update_cols) < 0)
	{
		LM_ERR("updating dialog\n");
		return -1;
	}

	return 1;
}

/******************************************************************************/

int update_contact_puadb(ua_pres_t *pres, str *contact)

{
	db_key_t q_cols[3], db_cols[1];
	db_val_t q_vals[3], db_vals[1];
	int n_query_cols= 0, n_update_cols=0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	/* cols and values used for search query */
	q_cols[n_query_cols] = &str_call_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->call_id;
	n_query_cols++;

	q_cols[n_query_cols] = &str_to_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->to_tag;
	n_query_cols++;

	q_cols[n_query_cols] = &str_from_tag_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->from_tag;
	n_query_cols++;

	/* we overwrite contact even if not changed */
	db_cols[n_update_cols] = &str_remote_contact_col;
	db_vals[n_update_cols].type = DB1_STR;
	db_vals[n_update_cols].nul = 0;
	db_vals[n_update_cols].val.str_val.s = contact->s;
	db_vals[n_update_cols].val.str_val.len = contact->len;
	n_update_cols++;


	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if(pua_dbf.update(pua_db, q_cols, 0, q_vals,
				db_cols,db_vals,n_query_cols,n_update_cols) < 0)
	{
		LM_ERR("DB update failed\n");
		return(-1);
	}

	return(0);
}


/******************************************************************************/

int update_version_puadb(ua_pres_t *pres)

{
	db_key_t q_cols[1], db_cols[1];
	db_val_t q_vals[1], db_vals[1];
	int n_query_cols= 0, n_update_cols=0;

	if (pres==NULL)
	{
		LM_ERR("called with NULL param\n");
		return(-1);
	}

	/* cols and values used for search query */
	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val = pres->id;
	n_query_cols++;

	/* we overwrite contact even if not changed */
	db_cols[n_update_cols] = &str_version_col;
	db_vals[n_update_cols].type = DB1_INT;
	db_vals[n_update_cols].nul = 0;
	db_vals[n_update_cols].val.int_val = pres->version;
	n_update_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return(-1);
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(-1);
	}

	if(pua_dbf.update(pua_db, q_cols, 0, q_vals,
				db_cols,db_vals,n_query_cols,n_update_cols) < 0)

	{
		LM_ERR("DB update failed\n");
		return(-1);
	}

	return(0);
}

/******************************************************************************/

list_entry_t *get_subs_list_puadb(str *did)
{
	list_entry_t *list = NULL;
	db_key_t q_cols[1], res_cols[1];
	db1_res_t *res= NULL;
	db_val_t q_vals[1];
	int n_query_cols= 0, n_res_cols = 0;

	/* cols and values used for search query */
	q_cols[n_query_cols] = &str_pres_id_col;
	q_vals[n_query_cols].type = DB1_STR;
	q_vals[n_query_cols].nul = 0;
	q_vals[n_query_cols].val.str_val.s = did->s;
	q_vals[n_query_cols].val.str_val.len = did->len;
	n_query_cols++;

	res_cols[n_res_cols] = &str_pres_uri_col;
	n_res_cols++;

	if(pua_db == NULL)
	{
		LM_ERR("null database connection\n");
		return list;
	}

	if (pua_dbf.use_table(pua_db, &db_table) < 0)
	{
		LM_ERR("error in use_table pua\n");
		return(list);
	}

	if(db_fetch_query(&pua_dbf, pua_fetch_rows, pua_db, q_cols, 0,
				q_vals, res_cols, n_query_cols, n_res_cols, 0, &res) < 0)
	{
		LM_ERR("DB query error\n");
		return list;
	}

	if (res == NULL)
	{
		LM_ERR("bad result\n");
		return list;
	}

	if (RES_ROW_N(res) == 0)
	{
		LM_INFO( "No records found\n");
		pua_dbf.free_result(pua_db, res);
		return list;
	}

	do {
		int i, nr_rows;
		db_row_t *rows;
		nr_rows = RES_ROW_N(res);
		rows = RES_ROWS(res);

		for (i=0; i < nr_rows; i++)
		{
			str strng, *tmp_str;
			strng.s = (char *) VAL_STRING(ROW_VALUES(rows+i));
			strng.len = strlen(VAL_STRING(ROW_VALUES(rows+i)));

			if ((tmp_str = (str *)pkg_malloc(sizeof(str))) == NULL)
			{
				PKG_MEM_ERROR;
				pua_dbf.free_result(pua_db, res);
				return list;
			}
			if ((tmp_str->s = (char *)pkg_malloc(sizeof(char) * strng.len + 1)) == NULL)
			{
				pkg_free(tmp_str);
				PKG_MEM_ERROR;
				pua_dbf.free_result(pua_db, res);
				return list;
			}
			memcpy(tmp_str->s, strng.s, strng.len);
			tmp_str->len = strng.len;
			tmp_str->s[tmp_str->len] = '\0';

			list = list_insert(tmp_str, list, NULL);
		}
	} while ((db_fetch_next(&pua_dbf, pua_fetch_rows, pua_db, &res)==1)
			&& (RES_ROWS(res)>0));

	pua_dbf.free_result(pua_db, res);

	return list;
}
