#  $Id: Driver.xst,v 11.14 2003/08/22 21:23:39 timbo Exp $
#  Copyright (c) 1997-2002  Tim Bunce  Ireland
#  Copyright (c) 2002       Jonathan Leffler
#
#  You may distribute under the terms of either the GNU General Public
#  License or the Artistic License, as specified in the Perl README file.


#include "Driver_xst.h"


MODULE = DBD::~DRIVER~	PACKAGE = DBD::~DRIVER~

REQUIRE:    1.929
PROTOTYPES: DISABLE

BOOT:
    items = 0;  /* avoid 'unused variable' warning */
    DBISTATE_INIT;
    /* XXX this interface will change: */
    DBI_IMP_SIZE("DBD::~DRIVER~::dr::imp_data_size", sizeof(imp_drh_t));
    DBI_IMP_SIZE("DBD::~DRIVER~::db::imp_data_size", sizeof(imp_dbh_t));
    DBI_IMP_SIZE("DBD::~DRIVER~::st::imp_data_size", sizeof(imp_sth_t));
    dbd_init(DBIS);


# ------------------------------------------------------------
# driver level interface
# ------------------------------------------------------------
MODULE = DBD::~DRIVER~	PACKAGE = DBD::~DRIVER~::dr


#ifdef dbd_discon_all

# disconnect_all renamed and ALIAS'd to avoid length clash on VMS :-(
void
discon_all_(drh)
    SV *	drh
    ALIAS:
	disconnect_all = 1
    CODE:
    D_imp_drh(drh);
    if (0) ix = ix;	/* avoid unused variable warning */
    ST(0) = dbd_discon_all(drh, imp_drh) ? &sv_yes : &sv_no;

#endif /* dbd_discon_all */


#ifdef dbd_dr_data_sources

void
data_sources(drh, attr = Nullsv)
    SV *drh
    SV *attr
    PPCODE:
    {
	D_imp_drh(drh);
	AV *av = dbd_dr_data_sources(drh, imp_drh, attr);
	if (av) {
	    int i;
	    int n = AvFILL(av)+1;
	    EXTEND(sp, n);
	    for (i = 0; i < n; ++i) {
		PUSHs(AvARRAY(av)[i]);
	    }
	}
    }

#endif


# ------------------------------------------------------------
# database level interface
# ------------------------------------------------------------
MODULE = DBD::~DRIVER~    PACKAGE = DBD::~DRIVER~::db


void
_login(dbh, dbname, username, password, attribs=Nullsv)
    SV *	dbh
    char *	dbname
    SV *	username
    SV *	password
    SV *	attribs
    CODE:
    {
    STRLEN lna;
    D_imp_dbh(dbh);
    char *u = (SvOK(username)) ? SvPV(username,lna) : "";
    char *p = (SvOK(password)) ? SvPV(password,lna) : "";
#ifdef dbd_db_login6
    ST(0) = dbd_db_login6(dbh, imp_dbh, dbname, u, p, attribs) ? &sv_yes : &sv_no;
#else
    ST(0) = dbd_db_login( dbh, imp_dbh, dbname, u, p) ? &sv_yes : &sv_no;
#endif
    }


