modules_k/db_oracle/res.c
14ae23bb
 /*
  * $Id$
  *
  * Oracle module result related functions
  *
  * Copyright (C) 2007,2008 TRUNK MOBILE
  *
27642a08
  * This file is part of Kamailio, a free SIP server.
14ae23bb
  *
27642a08
  * Kamailio is free software; you can redistribute it and/or modify
14ae23bb
  * 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
  *
27642a08
  * Kamailio is distributed in the hope that it will be useful,
14ae23bb
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 
 #include <string.h>
 #include <time.h>
 #include <oci.h>
4c1bbd98
 #include "../../lib/srdb1/db_res.h"
 #include "../../lib/srdb1/db_row.h"
14ae23bb
 #include "../../mem/mem.h"
 #include "../../dprint.h"
 #include "ora_con.h"
 #include "dbase.h"
 #include "asynch.h"
 #include "res.h"
 
 
 #define MAX_DEF_HANDLES 64
 
 struct dmap {
     OCIDefine* defh[MAX_DEF_HANDLES];
     union {
 	dvoid* v;
 	double* f;
 	int* i;
 	char* c;
 	OCIDate* o;
     }pv[MAX_DEF_HANDLES];
     dvoid* pval[MAX_DEF_HANDLES];
     ub2 ilen[MAX_DEF_HANDLES];
     sb2 ind[MAX_DEF_HANDLES];
     ub2 len[MAX_DEF_HANDLES];
 };
 typedef struct dmap dmap_t;
 
 
 /*
  * Get and convert columns from a result. Define handlers and buffers
  */
4c1bbd98
 static int get_columns(ora_con_t* con, db1_res_t* _r, OCIStmt* _c, dmap_t* _d)
