The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
   $Id: InterBase.xs,v 1.55 2006/10/25 16:13:18 edpratomo Exp $

   Copyright (c) 1999-2006  Edwin Pratomo
   Portions Copyright (c) 2001-2005  Daniel Ritz

   You may distribute under the terms of either the GNU General Public
   License or the Artistic License, as specified in the Perl README file,
   with the exception that it cannot be placed on a CD-ROM or similar media
   for commercial distribution without the prior approval of the author.

*/

#include "InterBase.h"

DBISTATE_DECLARE;

static int _cancel_callback(SV *dbh, IB_EVENT *ev)
{
    ISC_STATUS status[ISC_STATUS_LENGTH];
    D_imp_dbh(dbh);

    int ret = 0;
    if (ev->exec_cb) 
        croak("Can't be called from inside a callback");
    if (ev->perl_cb) {
        ev->state = INACTIVE;
        SvREFCNT_dec(ev->perl_cb);
        ev->perl_cb = (SV*)NULL;
        isc_cancel_events(status, &(imp_dbh->db), &(ev->id));
        if (ib_error_check(dbh, status))
            ret = 0;
        else
            ret = 1;
    } else 
        croak("No callback found for this event handle. Have you called ib_register_callback?");
    return ret;
}

static int _call_perlsub(IB_EVENT ISC_FAR *ev, short length, 
#if defined(INCLUDE_TYPES_PUB_H)
const ISC_UCHAR *updated
#else
char ISC_FAR *updated
#endif
)
{
    int retval = 1;
#if defined(USE_THREADS) || defined(USE_ITHREADS) || defined(MULTIPLICITY)
    /* save context, set context from dbh */
    void *context = PERL_GET_CONTEXT;
    PERL_SET_CONTEXT(ev->dbh->context);
    {
#else
    void *context = PERL_GET_CONTEXT;
    PerlInterpreter *cb_perl = perl_alloc();
    PERL_SET_CONTEXT(cb_perl);
    {
#endif
        dSP;
        int i, count;
        SV **svp;
        HV *posted_events = newHV();
        ISC_ULONG ecount[15];
#if defined(INCLUDE_TYPES_PUB_H)
        ISC_UCHAR *result = ev->result_buffer;
#else
        char ISC_FAR *result = ev->result_buffer;
#endif

        while (length--)
            *result++ = *updated++;
        isc_event_counts(ecount, ev->epb_length, ev->event_buffer,
                         ev->result_buffer);
        for (i = 0; i < ev->num; i++) 
        {
            if (ecount[i])
            {
                svp = hv_store(posted_events, *(ev->names + i), strlen(*(ev->names + i)),
                               newSViv(ecount[i]), 0);
                if (svp == NULL)
                    croak("Bad: key '%s' not stored", *(ev->names + i));
            }
        }
        ENTER;
        SAVETMPS;
        PUSHMARK(SP);
        XPUSHs(sv_2mortal(newRV_noinc((SV*)posted_events)));
        PUTBACK;
        count = perl_call_sv(ev->perl_cb, G_SCALAR);
        SPAGAIN;
        if (count > 0) 
            retval = POPi;
        PUTBACK;
        FREETMPS;
        LEAVE;
#if defined(USE_THREADS) || defined(USE_ITHREADS) || defined(MULTIPLICITY)
    }

    /* restore old context*/
    PERL_SET_CONTEXT(context);
#else
    }
    PERL_SET_CONTEXT(context);
    perl_free(cb_perl);
#endif
    return retval;
}

/* callback function for events, called by InterBase */
/* static isc_callback _async_callback(IB_EVENT ISC_FAR *ev, short length, char ISC_FAR *updated) */
static ISC_EVENT_CALLBACK _async_callback(IB_EVENT ISC_FAR *ev, 
#if defined(INCLUDE_TYPES_PUB_H)
ISC_USHORT length, const ISC_UCHAR *updated
#else
short length, char ISC_FAR *updated
#endif
)
{
    ISC_STATUS status[ISC_STATUS_LENGTH];

    switch (ev->state) {
    case INACTIVE:
        break;
    case ACTIVE:
        ev->exec_cb = 1;
        if (_call_perlsub(ev, length, updated) == 0) {
            ev->state = INACTIVE;
            ev->exec_cb = 0;
            break;
        }
        ev->exec_cb = 0;
        isc_que_events(
            status,
            &(ev->dbh->db),
            &(ev->id),
            ev->epb_length,
            ev->event_buffer,
            (ISC_EVENT_CALLBACK)_async_callback,
            ev
        );
    }
    return (0);
}