void
selectall_arrayref(...)
    PREINIT:
    SV *sth;
    SV **maxrows_svp;
    SV **tmp_svp;
    SV *attr = &PL_sv_undef;
    imp_sth_t *imp_sth;
    CODE:
    if (items > 2) {
	attr = ST(2);
	if (SvROK(attr) &&
		(DBD_ATTRIB_TRUE(attr,"Slice",5,tmp_svp) || DBD_ATTRIB_TRUE(attr,"Columns",7,tmp_svp))
	) {
	    /* fallback to perl implementation */
	    ST(0) = dbixst_bounce_method("DBD::~DRIVER~::db::SUPER::selectall_arrayref", items);
	    XSRETURN(1);
	}
    }
    /* --- prepare --- */
    if (SvROK(ST(1))) {
	sth = ST(1);
    }
    else {
	sth = dbixst_bounce_method("prepare", 3);
	if (!SvROK(sth))
	    XSRETURN_UNDEF;
    }
    imp_sth = (imp_sth_t*)(DBIh_COM(sth));
    /* --- bind_param --- */
    if (items > 3) {    /* need to bind params before execute */
        if (!dbdxst_bind_params(sth, imp_sth, items-2, ax+2) ) {
            XSRETURN_UNDEF;
        }
    }
    /* --- execute --- */
    DBIc_ROW_COUNT(imp_sth) = 0;
    if ( dbd_st_execute(sth, imp_sth) <= -2 ) { /* -2 == error */
        XSRETURN_UNDEF;
    }
    /* --- fetchall --- */
    maxrows_svp = DBD_ATTRIB_GET_SVP(attr, "MaxRows", 7);
    ST(0) = dbdxst_fetchall_arrayref(sth, &PL_sv_undef, (maxrows_svp) ? *maxrows_svp : &PL_sv_undef);


void
selectrow_arrayref(...)
    ALIAS:
	selectrow_array = 1
    PREINIT:
    imp_sth_t *imp_sth;
    SV *sth;
    AV *row_av;
    PPCODE:
    if (SvROK(ST(1))) {
	sth = ST(1);
    }
    else {
	/* --- prepare --- */
	sth = dbixst_bounce_method("prepare", 3);
	if (!SvROK(sth))
	    XSRETURN_UNDEF;
    }
    imp_sth = (imp_sth_t*)(DBIh_COM(sth));
    /* --- bind_param --- */
    if (items > 3) {	/* need to bind params before execute */
	if (!dbdxst_bind_params(sth, imp_sth, items-2, ax+2) ) {
	    XSRETURN_UNDEF;
	}
    }
    /* --- execute --- */
    DBIc_ROW_COUNT(imp_sth) = 0;
    if ( dbd_st_execute(sth, imp_sth) <= -2 ) {	/* -2 == error */
        XSRETURN_UNDEF;
    }
    /* --- fetchrow_arrayref --- */
    row_av = dbd_st_fetch(sth, imp_sth);
    if (!row_av) {
	if (GIMME == G_SCALAR)
	    PUSHs(&PL_sv_undef);
    }
    else if (ix == 1) { /* selectrow_array */
	int i;
	int num_fields = AvFILL(row_av)+1;
	if (GIMME == G_SCALAR)
	    num_fields = 1; /* return just first field */
	EXTEND(sp, num_fields);
	for(i=0; i < num_fields; ++i) {
	    PUSHs(AvARRAY(row_av)[i]);
	}
    }
    else {
	PUSHs( sv_2mortal(newRV((SV *)row_av)) );
    }
    /* --- finish --- */
#ifdef dbd_st_finish3
    dbd_st_finish3(sth, imp_sth, 0);
#else
    dbd_st_finish(sth, imp_sth);
#endif


#ifdef dbd_db_do4 /* deebeedee-deebee-doo, deebee-doobee-dah? */

void
do(dbh, statement, params = Nullsv)
    SV *	dbh
    char *	statement
    SV *	params
    CODE:
    {
    D_imp_dbh(dbh);
    IV retval;
    retval = dbd_db_do4(dbh, imp_dbh, statement, params);
    /* remember that dbd_db_do4 must return <= -2 for error	*/
    if (retval == 0)		/* ok with no rows affected	*/
	XST_mPV(0, "0E0");	/* (true but zero)		*/
    else if (retval < -1)	/* -1 == unknown number of rows	*/
	XST_mUNDEF(0);		/* <= -2 means error   		*/
    else
	XST_mIV(0, retval);	/* typically 1, rowcount or -1	*/
    }

#endif


#ifdef dbd_db_last_insert_id

void
last_insert_id(dbh, catalog, schema, table, field, attr=Nullsv)
    SV *	dbh
    SV *	catalog
    SV *	schema
    SV *	table
    SV *	field
    SV *	attr
    CODE:
    {
    D_imp_dbh(dbh);
    ST(0) = dbd_db_last_insert_id(dbh, imp_dbh, catalog, schema, table, field, attr);
    }

