/*
 * pua db - presence user agent database support
 *
 * Copyright (C) 2011 Crocodile RCS Ltd
 * Copyright (C) 2024 Victor Seva (Sipwise)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 columns */
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_uri, str pres_id, str *etag,
		ua_pres_t *result, db1_res_t **dbres)
{
	db_key_t q_cols[3];
	db_val_t q_vals[3], *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) {
		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++;
	}

	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;
}