MODULE = DBD::InterBase     PACKAGE = DBD::InterBase

INCLUDE: InterBase.xsi

MODULE = DBD::InterBase     PACKAGE = DBD::InterBase::db

void
_do(dbh, statement, attr=Nullsv)
    SV *        dbh
    SV *    statement
    SV *        attr
  PROTOTYPE: $$;$@
  CODE:
{
    D_imp_dbh(dbh);
    ISC_STATUS status[ISC_STATUS_LENGTH]; /* isc api status vector    */
    STRLEN     slen;
    int        retval;
    char       *sbuf = SvPV(statement, slen);

    DBI_TRACE_imp_xxh(imp_dbh, 1, (DBIc_LOGPIO(imp_dbh), "db::_do\n" "Executing : %s\n", sbuf));

    /* we need an open transaction */
    if (!imp_dbh->tr)
    {
        DBI_TRACE_imp_xxh(imp_dbh, 1, (DBIc_LOGPIO(imp_dbh), "starting new transaction..\n"));

        if (!ib_start_transaction(dbh, imp_dbh))
        {
            retval = -2;
            XST_mUNDEF(0);      /* <= -2 means error        */
            return;
        }

        DBI_TRACE_imp_xxh(imp_dbh, 1, (DBIc_LOGPIO(imp_dbh), "new transaction started.\n"));
    }

    /* only execute_immediate statment if NOT in soft commit mode */
    if (!(imp_dbh->soft_commit))
    {
        isc_dsql_execute_immediate(status, &(imp_dbh->db), &(imp_dbh->tr), 0,
                                   sbuf, imp_dbh->sqldialect, NULL);

        if (ib_error_check(dbh, status))
            retval = -2;
        else
            retval = -1 ;
    }
    /* for AutoCommit: prepare/getinfo/exec statment (count DDL statements)
     * an easier and also working way would be to do that from perl with
     *   $sth = $dbh->prepare(...); $sth->execute();
     * but this way is much faster (no bind params, etc.)
     */
    else
    {
        isc_stmt_handle stmt = 0L;        /* temp statment handle */
        static char     stmt_info[] = { isc_info_sql_stmt_type };
        char            info_buffer[20];  /* statment info buffer */

        retval = -2;

        do
        {
            /* init statement handle */
            if (isc_dsql_alloc_statement2(status, &(imp_dbh->db), &stmt))
                break;


            /* prepare statement */
            isc_dsql_prepare(status, &(imp_dbh->tr), &stmt, 0, sbuf,
                             imp_dbh->sqldialect, NULL);
            if (ib_error_check(dbh, status))
                break;


            /* get statement type */
            if (!isc_dsql_sql_info(status, &stmt, sizeof(stmt_info), stmt_info,
                              sizeof(info_buffer), info_buffer))
            {
                /* need to count DDL statments */
                short l = (short) isc_vax_integer((char *) info_buffer + 1, 2);
                if (isc_vax_integer((char *) info_buffer + 3, l) == isc_info_sql_stmt_ddl)
                    imp_dbh->sth_ddl++;
            }
            else
                break;


            /* exec the statement */
            isc_dsql_execute(status, &(imp_dbh->tr), &stmt, imp_dbh->sqldialect, NULL);
            if (!ib_error_check(dbh, status))
                retval = -1;

        } while (0);

        /* close statement */
        if (stmt)
           isc_dsql_free_statement(status, &stmt, DSQL_drop);

        if (retval != -2) retval = -1;
    }

    /* for AutoCommit: commit */
    if (DBIc_has(imp_dbh, DBIcf_AutoCommit))
    {
        if (!ib_commit_transaction(dbh, imp_dbh))
            retval = -2;
    }

    if (retval < -1)
        XST_mUNDEF(0);
    else
        XST_mIV(0, retval); /* typically 1, rowcount or -1  */
}

void
_ping(dbh)
    SV *    dbh
    CODE:
{
    int ret;
    ret = dbd_db_ping(dbh);
    if (ret == 0)
        XST_mUNDEF(0);
    else
        XST_mIV(0, ret);
}

