/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2004 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.0 of the PHP license,       |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_0.txt.                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: Stig Sæther Bakken <ssb@php.net>                            |
   |          Mitch Golden <mgolden@interport.net>                        |
   |          Rasmus Lerdorf <rasmus@php.net>                             |
   |          Andreas Karajannis <Andreas.Karajannis@gmd.de>              |
   |          Thies C. Arntzen <thies@thieso.net>                         |
   +----------------------------------------------------------------------+
 */

/* $Id: oracle.c,v 1.94 2004/01/08 08:16:41 andi Exp $ */

/* comment out the next line if you're on Oracle 7.x and don't have the olog 
   call. */
 
#define HAS_OLOG 1

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"

#include "zend_globals.h"

#if HAVE_ORACLE

#include "php_oracle.h"
#define HASH_DTOR (void (*)(void *))

#include "ext/standard/info.h"


#ifndef min
#define min(a, b) ((a) > (b) ? (b) : (a))
#endif

#ifdef PHP_WIN32
#define PHP_ORA_API __declspec(dllexport)
#else
#define PHP_ORA_API
#endif                                   

#ifdef ZTS
int ora_globals_id;
#else
PHP_ORA_API php_ora_globals ora_globals;
#endif

#define DB_SIZE 65536

#define ORA_FETCHINTO_ASSOC (1<<0)
#define ORA_FETCHINTO_NULLS (1<<1)

static oraCursor *ora_get_cursor(HashTable *, pval ** TSRMLS_DC);
static char *ora_error(Cda_Def *);
static int ora_describe_define(oraCursor *);
static void _close_oraconn(zend_rsrc_list_entry *rsrc TSRMLS_DC);
static int _close_oracur(oraCursor *cur TSRMLS_DC);
static int _ora_ping(oraConnection *conn);
int ora_set_param_values(oraCursor *cursor, int isout TSRMLS_DC);

void ora_do_logon(INTERNAL_FUNCTION_PARAMETERS, int persistent);

static int le_conn, le_pconn, le_cursor; 

/* {{{ prototypes
 */
PHP_FUNCTION(ora_bind);
PHP_FUNCTION(ora_close);
PHP_FUNCTION(ora_commit);
PHP_FUNCTION(ora_commitoff);
PHP_FUNCTION(ora_commiton);
PHP_FUNCTION(ora_do);
PHP_FUNCTION(ora_error);
PHP_FUNCTION(ora_errorcode);
PHP_FUNCTION(ora_exec);
PHP_FUNCTION(ora_fetch);
PHP_FUNCTION(ora_fetch_into);
PHP_FUNCTION(ora_columntype);
PHP_FUNCTION(ora_columnname);
PHP_FUNCTION(ora_columnsize);
PHP_FUNCTION(ora_getcolumn);
PHP_FUNCTION(ora_numcols);
PHP_FUNCTION(ora_numrows);
PHP_FUNCTION(ora_logoff);
PHP_FUNCTION(ora_logon);
PHP_FUNCTION(ora_plogon);
PHP_FUNCTION(ora_open);
PHP_FUNCTION(ora_parse);
PHP_FUNCTION(ora_rollback);

PHP_MINIT_FUNCTION(oracle);
PHP_RINIT_FUNCTION(oracle);
PHP_MSHUTDOWN_FUNCTION(oracle);
PHP_RSHUTDOWN_FUNCTION(oracle);
PHP_MINFO_FUNCTION(oracle);
/* }}} */

/* {{{ oracle_functions[]
 */
function_entry oracle_functions[] = {
	PHP_FE(ora_bind,								NULL)
	PHP_FE(ora_close,								NULL)
	PHP_FE(ora_commit,								NULL)
	PHP_FE(ora_commitoff,							NULL)
	PHP_FE(ora_commiton,							NULL)
	PHP_FE(ora_do,									NULL)
	PHP_FE(ora_error,								NULL)
	PHP_FE(ora_errorcode,							NULL)
	PHP_FE(ora_exec,								NULL)
	PHP_FE(ora_fetch,								NULL)
   	PHP_FE(ora_fetch_into,							second_arg_force_ref)
	PHP_FE(ora_columntype,							NULL)
	PHP_FE(ora_columnname,							NULL)
	PHP_FE(ora_columnsize,							NULL)
	PHP_FE(ora_getcolumn,							NULL)
	PHP_FE(ora_numcols,								NULL)
	PHP_FE(ora_numrows,								NULL)
	PHP_FE(ora_logoff,								NULL)
	PHP_FE(ora_logon,								NULL)
	PHP_FE(ora_plogon,								NULL)
	PHP_FE(ora_open,								NULL)
	PHP_FE(ora_parse,								NULL)
	PHP_FE(ora_rollback,							NULL)
	{NULL, NULL, NULL}
};
/* }}} */

/* {{{ oracle_module_entry
 */
zend_module_entry oracle_module_entry = {
    STANDARD_MODULE_HEADER,
	"oracle",
	oracle_functions,
	PHP_MINIT(oracle),       /* extension-wide startup function */
    PHP_MSHUTDOWN(oracle),   /* extension-wide shutdown function */
    PHP_RINIT(oracle),       /* per-request startup function */
    PHP_RSHUTDOWN(oracle),   /* per-request shutdown function */
    PHP_MINFO(oracle),
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};
/* }}} */

/* {{{ ora_func_tab[]
 */
