/*
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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
 *
 */

/*! \file
 *  \brief USRLOC - Userloc domain handling functions
 *  \ingroup usrloc
 *
 * - Module: \ref usrloc
 */

#include "udomain.h"
#include <string.h>
#include "../../core/parser/parse_methods.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db.h"
#include "../../core/socket_info.h"
#include "../../core/ut.h"
#include "../../core/hashes.h"
#include "../../core/sr_module.h"
#include "usrloc_mod.h" /* usrloc module parameters */
#include "usrloc.h"
#include "utime.h"
#include "ul_callback.h"
#include "ul_keepalive.h"
#include "urecord.h"

extern int ul_rm_expired_delay;
extern int ul_db_clean_tcp;

#ifdef STATISTICS
static char *build_stat_name(str *domain, char *var_name)
{
	int n;
	char *s;
	char *p;

	n = domain->len + 1 + strlen(var_name) + 1;
	s = (char *)shm_malloc(n);
	if(s == 0) {
		SHM_MEM_ERROR;
		return 0;
	}
	memcpy(s, domain->s, domain->len);
	p = s + domain->len;
	*(p++) = *ksr_stats_namesep;
	memcpy(p, var_name, strlen(var_name));
	p += strlen(var_name);
	*(p++) = 0;
	return s;
}
#endif


/*!
 * \brief Create a new domain structure
 * \param  _n is pointer to str representing name of the domain, the string is
 * not copied, it should point to str structure stored in domain list
 * \param _s is hash table size
 * \param _d new created domain
 * \return 0 on success, -1 on failure
 */
int new_udomain(str *_n, int _s, udomain_t **_d)
{
	int i;
#ifdef STATISTICS
	char *name;
#endif

	/* Must be always in shared memory, since
	 * the cache is accessed from timer which
	 * lives in a separate process
	 */
	*_d = (udomain_t *)shm_malloc(sizeof(udomain_t));
	if(!(*_d)) {
		SHM_MEM_ERROR;
		goto error0;
	}
	memset(*_d, 0, sizeof(udomain_t));

	(*_d)->table = (hslot_t *)shm_malloc(sizeof(hslot_t) * _s);
	if(!(*_d)->table) {
		SHM_MEM_ERROR;
		goto error1;
	}

	(*_d)->name = _n;

	for(i = 0; i < _s; i++) {
		if(init_slot(*_d, &((*_d)->table[i]), i) < 0) {
			LM_ERR("failed to init hash table slot %d\n", i);
			goto error2;
		}
	}

	(*_d)->size = _s;

#ifdef STATISTICS
	/* register the statistics */
	if((name = build_stat_name(_n, "users")) == 0
			|| register_stat("usrloc", name, &(*_d)->users,
					   STAT_NO_RESET | STAT_SHM_NAME)
					   != 0) {
		LM_ERR("failed to add stat variable\n");
		goto error2;
	}
	if((name = build_stat_name(_n, "contacts")) == 0
			|| register_stat("usrloc", name, &(*_d)->contacts,
					   STAT_NO_RESET | STAT_SHM_NAME)
					   != 0) {
		LM_ERR("failed to add stat variable\n");
		goto error2;
	}
	if((name = build_stat_name(_n, "expires")) == 0
			|| register_stat("usrloc", name, &(*_d)->expires, STAT_SHM_NAME)
					   != 0) {
		LM_ERR("failed to add stat variable\n");
		goto error2;
	}
#endif

	return 0;
#ifdef STATISTICS
error2:
	shm_free((*_d)->table);
#endif
error1:
	shm_free(*_d);
error0:
	return -1;
}


/*!
 * \brief Free all memory allocated for the domain
 * \param _d freed domain
 */
void free_udomain(udomain_t *_d)
{
	int i;

	if(_d->table) {
		for(i = 0; i < _d->size; i++) {
			deinit_slot(_d->table + i);
		}
		shm_free(_d->table);
	}
	shm_free(_d);
}


/*!
 * \brief Returns a static dummy urecord for temporary usage
 * \param _d domain (needed for the name)
 * \param _aor address of record
 * \param _r new created urecord
 */
static inline void get_static_urecord(
		udomain_t *_d, str *_aor, struct urecord **_r)
{
	static struct urecord r;

	memset(&r, 0, sizeof(struct urecord));
	r.aor = *_aor;
	r.aorhash = ul_get_aorhash(_aor);
	r.domain = _d->name;
	*_r = &r;
}


/*!
 * \brief Debugging helper function
 */
void print_udomain(FILE *_f, udomain_t *_d)
{
	int i;
	int max = 0, slot = 0, n = 0;
	struct urecord *r;
	fprintf(_f, "---Domain---\n");
	fprintf(_f, "name : '%.*s'\n", _d->name->len, ZSW(_d->name->s));
	fprintf(_f, "size : %d\n", _d->size);
	fprintf(_f, "table: %p\n", _d->table);
	/*fprintf(_f, "lock : %d\n", _d->lock); -- can be a structure --andrei*/
	fprintf(_f, "\n");
	for(i = 0; i < _d->size; i++) {
		r = _d->table[i].first;
		n += _d->table[i].n;
		if(max < _d->table[i].n) {
			max = _d->table[i].n;
			slot = i;
		}
		while(r) {
			print_urecord(_f, r);
			r = r->next;
		}
	}
	fprintf(_f, "\nMax slot: %d (%d/%d)\n", max, slot, n);
	fprintf(_f, "\n---/Domain---\n");
}


/*!
 * \brief Convert database values into ucontact_info
 *
 * Convert database values into ucontact_info,
 * expects 12 rows (contact, expires, q, callid, cseq, flags,
 * ua, received, path, socket, methods, last_modified)
 * \param vals database values
 * \param contact contact
 * \param rcon restore connection id
 * \return pointer to the ucontact_info on success, 0 on failure
 */