14ae23bb
 {
 	OCIParam *param;
 	size_t tsz;
 	ub4 i, n;
 	sword status;
 
 	status = OCIAttrGet(_c, OCI_HTYPE_STMT, &n, NULL, OCI_ATTR_PARAM_COUNT,
 		con->errhp);
 
 	if (status != OCI_SUCCESS) {
 		LM_ERR("driver: %s\n", db_oracle_error(con, status));
 		return -1;
 	}
 
 	if (!n) {
 		LM_ERR("no columns\n");
 		return -2;
 	}
 
 	if (n >= MAX_DEF_HANDLES) {
 		LM_ERR("too many res. Rebuild with MAX_DEF_HANDLES >= %u\n", n);
 		return -3;
 	}
 
 	if (db_allocate_columns(_r, n) != 0) {
 		LM_ERR("could not allocate columns");
 		return -4;
 	}
 	memset(RES_NAMES(_r), 0, sizeof(db_key_t) * n);
 
 	RES_COL_N(_r) = n;
 
 	tsz = 0;
 	memset(_d->defh, 0, sizeof(_d->defh[0]) * n);
 	for (i = 0; i < n; i++) {
 		ub4 len;
 		ub2 dtype;
 
 		status = OCIParamGet(_c, OCI_HTYPE_STMT, con->errhp,
 			(dvoid**)(dvoid*)&param, i+1);
 		if (status != OCI_SUCCESS) goto ora_err;
 
 		{
 			text* name;
 			str* sname;
 			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
 				(dvoid**)(dvoid*)&name,	&len, OCI_ATTR_NAME,
 				con->errhp);
 			if (status != OCI_SUCCESS) goto ora_err;
 			sname = (str*)pkg_malloc(sizeof(str)+len+1);
 			if (!sname) {
 				db_free_columns(_r);
 				LM_ERR("no private memory left\n");
 				return -5;
 			}
 			sname->len = len;
 			sname->s = (char*)sname + sizeof(str);
 			memcpy(sname->s, name, len);
 			sname->s[len] = '\0';
 			RES_NAMES(_r)[i] = sname;
 		}
 
 		status = OCIAttrGet(param, OCI_DTYPE_PARAM,
 			(dvoid**)(dvoid*)&dtype, NULL, OCI_ATTR_DATA_TYPE,
 			con->errhp);
 		if (status != OCI_SUCCESS) goto ora_err;
 
 		switch (dtype) {
 		case SQLT_UIN:		/* unsigned integer */
 set_bitmap:
4c1bbd98
 			LM_DBG("use DB1_BITMAP type");
 			RES_TYPES(_r)[i] = DB1_BITMAP;
14ae23bb
 			len = sizeof(VAL_BITMAP((db_val_t*)NULL));
 			break;
 
 		case SQLT_INT:		/* (ORANET TYPE) integer */
 set_int:
4c1bbd98
 			LM_DBG("use DB1_INT result type");
 			RES_TYPES(_r)[i] = DB1_INT;
14ae23bb
 			len = sizeof(VAL_INT((db_val_t*)NULL));
 			break;
 
 //		case SQLT_LNG:		/* long */
 		case SQLT_VNU:		/* NUM with preceding length byte */
 		case SQLT_NUM:		/* (ORANET TYPE) oracle numeric */
 			len = 0; /* PRECISION is ub1 */
 			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
 				(dvoid**)(dvoid*)&len, NULL, OCI_ATTR_PRECISION,
 				con->errhp);
 			if (status != OCI_SUCCESS) goto ora_err;
 			if (len <= 11) {
 				sb1 sc;
 				status = OCIAttrGet(param, OCI_DTYPE_PARAM,
 					(dvoid**)(dvoid*)&sc, NULL,
 					OCI_ATTR_SCALE, con->errhp);
 				if (status != OCI_SUCCESS) goto ora_err;
 				if (!sc) {
 					dtype = SQLT_INT;
 					if (len != 11) goto set_int;
 					dtype = SQLT_UIN;
 					goto set_bitmap;
 				}
 			}
 		case SQLT_FLT:		/* (ORANET TYPE) Floating point number */
 		case SQLT_BFLOAT:       /* Native Binary float*/
 		case SQLT_BDOUBLE:	/* NAtive binary double */
 		case SQLT_IBFLOAT:	/* binary float canonical */
 		case SQLT_IBDOUBLE:	/* binary double canonical */
 		case SQLT_PDN:		/* (ORANET TYPE) Packed Decimal Numeric */
4c1bbd98
 			LM_DBG("use DB1_DOUBLE result type");
 			RES_TYPES(_r)[i] = DB1_DOUBLE;
14ae23bb
 			len = sizeof(VAL_DOUBLE((db_val_t*)NULL));
 			dtype = SQLT_FLT;
 			break;
 
 //		case SQLT_TIME:		/* TIME */
 //		case SQLT_TIME_TZ:	/* TIME WITH TIME ZONE */
 		case SQLT_DATE:		/* ANSI Date */
 		case SQLT_DAT:		/* date in oracle format */
 		case SQLT_ODT:		/* OCIDate type */
 		case SQLT_TIMESTAMP:	/* TIMESTAMP */
 		case SQLT_TIMESTAMP_TZ:	/* TIMESTAMP WITH TIME ZONE */
 		case SQLT_TIMESTAMP_LTZ:/* TIMESTAMP WITH LOCAL TZ */
 //		case SQLT_INTERVAL_YM:	/* INTERVAL YEAR TO MONTH */
 //		case SQLT_INTERVAL_DS:	/* INTERVAL DAY TO SECOND */
4c1bbd98
 			LM_DBG("use DB1_DATETIME result type");
 			RES_TYPES(_r)[i] = DB1_DATETIME;
14ae23bb
 			len = sizeof(OCIDate);
 			dtype = SQLT_ODT;
 			break;
 
 		case SQLT_CLOB:		/* character lob */
 		case SQLT_BLOB:		/* binary lob */
 //		case SQLT_BFILEE:	/* binary file lob */
 //		case SQLT_CFILEE:	/* character file lob */
 //		case SQLT_BIN:		/* binary data(DTYBIN) */
 //		case SQLT_LBI:		/* long binary */
4c1bbd98
 			LM_DBG("use DB1_BLOB result type");
 			RES_TYPES(_r)[i] = DB1_BLOB;
14ae23bb
 			goto dyn_str;
 
 		case SQLT_CHR:		/* (ORANET TYPE) character string */
 		case SQLT_STR:		/* zero terminated string */
 		case SQLT_VST:		/* OCIString type */
 		case SQLT_VCS:		/* Variable character string */
 		case SQLT_AFC:		/* Ansi fixed char */
 		case SQLT_AVC:		/* Ansi Var char */
 //		case SQLT_RID:		/* rowid */
4c1bbd98
 			LM_DBG("use DB1_STR result type");
 			RES_TYPES(_r)[i] = DB1_STR;
14ae23bb
 dyn_str:
 			dtype = SQLT_CHR;
 			len = 0; /* DATA_SIZE is ub2 */
 			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
 				(dvoid**)(dvoid*)&len, NULL, OCI_ATTR_DATA_SIZE,
 				con->errhp);
 			if (status != OCI_SUCCESS) goto ora_err;
cec9a17f
 			if (len >= 4000) {
4c1bbd98
 				LM_DBG("use DB1_BLOB result type");
 				RES_TYPES(_r)[i] = DB1_BLOB;
cec9a17f
 			}