static const text *ora_func_tab[] =
{(text *) "unused",
/*  1, 2  */ (text *) "unused", (text *) "OSQL",
/*  3, 4  */ (text *) "unused", (text *) "OEXEC/OEXN",
/*  5, 6  */ (text *) "unused", (text *) "OBIND",
/*  7, 8  */ (text *) "unused", (text *) "ODEFIN",
/*  9, 10 */ (text *) "unused", (text *) "ODSRBN",
/* 11, 12 */ (text *) "unused", (text *) "OFETCH/OFEN",
/* 13, 14 */ (text *) "unused", (text *) "OOPEN",
/* 15, 16 */ (text *) "unused", (text *) "OCLOSE",
/* 17, 18 */ (text *) "unused", (text *) "unused",
/* 19, 20 */ (text *) "unused", (text *) "unused",
/* 21, 22 */ (text *) "unused", (text *) "ODSC",
/* 23, 24 */ (text *) "unused", (text *) "ONAME",
/* 25, 26 */ (text *) "unused", (text *) "OSQL3",
/* 27, 28 */ (text *) "unused", (text *) "OBNDRV",
/* 29, 30 */ (text *) "unused", (text *) "OBNDRN",
/* 31, 32 */ (text *) "unused", (text *) "unused",
/* 33, 34 */ (text *) "unused", (text *) "OOPT",
/* 35, 36 */ (text *) "unused", (text *) "unused",
/* 37, 38 */ (text *) "unused", (text *) "unused",
/* 39, 40 */ (text *) "unused", (text *) "unused",
/* 41, 42 */ (text *) "unused", (text *) "unused",
/* 43, 44 */ (text *) "unused", (text *) "unused",
/* 45, 46 */ (text *) "unused", (text *) "unused",
/* 47, 48 */ (text *) "unused", (text *) "unused",
/* 49, 50 */ (text *) "unused", (text *) "unused",
/* 51, 52 */ (text *) "unused", (text *) "OCAN",
/* 53, 54 */ (text *) "unused", (text *) "OPARSE",
/* 55, 56 */ (text *) "unused", (text *) "OEXFET",
/* 57, 58 */ (text *) "unused", (text *) "OFLNG",
/* 59, 60 */ (text *) "unused", (text *) "ODESCR",
/* 61, 62 */ (text *) "unused", (text *) "OBNDRA"
};
/* }}} */

#ifdef COMPILE_DL_ORACLE
ZEND_GET_MODULE(oracle)
# ifdef PHP_WIN32
# include "zend_arg_defs.c"
# endif
#endif

/* {{{ _close_oraconn
 */
static void _close_oraconn(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	oraConnection *conn = (oraConnection *)rsrc->ptr;
	
	conn->open = 0;

	ologof(&conn->lda);
	ORA(num_links)--;

	zend_hash_del(ORA(conns),(void*)&conn,sizeof(void*));

	if (conn->persistent) {
		ORA(num_persistent)--;
		free(conn);
	} else {
		efree(conn);
	}
}
/* }}} */

/* {{{ pval_ora_param_destructor
 */
static void
pval_ora_param_destructor(oraParam *param)
{
	if (param->progv) {
		efree(param->progv);
	}
}
/* }}} */

/* {{{ _close_oracur
 */
static int _close_oracur(oraCursor *cur TSRMLS_DC)
{
	int i;

	if (cur){
		if (cur->query){
			efree(cur->query);
		}
		if (cur->params){
			zend_hash_destroy(cur->params);
			efree(cur->params);
			cur->params = NULL;
		}
		if (cur->columns){
			for(i = 0; i < cur->ncols; i++){
				if (cur->columns[i].buf)
					efree(cur->columns[i].buf);
			}
			efree(cur->columns);
			cur->columns = NULL;
		}

		if (cur->open){
			oraConnection *db_conn;

			if (zend_hash_find(ORA(conns),(void*)&(cur->conn_ptr),sizeof(void*),(void **)&db_conn) == SUCCESS) {
				oclose(&cur->cda);
			} 
		}

	   	efree(cur);
	}
	
	return 1;
}
/* }}} */

/* {{{ php_close_ora_cursor
 */
static void php_close_ora_cursor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
	oraCursor *cur = (oraCursor *)rsrc->ptr;

	_close_oracur(cur TSRMLS_CC);
}
/* }}} */

/* {{{ php_ora_init_globals
 */