static inline ucontact_info_t *dbrow2info(
		db_val_t *vals, str *contact, int rcon)
{
	static ucontact_info_t ci;
	static str callid, ua, received, host, path;
	int port, proto;
	char *p;

	memset(&ci, 0, sizeof(ucontact_info_t));

	contact->s = (char *)VAL_STRING(vals);
	if(VAL_NULL(vals) || contact->s == 0 || contact->s[0] == 0) {
		LM_CRIT("bad contact\n");
		return 0;
	}
	contact->len = strlen(contact->s);

	if(VAL_NULL(vals + 1)) {
		LM_CRIT("empty expire\n");
		return 0;
	}
	ci.expires = UL_DB_EXPIRES_GET(vals + 1);

	if(VAL_NULL(vals + 2)) {
		LM_CRIT("empty q\n");
		return 0;
	}
	ci.q = double2q(VAL_DOUBLE(vals + 2));

	if(VAL_NULL(vals + 4)) {
		LM_CRIT("empty cseq_nr\n");
		return 0;
	}
	ci.cseq = VAL_INT(vals + 4);

	callid.s = (char *)VAL_STRING(vals + 3);
	if(VAL_NULL(vals + 3) || !callid.s || !callid.s[0]) {
		LM_CRIT("bad callid\n");
		return 0;
	}
	callid.len = strlen(callid.s);
	ci.callid = &callid;

	if(VAL_NULL(vals + 5)) {
		LM_CRIT("empty flag\n");
		return 0;
	}
	ci.flags = VAL_BITMAP(vals + 5);

	if(VAL_NULL(vals + 6)) {
		LM_CRIT("empty cflag\n");
		return 0;
	}
	ci.cflags = VAL_BITMAP(vals + 6);

	ua.s = (char *)VAL_STRING(vals + 7);
	if(VAL_NULL(vals + 7) || !ua.s || !ua.s[0]) {
		ua.s = 0;
		ua.len = 0;
	} else {
		ua.len = strlen(ua.s);
	}
	ci.user_agent = &ua;

	received.s = (char *)VAL_STRING(vals + 8);
	if(VAL_NULL(vals + 8) || !received.s || !received.s[0]) {
		received.len = 0;
		received.s = 0;
	} else {
		received.len = strlen(received.s);
	}
	ci.received = received;

	path.s = (char *)VAL_STRING(vals + 9);
	if(VAL_NULL(vals + 9) || !path.s || !path.s[0]) {
		path.len = 0;
		path.s = 0;
	} else {
		path.len = strlen(path.s);
	}
	ci.path = &path;

	/* socket name */
	p = (char *)VAL_STRING(vals + 10);
	if(VAL_NULL(vals + 10) || p == 0 || p[0] == 0) {
		ci.sock = 0;
	} else {
		if(parse_phostport(p, &host.s, &host.len, &port, &proto) != 0) {
			LM_ERR("bad socket <%s>\n", p);
			return 0;
		}
		ci.sock = grep_sock_info(&host, (unsigned short)port, proto);
		if(ci.sock == 0) {
			LM_DBG("non-local socket <%s>...ignoring\n", p);
			if(ul_skip_remote_socket) {
				return 0;
			}
		}
	}

	/* supported methods */
	if(VAL_NULL(vals + 11)) {
		ci.methods = ALL_METHODS;
	} else {
		ci.methods = VAL_BITMAP(vals + 11);
	}

	/* last modified time */
	if(!VAL_NULL(vals + 12)) {
		ci.last_modified = UL_DB_EXPIRES_GET(vals + 12);
	}

	/* record internal uid */
	if(!VAL_NULL(vals + 13)) {
		ci.ruid.s = (char *)VAL_STRING(vals + 13);
		ci.ruid.len = strlen(ci.ruid.s);
	}

	/* sip instance */
	if(!VAL_NULL(vals + 14)) {
		ci.instance.s = (char *)VAL_STRING(vals + 14);
		ci.instance.len = strlen(ci.instance.s);
	}

	/* reg-id */
	if(!VAL_NULL(vals + 15)) {
		ci.reg_id = VAL_UINT(vals + 15);
	}

	/* server_id */
	if(!VAL_NULL(vals + 16)) {
		ci.server_id = VAL_UINT(vals + 16);
	}

	/* tcp connection id (not restored always) */
	ci.tcpconn_id = -1;
	if(rcon == 1 && !VAL_NULL(vals + 17)) {
		ci.tcpconn_id = VAL_UINT(vals + 17);
	}

	/* keepalive */
	if(!VAL_NULL(vals + 18)) {
		ci.keepalive = VAL_UINT(vals + 18);
	}

	return &ci;
}

/*!
 * \brief Delete all location records with tcp connection
 *
 * \param _c database connection
 * \param _d loaded domain
 * \return 0 on success, -1 on failure
 */
int uldb_delete_tcp_records(db1_con_t *_c, udomain_t *_d)
{
	db_key_t keys[2];
	db_op_t ops[2];
	db_val_t vals[2];
	int nr_keys = 0;

	LM_DBG("delete location tcp records\n");

	keys[nr_keys] = &ul_con_id_col;
	;
	ops[nr_keys] = OP_GT;
	vals[nr_keys].type = DB1_INT;
	vals[nr_keys].nul = 0;
	vals[nr_keys].val.int_val = 0;
	nr_keys++;

	if(ul_db_srvid != 0) {
		keys[nr_keys] = &ul_srv_id_col;
		ops[nr_keys] = OP_EQ;
		vals[nr_keys].type = DB1_INT;
		vals[nr_keys].nul = 0;
		vals[nr_keys].val.int_val = server_id;
		nr_keys++;
	}

	if(ul_dbf.use_table(_c, _d->name) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}


	if(ul_dbf.delete(_c, keys, ops, vals, nr_keys) < 0) {
		LM_ERR("deleting from database failed\n");
		return -1;
	}

	return 0;
}


/*!
 * \brief Load all records from a udomain
 *
 * Load all records from a udomain, useful to populate the
 * memory cache on startup.
 * \param _c database connection
 * \param _d loaded domain
 * \return 0 on success, -1 on failure
 */
int preload_udomain(db1_con_t *_c, udomain_t *_d)
{
	char uri[MAX_URI_SIZE];
	ucontact_info_t *ci;
	db_row_t *row;
	db_key_t columns[21];
	db1_res_t *res = NULL;
	db_key_t keys[1]; /* where */
	db_val_t vals[1];
	db_op_t ops[1];
	str user, contact;
	char *domain;
	int i;
	int n;

	urecord_t *r;
	ucontact_t *c;

	if(ul_db_clean_tcp != 0) {
		uldb_delete_tcp_records(_c, _d);
	}

	columns[0] = &ul_user_col;
	columns[1] = &ul_contact_col;
	columns[2] = &ul_expires_col;
	columns[3] = &ul_q_col;
	columns[4] = &ul_callid_col;
	columns[5] = &ul_cseq_col;
	columns[6] = &ul_flags_col;
	columns[7] = &ul_cflags_col;
	columns[8] = &ul_user_agent_col;
	columns[9] = &ul_received_col;
	columns[10] = &ul_path_col;
	columns[11] = &ul_sock_col;
	columns[12] = &ul_methods_col;
	columns[13] = &ul_last_mod_col;
	columns[14] = &ul_ruid_col;
	columns[15] = &ul_instance_col;
	columns[16] = &ul_reg_id_col;
	columns[17] = &ul_srv_id_col;
	columns[18] = &ul_con_id_col;
	columns[19] = &ul_keepalive_col;
	columns[20] = &ul_domain_col;

	if(ul_dbf.use_table(_c, _d->name) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

#ifdef EXTRA_DEBUG
	LM_NOTICE("load start time [%d]\n", (int)time(NULL));
#endif

	if(ul_db_srvid) {
		LM_NOTICE("filtered by server_id[%d]\n", server_id);
		keys[0] = &ul_srv_id_col;
		ops[0] = OP_EQ;
		vals[0].type = DB1_INT;
		vals[0].nul = 0;
		vals[0].val.int_val = server_id;
	}

	if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
		if(ul_dbf.query(_c, (ul_db_srvid) ? (keys) : (0),
				   (ul_db_srvid) ? (ops) : (0), (ul_db_srvid) ? (vals) : (0),
				   columns, (ul_db_srvid) ? (1) : (0),
				   (ul_use_domain) ? (21) : (20), 0, 0)
				< 0) {
			LM_ERR("db_query (1) failed\n");
			return -1;
		}
		if(ul_dbf.fetch_result(_c, &res, ul_fetch_rows) < 0) {
			LM_ERR("fetching rows failed\n");
			return -1;
		}
	} else {
		if(ul_dbf.query(_c, (ul_db_srvid) ? (keys) : (0),
				   (ul_db_srvid) ? (ops) : (0), (ul_db_srvid) ? (vals) : (0),
				   columns, (ul_db_srvid) ? (1) : (0),
				   (ul_use_domain) ? (21) : (20), 0, &res)
				< 0) {
			LM_ERR("db_query failed\n");
			return -1;
		}
	}

	if(RES_ROW_N(res) == 0) {
		LM_DBG("table is empty\n");
		ul_dbf.free_result(_c, res);
		return 0;
	}


	n = 0;
	do {
		LM_DBG("loading records - cycle [%d]\n", ++n);
		for(i = 0; i < RES_ROW_N(res); i++) {
			row = RES_ROWS(res) + i;

			user.s = (char *)VAL_STRING(ROW_VALUES(row));
			if(VAL_NULL(ROW_VALUES(row)) || user.s == 0 || user.s[0] == 0) {
				LM_CRIT("empty username record in table %s...skipping\n",
						_d->name->s);
				continue;
			}
			user.len = strlen(user.s);

			ci = dbrow2info(ROW_VALUES(row) + 1, &contact, 0);
			if(ci == 0) {
				LM_ERR("skipping record for %.*s in table %s\n", user.len,
						user.s, _d->name->s);
				continue;
			}

			if(ul_use_domain) {
				domain = (char *)VAL_STRING(ROW_VALUES(row) + 20);
				if(VAL_NULL(ROW_VALUES(row) + 17) || domain == 0
						|| domain[0] == 0) {
					LM_CRIT("empty domain record for user %.*s...skipping\n",
							user.len, user.s);
					continue;
				}
				/* user.s cannot be NULL - checked previosly */
				user.len = snprintf(
						uri, MAX_URI_SIZE, "%.*s@%s", user.len, user.s, domain);
				user.s = uri;
				if(user.s[user.len] != 0) {
					LM_CRIT("URI '%.*s@%s' longer than %d\n", user.len, user.s,
							domain, MAX_URI_SIZE);
					continue;
				}
			}

			lock_udomain(_d, &user);
			if(get_urecord(_d, &user, &r) > 0) {
				if(mem_insert_urecord(_d, &user, &r) < 0) {
					LM_ERR("failed to create a record\n");
					unlock_udomain(_d, &user);
					goto error;
				}
			}

			if((c = mem_insert_ucontact(r, &contact, ci)) == 0) {
				LM_ERR("inserting contact failed\n");
				unlock_udomain(_d, &user);
				goto error1;
			}

			/* We have to do this, because insert_ucontact sets state to CS_NEW
			 * and we have the contact in the database already */
			c->state = CS_SYNC;
			unlock_udomain(_d, &user);
		}

		if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
			if(ul_dbf.fetch_result(_c, &res, ul_fetch_rows) < 0) {
				LM_ERR("fetching rows (1) failed\n");
				ul_dbf.free_result(_c, res);
				return -1;
			}
		} else {
			break;
		}
	} while(RES_ROW_N(res) > 0);

	ul_dbf.free_result(_c, res);

#ifdef EXTRA_DEBUG
	LM_NOTICE("load end time [%d]\n", (int)time(NULL));
#endif

	return 0;
error1:
	free_ucontact(c);
error:
	ul_dbf.free_result(_c, res);
	return -1;
}


/*!
 * \brief Loads from DB all contacts for an AOR
 * \param _c database connection
 * \param _d domain
 * \param _aor address of record
 * \return pointer to the record on success, 0 on errors or if nothing is found
 */
urecord_t *db_load_urecord(db1_con_t *_c, udomain_t *_d, str *_aor)
{
	char tname_buf[64];
	str tname;
	ucontact_info_t *ci;
	db_key_t columns[19];
	db_key_t keys[2];
	db_key_t order;
	db_val_t vals[2];
	db1_res_t *res = NULL;
	db_row_t *row;
	str aname;
	str avalue;
	sr_xval_t aval;
	str contact;
	char *domain;
	int i;

	urecord_t *r;
	ucontact_t *c;

	keys[0] = &ul_user_col;
	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	if(ul_use_domain) {
		keys[1] = &ul_domain_col;
		vals[1].type = DB1_STR;
		vals[1].nul = 0;
		domain = memchr(_aor->s, '@', _aor->len);
		vals[0].val.str_val.s = _aor->s;
		if(domain == 0) {
			vals[0].val.str_val.len = 0;
			vals[1].val.str_val = *_aor;
		} else {
			vals[0].val.str_val.len = domain - _aor->s;
			vals[1].val.str_val.s = domain + 1;
			vals[1].val.str_val.len = _aor->s + _aor->len - domain - 1;
		}
	} else {
		vals[0].val.str_val = *_aor;
	}

	columns[0] = &ul_contact_col;
	columns[1] = &ul_expires_col;
	columns[2] = &ul_q_col;
	columns[3] = &ul_callid_col;
	columns[4] = &ul_cseq_col;
	columns[5] = &ul_flags_col;
	columns[6] = &ul_cflags_col;
	columns[7] = &ul_user_agent_col;
	columns[8] = &ul_received_col;
	columns[9] = &ul_path_col;
	columns[10] = &ul_sock_col;
	columns[11] = &ul_methods_col;
	columns[12] = &ul_last_mod_col;
	columns[13] = &ul_ruid_col;
	columns[14] = &ul_instance_col;
	columns[15] = &ul_reg_id_col;
	columns[16] = &ul_srv_id_col;
	columns[17] = &ul_con_id_col;
	columns[18] = &ul_keepalive_col;

	if(ul_desc_time_order)
		order = &ul_last_mod_col;
	else
		order = &ul_q_col;

	if(ul_dbf.use_table(_c, _d->name) < 0) {
		LM_ERR("failed to use table %.*s\n", _d->name->len, _d->name->s);
		return 0;
	}

	if(ul_dbf.query(_c, keys, 0, vals, columns, (ul_use_domain) ? 2 : 1, 19,
			   order, &res)
			< 0) {
		LM_ERR("db_query failed\n");
		return 0;
	}

	if(RES_ROW_N(res) == 0) {
		LM_DBG("aor %.*s not found in table %.*s\n", _aor->len, _aor->s,
				_d->name->len, _d->name->s);
		ul_dbf.free_result(_c, res);
		return 0;
	}

	r = 0;

	for(i = 0; i < RES_ROW_N(res); i++) {
		ci = dbrow2info(ROW_VALUES(RES_ROWS(res) + i), &contact, 1);
		if(ci == 0) {
			LM_ERR("skipping record for %.*s in table %s\n", _aor->len, _aor->s,
					_d->name->s);
			continue;
		}

		if(r == 0)
			get_static_urecord(_d, _aor, &r);

		if((c = mem_insert_ucontact(r, &contact, ci)) == 0) {
			LM_ERR("mem_insert failed\n");
			free_urecord(r);
			ul_dbf.free_result(_c, res);
			return 0;
		}

		/* We have to do this, because insert_ucontact sets state to CS_NEW
		 * and we have the contact in the database already */
		c->state = CS_SYNC;
	}

	ul_dbf.free_result(_c, res);

	/* Retrieve ul attributes */
	if(ul_xavp_contact_name.s == NULL) {
		/* feature disabled by mod param */
		goto done;
	}

	if(_d->name->len + 6 >= 64) {
		LM_ERR("attributes table name is too big\n");
		goto done;
	}
	strncpy(tname_buf, _d->name->s, _d->name->len);
	tname_buf[_d->name->len] = '\0';
	strcat(tname_buf, "_attrs");
	tname.s = tname_buf;
	tname.len = _d->name->len + 6;

	keys[0] = &ulattrs_ruid_col;
	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	columns[0] = &ulattrs_aname_col;
	columns[1] = &ulattrs_atype_col;
	columns[2] = &ulattrs_avalue_col;

	if(ul_dbf.use_table(ul_dbh, &tname) < 0) {
		LM_ERR("sql use_table failed for %.*s\n", tname.len, tname.s);
		goto done;
	}

	if(r == 0)
		goto done;

	for(c = r->contacts; c != NULL; c = c->next) {
		vals[0].val.str_val.s = c->ruid.s;
		vals[0].val.str_val.len = c->ruid.len;

		if(ul_dbf.query(ul_dbh, keys, 0, vals, columns, 1, 3, 0, &res) < 0) {
			LM_ERR("db_query failed\n");
			continue;
		}

		if(RES_ROW_N(res) == 0) {
			LM_DBG("location attrs table is empty\n");
			ul_dbf.free_result(ul_dbh, res);
			continue;
		}

		for(i = 0; i < RES_ROW_N(res); i++) {
			row = RES_ROWS(res) + i;

			aname.s = (char *)VAL_STRING(ROW_VALUES(row));
			aname.len = strlen(aname.s);
			avalue.s = (char *)VAL_STRING(ROW_VALUES(row) + 2);
			avalue.len = strlen(avalue.s);
			memset(&aval, 0, sizeof(sr_xval_t));
			if(VAL_INT(ROW_VALUES(row) + 1) == 0) {
				/* string value */
				aval.v.s = avalue;
				aval.type = SR_XTYPE_STR;
			} else if(VAL_INT(ROW_VALUES(row) + 1) == 1) {
				/* long int value */
				str2slong(&avalue, &aval.v.l);
				aval.type = SR_XTYPE_LONG;
			} else {
				/* unknown type - ignore */
				continue;
			}

			/* add xavp to contact */
			if(c->xavp == NULL) {
				if(xavp_add_xavp_value(
						   &ul_xavp_contact_name, &aname, &aval, &c->xavp)
						== NULL)
					LM_INFO("cannot add first xavp to contact - ignoring\n");
			} else {
				if(c->xavp->val.type == SR_XTYPE_XAVP) {
					if(xavp_add_value(&aname, &aval, &c->xavp->val.v.xavp)
							== NULL)
						LM_INFO("cannot add values to contact xavp\n");
				}
			}
		}
		ul_dbf.free_result(ul_dbh, res);
	}


done:
	return r;
}

/*!
 * \brief Loads from DB all contacts for a RUID
 * \param _c database connection
 * \param _d domain
 * \param _ruid record unique id
 * \return pointer to the record on success, 0 on errors or if nothing is found
 */
urecord_t *db_load_urecord_by_ruid(db1_con_t *_c, udomain_t *_d, str *_ruid)
{
	ucontact_info_t *ci;
	db_key_t columns[21];
	db_key_t keys[1];
	db_key_t order;
	db_val_t vals[1];
	db1_res_t *res = NULL;
	db_row_t *row;
	str contact;
	str aor;
	static char aorbuf[512];
	str domain;

	urecord_t *r;
	ucontact_t *c;

	keys[0] = &ul_ruid_col;
	vals[0].type = DB1_STR;
	vals[0].nul = 0;
	vals[0].val.str_val = *_ruid;

	columns[0] = &ul_contact_col;
	columns[1] = &ul_expires_col;
	columns[2] = &ul_q_col;
	columns[3] = &ul_callid_col;
	columns[4] = &ul_cseq_col;
	columns[5] = &ul_flags_col;
	columns[6] = &ul_cflags_col;
	columns[7] = &ul_user_agent_col;
	columns[8] = &ul_received_col;
	columns[9] = &ul_path_col;
	columns[10] = &ul_sock_col;
	columns[11] = &ul_methods_col;
	columns[12] = &ul_last_mod_col;
	columns[13] = &ul_ruid_col;
	columns[14] = &ul_instance_col;
	columns[15] = &ul_reg_id_col;
	columns[16] = &ul_srv_id_col;
	columns[17] = &ul_con_id_col;
	columns[18] = &ul_keepalive_col;
	columns[19] = &ul_user_col;
	columns[20] = &ul_domain_col;

	if(ul_desc_time_order)
		order = &ul_last_mod_col;
	else
		order = &ul_q_col;

	if(ul_dbf.use_table(_c, _d->name) < 0) {
		LM_ERR("failed to use table %.*s\n", _d->name->len, _d->name->s);
		return 0;
	}

	if(ul_dbf.query(_c, keys, 0, vals, columns, 1, 21, order, &res) < 0) {
		LM_ERR("db_query failed\n");
		return 0;
	}

	if(RES_ROW_N(res) == 0) {
		LM_DBG("aor %.*s not found in table %.*s\n", _ruid->len, _ruid->s,
				_d->name->len, _d->name->s);
		ul_dbf.free_result(_c, res);
		return 0;
	}

	r = 0;

	/* use first row - shouldn't be more */
	row = RES_ROWS(res);

	ci = dbrow2info(ROW_VALUES(RES_ROWS(res)), &contact, 1);
	if(ci == 0) {
		LM_ERR("skipping record for %.*s in table %s\n", _ruid->len, _ruid->s,
				_d->name->s);
		goto done;
	}

	aor.s = (char *)VAL_STRING(ROW_VALUES(row) + 19);
	aor.len = strlen(aor.s);

	if(ul_use_domain) {
		domain.s = (char *)VAL_STRING(ROW_VALUES(row) + 20);
		if(VAL_NULL(ROW_VALUES(row) + 20) || domain.s == 0
				|| domain.s[0] == 0) {
			LM_CRIT("empty domain record for user %.*s...skipping\n", aor.len,
					aor.s);
			goto done;
		}
		domain.len = strlen(domain.s);
		if(aor.len + domain.len + 2 >= 512) {
			LM_ERR("AoR is too big\n");
			goto done;
		}
		memcpy(aorbuf, aor.s, aor.len);
		aorbuf[aor.len] = '@';
		memcpy(aorbuf + aor.len + 1, domain.s, domain.len);
		aor.len += 1 + domain.len;
		aor.s = aorbuf;
		aor.s[aor.len] = '\0';
	}
	get_static_urecord(_d, &aor, &r);

	if((c = mem_insert_ucontact(r, &contact, ci)) == 0) {
		LM_ERR("mem_insert failed\n");
		free_urecord(r);
		ul_dbf.free_result(_c, res);
		return 0;
	}

	/* We have to do this, because insert_ucontact sets state to CS_NEW
	 * and we have the contact in the database already */
	c->state = CS_SYNC;

done:
	ul_dbf.free_result(_c, res);
	return r;
}

/*!
 * \brief call contact expired call back for a domain with db_mode: DB_ONLY
 *
 * call contact expired call back for a domain with db_mode: DB_ONLY since
 * database rows are removed by the timer function: db_timer_udomain
 * \param _c database connection
 * \param _d loaded domain
 * \return 0 on success, -1 on failure
 */
int udomain_contact_expired_cb(db1_con_t *_c, udomain_t *_d)
{
	ucontact_info_t *ci;
	db_row_t *row;
	db_key_t columns[21], query_cols[3];
	db_op_t query_ops[3];
	db_val_t query_vals[3];
	int key_num = 2;
	db1_res_t *res = NULL;
	str user, contact, domain;
	int i;
	int n;
	urecord_t r;
	ucontact_t *c;
#define RUIDBUF_SIZE 128
	char ruidbuf[RUIDBUF_SIZE];
	str ruid;
#define AORBUF_SIZE 512
	char aorbuf[AORBUF_SIZE];
	str aor;

	if(ul_db_mode != DB_ONLY) {
		return 0;
	}

	columns[0] = &ul_user_col;
	columns[1] = &ul_contact_col;
	columns[2] = &ul_expires_col;
	columns[3] = &ul_q_col;
	columns[4] = &ul_callid_col;
	columns[5] = &ul_cseq_col;
	columns[6] = &ul_flags_col;
	columns[7] = &ul_cflags_col;
	columns[8] = &ul_user_agent_col;
	columns[9] = &ul_received_col;
	columns[10] = &ul_path_col;
	columns[11] = &ul_sock_col;
	columns[12] = &ul_methods_col;
	columns[13] = &ul_last_mod_col;
	columns[14] = &ul_ruid_col;
	columns[15] = &ul_instance_col;
	columns[16] = &ul_reg_id_col;
	columns[17] = &ul_srv_id_col;
	columns[18] = &ul_con_id_col;
	columns[19] = &ul_keepalive_col;
	columns[20] = &ul_domain_col;

	query_cols[0] = &ul_expires_col;
	query_ops[0] = "<";
	query_vals[0].nul = 0;
	UL_DB_EXPIRES_SET(&query_vals[0], ul_act_time + 1 - ul_rm_expired_delay);

	query_cols[1] = &ul_expires_col;
	query_ops[1] = OP_NEQ;
	query_vals[1].nul = 0;
	UL_DB_EXPIRES_SET(&query_vals[1], 0);

	if(ul_db_srvid != 0) {
		query_cols[2] = &ul_srv_id_col;
		query_ops[2] = OP_EQ;
		query_vals[2].type = DB1_INT;
		query_vals[2].nul = 0;
		query_vals[2].val.int_val = server_id;
		key_num = 3;
	}

	if(ul_dbf.use_table(_c, _d->name) < 0) {
		LM_ERR("sql use_table failed\n");
		return -1;
	}

#ifdef EXTRA_DEBUG
	LM_NOTICE("udomain contact-expired start time [%d]\n", (int)time(NULL));
#endif

	if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
		if(ul_dbf.query(_c, query_cols, query_ops, query_vals, columns, key_num,
				   (ul_use_domain) ? (21) : (20), 0, 0)
				< 0) {
			LM_ERR("db_query (1) failed\n");
			return -1;
		}
		if(ul_dbf.fetch_result(_c, &res, ul_fetch_rows) < 0) {
			LM_ERR("fetching rows failed\n");
			return -1;
		}
	} else {
		if(ul_dbf.query(_c, query_cols, query_ops, query_vals, columns, key_num,
				   (ul_use_domain) ? (21) : (20), 0, &res)
				< 0) {
			LM_ERR("db_query failed\n");
			return -1;
		}
	}

	if(RES_ROW_N(res) == 0) {
		LM_DBG("no rows to be contact expired\n");
		ul_dbf.free_result(_c, res);
		return 0;
	}

	n = 0;
	do {
		LM_DBG("calling contact expired records - cycle [%d]\n", ++n);
		for(i = 0; i < RES_ROW_N(res); i++) {
			row = RES_ROWS(res) + i;

			user.s = (char *)VAL_STRING(ROW_VALUES(row));
			if(VAL_NULL(ROW_VALUES(row)) || user.s == 0 || user.s[0] == 0) {
				LM_CRIT("empty username record in table %s...skipping\n",
						_d->name->s);
				continue;
			}
			user.len = strlen(user.s);
			if(user.len + 1 < AORBUF_SIZE) {
				memcpy(aorbuf, user.s, user.len);
				aor.s = aorbuf;
				aor.len = user.len;
			} else {
				LM_ERR("user is too long %d\n", user.len);
				continue;
			}

			ci = dbrow2info(ROW_VALUES(row) + 1, &contact, 0);
			if(ci == 0) {
				LM_CRIT("skipping record for %.*s in table %s\n", user.len,
						user.s, _d->name->s);
				continue;
			}

			if(ul_use_domain) {
				domain.s = (char *)VAL_STRING(ROW_VALUES(row) + 20);
				domain.len = strlen(domain.s);
				if(domain.len + aor.len + 2 < AORBUF_SIZE) {
					aorbuf[aor.len] = '@';
					memcpy(aorbuf + aor.len + 1, domain.s, domain.len);
					aor.len += domain.len + 1;
				} else {
					LM_ERR("aor too long, using user part only\n");
				}
			}

			lock_udomain(_d, &aor);
			/* don't use the same static value from get_static_urecord() */
			memset(&r, 0, sizeof(struct urecord));
			r.aor = aor;
			r.aorhash = ul_get_aorhash(&aor);
			r.domain = _d->name;

			if((c = mem_insert_ucontact(&r, &contact, ci)) == 0) {
				LM_ERR("inserting temporary contact failed for %.*s\n", aor.len,
						aor.s);
				release_urecord(&r);
				unlock_udomain(_d, &aor);
				goto error;
			}

			/* Call the contact-expired call-back if it exists for the contact */
			if(exists_ulcb_type(UL_CONTACT_EXPIRE)) {
				run_ul_callbacks(UL_CONTACT_EXPIRE, c);
			}
			c->state = CS_SYNC;
			ruid.len = 0;
			if(c->ruid.len > 0 && ul_xavp_contact_name.s != NULL) {
				/* clone ruid to delete attributes out of lock */
				if(c->ruid.len < RUIDBUF_SIZE - 2) {
					memcpy(ruidbuf, c->ruid.s, c->ruid.len);
					ruidbuf[c->ruid.len] = '\0';
					ruid.s = ruidbuf;
					ruid.len = c->ruid.len;
				} else {
					LM_ERR("ruid is too long %d for %.*s\n", c->ruid.len,
							aor.len, aor.s);
				}
			}
			release_urecord(&r);
			unlock_udomain(_d, &aor);
			if(ruid.len > 0 && ul_xavp_contact_name.s != NULL) {
				/* delete attributes by ruid */
				uldb_delete_attrs_ruid(_d->name, &ruid);
			}
		}

		if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
			if(ul_dbf.fetch_result(_c, &res, ul_fetch_rows) < 0) {
				LM_ERR("fetching rows (1) failed\n");
				ul_dbf.free_result(_c, res);
				return -1;
			}
		} else {
			break;
		}
	} while(RES_ROW_N(res) > 0);

	ul_dbf.free_result(_c, res);