14ae23bb
 			++len;
 			break;
 
 		default:
 			LM_ERR("unsupported datatype %d\n", dtype);
 			goto stop_load;
 		}
 		_d->ilen[i] = (ub2)len;
 		_d->pv[i].v = st_buf + tsz;
 		tsz += len;
 		status = OCIDefineByPos(_c, &_d->defh[i], con->errhp, i+1,
 			_d->pv[i].v, len, dtype, &_d->ind[i],
 			&_d->len[i], NULL, OCI_DEFAULT);
 		if (status != OCI_SUCCESS) goto ora_err;
 	}
 
 #if STATIC_BUF_LEN < 65536
 #error
 #endif
 	if (tsz > 65536) {
 		LM_ERR("Row size exceed 65K. IOB's are not supported");
 		goto stop_load;
 	}
 	return 0;
 
 ora_err:
 	LM_ERR("driver: %s\n", db_oracle_error(con, status));
 stop_load:
 	db_free_columns(_r);
 	return -6;
 }
 
 
 /*
  * Convert data fron db format to internal format
  */
4c1bbd98
 static int convert_row(db1_res_t* _res, db_row_t* _r, dmap_t* _d)
14ae23bb
 {
 	unsigned i, n = RES_COL_N(_res);
 
 	ROW_N(_r) = n;
 	ROW_VALUES(_r) = (db_val_t*)pkg_malloc(sizeof(db_val_t) * n);
 	if (!ROW_VALUES(_r)) {
 nomem:
 		LM_ERR("no private memory left\n");
 		return -1;
 	}
 	memset(ROW_VALUES(_r), 0, sizeof(db_val_t) * n);
 
 	for (i = 0; i < n; i++) {
 		static const str dummy_string = {"", 0};
 
 		db_val_t* v = &ROW_VALUES(_r)[i];
 		db_type_t t = RES_TYPES(_res)[i];
 
 		if (_d->ind[i] == -1) {
 			/* Initialize the string pointers to a dummy empty
 			 * string so that we do not crash when the NULL flag
 			 * is set but the module does not check it properly
 			 */
 			VAL_STRING(v) = dummy_string.s;
 			VAL_STR(v) = dummy_string;
 			VAL_BLOB(v) = dummy_string;
 			VAL_TYPE(v) = t;
 			VAL_NULL(v) = 1;
 			continue;
 		}
 
 		if (_d->ind[i])
 			LM_WARN("truncated value in DB\n");
 
 		VAL_TYPE(v) = t;
 		switch (t) {
4c1bbd98
 		case DB1_INT:
14ae23bb
 			VAL_INT(v) = *_d->pv[i].i;
 			break;
 
4c1bbd98
 		case DB1_BIGINT:
48ccd327
 			LM_ERR("BIGINT not supported");
 			return -1;
 
4c1bbd98
 		case DB1_BITMAP:
14ae23bb
 			VAL_BITMAP(v) = *_d->pv[i].i;
 			break;
 
4c1bbd98
 		case DB1_DOUBLE:
14ae23bb
 			VAL_DOUBLE(v) = *_d->pv[i].f;
 			break;
 
4c1bbd98
 		case DB1_DATETIME:
14ae23bb
 			{
 				struct tm tm;
 				memset(&tm, 0, sizeof(tm));
 				OCIDateGetTime(_d->pv[i].o, &tm.tm_hour,
 					&tm.tm_min, &tm.tm_sec);
 				OCIDateGetDate(_d->pv[i].o, &tm.tm_year,
 					&tm.tm_mon, &tm.tm_mday);
 				if (tm.tm_mon)
 					--tm.tm_mon;
 				if (tm.tm_year >= 1900)
 					tm.tm_year -= 1900;
 				VAL_TIME(v) = mktime(&tm);
 			}
 			break;
 
4c1bbd98
 		case DB1_STR:
 		case DB1_BLOB:
 		case DB1_STRING:
14ae23bb
 			{
 				size_t len = _d->len[i];
 				char *pstr = pkg_malloc(len+1);
 				if (!pstr) goto nomem;
 				memcpy(pstr, _d->pv[i].c, len);
 				pstr[len] = '\0';
 				VAL_FREE(v) = 1;
4c1bbd98
 				if (t == DB1_STR) {
14ae23bb
 					VAL_STR(v).s = pstr;
 					VAL_STR(v).len = len;
4c1bbd98
 				} else if (t == DB1_BLOB) {
14ae23bb
 					VAL_BLOB(v).s = pstr;
 					VAL_BLOB(v).len = len;
 				} else {
 					VAL_STRING(v) = pstr;
 				}
 			}
 			break;
 
 		default:
 			LM_ERR("unknown type mapping (%u)\n", t);
 			return -2;
 		}
 	}
 
 	return 0;
 }
 
 
 /*
  * Get rows and convert it from oracle to db API representation
  */