#endif


void
commit(dbh)
    SV *	dbh
    CODE:
    D_imp_dbh(dbh);
    if (DBIc_has(imp_dbh,DBIcf_AutoCommit) && DBIc_WARN(imp_dbh))
	warn("commit ineffective with AutoCommit enabled");
    ST(0) = dbd_db_commit(dbh, imp_dbh) ? &sv_yes : &sv_no;


void
rollback(dbh)
    SV *	dbh
    CODE:
    D_imp_dbh(dbh);
    if (DBIc_has(imp_dbh,DBIcf_AutoCommit) && DBIc_WARN(imp_dbh))
	warn("rollback ineffective with AutoCommit enabled");
    ST(0) = dbd_db_rollback(dbh, imp_dbh) ? &sv_yes : &sv_no;


void
disconnect(dbh)
    SV *	dbh
    CODE:
    D_imp_dbh(dbh);
    if ( !DBIc_ACTIVE(imp_dbh) ) {
	XSRETURN_YES;
    }
    /* pre-disconnect checks and tidy-ups */
    if (DBIc_CACHED_KIDS(imp_dbh)) {
	SvREFCNT_dec(DBIc_CACHED_KIDS(imp_dbh));      /* cast them to the winds	*/
	DBIc_CACHED_KIDS(imp_dbh) = Nullhv;
    }
    /* Check for disconnect() being called whilst refs to cursors	*/
    /* still exists. This possibly needs some more thought.		*/
    if (DBIc_ACTIVE_KIDS(imp_dbh) && DBIc_WARN(imp_dbh) && !dirty) {
	STRLEN lna;
	char *plural = (DBIc_ACTIVE_KIDS(imp_dbh)==1) ? "" : "s";
	warn("%s->disconnect invalidates %d active statement handle%s %s",
	    SvPV(dbh,lna), (int)DBIc_ACTIVE_KIDS(imp_dbh), plural,
	    "(either destroy statement handles or call finish on them before disconnecting)");
    }
    ST(0) = dbd_db_disconnect(dbh, imp_dbh) ? &sv_yes : &sv_no;
    DBIc_ACTIVE_off(imp_dbh);	/* ensure it's off, regardless */


void
STORE(dbh, keysv, valuesv)
    SV *	dbh
    SV *	keysv
    SV *	valuesv
    CODE:
    D_imp_dbh(dbh);
    if (SvGMAGICAL(valuesv))
	mg_get(valuesv);
    ST(0) = &sv_yes;
    if (!dbd_db_STORE_attrib(dbh, imp_dbh, keysv, valuesv))
	if (!DBIc_DBISTATE(imp_dbh)->set_attr(dbh, keysv, valuesv))
	    ST(0) = &sv_no;


void
FETCH(dbh, keysv)
    SV *	dbh
    SV *	keysv
    CODE:
    D_imp_dbh(dbh);
    SV *valuesv = dbd_db_FETCH_attrib(dbh, imp_dbh, keysv);
    if (!valuesv)
	valuesv = DBIc_DBISTATE(imp_dbh)->get_attr(dbh, keysv);
    ST(0) = valuesv;	/* dbd_db_FETCH_attrib did sv_2mortal	*/


void
DESTROY(dbh)
    SV *	dbh
    PPCODE:
    D_imp_dbh(dbh);
    ST(0) = &sv_yes;
    if (!DBIc_IMPSET(imp_dbh)) {	/* was never fully set up	*/
	STRLEN lna;
	if (DBIc_WARN(imp_dbh) && !dirty && DBIc_DBISTATE(imp_dbh)->debug >= 2)
	     PerlIO_printf(DBILOGFP,
		"         DESTROY for %s ignored - handle not initialised\n",
			SvPV(dbh,lna));
    }
    else {
	/* pre-disconnect checks and tidy-ups */
	if (DBIc_CACHED_KIDS(imp_dbh)) {
	    SvREFCNT_dec(DBIc_CACHED_KIDS(imp_dbh));  /* cast them to the winds	*/
	    DBIc_CACHED_KIDS(imp_dbh) = Nullhv;
	}
        if (DBIc_IADESTROY(imp_dbh)) {		  /* want's ineffective destroy	*/
            DBIc_ACTIVE_off(imp_dbh);
        }
	if (DBIc_ACTIVE(imp_dbh)) {
	    /* The application has not explicitly disconnected. That's bad.	*/
	    /* To ensure integrity we *must* issue a rollback. This will be	*/
	    /* harmless	if the application has issued a commit. If it hasn't	*/
	    /* then it'll ensure integrity. Consider a Ctrl-C killing perl	*/
	    /* between two statements that must be executed as a transaction.	*/
	    /* Perl will call DESTROY on the dbh and, if we don't rollback,	*/
	    /* the server may automatically commit! Bham! Corrupt database!	*/
	    if (!DBIc_has(imp_dbh,DBIcf_AutoCommit)) {
		if (DBIc_WARN(imp_dbh) && (!dirty || DBIc_DBISTATE(imp_dbh)->debug >= 3))
		     warn("Issuing rollback() for database handle being DESTROY'd without explicit disconnect()");
		dbd_db_rollback(dbh, imp_dbh);			/* ROLLBACK! */
	    }
	    dbd_db_disconnect(dbh, imp_dbh);
	    DBIc_ACTIVE_off(imp_dbh);	/* ensure it's off, regardless */
	}
	dbd_db_destroy(dbh, imp_dbh);
    }


#ifdef dbd_take_imp_data

void
take_imp_data(h)
    SV * h
    CODE:
    D_imp_xxh(h);
    ST(0) = (dbd_take_imp_data(h, imp_xxh, NULL))
	? dbixst_bounce_method("DBD::~DRIVER~::db::SUPER::take_imp_data", items)
	: &sv_undef;

#endif

#ifdef dbd_db_data_sources

void
data_sources(dbh, attr = Nullsv)
    SV *dbh
    SV *attr
    PPCODE:
    {
	D_imp_dbh(dbh);
	AV *av = dbd_db_data_sources(dbh, imp_dbh, attr);
	if (av) {
	    int i;
	    int n = AvFILL(av)+1;
	    EXTEND(sp, n);
	    for (i = 0; i < n; ++i) {
		PUSHs(AvARRAY(av)[i]);
	    }
	}
    }

#endif

# -- end of DBD::~DRIVER~::db

# ------------------------------------------------------------
# statement interface
# ------------------------------------------------------------
MODULE = DBD::~DRIVER~    PACKAGE = DBD::~DRIVER~::st


void
_prepare(sth, statement, attribs=Nullsv)
    SV *	sth
    char *	statement
    SV *	attribs
    CODE:
    {
    D_imp_sth(sth);
    DBD_ATTRIBS_CHECK("_prepare", sth, attribs);
    ST(0) = dbd_st_prepare(sth, imp_sth, statement, attribs) ? &sv_yes : &sv_no;
    }


#ifdef dbd_st_rows

void
rows(sth)
    SV *	sth
    CODE:
    D_imp_sth(sth);
    XST_mIV(0, dbd_st_rows(sth, imp_sth));

#endif /* dbd_st_rows */


void
bind_param(sth, param, value, attribs=Nullsv)
    SV *	sth
    SV *	param
    SV *	value
    SV *	attribs
    CODE:
    {
    IV sql_type = 0;
    D_imp_sth(sth);
    if (SvGMAGICAL(value))
	mg_get(value);
    if (attribs) {
	if (SvNIOK(attribs)) {
	    sql_type = SvIV(attribs);
	    attribs = Nullsv;
	}
	else {
	    SV **svp;
	    DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
	    /* XXX we should perhaps complain if TYPE is not SvNIOK */
	    DBD_ATTRIB_GET_IV(attribs, "TYPE",4, svp, sql_type);
	}
    }
    ST(0) = dbd_bind_ph(sth, imp_sth, param, value, sql_type, attribs, FALSE, 0)
		? &sv_yes : &sv_no;
    }


void
bind_param_inout(sth, param, value_ref, maxlen, attribs=Nullsv)
    SV *	sth
    SV *	param
    SV *	value_ref
    IV 		maxlen
    SV *	attribs
    CODE:
    {
    IV sql_type = 0;
    D_imp_sth(sth);
    SV *value;
    if (!SvROK(value_ref) || SvTYPE(SvRV(value_ref)) > SVt_PVMG)
	croak("bind_param_inout needs a reference to a scalar value");
    value = SvRV(value_ref);
    if (SvREADONLY(value))
	croak("Modification of a read-only value attempted");
    if (SvGMAGICAL(value))
	mg_get(value);
    if (attribs) {
	if (SvNIOK(attribs)) {
	    sql_type = SvIV(attribs);
	    attribs = Nullsv;
	}
	else {
	    SV **svp;
	    DBD_ATTRIBS_CHECK("bind_param", sth, attribs);
	    DBD_ATTRIB_GET_IV(attribs, "TYPE",4, svp, sql_type);
	}
    }
    ST(0) = dbd_bind_ph(sth, imp_sth, param, value, sql_type, attribs, TRUE, maxlen)
		? &sv_yes : &sv_no;
    }


void
execute(sth, ...)
    SV *	sth
    CODE:
    D_imp_sth(sth);
    int retval;
    if (items > 1) {	/* need to bind params */
	if (!dbdxst_bind_params(sth, imp_sth, items, ax) ) {
	    XSRETURN_UNDEF;
	}
    }
    /* XXX this code is duplicated in selectrow_arrayref above	*/
    if (DBIc_ROW_COUNT(imp_sth) > 0) /* reset for re-execute */
	DBIc_ROW_COUNT(imp_sth) = 0;
    retval = dbd_st_execute(sth, imp_sth);
    /* remember that dbd_st_execute must return <= -2 for error	*/
    if (retval == 0)		/* ok with no rows affected	*/
	XST_mPV(0, "0E0");	/* (true but zero)		*/
    else if (retval < -1)	/* -1 == unknown number of rows	*/
	XST_mUNDEF(0);		/* <= -2 means error   		*/
    else
	XST_mIV(0, retval);	/* typically 1, rowcount or -1	*/


#ifdef dbd_st_execute_for_fetch

void
execute_for_fetch(sth, fetch_tuple_sub, tuple_status = Nullsv)
    SV *	sth
    SV *	fetch_tuple_sub
    SV *	tuple_status
    CODE:
    {
    D_imp_sth(sth);
    ST(0) = dbd_st_execute_for_fetch(sth, imp_sth, fetch_tuple_sub, tuple_status);
    }

#endif



void
fetchrow_arrayref(sth)
    SV *	sth
    ALIAS:
	fetch = 1
    CODE:
    D_imp_sth(sth);
    AV *av;
    if (0) ix = ix;	/* avoid unused variable warning */
    av = dbd_st_fetch(sth, imp_sth);
    ST(0) = (av) ? sv_2mortal(newRV((SV *)av)) : &PL_sv_undef;


void
fetchrow_array(sth)
    SV *	sth
    ALIAS:
	fetchrow = 1
    PPCODE:
    D_imp_sth(sth);
    AV *av;
    av = dbd_st_fetch(sth, imp_sth);
    if (av) {
	int i;
	int num_fields = AvFILL(av)+1;
	EXTEND(sp, num_fields);
	for(i=0; i < num_fields; ++i) {
	    PUSHs(AvARRAY(av)[i]);
	}
	if (0) ix = ix;	/* avoid unused variable warning */
    }