#ifdef EXTRA_DEBUG
	LM_NOTICE("udomain contact-expired end time [%d]\n", (int)time(NULL));
#endif

	return 0;

error:
	ul_dbf.free_result(_c, res);
	return -1;
}


/*!
 * \brief Timer function to cleanup expired contacts, db_mode: DB_ONLY
 *   and for WRITE_BACK, WRITE_THROUGH on config param
 * \param _d cleaned domain
 * \return 0 on success, -1 on failure
 */
int db_timer_udomain(udomain_t *_d)
{
	db_key_t keys[3];
	db_op_t ops[3];
	db_val_t vals[3];
	int key_num = 2;

	/* If contact-expired callback exists, run it for a domain before deleting database rows */
	if(exists_ulcb_type(UL_CONTACT_EXPIRE)) {
		udomain_contact_expired_cb(ul_dbh, _d);
	}

	keys[0] = &ul_expires_col;
	ops[0] = "<";
	vals[0].nul = 0;
	UL_DB_EXPIRES_SET(&vals[0], ul_act_time + 1 - ul_rm_expired_delay);

	keys[1] = &ul_expires_col;
	ops[1] = OP_NEQ;
	vals[1].nul = 0;
	UL_DB_EXPIRES_SET(&vals[1], 0);

	if(ul_db_srvid != 0) {
		keys[2] = &ul_srv_id_col;
		ops[2] = OP_EQ;
		vals[2].type = DB1_INT;
		vals[2].nul = 0;
		vals[2].val.int_val = server_id;
		key_num = 3;
	}

	if(ul_dbf.use_table(ul_dbh, _d->name) < 0) {
		LM_ERR("use_table failed\n");
		return -1;
	}

	if(ul_dbf.delete(ul_dbh, keys, ops, vals, key_num) < 0) {
		LM_ERR("failed to delete from table %s\n", _d->name->s);
		return -1;
	}

	return 0;
}


/*!
 * \brief performs a dummy query just to see if DB is ok
 * \param con database connection
 * \param d domain
 */
int testdb_udomain(db1_con_t *con, udomain_t *d)
{
	db_key_t key[2], col[1];
	db_val_t val[2];
	db1_res_t *res = NULL;

	if(ul_dbf.use_table(con, d->name) < 0) {
		LM_ERR("failed to change table\n");
		return -1;
	}

	key[0] = &ul_user_col;
	key[1] = &ul_domain_col;

	col[0] = &ul_user_col;

	VAL_TYPE(val) = DB1_STRING;
	VAL_NULL(val) = 0;
	VAL_STRING(val) = "dummy_user";

	VAL_TYPE(val + 1) = DB1_STRING;
	VAL_NULL(val + 1) = 0;
	VAL_STRING(val + 1) = "dummy_domain";

	if(ul_dbf.query(con, key, 0, val, col, (ul_use_domain) ? 2 : 1, 1, 0, &res)
			< 0) {
		if(res)
			ul_dbf.free_result(con, res);
		LM_ERR("failure in db_query\n");
		return -1;
	}

	ul_dbf.free_result(con, res);
	return 0;
}


/*!
 * \brief Insert a new record into domain in memory
 * \param _d domain the record belongs to
 * \param _aor address of record
 * \param _r new created record
 * \return 0 on success, -1 on failure
 */
int mem_insert_urecord(udomain_t *_d, str *_aor, struct urecord **_r)
{
	int sl;

	if(new_urecord(_d->name, _aor, _r) < 0) {
		LM_ERR("creating urecord failed\n");
		return -1;
	}

	sl = ((*_r)->aorhash) & (_d->size - 1);
	slot_add(&_d->table[sl], *_r);
	update_stat(_d->users, 1);
	return 0;
}


/*!
 * \brief Remove a record from domain in memory
 * \param _d domain the record belongs to
 * \param _r deleted record
 */
void mem_delete_urecord(udomain_t *_d, struct urecord *_r)
{
	slot_rem(_r->slot, _r);
	free_urecord(_r);
	update_stat(_d->users, -1);
}


