DBD-InterBase
view release on metacpan or search on metacpan
/*
$Id: dbdimp.c 395 2008-01-08 05:33:11Z edpratomo $
Copyright (c) 1999-2008 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;
#define IB_SQLtimeformat(xxh, format, sv) \
do { \
STRLEN len; \
char *frmt = NULL; \
char *buf = SvPV(sv, len); \
if (len < 2 || len > 30) break; \
if ((frmt = (char*) safemalloc(sizeof(char)* (len + 1))) == NULL) \
{ \
do_error(xxh, 2, "Can't alloc SQL time format"); \
return FALSE; \
} \
strcpy(frmt, buf); \
if (format) safefree(format); \
format = frmt; \
} while (0)
#define IB_alloc_sqlda(sqlda, n) \
do { \
short len = n; \
if (sqlda) \
{ \
safefree(sqlda); \
sqlda = NULL; \
} \
if (!(sqlda = (XSQLDA*) safemalloc(XSQLDA_LENGTH(len)))) \
do_error(sth, 2, "Fail to allocate XSQLDA"); \
memset(sqlda, 0, XSQLDA_LENGTH(len)); \
sqlda->sqln = len; \
sqlda->version = SQLDA_OK_VERSION; \
} while (0)
int create_cursor_name(SV *sth, imp_sth_t *imp_sth)
{
ISC_STATUS status[ISC_STATUS_LENGTH];
if ((imp_sth->cursor_name = (char *) safemalloc(22)) == NULL)
{
do_error(sth, IB_ALLOC_FAIL, "Cannot allocate cursor name.");
return FALSE;
}
sprintf(imp_sth->cursor_name, "perl%016.16x", imp_sth->stmt);
isc_dsql_set_cursor_name(status, &(imp_sth->stmt), imp_sth->cursor_name, 0);
if (ib_error_check(sth, status))
return FALSE;
else
return TRUE;
}
void dbd_init(dbistate_t *dbistate)
{
DBISTATE_INIT;
}
void ib_cleanup_st_prepare (imp_sth_t *imp_sth)
{
FREE_SETNULL(imp_sth->in_sqlda);
FREE_SETNULL(imp_sth->out_sqlda);
FREE_SETNULL(imp_sth->dateformat);
#ifdef IB_API_V6
FREE_SETNULL(imp_sth->timeformat);
FREE_SETNULL(imp_sth->timestampformat);
#endif
}
void ib_cleanup_st_execute (imp_sth_t *imp_sth)
{
if (imp_sth->in_sqlda)
{
int i;
XSQLVAR *var = imp_sth->in_sqlda->sqlvar;
for (i = 0; i < imp_sth->in_sqlda->sqln; i++, var++)
{
safefree(var->sqldata);
var->sqldata = NULL;
if (var->sqlind)
*(var->sqlind) = -1; /* isNULL */
}
}
}
/* lower level error handling */
void do_error(SV *h, int rc, char *what)
{
D_imp_xxh(h);
SV *errstr = DBIc_ERRSTR(imp_xxh);
sv_setiv(DBIc_ERR(imp_xxh), (IV)rc);
sv_setpv(errstr, what);
if (DBIc_TRACE_LEVEL(imp_xxh) >= 2)
PerlIO_printf(DBIc_LOGPIO(imp_xxh), "%s error %d recorded: %s\n",
what, rc, SvPV(errstr,na));
}
#define CALC_AVAILABLE(buff) sizeof(buff) - strlen(buff) - 1
/* higher level error handling, check and decode status */
if (ib_error_check(sth, status))
{
ib_cleanup_st_prepare(imp_sth);
return;
}
/* realloc in_sqlda and rebind if not enough XSQLVAR for bind params */
if (imp_sth->in_sqlda->sqld > imp_sth->in_sqlda->sqln)
{
IB_alloc_sqlda(imp_sth->in_sqlda, imp_sth->in_sqlda->sqld);
if (imp_sth->in_sqlda == NULL)
{
do_error(sth, 1, "Fail to reallocate in_slqda");
ib_cleanup_st_prepare(imp_sth);
return;
}
else
{
isc_dsql_describe_bind(status, &(imp_sth->stmt), 1, imp_sth->in_sqlda);
if (ib_error_check(sth, status))
{
ib_cleanup_st_prepare(imp_sth);
return;
}
}
}
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_preparse: describe_bind passed.\n"
"dbd_preparse: exit; in_sqlda: sqld: %d, sqln: %d.\n",
imp_sth->in_sqlda->sqld, imp_sth->in_sqlda->sqln));
DBIc_NUM_PARAMS(imp_sth) = imp_sth->in_sqlda->sqld;
}
int dbd_st_prepare(SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs)
{
D_imp_dbh_from_sth;
ISC_STATUS status[ISC_STATUS_LENGTH];
int i;
short dtype;
static char stmt_info[1];
char info_buffer[20], count_item;
XSQLVAR *var;
DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "Enter dbd_st_prepare\n"));
if (!DBIc_ACTIVE(imp_dbh))
{
do_error(sth, -1, "Database disconnected");
return FALSE;
}
/* init values */
count_item = 0;
imp_sth->count_item = 0;
imp_sth->fetched = -1;
imp_sth->in_sqlda = NULL;
imp_sth->out_sqlda = NULL;
imp_sth->cursor_name = NULL;
imp_sth->dateformat = NULL;
#ifdef IB_API_V6
imp_sth->timestampformat = NULL;
imp_sth->timeformat = NULL;
#endif
/* double linked list */
imp_sth->prev_sth = NULL;
imp_sth->next_sth = NULL;
if (attribs)
{
SV **svp;
if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_time_all", 11)) != NULL)
{
IB_SQLtimeformat(sth, imp_sth->dateformat, *svp);
#ifdef IB_API_V6
IB_SQLtimeformat(sth, imp_sth->timestampformat, *svp);
IB_SQLtimeformat(sth, imp_sth->timeformat, *svp);
#endif
}
if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_dateformat", 13)) != NULL)
IB_SQLtimeformat(sth, imp_sth->dateformat, *svp);
#ifdef IB_API_V6
if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_timestampformat", 18)) != NULL)
IB_SQLtimeformat(sth, imp_sth->timestampformat, *svp);
if ((svp = DBD_ATTRIB_GET_SVP(attribs, "ib_timeformat", 13)) != NULL)
IB_SQLtimeformat(sth, imp_sth->timeformat, *svp);
#endif
}
/* allocate 1 XSQLVAR to in_sqlda */
IB_alloc_sqlda(imp_sth->in_sqlda, 1);
if (imp_sth->in_sqlda == NULL)
{
do_error(sth, 2, "Fail to allocate in_sqlda");
return FALSE;
}
/* allocate 1 XSQLVAR to out_sqlda */
IB_alloc_sqlda(imp_sth->out_sqlda, 1);
if (imp_sth->out_sqlda == NULL)
{
do_error(sth, 2, "Fail to allocate out_sqlda");
ib_cleanup_st_prepare(imp_sth);
return FALSE;
}
/* init statement handle */
isc_dsql_alloc_statement2(status, &(imp_dbh->db), &(imp_sth->stmt));
if (ib_error_check(sth, status))
{
ib_cleanup_st_prepare(imp_sth);
(imp_sth->out_sqlda && (imp_sth->out_sqlda->sqld > 0))?
imp_sth->out_sqlda: NULL);
if (ib_error_check(sth, status))
{
ib_cleanup_st_execute(imp_sth);
return result;
}
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: isc_dsql_execute2 succeed.\n"));
imp_sth->fetched = 0;
}
else /* all other types of SQL statements */
{
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: calling isc_dsql_execute..\n"));
/* check for valid in_sqlda */
if (!imp_sth->in_sqlda)
return FALSE;
isc_dsql_execute(status, &(imp_dbh->tr), &(imp_sth->stmt),
imp_dbh->sqldialect,
imp_sth->in_sqlda->sqld > 0 ? imp_sth->in_sqlda: NULL);
if (ib_error_check(sth, status))
{
ib_cleanup_st_execute(imp_sth);
/* rollback any active transaction */
if (DBIc_has(imp_dbh, DBIcf_AutoCommit) && imp_dbh->tr)
ib_commit_transaction(sth, imp_dbh);
return result;
}
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: isc_dsql_execute succeed.\n"));
}
/* Jika AutoCommit On, commit_transaction() (bukan retaining),
* dan reset imp_dbh->tr == 0L
* For SELECT statement, commit_transaction() is called after fetch,
* or within finish()
*/
if (DBIc_has(imp_dbh, DBIcf_AutoCommit)
&& imp_sth->type != isc_info_sql_stmt_select
&& imp_sth->type != isc_info_sql_stmt_select_for_upd
&& imp_sth->type != isc_info_sql_stmt_exec_procedure)
{
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: calling ib_commit_transaction..\n"));
if (!ib_commit_transaction(sth, imp_dbh))
{
ib_cleanup_st_execute(imp_sth);
return result;
}
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: ib_commit_transaction succeed.\n"));
}
/* Declare a unique cursor for this query */
if (imp_sth->type == isc_info_sql_stmt_select_for_upd)
{
/* We free the cursor_name buffer in dbd_st_destroy. */
if (!create_cursor_name(sth, imp_sth))
{
ib_cleanup_st_execute(imp_sth);
return result;
}
}
switch (imp_sth->type)
{
case isc_info_sql_stmt_select:
case isc_info_sql_stmt_select_for_upd:
case isc_info_sql_stmt_exec_procedure:
DBIc_NUM_FIELDS(imp_sth) = (imp_sth->out_sqlda)?
imp_sth->out_sqlda->sqld: 0;
DBIc_ACTIVE_on(imp_sth);
break;
}
if (imp_sth->count_item)
{
//PerlIO_printf(PerlIO_stderr(), "calculating row count\n");
row_count = ib_rows(sth, &(imp_sth->stmt), imp_sth->count_item);
if (row_count <= -2)
ib_cleanup_st_execute(imp_sth);
else
result = row_count;
} else
result = -1;
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_execute: row count: %d.\n"
"dbd_st_execute: count_item: %d.\n",
row_count, imp_sth->count_item));
return result;
}
/* from out_sqlda to AV */
AV *dbd_st_fetch(SV *sth, imp_sth_t *imp_sth)
{
D_imp_dbh_from_sth; /* declare imp_dbh from sth */
ISC_STATUS fetch = 0;
ISC_STATUS status[ISC_STATUS_LENGTH];
int chopBlanks; /* chopBlanks ? */
AV *av; /* array buffer */
SV *sv, **svp; /* buffers */
XSQLVAR *var; /* working pointer XSQLVAR */
int i; /* loop */
short dtype;
DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_fetch\n"));
if (!DBIc_ACTIVE(imp_sth))
{
do_error(sth, 0, "no statement executing (perhaps you need to call execute first)\n");
return Nullav;
}
chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks);
av = DBIS->get_fbav(imp_sth);
svp = AvARRAY(av);
/*
* if it's an execute procedure, we've already got the
* output from the isc_dsql_execute2() call in dbd_st_execute().
*/
if (imp_sth->type != isc_info_sql_stmt_exec_procedure)
{
fetch = isc_dsql_fetch(status, &(imp_sth->stmt), imp_dbh->sqldialect,
imp_sth->out_sqlda);
if (ib_error_check(sth, status))
return Nullav;
/*
* Code 100 means we've reached the end of the set
* of rows that the SELECT will return.
*/
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_fetch: fetch result: %d\n", fetch));
if (imp_sth->fetched < 0)
imp_sth->fetched = 0;
if (fetch == 100)
{
/* close the cursor */
isc_dsql_free_statement(status, &(imp_sth->stmt), DSQL_close);
if (ib_error_check(sth, status))
return Nullav;
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "isc_dsql_free_statement succeed.\n"));
DBIc_ACTIVE_off(imp_sth); /* dbd_st_finish is no longer needed */
/* if AutoCommit on XXX. what to return if fails? */
if (DBIc_has(imp_dbh, DBIcf_AutoCommit))
{
if (!ib_commit_transaction(sth, imp_dbh))
return Nullav;
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "fetch ends: ib_commit_transaction succeed.\n"));
}
return Nullav;
}
else if (fetch != 0) /* something bad */
{ do_error(sth, 0, "Fetch error");
DBIc_ACTIVE_off(imp_sth);
return Nullav;
}
} /* !exec_procedure */
else
{
/* we only fetch one row for exec procedure */
if (imp_sth->fetched)
return Nullav;
}
var = imp_sth->out_sqlda->sqlvar;
for (i = 0; i < imp_sth->out_sqlda->sqld; i++, var++)
{
sv = svp[i];
dtype = var->sqltype & ~1;
if ((var->sqltype & 1) && (*(var->sqlind) == -1))
/* if nullable field */
{
/* isNULL */
SvOK_off(sv);
}
else
{
/*
* Got a non-null field. Got to pass it back to the
* application, which means some datatype dependant code.
*/
switch (dtype)
{
case SQL_SHORT:
if (var->sqlscale) /* handle NUMERICs */
{
double numeric;
numeric = ((double) (*(short *) var->sqldata)) /
pow(10.0, (double) -var->sqlscale);
}
/* Clean up after ourselves. */
isc_close_blob(status, &blob_handle);
if (ib_error_check(sth, status))
return FALSE;
break;
}
case SQL_ARRAY:
#ifdef ARRAY_SUPPORT
!!! NOT IMPLEMENTED YET !!!
#else
sv_setpvn(sv, "** array **", 11);
#endif
break;
default:
sv_setpvn(sv, "** unknown **", 13);
}
/*
* I use the column's alias name because in the absence
* of an alias, it contains the column name anyway.
* Only if the alias AND the column names are zero-length
* do I want to use a generic "COLUMN%d" header.
* This happens, for example, when the column is a
* computed field and the query doesn't use an AS clause
* to label the column.
*/
/*
if (var->aliasname_length > 0)
{
sv_setpvn(sv, var->aliasname, var->aliasname_length));
}
else
{
char s[20];
sprintf(s, "COLUMN%d", i);
sv_setpvn(sv, s, strlen(s));
}
*/
}
}
imp_sth->fetched += 1;
return av;
}
int dbd_st_finish(SV *sth, imp_sth_t *imp_sth)
{
D_imp_dbh_from_sth;
ISC_STATUS status[ISC_STATUS_LENGTH];
DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_finish\n"));
if (!DBIc_ACTIVE(imp_sth)) /* already finished */
return TRUE;
/* Close the cursor, not drop the statement! */
if (imp_sth->type != isc_info_sql_stmt_exec_procedure)
isc_dsql_free_statement(status, (isc_stmt_handle *)&(imp_sth->stmt), DSQL_close);
if (ib_error_check(sth, status))
return FALSE;
DBI_TRACE_imp_xxh(imp_sth, 3, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: isc_dsql_free_statement passed.\n"));
/* set statement to inactive - must be before ib_commit_transaction 'cos
commit can call dbd_st_finish function again */
DBIc_ACTIVE_off(imp_sth);
/* if AutoCommit on */
if (DBIc_has(imp_dbh, DBIcf_AutoCommit))
{
DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Trying to call ib_commit_transaction.\n"));
if (!ib_commit_transaction(sth, imp_dbh))
{
DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Call ib_commit_transaction finished returned FALSE.\n"));
return FALSE;
}
DBI_TRACE_imp_xxh(imp_sth, 4, (DBIc_LOGPIO(imp_sth), "dbd_st_finish: Call ib_commit_transaction succeded.\n"));
}
return TRUE;
}
void dbd_st_destroy(SV *sth, imp_sth_t *imp_sth)
{
D_imp_dbh_from_sth;
ISC_STATUS status[ISC_STATUS_LENGTH];
DBI_TRACE_imp_xxh(imp_dbh, 2, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy\n"));
/* freeing cursor name */
FREE_SETNULL(imp_sth->cursor_name);
/* freeing in_sqlda */
if (imp_sth->in_sqlda)
{
int i;
XSQLVAR *var = imp_sth->in_sqlda->sqlvar;
DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: found in_sqlda..\n"));
for (i = 0; i < imp_sth->in_sqlda->sqld; i++, var++)
{
FREE_SETNULL(var->sqldata);
FREE_SETNULL(var->sqlind);
}
DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: freeing in_sqlda..\n"));
safefree(imp_sth->in_sqlda);
imp_sth->in_sqlda = NULL;
}
/* freeing out_sqlda */
if (imp_sth->out_sqlda)
{
int i;
XSQLVAR *var = imp_sth->out_sqlda->sqlvar;
for (i = 0; i < imp_sth->out_sqlda->sqld; i++, var++)
{
FREE_SETNULL(var->sqldata);
FREE_SETNULL(var->sqlind);
}
safefree(imp_sth->out_sqlda);
imp_sth->out_sqlda = NULL;
}
/* free all other resources */
FREE_SETNULL(imp_sth->dateformat);
#ifdef IB_API_V6
FREE_SETNULL(imp_sth->timeformat);
FREE_SETNULL(imp_sth->timestampformat);
#endif
/* Drop the statement */
if (imp_sth->stmt)
{
isc_dsql_free_statement(status, &(imp_sth->stmt), DSQL_drop);
if (ib_error_check(sth, status))
{
DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: isc_dsql_free_statement failed.\n"));
}
else
DBI_TRACE_imp_xxh(imp_dbh, 3, (DBIc_LOGPIO(imp_dbh), "dbd_st_destroy: isc_dsql_free_statement succeeded.\n"));
imp_sth->stmt = 0L;
}
/* remove sth from linked list */
/* handle prev element */
if (imp_sth->prev_sth == NULL)
result = newRV(sv_2mortal((SV*)av));
while(--i >= 0)
av_store(av, i, newSViv(imp_sth->out_sqlda->sqlvar[i].sqlscale));
}
/**************************************************************************/
else if (kl==9 && strEQ(key, "PRECISION"))
{
AV *av;
if (!imp_sth || !imp_sth->in_sqlda || !imp_sth->out_sqlda)
return Nullsv;
av = newAV();
result = newRV(sv_2mortal((SV*)av));
while(--i >= 0)
av_store(av, i, newSViv(imp_sth->out_sqlda->sqlvar[i].sqllen));
}
/**************************************************************************/
else if (kl==4 && strEQ(key, "NAME"))
{
AV *av;
if (!imp_sth || !imp_sth->in_sqlda || !imp_sth->out_sqlda)
return Nullsv;
av = newAV();
result = newRV(sv_2mortal((SV*)av));
while(--i >= 0)
{
if (imp_sth->out_sqlda->sqlvar[i].aliasname_length > 0)
{
av_store(av, i,
newSVpvn(imp_sth->out_sqlda->sqlvar[i].aliasname,
imp_sth->out_sqlda->sqlvar[i].aliasname_length));
}
else
{
char s[20];
sprintf(s, "COLUMN%d", i);
av_store(av, i, newSVpvn(s, strlen(s)));
}
}
}
/**************************************************************************/
else if (kl==8 && strEQ(key, "NULLABLE"))
{
AV *av;
if (!imp_sth || !imp_sth->in_sqlda || !imp_sth->out_sqlda)
return Nullsv;
av = newAV();
result = newRV(sv_2mortal((SV*)av));
while(--i >= 0)
av_store(av, i, boolSV((imp_sth->out_sqlda->sqlvar[i].sqltype & 1) != 0));
}
/**************************************************************************/
else if (kl==10 && strEQ(key, "CursorName"))
{
if (imp_sth->cursor_name == NULL)
return Nullsv;
else
result = newSVpv(imp_sth->cursor_name, strlen(imp_sth->cursor_name));
}
else
return Nullsv;
if (cacheit)
{ /* cache for next time (via DBI quick_FETCH) */
SV **svp = hv_fetch((HV*)SvRV(sth), key, kl, 1);
sv_free(*svp);
*svp = result;
(void)SvREFCNT_inc(result); /* so sv_2mortal won't free it */
}
return sv_2mortal(result);
}
int dbd_st_STORE_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv)
{
STRLEN kl;
char *key = SvPV(keysv, kl);
DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "dbd_st_STORE - %s\n", key));
return FALSE;
}
int dbd_discon_all(SV *drh, imp_drh_t *imp_drh)
{
dTHR;
/* The disconnect_all concept is flawed and needs more work */
if (!SvTRUE(perl_get_sv("DBI::PERL_ENDING", 0)))
{
sv_setiv(DBIc_ERR(imp_drh), (IV)1);
sv_setpv(DBIc_ERRSTR(imp_drh), (char*)"disconnect_all not implemented");
DBIh_EVENT2(drh, ERROR_event, DBIc_ERR(imp_drh), DBIc_ERRSTR(imp_drh));
return FALSE;
}
if (perl_destruct_level)
perl_destruct_level = 0;
return FALSE;
}
int ib_blob_write(SV *sth, imp_sth_t *imp_sth, XSQLVAR *var, SV *value)
{
D_imp_dbh_from_sth;
isc_blob_handle handle = NULL;
ISC_STATUS status[ISC_STATUS_LENGTH];
long total_length;
char *p, *seg;
int is_text_blob, seg_len;
DBI_TRACE_imp_xxh(imp_sth, 2, (DBIc_LOGPIO(imp_sth), "ib_blob_write\n"));
/* we need a transaction */
if (!imp_dbh->tr)
if (!ib_start_transaction(sth, imp_dbh))
return FALSE;
( run in 1.753 second using v1.01-cache-2.11-cpan-39bf76dae61 )