/*
 * $Id$
 *
 * Oracle module core functions
 *
 * Copyright (C) 2007,2008 TRUNK MOBILE
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <oci.h>
#include "../../core/mem/mem.h"
#include "../../core/dprint.h"
#include "../../lib/srdb1/db_pool.h"
#include "../../lib/srdb1/db_ut.h"
#include "../../lib/srdb1/db_res.h"
#include "../../lib/srdb1/db_query.h"
#include "val.h"
#include "ora_con.h"
#include "res.h"
#include "asynch.h"
#include "dbase.h"


#define MAX_BIND_HANDLES 128

char st_buf[STATIC_BUF_LEN];


/*
 * Make error message. Always return negative value
 */
int sql_buf_small(void)
{
	LM_ERR("static buffer too small\n");
	return -11;
}

/*
 * Decode error
 */
static char errbuf[512];

static const char *db_oracle_errorinfo(ora_con_t *con)
{
	sword errcd;
	if(OCIErrorGet(con->errhp, 1, NULL, &errcd, (OraText *)errbuf,
			   sizeof(errbuf), OCI_HTYPE_ERROR)
			!= OCI_SUCCESS)
		errbuf[0] = '\0';
	else
		switch(errcd) {
			case 28:	/* your session has been killed */
			case 30:	/* session ID does not exists */
			case 31:	/* session marked for kill */
			case 41:	/* active time limit exceeded session terminated */
			case 107:	/* failed to connect to oracle listener */
			case 115:	/* connection refused; dispatcher table is full */
			case 1033:	/* init/shutdown in progress */
			case 1034:	/* not available (startup) */
			case 1089:	/* server shutdown */
			case 1090:	/* shutdown wait after command */
			case 1092:	/* oracle instance terminated. Disconnection forced */
			case 1573:	/* shutdown instance, no futher change allowed */
			case 2049:	/* timeout: distributed transaction waiting lock */
			case 3113:	/* EOF on communication channel */
			case 3114:	/* not connected */
			case 3135:	/* lost connection */
			case 6033:	/* connect failed, partner rejected connection */
			case 6034:	/* connect failed, partner exited unexpectedly */
			case 6037:	/* connect failed, node unreacheable */
			case 6039:	/* connect failed */
			case 6042:	/* msgrcv failure (DNT) */
			case 6043:	/* msgsend failure (DNT) */
			case 6107:	/* network server not found */
			case 6108:	/* connect to host failed */
			case 6109:	/* msgrcv failure (TCP) */
			case 6110:	/* msgsend failure (TCP) */
			case 6114:	/* SID lookup failure */
			case 6124:	/* TCP timeout */
			case 6135:	/* connect rejected; server is stopping (TCP) */
			case 6144:	/* SID unavaliable (TCP) */
			case 6413:	/* connection not open */
			case 12150: /* tns can't send data, probably disconnect */
			case 12152: /* tns unable to send break message */
			case 12153: /* tns not connected */
			case 12161: /* tns internal error */
			case 12170: /* tns connect timeout */
			case 12224: /* tns no listener */
			case 12225: /* tns destination host unreacheable */
			case 12230: /* tns network error */
			case 12525: /* tns (internal) timeout */
			case 12521: /* tns can't resolve db name */
			case 12537: /* tns connection cloed */
			case 12541: /* tns not running */
			case 12543: /* tns destination host unreacheable */
			case 12547: /* tns lost contact */
			case 12560: /* tns protocol(transport) error */
			case 12561: /* tns unknown error */
			case 12608: /* tns send timeount */
			case 12609: /* tns receive timeount */
				LM_ERR("connection dropped\n");
				db_oracle_disconnect(con);
			default:
				break;
		}

	return errbuf;
}

const char *db_oracle_error(ora_con_t *con, sword status)
{
	switch(status) {
		case OCI_SUCCESS:
			return "internal (success)";

		case OCI_SUCCESS_WITH_INFO:
		case OCI_ERROR:
			return db_oracle_errorinfo(con);

		case OCI_NEED_DATA:
			return "need data";

		case OCI_NO_DATA:
			return "no data";

		case OCI_INVALID_HANDLE:
			return "invalid handle";

		case OCI_STILL_EXECUTING: // ORA-3123
			return "executing (logic)";

		case OCI_CONTINUE:
			return "continue (library)";

		default:
			snprintf(errbuf, sizeof(errbuf), "unknown status %u", status);
			return errbuf;
	}
}


/*
 * Initialize database module
 * No function should be called before this
 */
db1_con_t *db_oracle_init(const str *_url)
{
	return db_do_init(_url, (void *)db_oracle_new_connection);
}


/*
 * Shut down database module
 * No function should be called after this
 */
void db_oracle_close(db1_con_t *_h)
{
	db_do_close(_h, (void *)db_oracle_free_connection);
}


/*
 * Release a result set from memory
 */
int db_oracle_free_result(db1_con_t *_h, db1_res_t *_r)
{
	if(!_h || !_r) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	if(db_free_result(_r) < 0) {
		LM_ERR("failed to free result structure\n");
		return -1;
	}
	return 0;
}