/*!
 * \brief Run timer handler for given domain, delete urecords
 * \param _d domain
 * \param istart start of run
 * \param istep loop steps
 */
void mem_timer_udomain(udomain_t *_d, int istart, int istep)
{
	struct urecord *ptr, *t;
	int i;

	for(i = istart; i < _d->size; i += istep) {
		if(likely(destroy_modules_phase() == 0))
			lock_ulslot(_d, i);

		ptr = _d->table[i].first;

		while(ptr) {
			timer_urecord(ptr);
			/* Remove the entire record if it is empty */
			if(ptr->contacts == 0) {
				t = ptr;
				ptr = ptr->next;
				mem_delete_urecord(_d, t);
			} else {
				ul_ka_urecord(ptr);
				ptr = ptr->next;
			}
		}
		if(likely(destroy_modules_phase() == 0))
			unlock_ulslot(_d, i);
	}
}


/*!
 * \brief Get lock for a domain
 * \param _d domain
 * \param _aor address of record, used as hash source for the lock slot
 */
void lock_udomain(udomain_t *_d, str *_aor)
{
	unsigned int sl;
	if(ul_db_mode != DB_ONLY) {
		sl = ul_get_aorhash(_aor) & (_d->size - 1);

		rec_lock_get(&_d->table[sl].rlock);
	}
}


/*!
 * \brief Release lock for a domain
 * \param _d domain
 * \param _aor address of record, uses as hash source for the lock slot
 */
void unlock_udomain(udomain_t *_d, str *_aor)
{
	unsigned int sl;
	if(ul_db_mode != DB_ONLY) {
		sl = ul_get_aorhash(_aor) & (_d->size - 1);
		rec_lock_release(&_d->table[sl].rlock);
	}
}

/*!
 * \brief  Get lock for a slot
 * \param _d domain
 * \param i slot number
 */
void lock_ulslot(udomain_t *_d, int i)
{
	if(ul_db_mode != DB_ONLY)
		rec_lock_get(&_d->table[i].rlock);
}


/*!
 * \brief Release lock for a slot
 * \param _d domain
 * \param i slot number
 */
void unlock_ulslot(udomain_t *_d, int i)
{
	if(ul_db_mode != DB_ONLY)
		rec_lock_release(&_d->table[i].rlock);
}


/*!
 * \brief Create and insert a new record
 * \param _d domain to insert the new record
 * \param _aor address of the record
 * \param _r new created record
 * \return return 0 on success, -1 on failure
 */
int insert_urecord(udomain_t *_d, str *_aor, struct urecord **_r)
{
	if(ul_db_mode != DB_ONLY) {
		if(mem_insert_urecord(_d, _aor, _r) < 0) {
			LM_ERR("inserting record failed\n");
			return -1;
		}
	} else {
		get_static_urecord(_d, _aor, _r);
	}
	return 0;
}


/*!
 * \brief Obtain a urecord pointer if the urecord exists in domain
 * \param _d domain to search the record
 * \param _aor address of record
 * \param _r new created record
 * \return 0 if a record was found, 1 if nothing could be found
 */
int get_urecord(udomain_t *_d, str *_aor, struct urecord **_r)
{
	unsigned int sl, i, aorhash;
	urecord_t *r;
	ucontact_t *ptr = NULL;

	if(ul_db_mode != DB_ONLY) {
		/* search in cache */
		aorhash = ul_get_aorhash(_aor);
		sl = aorhash & (_d->size - 1);
		r = _d->table[sl].first;

		for(i = 0; r != NULL && i < _d->table[sl].n; i++) {
			if((r->aorhash == aorhash) && (r->aor.len == _aor->len)
					&& !memcmp(r->aor.s, _aor->s, _aor->len)) {
				if(ul_handle_lost_tcp) {
					for(ptr = r->contacts; ptr; ptr = ptr->next) {
						if(ptr->expires == UL_EXPIRED_TIME)
							continue;
						if(is_valid_tcpconn(ptr) && !is_tcp_alive(ptr))
							ptr->expires = UL_EXPIRED_TIME;
					}
				}
				*_r = r;
				return 0;
			}

			r = r->next;
		}
	} else {
		/* search in DB */
		r = db_load_urecord(ul_dbh, _d, _aor);
		if(r) {
			*_r = r;
			return 0;
		}
	}

	return 1; /* Nothing found */
}

/*!
 * \brief Obtain a urecord pointer if the urecord exists in domain (lock slot)
 * \param _d domain to search the record
 * \param _aorhash hash id for address of record
 * \param _ruid record internal unique id
 * \param _r store pointer to location record
 * \param _c store pointer to contact structure
 * \return 0 if a record was found, -1 if nothing could be found
 */
int get_urecord_by_ruid(udomain_t *_d, unsigned int _aorhash, str *_ruid,
		struct urecord **_r, struct ucontact **_c)
{
	unsigned int sl, i;
	urecord_t *r;
	ucontact_t *c;

	sl = _aorhash & (_d->size - 1);
	lock_ulslot(_d, sl);

	if(ul_db_mode != DB_ONLY) {
		/* search in cache */
		r = _d->table[sl].first;

		for(i = 0; i < _d->table[sl].n; i++) {
			if(r->aorhash == _aorhash) {
				c = r->contacts;
				while(c) {
					if(c->ruid.len == _ruid->len
							&& !memcmp(c->ruid.s, _ruid->s, _ruid->len)) {
						*_r = r;
						*_c = c;
						return 0;
					}
					c = c->next;
				}
			}
			r = r->next;
		}
	} else {
		/* search in DB */
		r = db_load_urecord_by_ruid(ul_dbh, _d, _ruid);
		if(r) {
			if(r->aorhash == _aorhash) {
				c = r->contacts;
				while(c) {
					if(c->ruid.len == _ruid->len
							&& !memcmp(c->ruid.s, _ruid->s, _ruid->len)) {
						*_r = r;
						*_c = c;
						return 0;
					}
					c = c->next;
				}
			}
		}
	}

	unlock_ulslot(_d, (_aorhash & (_d->size - 1)));
	return -1; /* Nothing found */
}

/*!
 * \brief Delete a urecord from domain
 * \param _d domain where the record should be deleted
 * \param _aor address of record
 * \param _r deleted record
 * \return 0 on success, -1 if the record could not be deleted
 */
int delete_urecord(udomain_t *_d, str *_aor, struct urecord *_r)
{
	struct ucontact *c, *t;

	if(ul_db_mode == DB_ONLY) {
		if(_r == 0)
			get_static_urecord(_d, _aor, &_r);
		if(db_delete_urecord(_r) < 0) {
			LM_ERR("DB delete failed\n");
			return -1;
		}
		free_urecord(_r);
		return 0;
	}

	if(_r == 0) {
		if(get_urecord(_d, _aor, &_r) > 0) {
			return 0;
		}
	}

	c = _r->contacts;
	while(c) {
		t = c;
		c = c->next;
		if(delete_ucontact(_r, t) < 0) {
			LM_ERR("deleting contact failed\n");
			return -1;
		}
	}
	release_urecord(_r);
	return 0;
}