void
ib_set_tx_param(dbh, ...)
    SV *dbh
    ALIAS:
    set_tx_param = 1
    PREINIT:
    STRLEN len;
    char   *tx_key, *tx_val, *tpb, *tmp_tpb;
    int    i, rc = 0;
    int    tpb_len;
    char   am_set = 0, il_set = 0, ls_set = 0;
    I32    j;
    AV     *av;
    HV     *hv;
    SV     *sv, *sv_value;
    HE     *he;

    CODE:
{
    D_imp_dbh(dbh);
    PERL_UNUSED_VAR(ix); /* -Wall */
    
    /* if no params or first parameter = 0 or undef -> reset TPB to NULL */
    if (items < 3)
    {
        if ((items == 1) || !(SvTRUE(ST(1))))
        {
            tpb     = NULL;
            tmp_tpb = NULL;
            tpb_len = 0;
            goto do_set_tpb;
        }
    }

    /* we need to know the max. size of TBP, (buffer overflow problem) */
    /* mem usage: -access_mode:     max. 1 byte                        */
    /*            -isolation_level: max. 2 bytes                       */
    /*            -lock_resolution: max. 1 byte                        */
    /*            -reserving:       max. 4 bytes + strlen(tablename)   */
    tpb_len = 5; /* 4 + 1 for tpb_version                              */

    /* we need to add the length of each table name + 4 bytes */
    for (i = 1; i < items-1; i += 2)
    {
        sv_value = ST(i + 1);
        if (strEQ(SvPV_nolen(ST(i)), "-reserving"))
            if (SvROK(sv_value) && SvTYPE(SvRV(sv_value)) == SVt_PVHV)
            {
                hv = (HV *)SvRV(sv_value);
                hv_iterinit(hv);
                while ((he = hv_iternext(hv)))
                {
                    /* retrieve the size of table name(s) */
                    HePV(he, len);
                    tpb_len += len + 4;
                }
            }
    }

    /* alloc it */
    tmp_tpb = (char *)safemalloc(tpb_len * sizeof(char));

    if (tmp_tpb == NULL)
        croak("set_tx_param: Can't alloc memory");

    /* do set TPB values */
    tpb = tmp_tpb;
    *tpb++ = isc_tpb_version3;

    for (i = 1; i < items; i += 2)
    {
        tx_key   = SvPV_nolen(ST(i));
        sv_value = ST(i + 1);

        /* value specified? */
        if (i >= items - 1)
        {
            safefree(tmp_tpb);
            croak("You must specify parameter => value pairs, but theres no value for %s", tx_key);
        }

        /**********************************************************************/
        if (strEQ(tx_key, "-access_mode"))
        {
            if (am_set)
            {
                warn("-access_mode already set; ignoring second try!");
                continue;
            }

            tx_val = SvPV_nolen(sv_value);
            if (strEQ(tx_val, "read_write"))
                *tpb++ = isc_tpb_write;
            else if (strEQ(tx_val, "read_only"))
                *tpb++ = isc_tpb_read;
            else
            {
                safefree(tmp_tpb);
                croak("Unknown -access_mode value %s", tx_val);
            }

            am_set = 1; /* flag */
        }
        /**********************************************************************/
        else if (strEQ(tx_key, "-isolation_level"))
        {
            if (il_set)
            {
                warn("-isolation_level already set; ignoring second try!");
                continue;
            }

            if (SvROK(sv_value) && SvTYPE(SvRV(sv_value)) == SVt_PVAV)
            {
                av = (AV *)SvRV(sv_value);

                /* sanity check */
                for (j = 0; (j <= av_len(av)) && !rc; j++)
                {
                    sv = *av_fetch(av, j, FALSE);
                    if (strEQ(SvPV_nolen(sv), "read_committed"))
                    {
                        rc = 1;
                        *tpb++ = isc_tpb_read_committed;
                    }
                }

                if (!rc)
                {
                    safefree(tmp_tpb);
                    croak("Invalid -isolation_level value");
                }

                for (j = 0; j <= av_len(av); j++)
                {
                    tx_val = SvPV_nolen(*(av_fetch(av, j, FALSE)));
                    if (strEQ(tx_val, "record_version"))
                    {
                        *tpb++ = isc_tpb_rec_version;
                        break;
                    }
                    else if (strEQ(tx_val, "no_record_version"))
                    {
                        *tpb++ = isc_tpb_no_rec_version;
                        break;
                    }
                    else if (!strEQ(tx_val, "read_committed"))
                    {
                        safefree(tmp_tpb);
                        croak("Unknown -isolation_level value %s", tx_val);
                    }
                }
            }
            else
            {
                tx_val = SvPV_nolen(sv_value);
                if (strEQ(tx_val, "read_committed"))
                    *tpb++ = isc_tpb_read_committed;
                else if (strEQ(tx_val, "snapshot"))
                    *tpb++ = isc_tpb_concurrency;
                else if (strEQ(tx_val, "snapshot_table_stability"))
                    *tpb++ = isc_tpb_consistency;
                else
                {
                    safefree(tmp_tpb);
                    croak("Unknown -isolation_level value %s", tx_val);
                }
            }

            il_set = 1; /* flag */
        }
        /**********************************************************************/
        else if (strEQ(tx_key, "-lock_resolution"))
        {
            if (ls_set)
            {
                warn("-lock_resolution already set; ignoring second try!");
                continue;
            }

            if (SvROK(sv_value) && SvTYPE(SvRV(sv_value)) == SVt_PVHV) {
#if defined(FB_API_VER) && FB_API_VER >= 20
                hv = (HV *)SvRV(sv_value);
                if (hv_exists(hv, "wait", 4)) {
                    *tpb++ = isc_tpb_wait;
                    sv = *hv_fetch(hv, "wait", 4, FALSE);
                    if (SvIOK(sv)) {
                        IV lock_timeout = SvIV(sv);
                        if (lock_timeout < 0) {
                            do_error(dbh, 2, "Wait timeout value must be positive integer");
                            XSRETURN_UNDEF;
                        } else if (lock_timeout > 0) {
                            *tpb++ = isc_tpb_lock_timeout;
                            *tpb++ = sizeof(ISC_LONG);      /* length = 4 bytes */
                            *(ISC_LONG*)tpb = lock_timeout; /* infinite timeout */
                            tpb += sizeof(ISC_LONG);
                        }
                    } else {
                        do_error(dbh, 2, "Wait timeout value must be positive integer");
                        XSRETURN_UNDEF;
                    }
                } else {
                    do_error(dbh, 2, "The only valid key is 'wait'");
                    XSRETURN_UNDEF;
                }
#else
                do_error(dbh, 2, "Hashref unsupported. Must be compiled with Firebird 2.0 client library");
                XSRETURN_UNDEF;
#endif
            } else {
                tx_val = SvPV_nolen(sv_value);
                if (strEQ(tx_val, "wait"))
                    *tpb++ = isc_tpb_wait;
                else if (strEQ(tx_val, "no_wait"))
                    *tpb++ = isc_tpb_nowait;
                else
                {
                    safefree(tmp_tpb);
                    croak("Unknown transaction parameter %s", tx_val);
                }
            }
            ls_set = 1; /* flag */
        }
        /**********************************************************************/
        else if (strEQ(tx_key, "-reserving"))
        {
            if (SvROK(sv_value) && SvTYPE(SvRV(sv_value)) == SVt_PVHV)
            {
                char *table_name;
                HV *table_opts;
                hv = (HV *)SvRV(sv_value);
                hv_iterinit(hv);
                while ((he = hv_iternext(hv)))
                {
                    /* check val type */
                    if (SvROK(HeVAL(he)) && SvTYPE(SvRV(HeVAL(he))) == SVt_PVHV)
                    {
                        table_opts = (HV*)SvRV(HeVAL(he));

                        if (hv_exists(table_opts, "access", 6))
                        {
                            /* access is optional */
                            sv = *hv_fetch(table_opts, "access", 6, FALSE);
                            if (strnEQ(SvPV_nolen(sv), "shared", 6))
                                *tpb++ = isc_tpb_shared;
                            else if (strnEQ(SvPV_nolen(sv), "protected", 9))
                                *tpb++ = isc_tpb_protected;
                            else
                            {
                                safefree(tmp_tpb);
                                croak("Invalid -reserving access value");
                            }
                        }

                        if (hv_exists(table_opts, "lock", 4))
                        {
                            /* lock is required */
                            sv = *hv_fetch(table_opts, "lock", 4, FALSE);
                            if (strnEQ(SvPV_nolen(sv), "read", 4))
                               *tpb++ = isc_tpb_lock_read;
                            else if (strnEQ(SvPV_nolen(sv), "write", 5))
                               *tpb++ = isc_tpb_lock_write;
                            else
                            {
                              safefree(tmp_tpb);
                              croak("Invalid -reserving lock value");
                            }
                        }
                        else /* lock */
                        {
                            safefree(tmp_tpb);
                            croak("Lock value is required in -reserving");
                        }

                        /* add the table name to TPB */
                        table_name = HePV(he, len);
                        *tpb++ = len + 1;
                        {
                            unsigned int k;
                            for (k = 0; k < len; k++)
                                *tpb++ = toupper(*table_name++);
                        }
                        *tpb++ = 0;
                    } /* end hashref check*/
                    else
                    {
                        safefree(tmp_tpb);
                        croak("Reservation for a given table must be hashref.");
                    }
                } /* end of while() */
            }
            else
            {
                safefree(tmp_tpb);
                croak("Invalid -reserving value. Must be hashref.");
            }
        } /* end table reservation */
        else
        {
            safefree(tmp_tpb);
            croak("Unknown transaction parameter %s", tx_key);
        }
    }

    /* an ugly label... */
    do_set_tpb:

    safefree(imp_dbh->tpb_buffer);
    imp_dbh->tpb_buffer = tmp_tpb;
    imp_dbh->tpb_length = tpb - imp_dbh->tpb_buffer;

    /* for AutoCommit: commit current transaction */
    if (DBIc_has(imp_dbh, DBIcf_AutoCommit))
    {
        imp_dbh->sth_ddl++;
        ib_commit_transaction(dbh, imp_dbh);
    }
}
#*******************************************************************************

# only for use within database_info!
#define DB_INFOBUF(name, len) \
if (strEQ(item, #name)) { \
    *p++ = (char) isc_info_##name; \
    res_len += len + 3; \
    item_buf_len++; \
    continue; \
}

#define DB_RESBUF_CASEHDR(name) \
case isc_info_##name:\
    keyname = #name;


HV *
ib_database_info(dbh, ...)
    SV *dbh
    PREINIT:
    unsigned int i, count;
    char  item_buf[30], *p, *old_p;
    char *res_buf;
    short item_buf_len, res_len;
    AV    *av;
    ISC_STATUS status[ISC_STATUS_LENGTH];
    CODE:
{
    D_imp_dbh(dbh);

    /* process input params, count max. result buffer length */
    p = item_buf;
    res_len = 0;
    item_buf_len = 0;

    /* array or array ref? */
    if (items == 2 && SvROK(ST(1)) && SvTYPE(SvRV(ST(1))) == SVt_PVAV)
    {
        av    = (AV *)SvRV(ST(1));
        count = av_len(av) + 1;
    }
    else
    {
        av    = NULL;
        count = items;
    }

    /* loop thru all elements */
    for (i = 0; i < count; i++)
    {
        char *item;

        /* fetch from array or array ref? */
        if (av)
            item = SvPV_nolen(*av_fetch(av, i, FALSE));
        else
            item = SvPV_nolen(ST(i + 1));

        /* database characteristics */
        DB_INFOBUF(allocation,        4);
        DB_INFOBUF(base_level,        2);
        DB_INFOBUF(db_id,           513);
        DB_INFOBUF(implementation,    3);
        DB_INFOBUF(no_reserve,        1);
#ifdef IB_API_V6
        DB_INFOBUF(db_read_only,      1);
#endif
        DB_INFOBUF(ods_minor_version, 1);
        DB_INFOBUF(ods_version,       1);
        DB_INFOBUF(page_size,         4);
        DB_INFOBUF(version,         257);
#ifdef IB_API_V6
        DB_INFOBUF(db_sql_dialect,    1);
#endif

        /* environmental characteristics */
        DB_INFOBUF(current_memory,    4);
        DB_INFOBUF(forced_writes,     1);
        DB_INFOBUF(max_memory,        4);
        DB_INFOBUF(num_buffers,       4);
        DB_INFOBUF(sweep_interval,    4);
        DB_INFOBUF(user_names,     1024); /* can be more, can be less */

        /* performance statistics */
        DB_INFOBUF(fetches, 4);
        DB_INFOBUF(marks,   4);
        DB_INFOBUF(reads,   4);
        DB_INFOBUF(writes,  4);
#if defined(FB_API_VER) && FB_API_VER >= 20
        /* FB 2.0 */
        DB_INFOBUF(active_tran_count, 4);
        DB_INFOBUF(creation_date,     sizeof(ISC_TIMESTAMP)); /* 2 x 4 bytes */
#endif
        /* database operation counts */
        /* XXX - not implemented (complicated: returns a descriptor for _each_
           table...how to fetch / store this??) but do we really need these? */
    }

    /* the end marker */
    *p++ = isc_info_end;
    item_buf_len++;

    /* allocate the result buffer */
    res_len += 256; /* add some safety...just in case */
    res_buf = (char *) safemalloc(res_len);

    /* call the function */
    isc_database_info(status, &(imp_dbh->db), item_buf_len, item_buf,
                      res_len, res_buf);

    if (ib_error_check(dbh, status))
    {
        safefree(res_buf);
        XSRETURN_UNDEF; // croak("isc_database_info failed!");
    }

    /* create a hash if function passed */
    RETVAL = newHV();
    if (!RETVAL)
    {
        safefree(res_buf);
        // croak("unable to allocate hash return value");
        do_error(dbh, 2, "unable to allocate hash return value");
        XSRETURN_UNDEF;
    }

    /* fill the hash with key/value pairs */
    for (p = res_buf; *p != isc_info_end; )
    {
        char *keyname;
        char item   = *p++;
        int  length = isc_vax_integer (p, 2);
        p += 2;
        old_p = p;

        switch (item)
        {
            /******************************************************************/
            /* database characteristics */
            DB_RESBUF_CASEHDR(allocation)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(base_level)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(++p, 1)), 0);
                break;

            DB_RESBUF_CASEHDR(db_id)
            {
                HV *reshv = newHV();
                ISC_LONG slen;

                hv_store(reshv, "connection", 10,
                         (isc_vax_integer(p++, 1) == 2)?
                             newSVpv("local", 0):
                             newSVpv("remote", 0),
                         0);

                slen = isc_vax_integer(p++, 1);
                hv_store(reshv, "database", 8, newSVpvn(p, slen), 0);
                p += slen;

                slen = isc_vax_integer(p++, 1);
                hv_store(reshv, "site", 8, newSVpvn(p, slen), 0);

                hv_store(RETVAL, keyname, strlen(keyname),
                         newRV_noinc((SV *) reshv), 0);
                break;
            }

            DB_RESBUF_CASEHDR(implementation)
            {
                HV *reshv = newHV();

                hv_store(reshv, "implementation", 14,
                         newSViv(isc_vax_integer(++p, 1)), 0);

                hv_store(reshv, "class", 5,
                         newSViv(isc_vax_integer(++p, 1)), 0);

                hv_store(RETVAL, keyname, strlen(keyname),
                         newRV_noinc((SV *) reshv), 0);

                break;
            }

            DB_RESBUF_CASEHDR(no_reserve)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;
#ifdef IB_API_V6
            DB_RESBUF_CASEHDR(db_read_only)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;
#endif
            DB_RESBUF_CASEHDR(ods_minor_version)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(ods_version)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(page_size)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(version)
            {
                ISC_LONG slen;
                slen = isc_vax_integer(++p, 1);
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSVpvn(++p, slen), 0);
                break;
            }
#ifdef isc_dpb_sql_dialect
            DB_RESBUF_CASEHDR(db_sql_dialect)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;
#endif

            /******************************************************************/
            /* environmental characteristics */
            DB_RESBUF_CASEHDR(current_memory)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(forced_writes)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(max_memory)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(num_buffers)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(sweep_interval)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(user_names)
            {
                AV *avres;
                SV **svp;
                ISC_LONG slen;

                /* array already existing? no -> create */
                if (!hv_exists(RETVAL, "user_names", 10))
                {
                    avres = newAV();
                    hv_store(RETVAL, "user_names", 10,
                             newRV_noinc((SV *) avres), 0);
                }
                else
                {
                    svp = hv_fetch(RETVAL, "user_names", 10, 0);
                    if (!svp || !SvROK(*svp))
                    {
                        safefree(res_buf);
                        croak("Error fetching hash value");
                    }

                    avres = (AV *) SvRV(*svp);
                }

                /* add value to the array */
                slen = isc_vax_integer(p++, 1);
                av_push(avres, newSVpvn(p, slen));

                break;
            }

            /******************************************************************/
            /* performance statistics */
            DB_RESBUF_CASEHDR(fetches)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(marks)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(reads)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(writes)
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSViv(isc_vax_integer(p, (short) length)), 0);
                break;
#if defined(FB_API_VER) && FB_API_VER >= 20
            /* FB 2.0 */
            DB_RESBUF_CASEHDR(active_tran_count)
                hv_store(RETVAL, keyname, strlen(keyname),
                        newSViv(isc_vax_integer(p, (short) length)), 0);
                break;

            DB_RESBUF_CASEHDR(creation_date)
            {
                struct tm times;
                ISC_TIMESTAMP cdatetime;
                char tbuf[100];
                memset(tbuf, 0, sizeof(tbuf));
                cdatetime.timestamp_date = isc_vax_integer(p, sizeof(ISC_DATE));
                cdatetime.timestamp_time = isc_vax_integer(p + sizeof(ISC_DATE), sizeof(ISC_TIME));
                isc_decode_timestamp(&cdatetime, &times);
                strftime(tbuf, sizeof(tbuf), "%c", &times);
                hv_store(RETVAL, keyname, strlen(keyname),
                         newSVpvn(tbuf, strlen(tbuf)), 0);
                break;
            }
#endif

            default:
                break;
        }

        p = old_p + length;
    }

    /* don't leak */
    safefree(res_buf);
}
    OUTPUT:
    RETVAL

    CLEANUP:
    SvREFCNT_dec(RETVAL);

#undef DB_INFOBUF
#undef DB_RESBUF_CASEHDR

#*******************************************************************************

IB_EVENT *
ib_init_event(dbh, ...)
    SV *dbh
    PREINIT:
    char *CLASS = "DBD::InterBase::Event";
    int i;
    D_imp_dbh(dbh);
    CODE:
{
    unsigned short cnt = items - 1;

    DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "Entering init_event(), %d items..\n", cnt));

    if (cnt > 0)
    {
        /* check for max number of events in a single call to event block allocation */
        if (cnt > MAX_EVENTS)
            croak("Max number of events exceeded.");

        RETVAL = (IB_EVENT *) safemalloc(sizeof(IB_EVENT));
        if (RETVAL == NULL)
            croak("Unable to allocate memory");

        /* init members */
        RETVAL->dbh           = imp_dbh;
        RETVAL->event_buffer  = NULL;
        RETVAL->result_buffer = NULL;
        RETVAL->id            = 0;
        RETVAL->num           = cnt;
        RETVAL->perl_cb       = NULL;
        RETVAL->state         = INACTIVE;
        RETVAL->exec_cb       = 0;
        
        RETVAL->names = (char **) safemalloc(sizeof(char*) * MAX_EVENTS);
        if (RETVAL->names == NULL)
            croak("Unable to allocate memory");

        for (i = 0; i < MAX_EVENTS; i++)
        {
            if (i < cnt) {
                /* dangerous! 
                *(RETVAL->names + i) = SvPV_nolen(ST(i + 1));
                */
                RETVAL->names[i] = (char*) safemalloc(sizeof(char) * (SvCUR(ST(i + 1)) + 1));
                if (RETVAL->names[i] == NULL) 
                    croak("Unable to allocate memory");
                strcpy(RETVAL->names[i], SvPV_nolen(ST(i + 1)));
            }
            else
                *(RETVAL->names + i) = NULL;
        }

        RETVAL->epb_length = (short) isc_event_block(
            &(RETVAL->event_buffer),
            &(RETVAL->result_buffer),
            cnt,
            *(RETVAL->names +  0),
            *(RETVAL->names +  1),
            *(RETVAL->names +  2),
            *(RETVAL->names +  3),
            *(RETVAL->names +  4),
            *(RETVAL->names +  5),
            *(RETVAL->names +  6),
            *(RETVAL->names +  7),
            *(RETVAL->names +  8),
            *(RETVAL->names +  9),
            *(RETVAL->names + 10),
            *(RETVAL->names + 11),
            *(RETVAL->names + 12),
            *(RETVAL->names + 13),
            *(RETVAL->names + 14));
    }
    else
        croak("Names of the events in interest are not specified");
    {
        ISC_STATUS status[ISC_STATUS_LENGTH];
		ISC_ULONG ecount[15];
        isc_wait_for_event(status, &(imp_dbh->db), RETVAL->epb_length, RETVAL->event_buffer,
                       RETVAL->result_buffer);
        if (ib_error_check(dbh, status))
            XSRETURN_UNDEF; //croak("error in isc_wait_for_event()");
        isc_event_counts(ecount, RETVAL->epb_length, RETVAL->event_buffer,
                       RETVAL->result_buffer);
    }
    DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "Leaving init_event()\n"));
}
    OUTPUT:
    RETVAL


int
ib_register_callback(dbh, ev, perl_cb)
    SV *dbh
    IB_EVENT *ev
    SV *perl_cb
    PREINIT:
    ISC_STATUS status[ISC_STATUS_LENGTH];
    D_imp_dbh(dbh);
    CODE:
{
    DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "Entering register_callback()..\n"));

    /* save the perl callback function */
    if (ev->perl_cb == (SV*)NULL) 
        ev->perl_cb = newSVsv(perl_cb);
    else {
        if (_cancel_callback(dbh, ev))
            SvSetSV(ev->perl_cb, perl_cb);
        else
            XSRETURN_UNDEF;
    }
    /* set up the events */
    isc_que_events(
        status,
        &(imp_dbh->db),
        &(ev->id),
        ev->epb_length,
        ev->event_buffer,
        (ISC_EVENT_CALLBACK)_async_callback,
        ev);
    if (ib_error_check(dbh, status))
        XSRETURN_UNDEF;
    else
        RETVAL = 1;
    ev->state = ACTIVE;
}
    OUTPUT:
    RETVAL


int
ib_cancel_callback(dbh, ev)
    SV *dbh
    IB_EVENT *ev
    PREINIT:
    CODE:
    RETVAL = _cancel_callback(dbh, ev);
    OUTPUT:
    RETVAL


HV*
ib_wait_event(dbh, ev)
    SV *dbh
    IB_EVENT *ev
    PREINIT:
    int i;
    SV **svp;
    ISC_STATUS status[ISC_STATUS_LENGTH];
    D_imp_dbh(dbh);
    CODE:
{
    isc_wait_for_event(status, &(imp_dbh->db), ev->epb_length, ev->event_buffer,
                       ev->result_buffer);
    if (ib_error_check(dbh, status))
    {
        do_error(dbh, 2, "ib_wait_event() error");
        XSRETURN_UNDEF;
    }
    else
    {
	ISC_ULONG ecount[15];
        isc_event_counts(ecount, ev->epb_length, ev->event_buffer,
                         ev->result_buffer);
        RETVAL = newHV();
        for (i = 0; i < ev->num; i++) 
        {
            if (ecount[i])
            {
                DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "Event %s caught %ld times.\n", *(ev->names + i), ecount[i]));
                svp = hv_store(RETVAL, *(ev->names + i), strlen(*(ev->names + i)),
                               newSViv(ecount[i]), 0);
                if (svp == NULL)
                    croak("Bad: key '%s' not stored", *(ev->names + i));
            }
        }
    }
}
    OUTPUT:
    RETVAL


MODULE = DBD::InterBase     PACKAGE = DBD::InterBase::Event
PROTOTYPES: DISABLE

void
DESTROY(evh)
    IB_EVENT *evh
    PREINIT:
    int i;
    ISC_STATUS status[ISC_STATUS_LENGTH];
    CODE:
{
    DBI_TRACE_imp_xxh(evh->dbh, 2, (DBIc_LOGPIO(evh->dbh), "Entering DBD::InterBase::Event::DESTROY..\n"));
#ifdef DBI_USE_THREADS
	if (PERL_GET_CONTEXT != evh->dbh->context) {
		DBI_TRACE_imp_xxh(evh->dbh, 2, (DBIc_LOGPIO(evh->dbh), 
			"DBD::InterBase::Event::DESTROY ignored because owned by thread %p not current thread %p\n",
			evh->dbh->context, (PerlInterpreter *)PERL_GET_CONTEXT)
		);
		XSRETURN(0);
	}
#endif
    for (i = 0; i < evh->num; i++)
        if (*(evh->names + i))
            safefree(*(evh->names + i));
    if (evh->names)
        safefree(evh->names);
    if (evh->perl_cb) {
        SvREFCNT_dec(evh->perl_cb);
        isc_cancel_events(status, &(evh->dbh->db), &(evh->id));
    }
    if (evh->event_buffer)
        isc_free(evh->event_buffer);
    if (evh->result_buffer)
        isc_free(evh->result_buffer);
}

MODULE = DBD::InterBase     PACKAGE = DBD::InterBase::st

char*
ib_plan(sth)
    SV *sth
    CODE:
{
    D_imp_sth(sth);
    ISC_STATUS  status[ISC_STATUS_LENGTH];
    char plan_info[1];
    char plan_buffer[PLAN_BUFFER_LEN];

    RETVAL = NULL;
    memset(plan_buffer, 0, PLAN_BUFFER_LEN);
    plan_info[0] = isc_info_sql_get_plan;

    if (isc_dsql_sql_info(status, &(imp_sth->stmt), sizeof(plan_info), plan_info,
                  sizeof(plan_buffer), plan_buffer)) 
    {
        if (ib_error_check(sth, status))
        {
            ib_cleanup_st_prepare(imp_sth);
            XSRETURN_UNDEF;
        }
    }
    if (plan_buffer[0] == isc_info_sql_get_plan) {
        short l = (short) isc_vax_integer((char *)plan_buffer + 1, 2);
        if ((RETVAL = (char*)safemalloc(sizeof(char) * (l + 2))) == NULL) {
            do_error(sth, 2, "Failed to allocate plan buffer");
            XSRETURN_UNDEF;
        }
        sprintf(RETVAL, "%.*s%s", l, plan_buffer + 3, "\n");
        //PerlIO_printf(PerlIO_stderr(), "Len: %d, orig len: %d\n", strlen(imp_sth->plan), l);
    }
}
    OUTPUT:
    RETVAL