/*
 * Send an SQL query to the server
 */
static int db_oracle_submit_query(const db1_con_t *_h, const str *_s)
{
	OCIBind *bind[MAX_BIND_HANDLES];
	OCIDate odt[sizeof(bind) / sizeof(bind[0])];
	str tmps;
	sword status;
	int pass;
	ora_con_t *con = CON_ORA(_h);
	query_data_t *pqd = con->pqdata;
	size_t hc = pqd->_n + pqd->_nw;
	OCIStmt *stmthp;

	if(hc >= sizeof(bind) / sizeof(bind[0])) {
		LM_ERR("too many bound. Rebuild with MAX_BIND_HANDLES >= %u\n",
				(unsigned)hc);
		return -1;
	}

	if(!pqd->_rs) {
		/*
		 * This method is at ~25% faster as set OCI_COMMIT_ON_SUCCESS
		 * in StmtExecute
		 */
		tmps.len = snprintf(st_buf, sizeof(st_buf),
				"begin %.*s; commit write batch nowait; end;", _s->len, _s->s);
		if((unsigned)tmps.len >= sizeof(st_buf))
			return sql_buf_small();
		tmps.s = st_buf;
		_s = &tmps;
	}

	pass = 1;
	if(!con->connected) {
		status = db_oracle_reconnect(con);
		if(status != OCI_SUCCESS) {
			LM_ERR("can't restore connection: %s\n",
					db_oracle_error(con, status));
			return -2;
		}
		LM_INFO("connection restored\n");
		--pass;
	}
repeat:
	stmthp = NULL;
	status = OCIHandleAlloc(
			con->envhp, (dvoid **)(dvoid *)&stmthp, OCI_HTYPE_STMT, 0, NULL);
	if(status != OCI_SUCCESS)
		goto ora_err;
	status = OCIStmtPrepare(stmthp, con->errhp, (text *)_s->s, _s->len,
			OCI_NTV_SYNTAX, OCI_DEFAULT);
	if(status != OCI_SUCCESS)
		goto ora_err;

	if(hc) {
		bmap_t bmap;
		size_t pos = 1;
		int i;

		memset(bind, 0, hc * sizeof(bind[0]));
		for(i = 0; i < pqd->_n; i++) {
			if(db_oracle_val2bind(&bmap, &pqd->_v[i], &odt[pos]) < 0)
				goto bind_err;
			status = OCIBindByPos(stmthp, &bind[pos], con->errhp, pos,
					bmap.addr, bmap.size, bmap.type, NULL, NULL, NULL, 0, NULL,
					OCI_DEFAULT);
			if(status != OCI_SUCCESS)
				goto ora_err;
			++pos;
		}
		for(i = 0; i < pqd->_nw; i++) {
			if(db_oracle_val2bind(&bmap, &pqd->_w[i], &odt[pos]) < 0) {
			bind_err:
				OCIHandleFree(stmthp, OCI_HTYPE_STMT);
				LM_ERR("can't map values\n");
				return -3;
			}
			status = OCIBindByPos(stmthp, &bind[pos], con->errhp, pos,
					bmap.addr, bmap.size, bmap.type, NULL, NULL, NULL, 0, NULL,
					OCI_DEFAULT);
			if(status != OCI_SUCCESS)
				goto ora_err;
			++pos;
		}
	}

	// timelimited operation
	status = begin_timelimit(con, 0);
	if(status != OCI_SUCCESS)
		goto ora_err;
	do
		status = OCIStmtExecute(con->svchp, stmthp, con->errhp, !pqd->_rs, 0,
				NULL, NULL,
				pqd->_rs ? OCI_STMT_SCROLLABLE_READONLY : OCI_DEFAULT);
	while(wait_timelimit(con, status));
	if(done_timelimit(con, status))
		goto stop_exec;
	switch(status) {
		case OCI_SUCCESS_WITH_INFO:
			LM_WARN("driver: %s\n", db_oracle_errorinfo(con));
			//PASS THRU
		case OCI_SUCCESS:
			if(pqd->_rs)
				*pqd->_rs = stmthp;
			else
				OCIHandleFree(stmthp, OCI_HTYPE_STMT);
			return 0;
		default:
			pass = -pass;
			break;
	}

ora_err:
	LM_ERR("driver: %s\n", db_oracle_error(con, status));
stop_exec:
	if(stmthp)
		OCIHandleFree(stmthp, OCI_HTYPE_STMT);
	if(pass == -1 && !con->connected) {
		/* Attempt to reconnect */
		if(db_oracle_reconnect(con) == OCI_SUCCESS) {
			LM_NOTICE("attempt repeat after reconnect\n");
			pass = 0;
			goto repeat;
		}
		LM_ERR("connection loss\n");
	}
	return -4;
}


/*
 * Query table for specified rows
 * _h: structure representing database connection
 * _k: key names
 * _op: operators
 * _v: values of the keys that must match
 * _c: column names to return
 * _n: number of key=values pairs to compare
 * _nc: number of columns to return
 * _o: order by the specified column
 */
int db_oracle_query(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_op,
		const db_val_t *_v, const db_key_t *_c, int _n, int _nc,
		const db_key_t _o, db1_res_t **_r)
{
	query_data_t cb;
	OCIStmt *reshp;
	int rc;

	if(!_h || !CON_TABLE(_h) || !_r) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	cb._rs = &reshp;
	cb._v = _v;
	cb._n = _n;
	cb._w = NULL;
	cb._nw = 0;
	CON_ORA(_h)->pqdata = &cb;
	CON_ORA(_h)->bindpos = 0;
	rc = db_do_query(_h, _k, _op, _v, _c, _n, _nc, _o, _r, db_oracle_val2str,
			db_oracle_submit_query, db_oracle_store_result);
	CON_ORA(_h)->pqdata = NULL; /* paranoid for next call */
	return rc;
}


/*
 * Execute a raw SQL query
 */
int db_oracle_raw_query(const db1_con_t *_h, const str *_s, db1_res_t **_r)
{
	query_data_t cb;
	OCIStmt *reshp;
	int len;
	const char *p;

	if(!_h || !_r || !_s || !_s->s) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}


	memset(&cb, 0, sizeof(cb));

	p = _s->s;
	len = _s->len;
	while(len && *p == ' ')
		++p, --len;
#define _S_DIFF(p, l, S) \
	(l <= sizeof(S) - 1 || strncasecmp(p, S, sizeof(S) - 1))
	if(!_S_DIFF(p, len, "select ")) {
		cb._rs = &reshp;
	} else {
		if(_S_DIFF(p, len, "insert ") && _S_DIFF(p, len, "delete ")
				&& _S_DIFF(p, len, "update ")) {
			LM_ERR("unknown raw_query: '%.*s'\n", _s->len, _s->s);
			return -2;
		}
#undef _S_DIFF
		cb._rs = NULL;
	}

	CON_ORA(_h)->pqdata = &cb;
	CON_ORA(_h)->bindpos = 0;
	len = db_do_raw_query(
			_h, _s, _r, db_oracle_submit_query, db_oracle_store_result);
	CON_ORA(_h)->pqdata = NULL; /* paranoid for next call */
	return len;
}


/*
 * Insert a row into specified table
 * _h: structure representing database connection
 * _k: key names
 * _v: values of the keys
 * _n: number of key=value pairs
 */
int db_oracle_insert(
		const db1_con_t *_h, const db_key_t *_k, const db_val_t *_v, int _n)
{
	query_data_t cb;
	int rc;

	if(!_h || !CON_TABLE(_h)) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	cb._rs = NULL;
	cb._v = _v;
	cb._n = _n;
	cb._w = NULL;
	cb._nw = 0;
	CON_ORA(_h)->pqdata = &cb;
	CON_ORA(_h)->bindpos = 0;
	rc = db_do_insert(
			_h, _k, _v, _n, db_oracle_val2str, db_oracle_submit_query);
	CON_ORA(_h)->pqdata = NULL; /* paranoid for next call */
	return rc;
}


/*
 * Delete a row from the specified table
 * _h: structure representing database connection
 * _k: key names
 * _o: operators
 * _v: values of the keys that must match
 * _n: number of key=value pairs
 */
int db_oracle_delete(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_o,
		const db_val_t *_v, int _n)
{
	query_data_t cb;
	int rc;

	if(!_h || !CON_TABLE(_h)) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	cb._rs = NULL;
	cb._v = _v;
	cb._n = _n;
	cb._w = NULL;
	cb._nw = 0;
	CON_ORA(_h)->pqdata = &cb;
	CON_ORA(_h)->bindpos = 0;
	rc = db_do_delete(
			_h, _k, _o, _v, _n, db_oracle_val2str, db_oracle_submit_query);
	CON_ORA(_h)->pqdata = NULL; /* paranoid for next call */
	return rc;
}


/*
 * Update some rows in the specified table
 * _h: structure representing database connection
 * _k: key names
 * _o: operators
 * _v: values of the keys that must match
 * _uk: updated columns
 * _uv: updated values of the columns
 * _n: number of key=value pairs
 * _un: number of columns to update
 */
int db_oracle_update(const db1_con_t *_h, const db_key_t *_k, const db_op_t *_o,
		const db_val_t *_v, const db_key_t *_uk, const db_val_t *_uv, int _n,
		int _un)
{
	query_data_t cb;
	int rc;

	if(!_h || !CON_TABLE(_h)) {
		LM_ERR("invalid parameter value\n");
		return -1;
	}

	cb._rs = NULL;
	cb._v = _uv;
	cb._n = _un;
	cb._w = _v;
	cb._nw = _n;
	CON_ORA(_h)->pqdata = &cb;
	CON_ORA(_h)->bindpos = 0;
	rc = db_do_update(_h, _k, _o, _v, _uk, _uv, _n, _un, db_oracle_val2str,
			db_oracle_submit_query);
	CON_ORA(_h)->pqdata = NULL; /* paranoid for next call */
	return rc;
}


/*
 * Store name of table that will be used by
 * subsequent database functions
 */
int db_oracle_use_table(db1_con_t *_h, const str *_t)
{
	return db_use_table(_h, _t);
}