void
fetchall_arrayref(sth, slice=&PL_sv_undef, batch_row_count=&PL_sv_undef)
    SV *	sth
    SV *	slice
    SV *	batch_row_count
    CODE:
    if (SvOK(slice)) {  /* fallback to perl implementation */
        ST(0) = dbixst_bounce_method("DBD::~DRIVER~::st::SUPER::fetchall_arrayref", 3);
    }
    else {
	ST(0) = dbdxst_fetchall_arrayref(sth, slice, batch_row_count);
    }


void
finish(sth)
    SV *	sth
    CODE:
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    if (!DBIc_ACTIVE(imp_sth)) {
	/* No active statement to finish	*/
	XSRETURN_YES;
    }
    if (!DBIc_ACTIVE(imp_dbh)) {
	/* Either an explicit disconnect() or global destruction	*/
	/* has disconnected us from the database. Finish is meaningless	*/
	DBIc_ACTIVE_off(imp_sth);
	XSRETURN_YES;
    }
#ifdef dbd_st_finish3
    ST(0) = dbd_st_finish3(sth, imp_sth, 0) ? &sv_yes : &sv_no;
#else
    ST(0) = dbd_st_finish(sth, imp_sth) ? &sv_yes : &sv_no;
#endif


void
blob_read(sth, field, offset, len, destrv=Nullsv, destoffset=0)
    SV *        sth
    int field
    long        offset
    long        len
    SV *        destrv
    long        destoffset
    CODE:
    {
    D_imp_sth(sth);
    if (!destrv)
        destrv = sv_2mortal(newRV(sv_2mortal(newSV(0))));
    if (dbd_st_blob_read(sth, imp_sth, field, offset, len, destrv, destoffset))
         ST(0) = SvRV(destrv);
    else ST(0) = &PL_sv_undef;
    }


void
STORE(sth, keysv, valuesv)
    SV *	sth
    SV *	keysv
    SV *	valuesv
    CODE:
    D_imp_sth(sth);
    if (SvGMAGICAL(valuesv))
	mg_get(valuesv);
    ST(0) = &sv_yes;
    if (!dbd_st_STORE_attrib(sth, imp_sth, keysv, valuesv))
	if (!DBIc_DBISTATE(imp_sth)->set_attr(sth, keysv, valuesv))
	    ST(0) = &sv_no;


# FETCH renamed and ALIAS'd to avoid case clash on VMS :-(
void
FETCH_attrib(sth, keysv)
    SV *	sth
    SV *	keysv
    ALIAS:
    FETCH = 1
    CODE:
    D_imp_sth(sth);
    SV *valuesv;
    if (0) ix = ix;	/* avoid unused variable warning */
    valuesv = dbd_st_FETCH_attrib(sth, imp_sth, keysv);
    if (!valuesv)
	valuesv = DBIc_DBISTATE(imp_sth)->get_attr(sth, keysv);
    ST(0) = valuesv;	/* dbd_st_FETCH_attrib did sv_2mortal	*/


void
DESTROY(sth)
    SV *	sth
    PPCODE:
    D_imp_sth(sth);
    ST(0) = &sv_yes;
    if (!DBIc_IMPSET(imp_sth)) {	/* was never fully set up	*/
	STRLEN lna;
	if (DBIc_WARN(imp_sth) && !dirty && DBIc_DBISTATE(imp_sth)->debug >= 2)
	     PerlIO_printf(DBIc_LOGPIO(imp_sth),
		"Statement handle %s DESTROY ignored - never set up\n",
		    SvPV(sth,lna));
    }
    else {
        if (DBIc_IADESTROY(imp_sth)) { /* want's ineffective destroy    */
            DBIc_ACTIVE_off(imp_sth);
        }
	if (DBIc_ACTIVE(imp_sth)) {
	    D_imp_dbh_from_sth;
	    if (!dirty && DBIc_ACTIVE(imp_dbh)) {
#ifdef dbd_st_finish3
		dbd_st_finish3(sth, imp_sth, 1);
#else
		dbd_st_finish(sth, imp_sth);
#endif
	    }
	    else {
		DBIc_ACTIVE_off(imp_sth);
	    }
	}
	dbd_st_destroy(sth, imp_sth);
    }

# end of ~DRIVER~.xst
