From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

//====================================================
//
// Copyright 2008-2010 iAnywhere Solutions, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
// While not a requirement of the license, if you do modify this file, we
// would appreciate hearing about it. Please email
// sqlany_interfaces@sybase.com
//
//====================================================
#define _WIN32_WINNT 0x0501
#include "SQLAnywhere.h"
#include "perlapi.h"
DBISTATE_DECLARE;
#ifndef PerlIO
# define PerlIO FILE
# define PerlIO_printf fprintf
# define PerlIO_stderr() stderr
# define PerlIO_stdout() stdout
#endif
/* XXX DBI should provide a better version of this */
#define IS_DBI_HANDLE(h) \
(SvROK(h) && SvTYPE(SvRV(h)) == SVt_PVHV && \
SvRMAGICAL(SvRV(h)) && (SvMAGIC(SvRV(h)))->mg_type == 'P')
#define _min( a, b ) (((a)<(b))?(a):(b))
#define _max( a, b ) (((a)>=(b))?(a):(b))
#define IS_SACAPI_V2() ((sacapi->api.sqlany_init_ex) != NULL)
SACAPI *StaticAPI_V1;
// DBI can free dbh handles _after_ freeing the driver handle so we use refcounting for the
// driver-related things that are still needed at dbh destruction time
SACAPI *
SACAPI_Alloc()
/************/
{
SACAPI *sacapi = (SACAPI *)safemalloc( sizeof(SACAPI) );
memset( sacapi, 0, sizeof( SACAPI ) );
sacapi->refcount = 1;
if( sqlany_initialize_interface( &sacapi->api, NULL ) ) {
unsigned max_api_ver;
if( IS_SACAPI_V2() ) {
sacapi->context = sacapi->api.sqlany_init_ex( "PerlDBD", SQLANY_API_VERSION_2, &max_api_ver );
if( sacapi->context == NULL ) {
sqlany_finalize_interface( &sacapi->api );
Safefree( sacapi );
sacapi = NULL;
}
} else {
// V1 uses a single global context within dbcapi.dll so we need our own global context
LOCK_DOLLARZERO_MUTEX;
if( StaticAPI_V1 == NULL ) {
if( sacapi->api.sqlany_init( "PerlDBD", SQLANY_API_VERSION_1, &max_api_ver ) ) {
StaticAPI_V1 = sacapi;
} else {
sqlany_finalize_interface( &sacapi->api );
Safefree( sacapi );
sacapi = NULL;
}
} else {
sqlany_finalize_interface( &sacapi->api );
Safefree( sacapi );
sacapi = StaticAPI_V1;
sacapi->refcount++;
}
UNLOCK_DOLLARZERO_MUTEX;
}
} else {
Safefree( sacapi );
sacapi = NULL;
}
return( sacapi );
}
SACAPI *
SACAPI_AddRef( SACAPI *sacapi )
/*****************************/
{
LOCK_DOLLARZERO_MUTEX;
++sacapi->refcount;
UNLOCK_DOLLARZERO_MUTEX;
return( sacapi );
}
void
SACAPI_Release( SACAPI *sacapi )
/******************************/
{
LOCK_DOLLARZERO_MUTEX;
if( sacapi->refcount ) {
if( --sacapi->refcount == 0 ) {
if( sacapi->api.initialized ) {
if( IS_SACAPI_V2() ) {
sacapi->api.sqlany_fini_ex( sacapi->context );
sacapi->context = NULL;
} else {
sacapi->api.sqlany_fini();
}
sqlany_finalize_interface( &sacapi->api );
}
memset( sacapi, 0, sizeof(SACAPI) );
Safefree( sacapi );
if( sacapi == StaticAPI_V1 ) {
StaticAPI_V1 = NULL;
}
}
} else {
croak( "SACAPI refcount is already zero" );
}
UNLOCK_DOLLARZERO_MUTEX;
}
void
dbd_init( dbistate_t *dbistate )
/******************************/
// Called at boot (library load) time
// *CAN* be called concurrently by two threads loading the driver
// at the same time!
{
DBISTATE_INIT;
//DBIS->debug = 3;
//DBILOGFP = PerlIO_stdout();
}
int
dbd_dr_init( SV *drh )
/********************/
// Called once when each driver object is created and locked so there
// is no concurrent access.
{
D_imp_drh( drh );
imp_drh->sacapi = SACAPI_Alloc();
if( imp_drh->sacapi == NULL ) {
return( FALSE );
}
DBIc_IMPSET_on( imp_drh ); // imp_drh set up now
return( TRUE );
}
int
dbd_dr_destroy( SV *drh )
/***********************/
// Called once when each driver object is created and locked so there
// is no concurrent access.
{
D_imp_drh( drh );
if( DBIc_IMPSET( imp_drh ) ) {
if( imp_drh->sacapi != NULL ) {
SACAPI_Release( imp_drh->sacapi );
}
DBIc_IMPSET_off( imp_drh );
}
return( TRUE );
}
int
dbd_discon_all( SV *drh, imp_drh_t *imp_drh )
/*******************************************/
{
dTHR;
/* The disconnect_all concept is flawed and needs more work */
if( !dirty && !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 );
}
/* Database specific error handling.
This will be split up into specific routines
for dbh and sth level.
Also split into helper routine to set number & string.
Err, many changes needed, ramble ...
*/
void
ssa_error( SV *h, a_sqlany_connection *conn, int sqlcode, char *what )
/********************************************************************/
{
D_imp_xxh(h);
SV *errstr = DBIc_ERRSTR(imp_xxh);
SV *state = DBIc_STATE(imp_xxh);
D_imp_drh( h ); // not yet a driver handle!
while( DBIc_TYPE( imp_drh ) != DBIt_DR ) {
imp_drh = (imp_drh_t *)(DBIc_PARENT_COM( imp_drh ));
}
if( conn ) { /* is SQLAnywhere error (allow for non-SQLAnywhere errors) */
char msg[256];
size_t len;
char sqlstate[6];
sqlcode = imp_drh->sacapi->api.sqlany_error( conn, msg, sizeof(msg) );
imp_drh->sacapi->api.sqlany_sqlstate( conn, sqlstate, sizeof(sqlstate) );
len = strlen( msg );
if( len && msg[len-1] == '\n' )
msg[len-1] = '\0'; /* trim off \n from end of message */
sv_setpv( errstr, msg );
if( what ) {
sv_catpv( errstr, " (DBD: " );
sv_catpv( errstr, what );
sv_catpv( errstr, ")" );
}
sv_setiv( DBIc_ERR(imp_xxh), (IV)sqlcode );
imp_drh->sacapi->api.sqlany_sqlstate( conn, sqlstate, sizeof(sqlstate) );
sv_setpv( state, sqlstate );
} else {
sv_setpv( errstr, what );
sv_setiv( DBIc_ERR(imp_xxh), (IV)sqlcode );
sv_setpv( errstr, (what ? what : "") );
sv_setpv( state, "" );
}
DBIh_EVENT2( h, ERROR_event, DBIc_ERR(imp_xxh), errstr );
if( DBIS->debug >= 2 ) {
PerlIO_printf( DBILOGFP, "%s error %d recorded: %s\n",
what, sqlcode, SvPV(errstr,na) );
}
}
/* ================================================================== */
int
dbd_db_login( SV *dbh,
imp_dbh_t *imp_dbh,
char *conn_str,
char *server_side_sqlca_str,
char *ignored )
/********************************/
{
return( dbd_db_login6( dbh, imp_dbh, conn_str, server_side_sqlca_str, ignored, Nullsv ) );
}
int
dbd_db_login6( SV *dbh,
imp_dbh_t *imp_dbh,
char *conn_str,
char *server_side_sqlca_str,
char *ignored,
SV *attr )
/*****************************/
{
dTHR;
D_imp_drh_from_dbh;
SACAPI *sacapi = SACAPI_AddRef( imp_drh->sacapi );
imp_dbh->sacapi = sacapi;
if( sacapi == NULL || !sacapi->api.initialized ) {
ssa_error( dbh, NULL, SQLE_ERROR, "SQLAnwyhere C API (dbcapi) could not be loaded." );
return( 0 );
}
// SQLAnywhere.pm will pass a connection pointer in the uid field if this
// connect is for server-side perl's default connection.
imp_dbh->conn = NULL;
imp_dbh->ss_sqlca = NULL;
if( server_side_sqlca_str != NULL && *server_side_sqlca_str != '\0' ) {
sscanf( server_side_sqlca_str, "%p", &imp_dbh->ss_sqlca );
if( IS_SACAPI_V2() ) {
imp_dbh->conn = sacapi->api.sqlany_make_connection_ex( sacapi->context,
imp_dbh->ss_sqlca );
} else {
imp_dbh->conn = sacapi->api.sqlany_make_connection( imp_dbh->ss_sqlca );
}
if( imp_dbh->conn == NULL ) {
ssa_error( dbh, NULL, SQLE_ERROR, "failed to establish server-side connection" );
return( 0 );
}
} else {
// printf( "Connect string: %s\n", conn_str );
if( IS_SACAPI_V2() ) {
imp_dbh->conn = sacapi->api.sqlany_new_connection_ex( sacapi->context );
} else {
imp_dbh->conn = sacapi->api.sqlany_new_connection();
}
if( imp_dbh->conn == NULL ) {
ssa_error( dbh, NULL, SQLE_ERROR, "failed to allocate connection" );
return( 0 );
}
if( !sacapi->api.sqlany_connect( imp_dbh->conn, conn_str ) ) {
ssa_error( dbh, imp_dbh->conn, SQLE_ERROR, "login failed" );
return( 0 );
}
}
DBIc_IMPSET_on( imp_dbh ); /* imp_dbh set up now */
DBIc_ACTIVE_on( imp_dbh ); /* call disconnect before freeing */
DBIc_LongReadLen( imp_dbh ) = DEFAULT_LONG_READ_LENGTH;
DBIc_off( imp_dbh, DBIcf_LongTruncOk );
DBIc_on( imp_dbh, DBIcf_AutoCommit );
return( 1 );
}
int
dbd_db_commit( SV *dbh, imp_dbh_t *imp_dbh )
/******************************************/
{
SACAPI *sacapi = imp_dbh->sacapi;
if( !sacapi->api.sqlany_commit( imp_dbh->conn ) ) {
ssa_error( dbh, imp_dbh->conn, SQLE_ERROR, "commit failed" );
return( 0 );
}
return( 1 );
}
int
dbd_db_rollback( SV *dbh, imp_dbh_t *imp_dbh )
/********************************************/
{
SACAPI *sacapi = imp_dbh->sacapi;
if( !sacapi->api.sqlany_rollback( imp_dbh->conn ) ) {
ssa_error( dbh, imp_dbh->conn, SQLE_ERROR, "rollback failed" );
return( 0 );
}
return( 1 );
}
int
dbd_db_disconnect( SV *dbh, imp_dbh_t *imp_dbh )
/**********************************************/
{
dTHR;
SACAPI *sacapi = imp_dbh->sacapi;
// don't close the connection if it was opened externally
if( imp_dbh->ss_sqlca ) {
return( 1 );
}
/* We assume that disconnect will always work */
/* since most errors imply already disconnected. */
DBIc_ACTIVE_off( imp_dbh );
if( !sacapi->api.sqlany_disconnect( imp_dbh->conn ) ) {
ssa_error( dbh, imp_dbh->conn, SQLE_ERROR, "disconnect error" );
return( 0 );
}
/* We don't free imp_dbh since a reference still exists */
/* The DESTROY method is the only one to 'free' memory. */
/* Note that statement objects may still exists for this dbh! */
return( 1 );
}
void
dbd_db_destroy( SV *dbh, imp_dbh_t *imp_dbh )
/*******************************************/
{
SACAPI *sacapi = imp_dbh->sacapi;
if( DBIc_IMPSET( imp_dbh ) ) {
D_imp_drh_from_dbh;
// don't close the connection if it was opened externally
if( imp_dbh->ss_sqlca == NULL ) {
if( DBIc_ACTIVE( imp_dbh ) ) {
dbd_db_disconnect( dbh, imp_dbh );
}
}
sacapi->api.sqlany_free_connection( imp_dbh->conn );
SACAPI_Release( imp_dbh->sacapi );
imp_dbh->sacapi = NULL;
/* Nothing in imp_dbh to be freed */
DBIc_IMPSET_off( imp_dbh );
}
}
int
dbd_db_STORE_attrib( SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv )
/************************************************************************/
{
STRLEN kl;
char *key = SvPV( keysv, kl );
SV *cachesv = NULL;
int was_off;
int on = SvTRUE( valuesv );
SACAPI *sacapi = imp_dbh->sacapi;
if( kl==10 && strEQ( key, "AutoCommit" ) ) {
was_off = !DBIc_has(imp_dbh,DBIcf_AutoCommit);
if( was_off && on ) {
sacapi->api.sqlany_commit( imp_dbh->conn );
}
cachesv = (on) ? &sv_yes : &sv_no; /* cache new state */
DBIc_set( imp_dbh, DBIcf_AutoCommit, on );
} else {
return FALSE;
}
if( cachesv ) { /* cache value for later DBI 'quick' fetch? */
hv_store( (HV*)SvRV(dbh), key, (I32)kl, cachesv, 0 );
}
return( TRUE );
}
SV *
dbd_db_FETCH_attrib( SV *dbh, imp_dbh_t *imp_dbh, SV *keysv )
/***********************************************************/
{
STRLEN kl;
char *key = SvPV(keysv,kl);
SV *retsv = Nullsv;
/* Default to caching results for DBI dispatch quick_FETCH */
int cacheit = FALSE;
if( kl==10 && strEQ(key, "AutoCommit") ) {
retsv = boolSV(DBIc_has(imp_dbh,DBIcf_AutoCommit));
}
if( retsv == Nullsv ) {
return( Nullsv );
}
if( cacheit ) { /* cache for next time (via DBI quick_FETCH) */
SV **svp = hv_fetch( (HV*)SvRV(dbh), key, (I32)kl, 1 );
sv_free( *svp );
*svp = retsv;
(void)SvREFCNT_inc( retsv ); /* so sv_2mortal won't free it */
}
return( sv_2mortal( retsv ) );
}
/* ================================================================== */
int
dbd_st_prepare( SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs )
/*************************************************************************/
{
D_imp_dbh_from_sth;
char *_statement;
SACAPI *sacapi = imp_dbh->sacapi;
/* scan statement for '?', ':1' and/or ':foo' style placeholders */
dbd_preparse( imp_sth, statement );
_statement = (char *)imp_sth->sql_statement;
//PerlIO_printf( PerlIO_stderr(), "\n\nPrepare: '%s'\n\n", _statement ); fflush(stdout);
imp_sth->statement = sacapi->api.sqlany_prepare( imp_dbh->conn, _statement );
if( imp_sth->statement == NULL ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "prepare failed" );
return( 0 );
}
imp_sth->num_bind_params = sacapi->api.sqlany_num_params( imp_sth->statement );
DBIc_NUM_PARAMS( imp_sth ) = imp_sth->num_bind_params;
DBIc_NUM_FIELDS( imp_sth ) = 0; // FIXME: must be replaced with estimated column count
DBIc_IMPSET_on( imp_sth );
return( 1 );
}
void
dbd_preparse( imp_sth_t *imp_sth, char *statement )
/*************************************************/
{
char quote = '\0';
char *src, *start, *dest;
phs_t phs_tpl;
SV *phs_sv;
int idx=0, style=0, laststyle=0;
int curr_ordinal = 1;
char _ph_name_buf[10];
char *ph_name;
size_t ph_name_len;
/* allocate room for copy of statement with spare capacity */
/* for editing ':1' into ':p1' so we can use obndrv. */
imp_sth->sql_statement = (char *)safemalloc( strlen(statement) + 1 );
/* initialise phs ready to be cloned per placeholder */
memset( &phs_tpl, '\0', sizeof(phs_tpl) );
src = statement;
dest = imp_sth->sql_statement;
while( *src ) {
if( *src == '\'' || *src == '\"' ) {
if( quote ) {
if( *src == quote ) {
if( src[1] == quote ) {
*dest++ = *src++;
} else {
quote = '\0';
}
}
} else {
quote = *src;
}
}
if( (*src != ':' && *src != '?') || quote ) {
*dest++ = *src++;
continue;
}
start = dest; /* save name inc colon */
*dest++ = *src++;
ph_name = NULL;
ph_name_len = 0;
if( *start == '?' ) { /* X/Open standard */
style = 3;
} else if( isDIGIT(*src) ) { /* ':1' */
*start = '?';
idx = atoi( src );
if( idx <= 0 ) {
croak( "Placeholder :%d must be a positive number", idx );
}
if( idx != curr_ordinal ) {
croak( "Cannot handle unordered ':numeric' placeholders" );
}
while( isDIGIT(*src) ) {
++src;
}
style = 1;
} else if( isALNUM(*src) ) { /* ':foo' */
*start = '?';
ph_name = src-1;
++ph_name_len; // for ':'
while( isALNUM(*src) ) { /* includes '_' */
++ph_name_len;
++src;
}
style = 2;
} else { /* perhaps ':=' PL/SQL construct */
continue;
}
*dest = '\0'; /* handy for debugging */
if( laststyle && style != laststyle ) {
croak( "Can't mix placeholder styles (%d/%d)", style, laststyle );
}
laststyle = style;
if( imp_sth->bind_names == NULL ) {
imp_sth->bind_names = newHV();
}
phs_tpl.ordinal = curr_ordinal;
phs_tpl.sv = &sv_undef;
phs_sv = newSVpv( (char*)&phs_tpl, sizeof(phs_tpl) );
if( ph_name == NULL ) {
ph_name = _ph_name_buf;
sprintf( ph_name, ":p%d", curr_ordinal );
ph_name_len = strlen( ph_name );
}
hv_store( imp_sth->bind_names, ph_name, (I32)ph_name_len,
phs_sv, 0 );
++curr_ordinal;
/* warn("bind_names: '%s'\n", start); */
}
*dest = '\0';
if( imp_sth->bind_names ) {
imp_sth->num_bind_params_scanned = (int)HvKEYS(imp_sth->bind_names);
if( DBIS->debug >= 2 ) {
PerlIO_printf( DBILOGFP, "scanned %d distinct placeholders\n",
imp_sth->num_bind_params_scanned );
}
}
}
int
dbd_bind_ph( SV *sth,
imp_sth_t *imp_sth,
SV *ph_namesv,
SV *newvalue,
IV sql_type,
SV *attribs,
int is_inout,
IV maxlen )
/******************************/
{
D_imp_dbh_from_sth;
SV **svp;
STRLEN name_len;
char *name;
phs_t *phs;
char buf[10];
if( SvNIOK( ph_namesv ) ) { /* passed as a number */
name = buf;
sprintf( name, ":p%d", (int)SvIV( ph_namesv ) );
name_len = strlen(name);
} else {
name = SvPV( ph_namesv, name_len );
}
// FIXME: Why croak() and not just report an error?
if( SvTYPE(newvalue) > SVt_PVLV ) { /* hook for later array logic */
croak( "Can't bind a non-scalar value", neatsvpv(newvalue,0) );
}
if( SvROK(newvalue) && !IS_DBI_HANDLE(newvalue) ) {
/* dbi handle allowed for cursor variables */
croak( "Can't bind a reference (%s)", neatsvpv(newvalue,0) );
}
if( SvTYPE(newvalue) == SVt_PVLV && is_inout ) { /* may allow later */
croak( "Can't bind ``lvalue'' mode scalar as inout parameter" );
}
if( DBIS->debug >= 2 ) {
PerlIO_printf( DBILOGFP, " bind %s <== %s (type %ld",
name, neatsvpv(newvalue,0), (long)sql_type );
if( is_inout ) {
PerlIO_printf( DBILOGFP, ", inout 0x%p", newvalue );
}
if( attribs ) {
PerlIO_printf( DBILOGFP, ", attribs: %s", SvPV(attribs,na) );
}
PerlIO_printf( DBILOGFP, ")\n" );
}
svp = hv_fetch( imp_sth->bind_names, name, (I32)name_len, 0 );
if( svp == NULL ) {
croak( "Can't bind unknown placeholder '%s' (%s)", name, neatsvpv(ph_namesv,0) );
}
if( is_inout && SvREADONLY( newvalue ) ) {
croak( no_modify );
}
phs = (phs_t *)((void*)SvPVX(*svp)); /* placeholder struct */
if( phs->ordinal == 0 ) {
croak( "bind_param internal error: unknown ordinal for '%s'\n", name );
}
if( phs->sv != &sv_undef ) { /* first bind for this placeholder */
SvREFCNT_dec( phs->sv );
}
phs->sv = SvREFCNT_inc( newvalue );
phs->is_inout = is_inout;
phs->maxlen = maxlen;
phs->sql_type = (int)sql_type;
if( DBIS->debug >= 2 ) {
PerlIO_printf( DBILOGFP, "Binding input hostvar '%s' to ordinal %d\n",
name, phs->ordinal );
}
return( 1 );
}
static int
assign_from_result_set( SV *sth, imp_sth_t *imp_sth, SV *sv, int index )
/**********************************************************************/
{
D_imp_dbh_from_sth;
a_sqlany_data_info dinfo;
SACAPI *sacapi = imp_dbh->sacapi;
if( !sacapi->api.sqlany_get_data_info( imp_sth->statement, index, &dinfo ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "get_data_info failed" );
return( FALSE );
}
if( dinfo.is_null ) {
SvOK_off( sv );
} else if( dinfo.type == A_STRING || dinfo.type == A_BINARY ) {
IV len = dinfo.data_size;
IV longreadlen = DBIc_LongReadLen( imp_dbh );
char *dest;
if( len > longreadlen ) {
if( !DBIc_has( imp_sth, DBIcf_LongTruncOk ) ) {
ssa_error( sth, NULL, SQLE_TRUNCATED, "long value truncated" );
return( FALSE );
}
len = DBIc_LongReadLen( imp_dbh );
}
SvUPGRADE( sv, SVt_PV );
dest = SvGROW( sv, (STRLEN)len+1 );
if( len != 0 && sacapi->api.sqlany_get_data( imp_sth->statement, index, 0, dest, len ) < 0 ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "get_data failed" );
return( FALSE );
}
SvCUR_set( sv, len );
*SvEND( sv ) = '\0';
SvPOK_only( sv );
} else {
a_sqlany_data_value val;
char numbuf[40];
if( !sacapi->api.sqlany_get_column( imp_sth->statement, index, &val ) ) {
SvOK_off( sv ); // shouldn't get here
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "get_column failed" );
return( FALSE );
}
switch( dinfo.type ) {
case A_VAL8 :
sv_setiv( sv, (IV)*(signed char *)val.buffer );
break;
case A_VAL16 :
sv_setiv( sv, (IV)*(short *)val.buffer );
break;
case A_VAL32 :
sv_setiv( sv, (IV)*(int *)val.buffer );
break;
case A_UVAL8 :
sv_setuv( sv, (UV)*(unsigned char *)val.buffer );
break;
case A_UVAL16 :
sv_setuv( sv, (UV)*(unsigned short *)val.buffer );
break;
case A_UVAL32 :
sv_setuv( sv, (UV)*(unsigned *)val.buffer );
break;
case A_VAL64 :
sprintf( numbuf, "%lld", *(long long *)val.buffer );
sv_setpv( sv, numbuf );
break;
case A_UVAL64 :
sprintf( numbuf, "%llu", *(unsigned long long *)val.buffer );
sv_setpv( sv, numbuf );
break;
case A_DOUBLE :
sv_setnv( sv, *(double *)val.buffer );
break;
default :
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "internal error: unhandled SA data type" );
SvOK_off( sv );
return( FALSE );
}
}
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, " %d: '%s'\n",
index, SvOK(sv) ? SvPV(sv,na) : "NULL" );
}
return( TRUE );
}
static int
really_bind( SV *sth, imp_sth_t *imp_sth )
/****************************************/
{
D_imp_dbh_from_sth;
HE *he;
HV *hv;
SV *sv;
phs_t *phs;
SACAPI *sacapi = imp_dbh->sacapi;
hv = imp_sth->bind_names;
if( hv == NULL ) {
return( TRUE );
}
hv_iterinit( hv );
while( (he=hv_iternext( hv )) != NULL ) {
sv = hv_iterval( hv, he );
phs = (phs_t *)((void *)SvPVX(sv)); /* placeholder struct */
if( phs->ordinal != 0 && phs->ordinal <= imp_sth->num_bind_params ) {
a_sqlany_bind_param desc;
a_sqlany_data_type bind_type;
if( !sacapi->api.sqlany_describe_bind_param( imp_sth->statement, phs->ordinal-1, &desc ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "failed to get description for bind param" );
return( FALSE );
}
if( phs->sql_type == SQL_BINARY ||
phs->sql_type == SQL_VARBINARY ||
phs->sql_type == SQL_LONGVARBINARY ) {
bind_type = A_BINARY;
} else {
bind_type = A_STRING;
}
if( phs->is_inout && (desc.direction&DD_OUTPUT) ) {
a_sqlany_bind_param bp = desc;
SV *lcl_undef = &sv_undef;
char *lcl_p = NULL;
bp.direction = DD_OUTPUT;
bp.value.type = bind_type;
bp.value.length = &phs->out_param_length;
bp.value.is_null = &phs->out_param_is_null;
phs->out_param_length = 0;
phs->out_param_is_null = TRUE;
// ensure room for result, 28 is magic number (see sv_2pv)
// We ignore the size given by the server and use the max_size provided by the user
bp.value.buffer_size = _min( bp.value.buffer_size, (size_t)phs->maxlen );
bp.value.buffer_size = _max( 28, bp.value.buffer_size );
SvUPGRADE( phs->sv, SVt_PV ); // Also does a backoff so that SvPVX returns beginning of buffer
bp.value.buffer = SvGROW( phs->sv, bp.value.buffer_size+1 );
if( !sacapi->api.sqlany_bind_param( imp_sth->statement, phs->ordinal-1, &bp ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "bind for output parameter failed" );
return( FALSE );
}
}
if( desc.direction&DD_INPUT ) {
a_sqlany_bind_param bp = desc;
bp.direction = DD_INPUT;
bp.value.type = bind_type;
bp.value.length = &phs->in_param_length;
bp.value.is_null = &phs->in_param_is_null;
if( !SvOK( phs->sv ) ) {
bp.value.buffer = NULL;
phs->in_param_length = 0;
phs->in_param_is_null = TRUE;
} else {
bp.value.buffer = SvPV( phs->sv, na );
phs->in_param_length = bp.value.buffer_size = SvCUR( phs->sv );
phs->in_param_is_null = FALSE;
}
if( !sacapi->api.sqlany_bind_param( imp_sth->statement, phs->ordinal-1, &bp ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "bind for input parameter failed" );
return( FALSE );
}
}
}
}
return( TRUE );
}
static int
assign_output_parameters( SV *sth, imp_sth_t *imp_sth )
/*****************************************************/
{
D_imp_dbh_from_sth;
HE *he;
HV *hv;
SV *sv;
phs_t *phs;
SACAPI *sacapi = imp_dbh->sacapi;
hv = imp_sth->bind_names;
if( hv == NULL ) {
return( TRUE );
}
hv_iterinit( hv );
while( (he=hv_iternext( hv )) != NULL ) {
sv = hv_iterval( hv, he );
phs = (phs_t *)((void *)SvPVX(sv)); /* placeholder struct */
if( phs->ordinal != 0 && phs->ordinal <= imp_sth->num_bind_params ) {
a_sqlany_bind_param desc;
if( !sacapi->api.sqlany_describe_bind_param( imp_sth->statement, phs->ordinal-1, &desc ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "failed to get description for bind param" );
return( FALSE );
}
if( phs->is_inout && (desc.direction&DD_OUTPUT) ) {
a_sqlany_bind_param_info bp;
if( !sacapi->api.sqlany_get_bind_param_info( imp_sth->statement, phs->ordinal-1, &bp ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "failed to get bind param info" );
return( FALSE );
}
if( phs->out_param_is_null ) {
SvOK_off( phs->sv ); // undef
} else {
STRLEN len = (STRLEN)phs->out_param_length;
if( (SvLEN( phs->sv ) < len+1) || (SvPVX( phs->sv ) != bp.output_value.buffer) ) {
// This shouldn't happen -- we already grew the dest fit the data
croak( "internal error: output buffer for bind parameter %d changed", phs->ordinal );
}
SvCUR_set( phs->sv, len );
*SvEND( phs->sv ) = '\0';
SvPOK_only( phs->sv );
}
}
}
}
return( TRUE );
}
int
dbd_st_execute( SV *sth, imp_sth_t *imp_sth )
/*******************************************/
// return value <= -2:error, >=0:ok row count, (-1=unknown count) */
{
dTHR;
D_imp_dbh_from_sth;
int do_commit = FALSE;
int sqlcode;
int num_cols;
SACAPI *sacapi = imp_dbh->sacapi;
// If a cursor is still open, it must be closed before we open another
// one on the same handle.
dbd_st_finish( sth, imp_sth );
if( !really_bind( sth, imp_sth ) ) {
return( -2 );
}
sacapi->api.sqlany_execute( imp_sth->statement );
sqlcode = sacapi->api.sqlany_error( imp_dbh->conn, NULL, 0 );
num_cols = sacapi->api.sqlany_num_cols( imp_sth->statement );
// A failure to execute or there is no cursor open
if( sqlcode == SQLE_NOTFOUND ) {
// num_cols == 0 implies it was execute-only (and no cursor)
if( num_cols == 0 && !assign_output_parameters( sth, imp_sth ) ) {
return( -2 );
}
sv_setpv( DBIc_ERR(imp_sth), "" );
return( 0 ); // No rows affected
}
// This error case for SQLE_TRUNCATED as well because there is no
// way to call GET DATA without a cursor.
if( sqlcode < 0 ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "execute failed" );
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, " dbd_st_execute failed, rc=%d", sqlcode );
}
return( -2 );
}
if( sqlcode > 0 ) {
// Just a warning
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "warning during execute" );
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, " dbd_st_execute warning, rc=%d", sqlcode );
}
}
if( num_cols == 0 ) {
// executed already & no cursor
if( !assign_output_parameters( sth, imp_sth ) ) {
return( -2 );
}
imp_sth->row_count = sacapi->api.sqlany_affected_rows( imp_sth->statement );
if( DBIc_has(imp_dbh,DBIcf_AutoCommit) ) {
sacapi->api.sqlany_commit( imp_dbh->conn );
}
} else {
// A cursor is open
if( DBIS->debug >= 2 ) {
PerlIO_printf( DBILOGFP, "Cursor opened\n" );
}
imp_sth->row_count = sacapi->api.sqlany_num_rows( imp_sth->statement );
}
DBIc_NUM_FIELDS(imp_sth) = num_cols;
DBIc_ACTIVE_on(imp_sth);
// Negative row-counts are estimates but dbperl wants a positive
return( imp_sth->row_count < 0 ? -imp_sth->row_count : imp_sth->row_count );
}
AV *
dbd_st_fetch( SV *sth, imp_sth_t *imp_sth )
/*****************************************/
{
D_imp_dbh_from_sth;
int debug = DBIS->debug;
int num_fields;
int i;
AV *av;
int sqlcode;
SACAPI *sacapi = imp_dbh->sacapi;
/* Check that execute() was executed sucessfuly. */
if( !DBIc_ACTIVE(imp_sth) ) {
ssa_error( sth, NULL, SQLE_CURSOR_NOT_OPEN, "no statement executing" );
return( Nullav );
}
if( imp_sth->statement == NULL ) {
return( Nullav ); // we figured it was just an EXECUTE
}
// printf( "Fetch (%p)\n", imp_sth ); fflush( stdout );
sacapi->api.sqlany_fetch_next( imp_sth->statement );
sqlcode = sacapi->api.sqlany_error( imp_dbh->conn, NULL, 0 );
if( sqlcode == SQLE_NOTFOUND ) {
sv_setpv( DBIc_ERR(imp_sth), "" ); /* just end-of-fetch */
return( Nullav );
} else if( sqlcode < 0 ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "fetch failed" );
if( debug >= 3 ) {
PerlIO_printf( DBILOGFP, " dbd_st_fetch failed, rc=%d", sqlcode );
}
return( Nullav );
}
if( sqlcode > 0 ) {
// Just a warning
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "warning during fetch" );
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, " dbd_st_fetch warning, rc=%d", sqlcode );
}
}
av = DBIS->get_fbav( imp_sth );
num_fields = DBIc_NUM_FIELDS( imp_sth );
av_fill( av, num_fields - 1 ); // -1 is Okay here
if( debug >= 3 ) {
PerlIO_printf( DBILOGFP, " dbd_st_fetch %d fields\n", num_fields );
}
for( i=0; i < num_fields; ++i ) {
SV *sv = AvARRAY(av)[i]; /* Note: we (re)use the SV in the AV */
if( !assign_from_result_set( sth, imp_sth, sv, i ) ) {
return( Nullav );
}
}
return( av );
}
int
dbd_st_blob_read( SV *sth, imp_sth_t *imp_sth,
int field, long offset, long len, SV *destrv, long destoffset )
/*******************************************************************************/
{
D_imp_dbh_from_sth;
SV *bufsv;
a_sqlany_data_info dinfo;
char *dest;
int retlen;
SACAPI *sacapi = imp_dbh->sacapi;
/* Check that execute() was executed sucessfuly. This also implies */
/* that dbd_describe() executed sucessfuly so the memory buffers */
/* are allocated and bound. */
if( !DBIc_ACTIVE(imp_sth) ) {
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, "blob_read on inactive handle\n" );
}
ssa_error( sth, NULL, SQLE_CURSOR_NOT_OPEN, "no statement executing" );
return( 0 );
}
if( imp_sth->statement == NULL ) {
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, "blob_read on non-cursor statement\n" );
}
return( 0 ); // we figured it was just an EXECUTE
}
if( field >= sacapi->api.sqlany_num_cols( imp_sth->statement ) ) {
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, "blob_read: field number too large\n" );
}
return( 0 );
}
if( !sacapi->api.sqlany_get_data_info( imp_sth->statement, field, &dinfo ) ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "get_data_info failed" );
return( 0 );
}
if( dinfo.type != A_STRING && dinfo.type != A_BINARY ) {
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP, "blob_read: field is neither string nor binary\n" );
}
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "blob_read: field is neither string nor binary\n" );
return( 0 );
}
if( dinfo.is_null ) {
return( 0 );
}
bufsv = SvRV( destrv );
sv_setpvn( bufsv, "", 0 ); /* ensure it's writable string */
dest = SvGROW( bufsv, (STRLEN)destoffset+len+1 ); /* SvGROW doesn't do +1 */
dest += destoffset;
retlen = sacapi->api.sqlany_get_data( imp_sth->statement, field, offset, dest, len );
if( retlen < 0 ) {
ssa_error( sth, imp_dbh->conn, SQLE_ERROR, "get_data failed" );
return( 0 );
}
if( DBIS->debug >= 3 ) {
PerlIO_printf( DBILOGFP,
" blob_read field %d, type %d, offset %ld (ignored), len %ld, destoffset %ld, retlen %ld\n",
field, dinfo.type, offset, len, destoffset, (long)retlen );
}
SvCUR_set( bufsv, destoffset + retlen );
*SvEND(bufsv) = '\0'; /* consistent with perl sv_setpvn etc */
if( retlen == 0 ) {
return( 0 );
}
return( 1 );
}
int
dbd_st_rows( SV *sth, imp_sth_t *imp_sth )
/****************************************/
{
return( imp_sth->row_count );
}
int
dbd_st_finish( SV *sth, imp_sth_t *imp_sth )
/******************************************/
{
dTHR;
D_imp_dbh_from_sth;
SACAPI *sacapi = imp_dbh->sacapi;
if( DBIc_ACTIVE(imp_dbh) ) {
//printf( "Closing %p\n", imp_sth ); fflush( stdout );
if( imp_sth->statement && sacapi->api.sqlany_num_cols( imp_sth->statement ) > 0 ) {
// Cursor is open
sacapi->api.sqlany_reset( imp_sth->statement );
if( DBIc_has(imp_dbh,DBIcf_AutoCommit) ) {
sacapi->api.sqlany_commit( imp_dbh->conn );
}
}
}
DBIc_ACTIVE_off(imp_sth);
return( 1 );
}
void
release_bind_params( SV *sth, imp_sth_t *imp_sth )
/************************************************/
{
D_imp_dbh_from_sth;
HE *he;
HV *hv;
SV *sv;
phs_t *phs;
hv = imp_sth->bind_names;
if( hv == NULL ) {
return;
}
hv_iterinit( hv );
while( (he=hv_iternext( hv )) != NULL ) {
sv = hv_iterval( hv, he );
phs = (phs_t *)((void *)SvPVX(sv)); /* placeholder struct */
if( phs->sv != &sv_undef ) {
SvREFCNT_dec( phs->sv );
}
}
}
void
dbd_st_destroy( SV *sth, imp_sth_t *imp_sth )
/*******************************************/
{
D_imp_dbh_from_sth;
SACAPI *sacapi = imp_dbh->sacapi;
dbd_st_finish( sth, imp_sth );
if( DBIc_ACTIVE(imp_dbh) ) {
if( imp_sth->statement ) {
sacapi->api.sqlany_free_stmt( imp_sth->statement );
imp_sth->statement = NULL;
release_bind_params( sth, imp_sth );
Safefree( imp_sth->sql_statement );
imp_sth->sql_statement = NULL;
}
}
DBIc_IMPSET_off(imp_sth); /* let DBI know we've done it */
}
int
dbd_st_STORE_attrib( SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv )
/************************************************************************/
{
// FIXME: NYI
return( FALSE );
}
#ifndef SQL_DATETIME
#define SQL_DATETIME 9
#endif
static int
native_to_odbc_type( short int sqltype )
/**************************************/
{
int odbc_type;
switch( sqltype ) {
case DT_BIT : odbc_type = SQL_BIT; break;
case DT_TINYINT : odbc_type = SQL_TINYINT; break;
case DT_UNSSMALLINT :
case DT_SMALLINT : odbc_type = SQL_SMALLINT; break;
case DT_UNSINT :
case DT_INT : odbc_type = SQL_INTEGER; break;
case DT_UNSBIGINT :
case DT_BIGINT : odbc_type = SQL_BIGINT; break;
case DT_DATE : odbc_type = SQL_DATE; break;
case DT_TIME : odbc_type = SQL_TIME; break;
case DT_TIMESTAMP : odbc_type = SQL_TIMESTAMP; break;
case DT_DECIMAL : odbc_type = SQL_DECIMAL; break;
case DT_FLOAT : odbc_type = SQL_FLOAT; break;
case DT_DOUBLE : odbc_type = SQL_DOUBLE; break;
case DT_STRING :
case DT_FIXCHAR : odbc_type = SQL_CHAR; break;
case DT_VARCHAR : odbc_type = SQL_VARCHAR; break;
case DT_LONGVARCHAR : odbc_type = SQL_LONGVARCHAR; break;
case DT_BINARY : odbc_type = SQL_VARBINARY; break;
case DT_LONGBINARY : odbc_type = SQL_LONGVARBINARY; break;
default:
odbc_type = SQL_ALL_TYPES; // whatever
break;
}
return( odbc_type );
}
SV *
dbd_st_FETCH_attrib( SV *sth, imp_sth_t *imp_sth, SV *keysv )
/***********************************************************/
{
D_imp_dbh_from_sth;
STRLEN kl;
char *key = SvPV(keysv,kl);
int i;
SV *retsv = NULL;
a_sqlany_column_info cinfo;
SACAPI *sacapi = imp_dbh->sacapi;
/* Default to caching results for DBI dispatch quick_FETCH */
int cacheit = TRUE;
if( kl==13 && strEQ(key, "NUM_OF_PARAMS") ) { /* handled by DBI */
return( Nullsv );
}
i = DBIc_NUM_FIELDS(imp_sth);
if( kl == 4 && strEQ( key, "NAME" ) ) {
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
// FIXME: what if no result set? (ie. stmt not executed yet)
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
av_store( av, i, newSVpv( cinfo.name, 0 ) );
}
} else if( kl == 7 && strEQ( key, "ASATYPE" ) ) {
// Translating types to ODBC type can be lossy
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
av_store( av, i, newSViv( cinfo.native_type ) );
}
} else if( kl == 4 && strEQ( key, "TYPE" ) ) {
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
av_store( av, i, newSViv( native_to_odbc_type( cinfo.native_type ) ) );
}
} else if( kl == 5 && strEQ( key, "SCALE" ) ) {
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
switch( cinfo.native_type ) {
case DT_DECIMAL :
case DT_BASE100 :
av_store( av, i, newSViv( cinfo.scale ) );
break;
}
}
} else if( kl == 9 && strEQ( key, "PRECISION" ) ) {
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
switch( cinfo.native_type ) {
case DT_DECIMAL :
case DT_BASE100 :
av_store( av, i, newSViv( cinfo.precision ) );
break;
case DT_FLOAT :
av_store( av, i, newSViv(10) );
break;
case DT_DOUBLE :
av_store( av, i, newSViv(15) );
break;
// For the integer types, I assume I am supposed to return the max field width (which
// is also the number of significant digits) in base 10, disregarding negative signs
// (as documented for numerics)
case DT_BIT :
av_store( av, i, newSViv(1) );
break;
case DT_TINYINT :
av_store( av, i, newSViv(3) );
break;
case DT_SMALLINT :
case DT_UNSSMALLINT :
av_store( av, i, newSViv(5) );
break;
case DT_UNSINT :
case DT_INT :
av_store( av, i, newSViv(10) );
break;
case DT_BIGINT :
case DT_UNSBIGINT :
av_store( av, i, newSViv(20) );
break;
case DT_VARCHAR :
case DT_BINARY :
case DT_FIXCHAR :
case DT_STRING :
case DT_LONGVARCHAR :
case DT_LONGBINARY :
default :
av_store( av, i, newSViv( cinfo.max_size ) );
break;
}
}
} else if( kl == 8 && strEQ( key, "NULLABLE" ) ) {
AV *av = newAV();
retsv = newRV( sv_2mortal( (SV*)av ) );
while( --i >= 0 ) {
sacapi->api.sqlany_get_column_info( imp_sth->statement, i, &cinfo );
av_store( av, i, boolSV( cinfo.nullable ) );
}
} else if( kl == 10 && strEQ( key, "CursorName" ) ) {
retsv = &sv_undef;
} else if( kl == 9 && strEQ( key, "Statement" ) ) {
retsv = newSVpv( (char *)imp_sth->sql_statement, 0 );
} else if( kl == 11 && strEQ( key, "RowsInCache" ) ) {
retsv = &sv_undef;
} else {
return( Nullsv );
}
if( cacheit ) { /* cache for next time (via DBI quick_FETCH) */
SV **svp = hv_fetch( (HV*)SvRV(sth), key, (I32)kl, 1 );
sv_free( *svp );
*svp = retsv;
(void)SvREFCNT_inc( retsv ); /* so sv_2mortal won't free it */
}
return( sv_2mortal( retsv ) );
}
/* --------------------------------------- */