4c1bbd98
 static int get_rows(ora_con_t* con, db1_res_t* _r, OCIStmt* _c, dmap_t* _d)
14ae23bb
 {
 	ub4 rcnt;
 	sword status;
 	unsigned n = RES_COL_N(_r);
 
 	memcpy(_d->len, _d->ilen, sizeof(_d->len[0]) * n);
 
 	// timelimited operation
 	status = begin_timelimit(con, 0);
 	if (status != OCI_SUCCESS) goto ora_err;
 	do status = OCIStmtFetch2(_c, con->errhp, 1, OCI_FETCH_LAST, 0,
 		OCI_DEFAULT);
 	while (wait_timelimit(con, status));
 	if (done_timelimit(con, status)) goto stop_load;
 	if (status != OCI_SUCCESS) {
 		if (status != OCI_NO_DATA)
 			goto ora_err;
 
 		RES_ROW_N(_r) = 0;
 		RES_ROWS(_r) = NULL;
 		return 0;
 	}
 
 	status = OCIAttrGet(_c, OCI_HTYPE_STMT, &rcnt, NULL,
 		OCI_ATTR_CURRENT_POSITION, con->errhp);
 	if (status != OCI_SUCCESS) goto ora_err;
 	if (!rcnt) {
 		LM_ERR("lastpos==0\n");
 		goto stop_load;
 	}
 
 	RES_ROW_N(_r) = rcnt;
 	RES_ROWS(_r) = (db_row_t*)pkg_malloc(sizeof(db_row_t) * rcnt);
 	if (!RES_ROWS(_r)) {
 		LM_ERR("no private memory left\n");
 		return -1;
 	}
 	memset(RES_ROWS(_r), 0, sizeof(db_row_t) * rcnt);
 
 	while ( 1 ) {
 		if (convert_row(_r, &RES_ROWS(_r)[--rcnt], _d) < 0) {
 			LM_ERR("erroc convert row\n");
 			goto stop_load;
 		}
 
 		if (!rcnt)
 			return 0;
 
 		memcpy(_d->len, _d->ilen, sizeof(_d->len[0]) * n);
 		// timelimited operation
 		status = begin_timelimit(con, 0);
 		if (status != OCI_SUCCESS) goto ora_err;
 		do status = OCIStmtFetch2(_c, con->errhp, 1, OCI_FETCH_PRIOR, 0,
 			OCI_DEFAULT);
 		while (wait_timelimit(con, status));
 		if (done_timelimit(con, status)) goto stop_load;
 		if (status != OCI_SUCCESS) break;
 	}
 ora_err:
 	LM_ERR("driver: %s\n", db_oracle_error(con, status));
 stop_load:
 	db_free_rows(_r);
 	RES_ROW_N(_r) = 0;	/* TODO: skipped in db_res.c :) */
 	return -3;
 }
 
 
 /*
  * Read database answer and fill the structure
  */
4c1bbd98
 int db_oracle_store_result(const db1_con_t* _h, db1_res_t** _r)
14ae23bb
 {
 	dmap_t dmap;
 	int rc;
4c1bbd98
 	db1_res_t* r;
14ae23bb
 	ora_con_t* con;
 	OCIStmt* hs;
 
 	if (!_h || !_r) {
 badparam:
 		LM_ERR("invalid parameter\n");
 		return -1;
 	}
 
 	con = CON_ORA(_h);
 	{
 	    query_data_t *pcb = con->pqdata;
 	    
 
 	    if (!pcb || !pcb->_rs)
 		    goto badparam;
 		    
 	    hs = *pcb->_rs;
 	    pcb->_rs = NULL; /* paranoid for next call */
 	}	    
 	
 	rc = -1;
 	if (_r)	*_r = NULL;	/* unification for all errors */
 
 	r = db_new_result();
 	if (!r) {
 		LM_ERR("no memory left\n");
 		goto done;
 	}
 
 	if (get_columns(con, r, hs, &dmap) < 0) {
 		LM_ERR("error while getting column names\n");
 		goto done;
 	}
 
 	if (get_rows(con, r, hs, &dmap) < 0) {
 		LM_ERR("error while converting rows\n");
 		db_free_columns(r);
 		goto done;
 	}
 
 	rc = 0;
 	*_r = r;
 done:
 	OCIHandleFree(hs, OCI_HTYPE_STMT);
 	return rc;
 }