static void php_ora_init_globals(php_ora_globals *ora_globals_p TSRMLS_DC)
{
	if (cfg_get_long("oracle.allow_persistent",
			 &ORA(allow_persistent))
		== FAILURE) {
	  ORA(allow_persistent) = -1;
	}
	if (cfg_get_long("oracle.max_persistent",
					 &ORA(max_persistent))
	    == FAILURE) {
		ORA(max_persistent) = -1;
	}
	if (cfg_get_long("oracle.max_links",
					 &ORA(max_links))
	    == FAILURE) {
		ORA(max_links) = -1;
	}
	
	ORA(num_persistent) = 0;
	
	ORA(conns) = malloc(sizeof(HashTable));
	zend_hash_init(ORA(conns), 13, NULL, NULL, 1);
	
	memset((void*) &ORA(db_err_conn),0,sizeof(ORA(db_err_conn)));
}
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(oracle)
{
#ifdef ZTS
	ts_allocate_id(&ora_globals_id, sizeof(php_ora_globals), (ts_allocate_ctor) php_ora_init_globals, NULL);
#else
	php_ora_init_globals(&ora_globals TSRMLS_CC);
#endif

	le_cursor = zend_register_list_destructors_ex(php_close_ora_cursor, NULL, "oracle cursor", module_number);
	le_conn = zend_register_list_destructors_ex(_close_oraconn, NULL, "oracle link", module_number);
	le_pconn = zend_register_list_destructors_ex(NULL, _close_oraconn, "oracle link persistent", module_number);

	REGISTER_LONG_CONSTANT("ORA_BIND_INOUT", 0, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("ORA_BIND_IN",    1, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("ORA_BIND_OUT",   2, CONST_CS | CONST_PERSISTENT);

	REGISTER_LONG_CONSTANT("ORA_FETCHINTO_ASSOC",ORA_FETCHINTO_ASSOC, CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("ORA_FETCHINTO_NULLS",ORA_FETCHINTO_NULLS, CONST_CS | CONST_PERSISTENT);

#ifdef ZTS
	opinit(OCI_EV_TSF); 
#endif

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(oracle)
{
	ORA(num_links) = 
		ORA(num_persistent);
	/*
	  ORA(defaultlrl) = 0;
	  ORA(defaultbinmode) = 0;
	  ORA(defaultconn) = 0;
	*/
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(oracle)
{
	zend_hash_destroy(ORA(conns));
	free(ORA(conns));
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(oracle)
{
	return SUCCESS;
}
/* }}} */

/* {{{ _ora_ping
 */
static int _ora_ping(oraConnection *conn)
{
	Cda_Def cda;

	if (oopen(&cda, &conn->lda, (text *) 0, -1, -1, (text *) 0, -1)) {
		return 0;
	}

	if (oparse(&cda, "select sysdate from dual", (sb4) - 1, 0, VERSION_7)) {
		oclose(&cda);
		return 0;
	}

	oclose(&cda);
	return 1;

}
/* }}} */

/*
   ** PHP functions
*/

/* {{{ proto resource ora_logon(string user, string password)
   Open an Oracle connection */
PHP_FUNCTION(ora_logon)
{
	ora_do_logon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto resource ora_plogon(string user, string password)
   Open a persistent Oracle connection */
PHP_FUNCTION(ora_plogon)
{
	ora_do_logon(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

/* {{{ ora_do_logon
 */
void ora_do_logon(INTERNAL_FUNCTION_PARAMETERS, int persistent)
{
	char *user,*passwd;
	pval **arg1, **arg2;
	char *hashed_details;
	int hashed_details_length;
	oraConnection *db_conn;

	if (zend_get_parameters_ex(2, &arg1, &arg2) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
  
	convert_to_string_ex(arg1);
	convert_to_string_ex(arg2);
  
	user = Z_STRVAL_PP(arg1);
	passwd = Z_STRVAL_PP(arg2);

	hashed_details_length = sizeof("oracle__")-1+strlen(user)+strlen(passwd);
	hashed_details = (char *) emalloc(hashed_details_length+1);
	sprintf(hashed_details,"oracle_%s_%s",user,passwd);

	if (!ORA(allow_persistent)) {
		persistent=0;
	}
	if (persistent) {
		list_entry *le;
		
		/* try to find if we already have this link in our persistent list */
		if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) {  /* we don't */
			list_entry new_le;
			
			if (ORA(max_links)!=-1 && ORA(num_links)>=ORA(max_links)) {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Oracle:  Too many open links (%d)",ORA(num_links));
				efree(hashed_details);
				RETURN_FALSE;
			}
			if (ORA(max_persistent)!=-1 && ORA(num_persistent)>=ORA(max_persistent)) {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Oracle:  Too many open persistent links (%d)",ORA(num_persistent));
				efree(hashed_details);
				RETURN_FALSE;
			}
			/* create the link */
			db_conn = (oraConnection *)malloc(sizeof(oraConnection));
			memset((void *) db_conn,0,sizeof(oraConnection));	
			db_conn->persistent = 1;

			if (
#if HAS_OLOG
				olog(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0, -1, OCI_LM_DEF)
#else
				orlon(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0)
#endif
				) {
				ORA(db_err_conn) = *db_conn;
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to ORACLE (%s)",ora_error(&db_conn->lda));
				
				if (persistent) {
					free(db_conn);
				} else {
					efree(db_conn);
				}
				
				efree(hashed_details);
				RETURN_FALSE;
			}

			/* hash it up */
			Z_TYPE(new_le) = le_pconn;
			new_le.ptr = db_conn;
			if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(list_entry), NULL)==FAILURE) {
				free(db_conn);
				efree(hashed_details);
				RETURN_FALSE;
			}
			ORA(num_persistent)++;
			ORA(num_links)++;
			zend_hash_add(ORA(conns),(void*)&db_conn,sizeof(void*),(void*)&db_conn,sizeof(void*),NULL);
		} else {  /* we do */
			if (Z_TYPE_P(le) != le_pconn) {
				RETURN_FALSE;
			}

			db_conn = (oraConnection *) le->ptr;

			/* ensure that the link did not die */

			if (!_ora_ping(db_conn)) {
				if (
#if HAS_OLOG
					olog(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0, -1, OCI_LM_DEF)
#else
					orlon(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0)
#endif
					) {
					ORA(db_err_conn) = *db_conn;
					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Oracle: Link to server lost, unable to reconnect",ora_error(&db_conn->lda));
					zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_length+1);
					efree(hashed_details);
					RETURN_FALSE;
				}
			}
		}
		ZEND_REGISTER_RESOURCE(return_value, db_conn, le_pconn);
	} else { /* non persistent */
		list_entry *index_ptr,new_index_ptr;
		
		/* first we check the hash for the hashed_details key.  if it exists,
		 * it should point us to the right offset where the actual Oracle link sits.
		 * if it doesn't, open a new Oracle link, add it to the resource list,
		 * and add a pointer to it with hashed_details as the key.
		 */
		if (zend_hash_find(&EG(regular_list),hashed_details,hashed_details_length+1,(void **) &index_ptr)==SUCCESS) {
			int type,link;
			void *ptr;

			if (Z_TYPE_P(index_ptr) != le_index_ptr) {
				RETURN_FALSE;
			}
			link = (int) index_ptr->ptr;
			ptr = zend_list_find(link,&type);   /* check if the link is still there */
			if (ptr && (type==le_conn || type==le_pconn)) {
				zend_list_addref(link);
				Z_LVAL_P(return_value) = link;
				Z_TYPE_P(return_value) = IS_RESOURCE;
				efree(hashed_details);
				return;
			} else {
				zend_hash_del(&EG(regular_list),hashed_details,hashed_details_length+1);
			}
		}
		if (ORA(max_links)!=-1 && ORA(num_links)>=ORA(max_links)) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Oracle:  Too many open links (%d)",ORA(num_links));
			efree(hashed_details);
			RETURN_FALSE;
		}

		db_conn = (oraConnection *) emalloc(sizeof(oraConnection));
		memset((void *) db_conn,0,sizeof(oraConnection));	
		db_conn->persistent = 0;

		if (
#if HAS_OLOG
			olog(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0, -1, OCI_LM_DEF)
#else
			orlon(&db_conn->lda, db_conn->hda, user,strlen(user), passwd, strlen(passwd), 0)
#endif
			) {
			ORA(db_err_conn) = *db_conn;
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Oracle: Connection Failed: %s\n",ora_error(&db_conn->lda));
			efree(hashed_details);
			efree(db_conn);
			RETURN_FALSE;
		}

		/* add it to the list */
		ZEND_REGISTER_RESOURCE(return_value, db_conn, le_conn);

		/* add it to the hash */
		new_index_ptr.ptr = (void *) Z_LVAL_P(return_value);
		Z_TYPE(new_index_ptr) = le_index_ptr;
		if (zend_hash_update(&EG(regular_list),hashed_details,hashed_details_length+1,(void *) &new_index_ptr, sizeof(list_entry), NULL)==FAILURE) {
			efree(hashed_details);
			RETURN_FALSE;
		}
		zend_hash_add(ORA(conns),(void*)&db_conn,sizeof(void*),(void*)&db_conn,sizeof(void*),NULL);
		ORA(num_links)++;
	}

	efree(hashed_details);
}
/* }}} */

/* {{{ proto bool ora_logoff(resource connection)
   Close an Oracle connection */
PHP_FUNCTION(ora_logoff)
{								/* conn_index */
	oraConnection *conn;
	pval **arg;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	zend_list_delete(Z_LVAL_PP(arg));

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto resource ora_open(resource connection)
   Open an Oracle cursor */
PHP_FUNCTION(ora_open)
{								/* conn_index */
	pval **arg;
	oraConnection *conn = NULL;
	oraCursor *cursor = NULL;

	if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	cursor = (oraCursor *)emalloc(sizeof(oraCursor));
	memset(cursor, 0, sizeof(oraCursor));
	if (oopen(&cursor->cda, &conn->lda, (text *) 0, -1, -1, (text *) 0, -1)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open new cursor (%s)",
				   ora_error(&cursor->cda));
		efree(cursor);
		RETURN_FALSE;
	}
	cursor->open = 1;
	cursor->conn_ptr = conn;	
	ZEND_REGISTER_RESOURCE(return_value, cursor, le_cursor);
	cursor->conn_id = Z_LVAL_P(return_value);
}

/* }}} */

/* {{{ proto bool ora_close(resource cursor)
   Close an Oracle cursor */
PHP_FUNCTION(ora_close)
{								/* conn_index */
	pval **arg;
	oraCursor *cursor;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(cursor, oraCursor *, arg, -1, "Oracle-Cursor", le_cursor);

	zend_list_delete(Z_LVAL_PP(arg));

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ora_commitoff(resource connection)
   Disable automatic commit */
PHP_FUNCTION(ora_commitoff)
{								/* conn_index */
	pval **arg;
	oraConnection *conn;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	if (ocof(&conn->lda)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to turn off auto-commit (%s)",
				   ora_error(&conn->lda));
		RETURN_FALSE;
	}
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ora_commiton(resource connection)
   Enable automatic commit */
PHP_FUNCTION(ora_commiton)
{								/* conn_index */
	pval **arg;
	oraConnection *conn;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	if (ocon(&conn->lda)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to turn on auto-commit (%s)",
				   ora_error(&conn->lda));
		RETURN_FALSE;
	}
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ora_commit(resource connection)
   Commit an Oracle transaction */
PHP_FUNCTION(ora_commit)
{								/* conn_index */
	pval **arg;
	oraConnection *conn;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	if (ocom(&conn->lda)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to commit transaction (%s)",
				   ora_error(&conn->lda));
		RETURN_FALSE;
	}
	RETVAL_TRUE;
}
/* }}} */

/* {{{ proto bool ora_rollback(resource connection)
   Roll back an Oracle transaction */
PHP_FUNCTION(ora_rollback)
{								/* conn_index */
	pval **arg;
	oraConnection *conn;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, arg, -1, "Oracle-Connection", le_conn, le_pconn);

	if (orol(&conn->lda)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to roll back transaction (%s)",
				   ora_error(&conn->lda));
		RETURN_FALSE;
	}
	RETVAL_TRUE;
}
/* }}} */

/* {{{ proto bool ora_parse(resource cursor, string sql_statement [, int defer])
   Parse an Oracle SQL statement */
PHP_FUNCTION(ora_parse)
{	
    pval **curs, **sql, **def;
	oraCursor *cursor;
	sword defer = 0;
	text *query;

	switch (ZEND_NUM_ARGS()) {
	case 3:
		zend_get_parameters_ex(3,&curs,&sql,&def);
		convert_to_long_ex(def);
		if (Z_LVAL_PP(def)) {
			defer = DEFER_PARSE;
		}
		break;
	case 2:
		zend_get_parameters_ex(2,&curs,&sql);
		break;
	default:
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(sql);
 	query = (text *) estrndup(Z_STRVAL_PP(sql),Z_STRLEN_PP(sql));

	if (query == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid query");
		RETURN_FALSE;
	}

	if (!(cursor = ora_get_cursor(&EG(regular_list),curs TSRMLS_CC))){
		efree(query);
		RETURN_FALSE;
	}

	if (cursor->query) {
		efree(cursor->query);
	}

	cursor->query = query;
	cursor->fetched = 0;

	if (cursor->params && cursor->nparams > 0){
		zend_hash_destroy(cursor->params);
		efree(cursor->params);
		cursor->params = NULL;
		cursor->nparams = 0;
	}
	
	if (oparse(&cursor->cda, query, (sb4) - 1, defer, VERSION_7)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Parse failed (%s)",ora_error(&cursor->cda));
		RETURN_FALSE;
	}
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool ora_bind(resource cursor, string php_variable_name, string sql_parameter_name, int length [, int type])
   Bind a PHP variable to an Oracle parameter */
PHP_FUNCTION(ora_bind)
{ 
	pval **curs, **pvar, **svar, **plen, **ptyp;
	int inout = 0;
	oraParam *newparam, *paramptr;
	oraCursor *cursor;
	char *paramname;

	switch (ZEND_NUM_ARGS()) {
	case 5:
		zend_get_parameters_ex(5,&curs,&pvar,&svar,&plen,&ptyp);
		convert_to_long_ex(ptyp);
		inout = Z_LVAL_PP(ptyp);
		break;
	case 4:
		zend_get_parameters_ex(4,&curs,&pvar,&svar,&plen);
		break;
	default:
		WRONG_PARAM_COUNT;
	}

	cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC);
	if (cursor == NULL) {
		RETURN_FALSE;
	}

	convert_to_string_ex(pvar);
	convert_to_string_ex(svar);
	convert_to_long_ex(plen);

	if (cursor->params == NULL) {
		ALLOC_HASHTABLE(cursor->params);
		if (!cursor->params ||
			zend_hash_init(cursor->params, 19, NULL,
						   HASH_DTOR pval_ora_param_destructor, 0) == FAILURE) {
			php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to initialize parameter list");
			RETURN_FALSE;
		}
	}
	newparam = (oraParam *)emalloc(sizeof(oraParam));

	if ((paramname = estrndup(Z_STRVAL_PP(pvar), Z_STRLEN_PP(pvar))) == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Out of memory for parametername");
		efree(newparam);
		RETURN_FALSE;
	}

	if (zend_hash_add(cursor->params, paramname, Z_STRLEN_PP(pvar) + 1, 
					  newparam, sizeof(oraParam), (void **)&paramptr) == FAILURE) {
		/* XXX zend_hash_destroy */
		efree(paramname);
		efree(newparam);
		php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not make parameter placeholder");
		RETURN_FALSE;
	}

	efree(newparam);
	efree(paramname);
	
	paramptr->progvl = Z_LVAL_PP(plen) + 1;
	paramptr->inout = inout;

	paramptr->progv = (text *)emalloc(paramptr->progvl);

/* XXX Maximum for progvl */
	paramptr->alen = paramptr->progvl;

	if (obndra(&cursor->cda,              
			   Z_STRVAL_PP(svar),
			   -1,
			   (ub1 *)paramptr->progv,
			   paramptr->progvl,
			   SQLT_STR, /* ftype */
			   -1, /* scale */
			   0/*&paramptr->ind*/, /* ind */
			   &paramptr->alen, /* alen */
			   0 /*&paramptr->arcode*/,
			   0, /* maxsize */
			   0,
			   0,
			   -1,
			   -1)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Bind failed (%s)",
				   ora_error(&cursor->cda));
		RETURN_FALSE;
	}

	cursor->nparams++;
	RETURN_TRUE;
}
/* }}} */

/*
  XXX Make return values compatible with old module ? 
 */
/* {{{ proto bool ora_exec(resource cursor)
   Execute a parsed statement */
PHP_FUNCTION(ora_exec)
{								/* cursor_index */
	pval **arg;
	oraCursor *cursor = NULL;

	if (zend_get_parameters_ex(1, &arg) == FAILURE)
		WRONG_PARAM_COUNT;

	if ((cursor = ora_get_cursor(&EG(regular_list), arg TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	if (cursor->cda.ft == FT_SELECT) {
		if (ora_describe_define(cursor) < 0) {
			/* error message is given by ora_describe_define() */
			RETURN_FALSE;
		}
	}

	if(cursor->nparams > 0){
		if(!ora_set_param_values(cursor, 0 TSRMLS_CC)){
			RETURN_FALSE;
		}
	}

	if (oexec(&cursor->cda)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Exec failed (%s)",
				   ora_error(&cursor->cda));
		RETURN_FALSE;
	}
	
	if(cursor->nparams > 0){
		if(!ora_set_param_values(cursor, 1 TSRMLS_CC)){
			RETURN_FALSE;
		}
	}
	RETURN_TRUE;
}
/* }}} */

/* {{{ proto int ora_numcols(resource cursor)
   Returns the numbers of columns in a result */
PHP_FUNCTION(ora_numcols)
{								/* cursor_index */
	pval **arg;
	oraCursor *cursor = NULL;

	if (zend_get_parameters_ex(1, &arg) == FAILURE)
		WRONG_PARAM_COUNT;

	if ((cursor = ora_get_cursor(&EG(regular_list), arg TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	RETURN_LONG(cursor->ncols); 
}
/* }}} */

/* {{{ proto int ora_numrows(resource cursor)
   Returns the number of rows in a result */
PHP_FUNCTION(ora_numrows)
{								/* cursor_index */
	pval **arg;
	oraCursor *cursor = NULL;

	if(zend_get_parameters_ex(1, &arg) == FAILURE)
		WRONG_PARAM_COUNT;

	if((cursor = ora_get_cursor(&EG(regular_list), arg TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	RETURN_LONG(cursor->cda.rpc); 
}
/* }}} */

/* prepares/executes/fetches 1st row if avail*/
/* {{{ proto resource ora_do(resource connection, resource cursor)
   Parse and execute a statement and fetch first result row */ 
PHP_FUNCTION(ora_do)
{
	pval **con,**sql;
	oraConnection *conn = NULL;
	oraCursor *cursor = NULL;
	text *query;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &con,&sql) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE2(conn, oraConnection *, con, -1, "Oracle-Connection", le_conn, le_pconn);

	convert_to_string_ex(sql);

	cursor = (oraCursor *)emalloc(sizeof(oraCursor));

	memset(cursor, 0, sizeof(oraCursor));

	query = (text *) estrndup(Z_STRVAL_PP(sql),Z_STRLEN_PP(sql));

	if (query == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid query in Ora_Do");
		RETURN_FALSE;
	}
	
	cursor->query = query;
	
	if (oopen(&cursor->cda, &conn->lda, (text *) 0, -1, -1, (text *) 0, -1)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to open new cursor (%s)",
				  ora_error(&cursor->cda));
		efree(cursor);
		RETURN_FALSE;
	}
	cursor->open = 1;
	cursor->conn_ptr = conn;	
	cursor->conn_id = Z_LVAL_PP(con);	
	
	/* Prepare stmt */

	if (oparse(&cursor->cda, query, (sb4) - 1, 1, VERSION_7)){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Do failed (%s)",
				  ora_error(&cursor->cda));
		_close_oracur(cursor TSRMLS_CC);
		RETURN_FALSE;
	}
	
	/* Execute stmt (and fetch 1st row for selects) */
	if (cursor->cda.ft == FT_SELECT) {
		if (ora_describe_define(cursor) < 0){
			/* error message is given by ora_describe_define() */
			_close_oracur(cursor TSRMLS_CC);
			RETURN_FALSE;
		}
		if (oexfet(&cursor->cda, 1, 0, 0)) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Do failed (%s)",
					  ora_error(&cursor->cda));
			_close_oracur(cursor TSRMLS_CC);
			RETURN_FALSE;
		}
		cursor->fetched = 1;
	} else {
		if (oexec(&cursor->cda)) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Do failed (%s)",
					  ora_error(&cursor->cda));
			_close_oracur(cursor TSRMLS_CC);
			RETURN_FALSE;
		}
	}
	
	ZEND_REGISTER_RESOURCE(return_value, cursor, le_cursor);
}
/* }}} */

/* {{{ proto bool ora_fetch(resource cursor)
   Fetch a row of result data from a cursor */
PHP_FUNCTION(ora_fetch)
{								/* cursor_index */
	pval **arg;
	oraCursor *cursor;

	if (zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if ((cursor = ora_get_cursor(&EG(regular_list), arg TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available on this cursor");
		RETURN_FALSE;
	}

	/* Get data from Oracle */
	if (ofetch(&cursor->cda)) {
		if (cursor->cda.rc != NO_DATA_FOUND) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Fetch failed (%s)",
					   ora_error(&cursor->cda));
		}
		RETURN_FALSE;
	}
	cursor->fetched++;
	RETVAL_TRUE;
}
/* }}} */

/* {{{ proto int ora_fetch_into(resource cursor, array result [, int flags])
   Fetch a row into the specified result array */
PHP_FUNCTION(ora_fetch_into)
{
	pval **curs, **arr, **flg, *tmp;
	oraCursor *cursor;
	int i;
	int flags = 0;

	switch(ZEND_NUM_ARGS()){
	case 2:
		zend_get_parameters_ex(2, &curs, &arr);
		break;
		
	case 3:
		zend_get_parameters_ex(3, &curs, &arr, &flg);
		convert_to_long_ex(flg);
		flags = Z_LVAL_PP(flg);
		break;
		
	default:
		WRONG_PARAM_COUNT;
		break;
	}
	
	/* Find the cursor */
	if ((cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available on this cursor");
		RETURN_FALSE;
	}

	if (ofetch(&cursor->cda)) {
		if (cursor->cda.rc != NO_DATA_FOUND) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Fetch_Into failed (%s)",ora_error(&cursor->cda));
		}
		RETURN_FALSE;
	}
	cursor->fetched++;

	if (Z_TYPE_PP(arr) != IS_ARRAY){
		pval_destructor(*arr);
		array_init(*arr);
	}
	zend_hash_internal_pointer_reset(Z_ARRVAL_PP(arr));

	for (i = 0; i < cursor->ncols; i++) {
		if (cursor->columns[i].col_retcode == 1405) {
			if (!(flags&ORA_FETCHINTO_NULLS)){
				continue; /* don't add anything for NULL columns, unless the calles wants it */
			} else {
				MAKE_STD_ZVAL(tmp);
				ZVAL_NULL(tmp);
			}
		} else if (cursor->columns[i].col_retcode != 0 &&
				   cursor->columns[i].col_retcode != 1406) {
			/* So error fetching column.  The most common is 1405, a NULL */
			/* was retreived.  1406 is ASCII or string buffer data was */
			/* truncated. The converted data from the database did not fit */
			/* into the buffer.  Since we allocated the buffer to be large */
			/* enough, this should not occur.  Anyway, we probably want to */
			/* return what we did get, in that case */
			RETURN_FALSE;
		} else {
			MAKE_STD_ZVAL(tmp);

			Z_TYPE_P(tmp) = IS_STRING;
			Z_STRLEN_P(tmp) = 0;

			switch(cursor->columns[i].dbtype) {
				case SQLT_LNG:
				case SQLT_LBI:
					{ 
						ub4 ret_len;
						int offset = cursor->columns[i].col_retlen;
						sb2 result;
						
						if (cursor->columns[i].col_retcode == 1406) { /* truncation -> get the rest! */
							while (1) {
								cursor->columns[i].buf = erealloc(cursor->columns[i].buf,offset + DB_SIZE + 1);
								
								if (! cursor->columns[i].buf) {
									offset = 0;
									break;
								}
								
								result = oflng(&cursor->cda, 
											   (sword)(i + 1),
											   cursor->columns[i].buf + offset, 
											   DB_SIZE, 
											   1,
											   &ret_len, 
											   offset);
								if (result) {
									break;
								}
								
								if (ret_len <= 0) {
									break;
								}
								
								offset += ret_len;
							}
						}
						if (cursor->columns[i].buf && offset) {
							Z_STRLEN_P(tmp) = offset;
						} else {
							Z_STRLEN_P(tmp) = 0;
						}
					}
					break;
				default:
					Z_STRLEN_P(tmp) = min(cursor->columns[i].col_retlen,
											 cursor->columns[i].dsize);
					break;
			}
			Z_STRVAL_P(tmp) = estrndup(cursor->columns[i].buf,Z_STRLEN_P(tmp));
		}

		if (flags&ORA_FETCHINTO_ASSOC){
			zend_hash_update(Z_ARRVAL_PP(arr), cursor->columns[i].cbuf, cursor->columns[i].cbufl+1, (void *) &tmp, sizeof(pval*), NULL);
		} else {
			zend_hash_index_update(Z_ARRVAL_PP(arr), i, (void *) &tmp, sizeof(pval*), NULL);
		}

	}

	RETURN_LONG(cursor->ncols); 
}
/* }}} */

/* {{{ proto string ora_columnname(resource cursor, int column)
   Get the name of an Oracle result column */
PHP_FUNCTION(ora_columnname)
{								/* cursor_index, column_index */
	pval **curs, **col;
	oraCursor *cursor = NULL;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &curs, &col) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if ((cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	convert_to_long_ex(col);
	
	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available at this cursor index");
		RETURN_FALSE;
	}
        
	if (Z_LVAL_PP(col) >= cursor->ncols){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column index larger than number of columns");
		RETURN_FALSE;
	}

	if (Z_LVAL_PP(col) < 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column numbering starts at 0");
		RETURN_FALSE;
	}
        
	RETURN_STRINGL(cursor->columns[Z_LVAL_PP(col)].cbuf,
				   cursor->columns[Z_LVAL_PP(col)].cbufl,1);
}
/* }}} */

/* {{{ proto string ora_columntype(resource cursor, int column)
   Get the type of an Oracle result column */
PHP_FUNCTION(ora_columntype)
{								/* cursor_index, column_index */
	pval **curs, **col;
	int colno;
	oraCursor *cursor = NULL;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &curs, &col) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if ((cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	convert_to_long_ex(col);
	colno = Z_LVAL_PP(col);

	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available at this cursor index");
		RETURN_FALSE;
	}
        
	if (colno >= cursor->ncols){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column index larger than number of columns");
		RETURN_FALSE;
	}

	if (colno < 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column numbering starts at 0");
		RETURN_FALSE;
	}

	switch (cursor->columns[colno].dbtype) {
		case SQLT_CHR:
			RETURN_STRINGL("VARCHAR2", 8, 1);
		case SQLT_VCS:
	    case SQLT_AVC:
			RETURN_STRINGL("VARCHAR", 7, 1);
		case SQLT_STR:
	    case SQLT_AFC:
			RETURN_STRINGL("CHAR", 4, 1);
		case SQLT_NUM: case SQLT_INT:
		case SQLT_FLT: case SQLT_UIN:
			RETURN_STRINGL("NUMBER", 6, 1);
		case SQLT_LNG:
			RETURN_STRINGL("LONG", 4, 1);
		case SQLT_LBI:
			RETURN_STRINGL("LONG RAW", 8, 1);
		case SQLT_RID:
			RETURN_STRINGL("ROWID", 5, 1);
		case SQLT_DAT:
			RETURN_STRINGL("DATE", 4, 1);
#ifdef SQLT_CUR
		case SQLT_CUR:
			RETURN_STRINGL("CURSOR", 6, 1);
#endif
		default:
		{
			char numbuf[21];
			snprintf(numbuf, 20, "UNKNOWN(%d)", cursor->columns[colno].dbtype);
			numbuf[20] = '\0';
			RETVAL_STRING(numbuf,1);
		}
	}
}
/* }}} */

/* {{{ proto int ora_columnsize(int cursor, int column)
   Return the size of the column */
PHP_FUNCTION(ora_columnsize)
{								/* cursor_index, column_index */
	pval **curs, **col;
	oraCursor *cursor = NULL;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &curs, &col) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	/* Find the cursor */
	if ((cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	convert_to_long_ex(col);
	
	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available at this cursor index");
		RETURN_FALSE;
	}
        
	if (Z_LVAL_PP(col) >= cursor->ncols){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column index larger than number of columns");
		RETURN_FALSE;
	}

	if (Z_LVAL_PP(col) < 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column numbering starts at 0");
		RETURN_FALSE;
	}
        
	RETURN_LONG(cursor->columns[Z_LVAL_PP(col)].dbsize);
}
/* }}} */

/* {{{ proto mixed ora_getcolumn(resource cursor, int column)
   Get data from a fetched row */
PHP_FUNCTION(ora_getcolumn)
{								/* cursor_index, column_index */
	pval **curs,**col;
	int colno;
	oraCursor *cursor = NULL;
	oraColumn *column = NULL;
	int len;
	sb2 type;

	if (ZEND_NUM_ARGS() != 2 || zend_get_parameters_ex(2, &curs, &col) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if ((cursor = ora_get_cursor(&EG(regular_list), curs TSRMLS_CC)) == NULL) {
		RETURN_FALSE;
	}

	if (cursor->ncols == 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No tuples available at this cursor index");
		RETURN_FALSE;
	}

	convert_to_long_ex(col);
	colno = Z_LVAL_PP(col);        

	if (colno >= cursor->ncols){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column index larger than number of columns");
		RETURN_FALSE;
	}

	if (colno < 0){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Column numbering starts at 0");
		RETURN_FALSE;
	}

	if (cursor->fetched == 0){
		if (ofetch(&cursor->cda)) {
			if (cursor->cda.rc != NO_DATA_FOUND) {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_Fetch failed (%s)",
						   ora_error(&cursor->cda));
			}
			RETURN_FALSE;
		}
		cursor->fetched++;		
	}

 	column = &cursor->columns[colno]; 

 	type = column->dbtype; 

	if (column->col_retcode == 1405) {
		RETURN_NULL();
	}

	if (column->col_retcode != 0 && column->col_retcode != 1406) {
		/* So error fetching column.  The most common is 1405, a NULL
		 * was retreived.  1406 is ASCII or string buffer data was
		 * truncated. The converted data from the database did not fit
		 * into the buffer.  Since we allocated the buffer to be large
		 * enough, this should not occur.  Anyway, we probably want to
		 * return what we did get, in that case
		 */
		RETURN_FALSE;
	} else {
		switch(type) {
		case SQLT_CHR:
		case SQLT_NUM:
		case SQLT_INT: 
		case SQLT_FLT:
		case SQLT_STR:
		case SQLT_UIN:
		case SQLT_AFC:
		case SQLT_AVC:
		case SQLT_DAT:
			len = min(column->col_retlen, column->dsize);
			RETURN_STRINGL(column->buf,len,1);

		case SQLT_LNG:
		case SQLT_LBI:
			{ 
				ub4 ret_len;
				int offset = column->col_retlen;
				sb2 result;
				
				if (column->col_retcode == 1406) { /* truncation -> get the rest! */
					while (1) {
						column->buf = erealloc(column->buf,offset + DB_SIZE + 1);
						
						if (! column->buf) {
							offset = 0;
							break;
						}
						
						result = oflng(&cursor->cda, 
									   (sword)(colno + 1),
									   column->buf + offset, 
									   DB_SIZE, 
									   1,
									   &ret_len, 
									   offset);
						if (result) {
							break;
						}
						
						if (ret_len <= 0) {
							break;
						}
						
						offset += ret_len;
					}
				}
				if (column->buf && offset) {
					RETURN_STRINGL(column->buf, offset, 1);
				} else {
					RETURN_FALSE;
				}
			}
		default:
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Ora_GetColumn found invalid type (%d)", type);
			RETURN_FALSE;
		}
	}
}
/* }}} */

/* {{{ proto string ora_error(resource cursor_or_connection)
   Get an Oracle error message */
PHP_FUNCTION(ora_error)
{
	pval **arg;
	oraCursor *cursor;
	oraConnection *conn;
	void *res;
	int what;
	int argc = ZEND_NUM_ARGS();

	if (argc < 0 || argc >> 1 || zend_get_parameters_ex(argc, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if (argc == 1) {
		res = zend_fetch_resource(arg TSRMLS_CC, -1,"Oracle-Connection/Cursor",&what,3,le_conn, le_pconn, le_cursor);
		ZEND_VERIFY_RESOURCE(res);

		if (what == le_cursor) {
			cursor = (oraCursor *) res;
			RETURN_STRING(ora_error(&cursor->cda),1);
		} else {
			conn = (oraConnection *) res;
			RETURN_STRING(ora_error(&conn->lda),1);
		}
	} else {
		RETURN_STRING(ora_error(&ORA(db_err_conn).lda),1);
	}
}
/* }}} */

/* {{{ proto int ora_errorcode(resource cursor_or_connection)
   Get an Oracle error code */
PHP_FUNCTION(ora_errorcode)
{
	pval **arg;
	oraCursor *cursor;
	oraConnection *conn;
	void *res;
	int what;
	int argc = ZEND_NUM_ARGS();

	if (argc < 0 || argc >> 1 || zend_get_parameters_ex(argc, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if (argc == 1) {
		res = zend_fetch_resource(arg TSRMLS_CC, -1,"Oracle-Connection/Cursor",&what,3,le_conn, le_pconn, le_cursor);
		ZEND_VERIFY_RESOURCE(res);

		if (what == le_cursor) {
			cursor = (oraCursor *) res;
			RETURN_LONG(cursor->cda.rc);
		} else {
			conn = (oraConnection *) res;
			RETURN_LONG(conn->lda.rc);
		}
	} else {
		RETURN_LONG(ORA(db_err_conn).lda.rc);
	}
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(oracle)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "Oracle Support", "enabled");
#ifndef PHP_WIN32
    php_info_print_table_row(2, "Oracle Version", PHP_ORACLE_VERSION );
    php_info_print_table_row(2, "Compile-time ORACLE_HOME", PHP_ORACLE_DIR );
    php_info_print_table_row(2, "Libraries Used", PHP_ORACLE_SHARED_LIBADD );                                                                                       
#endif
	php_info_print_table_end();
}
/* }}} */

/*
** Functions internal to this module.
*/

/* {{{ ora_get_cursor
 */
static oraCursor *
ora_get_cursor(HashTable *list, pval **ind TSRMLS_DC)
{
	oraCursor *cursor;
	oraConnection *db_conn;

	cursor = (oraCursor *) zend_fetch_resource(ind TSRMLS_CC, -1, "Oracle-Cursor", NULL, 1, le_cursor);
	if (! cursor) {
		return NULL;
	}

	if (zend_hash_find(ORA(conns),(void*)&(cursor->conn_ptr),sizeof(void*),(void **)&db_conn) == FAILURE) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connection already closed for cursor index %d", ind);
		return NULL;
	}

	return cursor;
}
/* }}} */

/* {{{ ora_error
 */
static char *
ora_error(Cda_Def * cda)
{
	sword n, l;
	static text errmsg[ 512 ];

	n = oerhms(cda, cda->rc, errmsg, 400);

	/* remove the last newline */
	l = strlen(errmsg);
	if (l < 400 && errmsg[l - 1] == '\n') {
		errmsg[l - 1] = '\0';
		l--;
	}
	if (cda->fc > 0) {
		strcat(errmsg, " -- while processing OCI function ");
		strncat(errmsg, ora_func_tab[cda->fc], 75);  /* 512 - 400 - 36 */
	}
	return (char *) errmsg;
}
/* }}} */

/* {{{ ora_describe_define
 */
static sword
ora_describe_define(oraCursor * cursor)
{
	long col = 0;
	int i;
	sb2 type;
	sb4 dbsize;
	TSRMLS_FETCH();

	if (cursor == NULL) {
		return -1;
	}

	if (cursor->columns) {
		for(i = 0; i < cursor->ncols; i++){
			if (cursor->columns[i].buf)
				efree(cursor->columns[i].buf);
		}
		efree(cursor->columns);
	} 

	cursor->ncols = 0;

	while(1){
		if (odescr(&cursor->cda, (sword) cursor->ncols + 1, &dbsize, (sb2 *)0, (sb1 *)0, 
			   (sb4 *)0, (sb4 *)0,	(sb2 *)0, (sb2 *)0, (sb2 *)0)){
			if (cursor->cda.rc == VAR_NOT_IN_LIST) {
				break;
			} else {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", ora_error(&cursor->cda));
				cursor->ncols = 0;
				return -1;
			}
		}
		cursor->ncols++;
	}

	if (cursor->ncols > 0){
		cursor->columns = (oraColumn *) safe_emalloc(sizeof(oraColumn), cursor->ncols, 0);
		memset(cursor->columns,0,sizeof(oraColumn) * cursor->ncols);
	}

	for(col = 0; col < cursor->ncols; col++){
		cursor->columns[col].cbufl = ORANAMELEN;
		
		if (odescr(&cursor->cda, (sword)col + 1, &cursor->columns[col].dbsize,
				   &cursor->columns[col].dbtype, &cursor->columns[col].cbuf[0],
				   &cursor->columns[col].cbufl, &cursor->columns[col].dsize,
				   &cursor->columns[col].prec, &cursor->columns[col].scale,
				   &cursor->columns[col].nullok)) {
			if (cursor->cda.rc == VAR_NOT_IN_LIST) {
				break;
			} else {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", ora_error(&cursor->cda));
				return -1;
			}
		}

		cursor->columns[col].cbuf[cursor->columns[col].cbufl] = '\0';

		switch (cursor->columns[col].dbtype) {
			case SQLT_LBI:
				cursor->columns[col].dsize = DB_SIZE;
				type = SQLT_LBI;
				break;
			case SQLT_LNG: 
				cursor->columns[col].dsize = DB_SIZE;
			default:
				type = SQLT_STR;
				break;
		}
		
		cursor->columns[col].buf = (ub1 *) emalloc(cursor->columns[col].dsize + 1);

		/* Define an output variable for the column */
		if (odefin(&cursor->cda, (sword)col + 1, cursor->columns[col].buf, 
				   cursor->columns[col].dsize + 1, type, -1, &cursor->columns[col].indp,
				   (text *) 0, -1, -1, &cursor->columns[col].col_retlen, 
				   &cursor->columns[col].col_retcode)) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", ora_error(&cursor->cda));
			return -1;
		}
	}
	return 1;
}
/* }}} */

/* {{{ ora_set_param_values
 */
int ora_set_param_values(oraCursor *cursor, int isout TSRMLS_DC)
{
	char *paramname;
	oraParam *param;
	pval **pdata;
	int i, len, plen;

	zend_hash_internal_pointer_reset(cursor->params);

	if(zend_hash_num_elements(cursor->params) != cursor->nparams){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Mismatch in number of parameters");
		return 0;
	}

	for(i = 0; i < cursor->nparams; i++, zend_hash_move_forward(cursor->params)){
		if(zend_hash_get_current_key(cursor->params, &paramname, NULL, 0) != HASH_KEY_IS_STRING){
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't get parameter name");
			return 0;
		}

		if(zend_hash_get_current_data(cursor->params, (void **)&param) == FAILURE){
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't get parameter data");
			return 0;
		}

		if(isout){
			SET_VAR_STRINGL(paramname, estrdup(param->progv), strlen(param->progv));
			continue;
		}
		
		/* doing the in-loop */

		if (zend_hash_find(&EG(symbol_table), paramname, strlen(paramname) + 1, (void **)&pdata) == FAILURE){
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't find variable for parameter");
			return 0;
		}

		convert_to_string(*pdata);
		plen = Z_STRLEN_PP(pdata);

 		if (param->progvl <= plen){
  			php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Input value will be truncated");
  		}

		len = min(param->progvl - 1, plen);

		strncpy(param->progv, Z_STRVAL_PP(pdata), len);
		param->progv[len] = '\0';
	}

	return 1;
}
/* }}} */

#endif							/* HAVE_ORACLE */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */
