/*
 * BDB Database Driver for Kamailio
 *
 * Copyright (C) 2008 iptelorg GmbH
 *
 * 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.
 */

/*! \addtogroup bdb
 * @{
 */

/*! \file
 * Berkeley DB : Implementation of functions related to database commands.
 *
 * \ingroup database
 */

#include <string.h>

#include "../../core/mem/mem.h"
#include "../../core/dprint.h"
#include "../../core/ut.h"

#include "bdb_cmd.h"
#include "bdb_fld.h"
#include "bdb_con.h"
#include "bdb_uri.h"
#include "bdb_res.h"
#include "bdb_lib.h"
#include "bdb_crs_compat.h"

#define BDB_BUF_SIZE 1024

/** Destroys a bdb_cmd structure.
 * This function frees all memory used by ld_cmd structure.
 * @param cmd A pointer to generic db_cmd command being freed.
 * @param payload A pointer to ld_cmd structure to be freed.
 */
static void bdb_cmd_free(db_cmd_t *cmd, bdb_cmd_t *payload)
{
	db_drv_free(&payload->gen);
	if(payload->dbcp)
		payload->dbcp->CLOSE_CURSOR(payload->dbcp);
	if(payload->skey.s)
		pkg_free(payload->skey.s);
	pkg_free(payload);
}

/** Prepare a query
 * @param cmd DB command structure
 * @param bcmd berkeley DB command structure
 * @return 0 on success, -1 on error
 */
int bdb_prepare_query(db_cmd_t *cmd, bdb_cmd_t *bcmd)
{
	bdb_tcache_t *tbc = NULL;
	bdb_table_t *tp = NULL;
	bdb_fld_t *f;
	db_fld_t *fld;
	int mode;
	int i;

	if(bcmd->bcon == NULL || bcmd->bcon->dbp == NULL)
		return -1;

	tbc = bdblib_get_table(bcmd->bcon->dbp, &cmd->table);
	if(tbc == NULL) {
		ERR("bdb: table does not exist!\n");
		return -1;
	}

	tp = tbc->dtp;
	if(tp == NULL || tp->db == NULL) {
		ERR("bdb: table not loaded!\n");
		return -1;
	}

	mode = 0;
	if(!DB_FLD_EMPTY(cmd->result)) { /* columns to be returned provided */
		if(cmd->result_count > tp->ncols) {
			ERR("bdb: too many columns in query\n");
			goto error;
		}
	} else {
		mode = 1;
		cmd->result = db_fld(tp->ncols + 1);
		cmd->result_count = tp->ncols;
		for(i = 0; i < cmd->result_count; i++) {
			if(bdb_fld(cmd->result + i, cmd->table.s) < 0)
				goto error;
		}
	}

	for(i = 0; i < cmd->result_count; i++) {
		fld = cmd->result + i;
		f = DB_GET_PAYLOAD(fld);
		if(mode == 1) {
			DBG("bdb: column name [%.*s]\n", tp->colp[i]->name.len,
					tp->colp[i]->name.s);

			f->name = pkg_malloc(tp->colp[i]->name.len + 1);
			if(f->name == NULL) {
				ERR("bdb: Out of private memory\n");
				goto error;
			}
			strncpy(f->name, tp->colp[i]->name.s, tp->colp[i]->name.len);
			f->name[tp->colp[i]->name.len] = '\0';
			fld->name = f->name;
			fld->type = tp->colp[i]->type;
			f->col_pos = i;
		} else {
			f->col_pos = bdb_get_colpos(tp, fld->name);
			if(f->col_pos == -1) {
				ERR("bdb: Column not found\n");
				goto error;
			}
		}
		switch(fld->type) {
			case DB_INT:
			case DB_BITMAP:
			case DB_FLOAT:
			case DB_DOUBLE:
			case DB_DATETIME:
			case DB_STR:
				if(!f->buf.s)
					f->buf.s = pkg_malloc(BDB_BUF_SIZE);
				if(f->buf.s == NULL) {
					ERR("bdb: No memory left\n");
					goto error;
				}
				fld[i].v.lstr.s = f->buf.s;
				break;

			case DB_CSTR:
				if(!f->buf.s)
					f->buf.s = pkg_malloc(BDB_BUF_SIZE);
				if(f->buf.s == NULL) {
					ERR("bdb: No memory left\n");
					goto error;
				}
				fld[i].v.cstr = f->buf.s;
				break;

			case DB_BLOB:
				if(!f->buf.s)
					f->buf.s = pkg_malloc(BDB_BUF_SIZE);
				if(f->buf.s == NULL) {
					ERR("mysql: No memory left\n");
					goto error;
				}
				fld[i].v.blob.s = f->buf.s;
				break;

			case DB_NONE:
				/* Eliminates gcc warning */
				break;
		}
	}

	if(!DB_FLD_EMPTY(cmd->match)) {
		if(cmd->match_count > tp->ncols) {
			ERR("bdb: too many columns in match struct of query\n");
			goto error;
		}
		for(i = 0; i < cmd->match_count; i++) {
			fld = cmd->result + i;
			f = DB_GET_PAYLOAD(fld);
			f->col_pos = bdb_get_colpos(tp, fld->name);
			if(f->col_pos == -1) {
				ERR("bdb: Match column not found\n");
				goto error;
			}
		}
	}

	return 0;

error:
	return -1;
}

/**
 * Execute a query
 * @param cmd DB command structure
 * @param bcmd Berkeley DB command structure
 * @return 0 on success, -1 on error
 */
int bdb_query(db_cmd_t *cmd, bdb_cmd_t *bcmd)
{
	DBT key;
	DB *db;
	static char kbuf[MAX_ROW_SIZE];
	int klen;

	bdb_tcache_t *tbc = NULL;
	bdb_table_t *tp = NULL;

	if(bcmd->bcon == NULL || bcmd->bcon->dbp == NULL)
		return -1;

	tbc = bdblib_get_table(bcmd->bcon->dbp, &cmd->table);
	if(tbc == NULL) {
		ERR("bdb: table does not exist!\n");
		return -1;
	}

	tp = tbc->dtp;
	if(tp == NULL) {
		ERR("bdb: table not loaded!\n");
		return -1;
	}
	db = tp->db;
	if(db == NULL) {
		ERR("bdb: db structure not initialized!\n");
		return -1;
	}

	if(DB_FLD_EMPTY(cmd->match)) { /* no match constraint */
		if(db->cursor(db, NULL, &bcmd->dbcp, 0) != 0) {
			ERR("bdb: error creating cursor\n");
			goto error;
		}
		bcmd->skey.len = 0;
		return 0;
	}

	memset(&key, 0, sizeof(DBT));
	memset(kbuf, 0, MAX_ROW_SIZE);

	klen = MAX_ROW_SIZE;
	if(bdblib_valtochar(tp, cmd->match, cmd->match_count, kbuf, &klen, BDB_KEY)
			!= 0) {
		ERR("bdb: error creating key\n");
		goto error;
	}

	if(klen > bcmd->skey_size || bcmd->skey.s == NULL) {
		if(bcmd->skey.s != NULL)
			pkg_free(bcmd->skey.s);
		bcmd->skey.s = (char *)pkg_malloc(klen * sizeof(char));
		if(bcmd->skey.s == NULL) {
			ERR("bdb: no pkg memory\n");
			goto error;
		}
		bcmd->skey_size = klen;
	}
	memcpy(bcmd->skey.s, kbuf, klen);
	bcmd->skey.len = klen;

	return 0;
error:
	return -1;
}

int bdb_cmd(db_cmd_t *cmd)
{
	bdb_cmd_t *bcmd;
	db_con_t *con;
	bdb_con_t *bcon;

	bcmd = (bdb_cmd_t *)pkg_malloc(sizeof(bdb_cmd_t));
	if(bcmd == NULL) {
		ERR("bdb: No memory left\n");
		goto error;
	}
	memset(bcmd, '\0', sizeof(bdb_cmd_t));
	if(db_drv_init(&bcmd->gen, bdb_cmd_free) < 0)
		goto error;

	con = cmd->ctx->con[db_payload_idx];
	bcon = DB_GET_PAYLOAD(con);
	bcmd->bcon = bcon;

	switch(cmd->type) {
		case DB_PUT:
		case DB_DEL:
		case DB_UPD:
			ERR("bdb: The driver does not support DB modifications yet.\n");
			goto error;
			break;

		case DB_GET:
			if(bdb_prepare_query(cmd, bcmd) != 0) {
				ERR("bdb: error preparing query.\n");
				goto error;
			}
			break;

		case DB_SQL:
			ERR("bdb: The driver does not support raw queries yet.\n");
			goto error;
	}

	DB_SET_PAYLOAD(cmd, bcmd);
	return 0;

error:
	if(bcmd) {
		DB_SET_PAYLOAD(cmd, NULL);
		db_drv_free(&bcmd->gen);
		pkg_free(bcmd);
	}
	return -1;
}


int bdb_cmd_exec(db_res_t *res, db_cmd_t *cmd)
{
	db_con_t *con;
	bdb_cmd_t *bcmd;
	bdb_con_t *bcon;

	/* First things first: retrieve connection info from the currently active
	 * connection and also mysql payload from the database command
	 */
	con = cmd->ctx->con[db_payload_idx];
	bcmd = DB_GET_PAYLOAD(cmd);
	bcon = DB_GET_PAYLOAD(con);

	if((bcon->flags & BDB_CONNECTED) == 0) {
		ERR("bdb: not connected\n");
		return -1;
	}
	bcmd->next_flag = -1;
	switch(cmd->type) {
		case DB_DEL:
		case DB_PUT:
		case DB_UPD:
			/* no result expected - cleanup */
			DBG("bdb: query with no result.\n");
			break;
		case DB_GET:
			return bdb_query(cmd, bcmd);
			break;
		default:
			/* result expected - no cleanup */
			DBG("bdb: query with result.\n");
	}

	return 0;
}

int bdb_update_result(db_cmd_t *cmd, DBT *data)
{
	bdb_fld_t *f;
	db_fld_t *fld;
	int i;
	int col;
	char *s;
	static str col_map[MAX_NUM_COLS];

	memset(col_map, 0, MAX_NUM_COLS * sizeof(str));

	col = 0;
	s = (char *)data->data;
	col_map[col].s = s;
	while(*s != '\0') {
		if(*s == *DELIM) {
			col_map[col].len = s - col_map[col].s;
			col++;
			col_map[col].s = s + 1;
		}
		s++;
	}
	col_map[col].len = s - col_map[col].s;

	for(i = 0; i < cmd->result_count; i++) {
		fld = cmd->result + i;
		f = DB_GET_PAYLOAD(fld);
		if(col_map[f->col_pos].len == 0) {
			fld->flags |= DB_NULL;
			continue;
		}
		fld->flags &= ~DB_NULL;

		switch(fld->type) {
			case DB_STR:
				fld->v.lstr.s = f->buf.s;
				if(col_map[f->col_pos].len < BDB_BUF_SIZE) {
					fld->v.lstr.len = col_map[f->col_pos].len;
				} else {
					/* truncate ?!? */
					fld->v.lstr.len = BDB_BUF_SIZE - 1;
				}
				memcpy(fld->v.lstr.s, col_map[f->col_pos].s, fld->v.lstr.len);
				break;

			case DB_BLOB:
				fld->v.blob.s = f->buf.s;
				if(col_map[f->col_pos].len < BDB_BUF_SIZE) {
					fld->v.blob.len = col_map[f->col_pos].len;
				} else {
					/* truncate ?!? */
					fld->v.blob.len = BDB_BUF_SIZE - 1;
				}
				memcpy(fld->v.blob.s, col_map[f->col_pos].s, fld->v.blob.len);

				break;

			case DB_CSTR:
				fld->v.cstr = f->buf.s;
				if(col_map[f->col_pos].len < BDB_BUF_SIZE) {
					memcpy(fld->v.cstr, col_map[f->col_pos].s,
							col_map[f->col_pos].len);
					fld->v.cstr[col_map[f->col_pos].len] = '\0';
				} else {
					/* truncate ?!? */
					memcpy(fld->v.cstr, col_map[f->col_pos].s,
							BDB_BUF_SIZE - 1);
					fld->v.cstr[BDB_BUF_SIZE - 1] = '\0';
					;
				}

				break;

			case DB_DATETIME:
				/* str to time */
				col_map[f->col_pos].s[col_map[f->col_pos].len] = '\0';
				if(bdb_str2time(col_map[f->col_pos].s, &fld->v.time) < 0) {
					ERR("Error while converting INT value from string\n");
					return -1;
				}
				break;

			case DB_INT:
				/* str to int */
				col_map[f->col_pos].s[col_map[f->col_pos].len] = '\0';
				if(bdb_str2int(col_map[f->col_pos].s, &fld->v.int4) < 0) {
					ERR("Error while converting INT value from string\n");
					return -1;
				}
				break;

			case DB_FLOAT:
			case DB_DOUBLE:
				/* str to dowuble */
				col_map[f->col_pos].s[col_map[f->col_pos].len] = '\0';
				if(bdb_str2double(col_map[f->col_pos].s, &fld->v.dbl) < 0) {
					ERR("Error while converting DOUBLE value from string\n");
					return -1;
				}
				break;

			case DB_BITMAP:
				/* str to int */
				col_map[f->col_pos].s[col_map[f->col_pos].len] = '\0';
				if(bdb_str2int(col_map[f->col_pos].s, &fld->v.int4) < 0) {
					ERR("Error while converting BITMAP value from string\n");
					return -1;
				}
				break;

			case DB_NONE:
				break;
		}
	}
	return 0;
}

int bdb_cmd_first(db_res_t *res)
{
	bdb_cmd_t *bcmd;

	bcmd = DB_GET_PAYLOAD(res->cmd);
	switch(bcmd->next_flag) {
		case -2: /* table is empty */
			return 1;
		case 0: /* cursor position is 0 */
			return 0;
		case 1: /* next row */
		case 2: /* EOF */
			ERR("bdb: no next row.\n");
			return -1;
		default:
			return bdb_cmd_next(res);
	}
}


int bdb_cmd_next(db_res_t *res)
{
	bdb_cmd_t *bcmd;
	DBT key, data;
	int ret;
	static char dbuf[MAX_ROW_SIZE];

	bcmd = DB_GET_PAYLOAD(res->cmd);

	if(bcmd->next_flag == 2 || bcmd->next_flag == -2)
		return 1;

	memset(&key, 0, sizeof(DBT));
	memset(&data, 0, sizeof(DBT));
	memset(dbuf, 0, MAX_ROW_SIZE);

	data.data = dbuf;
	data.ulen = MAX_ROW_SIZE;
	data.flags = DB_DBT_USERMEM;

	ret = 0;
	if(bcmd->skey.len == 0) {
		while((ret = bcmd->dbcp->c_get(bcmd->dbcp, &key, &data, DB_NEXT))
				== 0) {
			if(!strncasecmp((char *)key.data, "METADATA", 8))
				continue;
			break;
		}
		if(ret != 0) {
			bcmd->next_flag = bcmd->next_flag < 0 ? -2 : 2;
			return 1;
		}
	} else {
		key.data = bcmd->skey.s;
		key.ulen = bcmd->skey_size;
		key.flags = DB_DBT_USERMEM;
		key.size = bcmd->skey.len;
		ret = bcmd->dbcp->c_get(bcmd->dbcp, &key, &data, DB_NEXT);
		if(ret != 0) {
			bcmd->next_flag = bcmd->next_flag < 0 ? -2 : 2;
			return 1;
		}
	}

	if(bcmd->next_flag <= 0) {
		bcmd->next_flag++;
	}

	if(bdb_update_result(res->cmd, &data) < 0) {
		return -1;
	}

	res->cur_rec->fld = res->cmd->result;
	return 0;
}


/** @} */