/*!
 * \brief Load all location attributes from an udomain
 *
 * Load all location attributes from a udomain, useful to populate the
 * memory cache on startup.
 * \param _d loaded domain
 * \return 0 on success, -1 on failure
 */
int uldb_preload_attrs(udomain_t *_d)
{
	char uri[MAX_URI_SIZE];
	str suri;
	char tname_buf[64];
	str tname;
	db_row_t *row;
	db_key_t columns[6];
	db1_res_t *res = NULL;
	str user = {0};
	str domain = {0};
	str ruid;
	str aname;
	str avalue;
	sr_xval_t aval;
	int i;
	int n;

	urecord_t *r;
	ucontact_t *c;

	if(ul_xavp_contact_name.s == NULL) {
		/* feature disabled by mod param */
		return 0;
	}

	if(_d->name->len + 6 >= 64) {
		LM_ERR("attributes table name is too big\n");
		return -1;
	}
	strncpy(tname_buf, _d->name->s, _d->name->len);
	tname_buf[_d->name->len] = '\0';
	strcat(tname_buf, "_attrs");
	tname.s = tname_buf;
	tname.len = _d->name->len + 6;

	columns[0] = &ulattrs_user_col;
	columns[1] = &ulattrs_ruid_col;
	columns[2] = &ulattrs_aname_col;
	columns[3] = &ulattrs_atype_col;
	columns[4] = &ulattrs_avalue_col;
	columns[5] = &ulattrs_domain_col;

	if(ul_dbf.use_table(ul_dbh, &tname) < 0) {
		LM_ERR("sql use_table failed for %.*s\n", tname.len, tname.s);
		return -1;
	}

#ifdef EXTRA_DEBUG
	LM_NOTICE("load start time [%d]\n", (int)time(NULL));
#endif

	if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
		if(ul_dbf.query(ul_dbh, 0, 0, 0, columns, 0,
				   (ul_use_domain) ? (6) : (5), 0, 0)
				< 0) {
			LM_ERR("db_query (1) failed\n");
			return -1;
		}
		if(ul_dbf.fetch_result(ul_dbh, &res, ul_fetch_rows) < 0) {
			LM_ERR("fetching rows failed\n");
			return -1;
		}
	} else {
		if(ul_dbf.query(ul_dbh, 0, 0, 0, columns, 0,
				   (ul_use_domain) ? (6) : (5), 0, &res)
				< 0) {
			LM_ERR("db_query failed\n");
			return -1;
		}
	}

	if(RES_ROW_N(res) == 0) {
		LM_DBG("location attrs table is empty\n");
		ul_dbf.free_result(ul_dbh, res);
		return 0;
	}


	n = 0;
	do {
		LM_DBG("loading records - cycle [%d]\n", ++n);
		for(i = 0; i < RES_ROW_N(res); i++) {
			row = RES_ROWS(res) + i;

			user.s = (char *)VAL_STRING(ROW_VALUES(row));
			if(VAL_NULL(ROW_VALUES(row)) || user.s == 0 || user.s[0] == 0) {
				LM_CRIT("empty username record in table %s...skipping\n",
						_d->name->s);
				continue;
			}
			user.len = strlen(user.s);

			ruid.s = (char *)VAL_STRING(ROW_VALUES(row) + 1);
			ruid.len = strlen(ruid.s);
			aname.s = (char *)VAL_STRING(ROW_VALUES(row) + 2);
			aname.len = strlen(aname.s);
			avalue.s = (char *)VAL_STRING(ROW_VALUES(row) + 4);
			avalue.len = strlen(avalue.s);
			memset(&aval, 0, sizeof(sr_xval_t));
			if(VAL_INT(ROW_VALUES(row) + 3) == 0) {
				/* string value */
				aval.v.s = avalue;
				aval.type = SR_XTYPE_STR;
			} else if(VAL_INT(ROW_VALUES(row) + 3) == 1) {
				/* long int value */
				str2slong(&avalue, &aval.v.l);
				aval.type = SR_XTYPE_LONG;
			} else {
				/* unknown type - ignore */
				continue;
			}

			if(ul_use_domain) {
				domain.s = (char *)VAL_STRING(ROW_VALUES(row) + 5);
				if(VAL_NULL(ROW_VALUES(row) + 5) || domain.s == 0
						|| domain.s[0] == 0) {
					LM_CRIT("empty domain record for user %.*s...skipping\n",
							user.len, user.s);
					continue;
				}
				domain.len = strlen(domain.s);
				/* user.s cannot be NULL - checked previosly */
				suri.len = snprintf(uri, MAX_URI_SIZE, "%.*s@%s", user.len,
						user.s, domain.s);
				suri.s = uri;
				if(suri.s[suri.len] != 0) {
					LM_CRIT("URI '%.*s@%s' longer than %d\n", user.len, user.s,
							domain.s, MAX_URI_SIZE);
					continue;
				}
			} else {
				suri = user;
			}

			if(get_urecord_by_ruid(_d, ul_get_aorhash(&suri), &ruid, &r, &c)
					< 0) {
				/* delete attrs records from db table */
				LM_INFO("no contact record for this ruid\n");
				uldb_delete_attrs(_d->name, &user, &domain, &ruid);
			} else {
				/* add xavp to contact */
				if(c->xavp == NULL) {
					if(xavp_add_xavp_value(
							   &ul_xavp_contact_name, &aname, &aval, &c->xavp)
							== NULL)
						LM_INFO("cannot add first xavp to contact - "
								"ignoring\n");
				} else {
					if(c->xavp->val.type == SR_XTYPE_XAVP) {
						if(xavp_add_value(&aname, &aval, &c->xavp->val.v.xavp)
								== NULL)
							LM_INFO("cannot add values to contact xavp\n");
					}
				}
				/* get_urecord_by_ruid() locks the slot */
				unlock_udomain(_d, &suri);
			}
		}

		if(DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
			if(ul_dbf.fetch_result(ul_dbh, &res, ul_fetch_rows) < 0) {
				LM_ERR("fetching rows (1) failed\n");
				ul_dbf.free_result(ul_dbh, res);
				return -1;
			}
		} else {
			break;
		}
	} while(RES_ROW_N(res) > 0);

	ul_dbf.free_result(ul_dbh, res);

#ifdef EXTRA_DEBUG
	LM_NOTICE("load end time [%d]\n", (int)time(NULL));
#endif

	return 0;
}
