/** * sql_server.cpp, ABr * * A generic wrapper around SQL Server, for use by developers wishing * to extend SQL Server by using Extended Stored Procedures. * * Accessing SQL Server directly using the extended stored procedure * support functions is time-consuming, error-prone, and extremely * messy. This implementation file exists to wrap access to the * low-level functions in such a way that the application developer * can concentrate on business-level logic rather than on the gritty * underlying functions. * * All the major functions are supported through this interface. It's * easy to define dynamic result sets, support input and output * variables, and send logging messages back to SQL Server using these * classes. * * Copyright (c) 2003-05 softwareAB Co. * All rights reserved * * SOFTWAREAB MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SOFTWAREAB * SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. * * Developed by softwareAB, Inc. * 6806 Silver Ann Drive * Lorton, VA 22079 */ // pre-compiled header support #include "pch.h" // softwareAB #include #include "sql_server.h" // ensure that we have access to standard data types #define DBNTWIN32 #include #include // Include ODS headers (one place in this app--protects us) #ifdef __cplusplus extern "C" { #endif #pragma warning( disable: 4121 ) #include // Main header file that includes all other header files #pragma warning( default: 4121 ) #ifdef __cplusplus } #endif namespace { SRV_PROC *_srv( abrSRV_PROC *pSrv ) { return reinterpret_cast(pSrv) ; } //srv } //namespace using namespace ABR ; #define IS_LEAP( x ) ( ( 0 == (x) % 4 ) && ( ( 0 != (x) % 100 ) || ( 0 == ( x) % 400 ) ) ) //////////////////////////////////////////////////////////////////////// // LOCAL FUNCTIONS namespace { ABR::SqlServerDatetime xlat_dbdatetime( const DBDATETIME &_dt ) { DBDATETIME dt = _dt ; ABR::SqlServerDatetime result ; // assume 365 day year int nYearDays = 365 ; // account for the years first (since 1900, remember) int nYear = 1900 + ( dt.dtdays / 365 ) ; dt.dtdays %= 365 ; // account for bogus extra leapyear day if( IS_LEAP( nYear ) ) { ++dt.dtdays ; } //if // account for leapyears in the days count dt.dtdays -= ( nYear - 1900 ) / 4 ; nYear += dt.dtdays < 0? -1: 0 ; nYearDays += IS_LEAP( nYear )? 1: 0 ; if( dt.dtdays < 0 ) { dt.dtdays += nYearDays ; } //if // always add one back to the days count, since we subtracted // an extra day for 1900 (a non-leapyear) ++dt.dtdays ; // set year in result result.year( nYear ) ; // now we have number of days used in the year. // build the number of months. int anMonths[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } ; if( IS_LEAP( nYear ) ) ++anMonths[1] ; int nMonth = 0 ; while( dt.dtdays > anMonths[nMonth] ) { dt.dtdays -= anMonths[nMonth] ; ++nMonth ; } //while result.month( nMonth + 1 ) ; result.day( dt.dtdays ) ; // now convert the 300ths/second to hours/minutes/seconds/millis #define ONE_SECOND 300U #define ONE_MINUTE static_cast(ONE_SECOND * 60U) #define ONE_HOUR static_cast(ONE_MINUTE * 60U) result.hour( static_cast(dt.dttime / ONE_HOUR) ) ; dt.dttime %= ONE_HOUR ; result.minute( static_cast(dt.dttime / ONE_MINUTE) ) ; dt.dttime %= ONE_MINUTE ; result.second( static_cast(dt.dttime / ONE_SECOND) ) ; dt.dttime %= ONE_SECOND ; result.millis( static_cast(dt.dttime * 10U / 3U) ) ; return result ; } //xlat_dbdatetime DBDATETIME xlat_datetime( const ABR::SqlServerDatetime &_dt ) { int nYear = _dt.year() - 1900 ; // thanks to Gowri for this code long nl300thsSec = ( ( ( _dt.hour() * 60 ) + _dt.minute() ) * 60 + _dt.second() ) * 300 ; nl300thsSec += static_cast( static_cast(_dt.millis()) * 3.03334 / 10.0 ) ; long nlDaysSinceJan1st1900 = ( nYear / 4 ) * (365 * 4 + 1 /*leap year*/) - 1 /*1900*/ ; nlDaysSinceJan1st1900 = nlDaysSinceJan1st1900 < 0 ? 0 : nlDaysSinceJan1st1900 ; nlDaysSinceJan1st1900 += ((nYear % 4) * 365) ; nlDaysSinceJan1st1900 += ((nYear % 4) > 0)? 1: 0 ; // get CRT to give us the yearday struct tm _tm ; memset( &_tm, 0, sizeof( _tm ) ) ; _tm.tm_year = nYear ; _tm.tm_mon = _dt.month() - 1 ; _tm.tm_mday = _dt.day() ; _tm.tm_isdst = -1 ; ::mktime( &_tm ) ; nlDaysSinceJan1st1900 += _tm.tm_yday ; // set the structure used by SQL Server DBDATETIME dt ; memset( &dt, 0, sizeof( dt ) ) ; dt.dtdays = nlDaysSinceJan1st1900 ; dt.dttime = static_cast(nl300thsSec) ; return dt ; } //xlat_datetime } //namespace //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerUtils bool ABR::SqlServerUtils::is_int( int nDataType ) { switch( nDataType ) { case SRVBIT: case SRVINT1: case SRVINT2: case SRVINT4: case SRVINTN: return true ; default: return false ; } //switch } //is_int bool ABR::SqlServerUtils::is_float( int nDataType ) { switch( nDataType ) { case SRVDECIMAL: case SRVDECIMALN: case SRVFLT4: case SRVFLT8: case SRVFLTN: case SRVMONEY4: case SRVMONEY: case SRVMONEYN: case SRVNUMERIC: case SRVNUMERICN: return true ; default: return false ; } //switch } //is_float bool ABR::SqlServerUtils::is_string( int nDataType ) { switch( nDataType ) { case SRVCHAR: case SRVTEXT: case SRVVARCHAR: return true ; default: return false ; } //switch } //is_string bool ABR::SqlServerUtils::is_char( int nDataType ) { switch( nDataType ) { case SRVCHAR: return true ; default: return false ; } //switch } //is_char bool ABR::SqlServerUtils::is_varchar( int nDataType ) { switch( nDataType ) { case SRVVARCHAR: return true ; default: return false ; } //switch } //is_varchar bool ABR::SqlServerUtils::is_int4( int nDataType ) { switch( nDataType ) { case SRVINT4: return true ; default: return false ; } //switch } //is_int4 bool ABR::SqlServerUtils::is_intn( int nDataType ) { switch( nDataType ) { case SRVINTN: return true ; default: return false ; } //switch } //is_intn bool ABR::SqlServerUtils::is_binary( int nDataType ) { switch( nDataType ) { case SRVBINARY: //lint !e1924 case SRVVARBINARY: //lint !e1924 return true ; default: return false ; } //switch } //is_varbinary bool ABR::SqlServerUtils::is_datetime( int nDataType ) { switch( nDataType ) { case SRVDATETIME: case SRVDATETIMN: return true ; default: return false ; } //switch } //is_datetime //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerDatetime_raw ABR::SqlServerDatetime_raw::SqlServerDatetime_raw() : m_data( 0 ) { m_data = new DBDATETIME ; memset( m_data, 0, sizeof( m_data ) ) ; } //ctor ABR::SqlServerDatetime_raw::~SqlServerDatetime_raw() { delete reinterpret_cast(m_data) ; } //dtor //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerDatetime ABR::SqlServerDatetime_rawPtr ABR::SqlServerDatetime::raw_ptr() const { // build a DBDATETIME from ourselves DBDATETIME dt = xlat_datetime( *this ) ; // build an object and copy the xlated data to it SqlServerDatetime_rawPtr result( new SqlServerDatetime_raw ) ; memcpy( result->data(), &dt, sizeof( dt ) ) ; // managed automatically by the language return result ; } //raw_ptr //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerParm ABR::SqlServerDatetime::SqlServerDatetime( const time_t &_o ) : m_year( 0 ) , m_month( 0 ) , m_day( 0 ) , m_hour( 0 ) , m_minute( 0 ) , m_second( 0 ) , m_millis( 0 ) { struct tm _tm = *::localtime( &_o ) ; year( _tm.tm_year + 1900 ) ; month( _tm.tm_mon + 1 ) ; day( _tm.tm_mday ) ; hour( _tm.tm_hour ) ; minute( _tm.tm_min ) ; second( _tm.tm_sec ) ; } //ctor //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerCol int ABR::SqlServerCol::data_length( Types nDataType ) { switch( nDataType ) { case type_int: return sizeof( int ) ; case type_float: return sizeof( double ) ; case type_string: return 1 ; case type_char: return 1 ; case type_binary: return 1 ; case type_datetime: return sizeof( DBDATETIME ) ; case type_bit: return sizeof( int ) ; default: break ; } //switch return 0 ; } //data_length ABR::SqlServerCol::SqlServerCol() : m_col_number( 0 ) , m_col_type( type_none ) , m_col_data( NULL ) , m_col_size( 0 ) { } //ctor ABR::SqlServerCol::SqlServerCol( int _col_number, Types _col_type, const void *_col_data, int _col_size ) : m_col_number( _col_number ) , m_col_type( _col_type ) , m_col_data( NULL ) , m_col_size( _col_size > 0? _col_size: 0 ) { if( _col_data && ( col_size() > 0 ) ) { m_col_data = calloc( 1, static_cast(col_size()) ) ; if( m_col_data ) { memcpy( m_col_data, _col_data, static_cast(col_size()) ) ; } else { m_col_size = 0 ; } //if } //if } //ctor //lint -e668 ABR::SqlServerCol::SqlServerCol( const SqlServerCol &o ) : m_col_number( o.col_number() ) , m_col_type( o.col_type() ) , m_col_data( NULL ) , m_col_size( o.col_size() > 0? o.col_size(): 0 ) { if( o.col_data() && ( col_size() > 0 ) ) { m_col_data = calloc( 1, static_cast(col_size()) ) ; if( m_col_data ) { memcpy( m_col_data, o.col_data(), static_cast(col_size()) ) ; } else { m_col_size = 0 ; } //if } //if } //ctor //lint -e668 ABR::SqlServerCol & ABR::SqlServerCol::operator=( const SqlServerCol &o ) { if( this != &o ) { m_col_number = o.col_number() ; m_col_type = o.col_type() ; m_col_size = o.col_size() ; if( m_col_data ) free( m_col_data ) ; m_col_data = NULL ; if( o.col_data() && ( col_size() > 0 ) ) { m_col_data = calloc( 1, static_cast(col_size()) ) ; if( m_col_data ) { memcpy( m_col_data, o.col_data(), static_cast(col_size()) ) ; } else { m_col_size = 0 ; } //if } //if } //if return *this ; } //= ABR::SqlServerCol::~SqlServerCol() { if( m_col_data ) { free( m_col_data ) ; } //if m_col_data = NULL ; } //dtor void ABR::SqlServerCol::set_data( const void *_col_data, int _col_size ) { if( m_col_data ) { free( m_col_data ) ; } //if m_col_data = NULL ; m_col_size = 0 ; if( _col_data && ( _col_size > 0 ) ) { m_col_data = calloc( 1, static_cast(_col_size) ) ; if( m_col_data ) { m_col_size = _col_size ; memcpy( m_col_data, _col_data, static_cast(col_size()) ) ; } //if } //if } //set_data void ABR::SqlServerCol::set_data( const SqlServerDatetime &_dt ) { DBDATETIME dt = xlat_datetime( _dt ) ; set_data( &dt, sizeof( dt ) ) ; } //set_data ABR::SqlServerDatetime ABR::SqlServerCol::as_datetime() const { // sanity const void *p = as_binary() ; if( !p ) { return SqlServerDatetime() ; } //if // convert return xlat_dbdatetime( *reinterpret_cast(p) ) ; } //as_datetime //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServerParm ABR::SqlServerParm::SqlServerParm( DebugUtil &_debug , abrSRV_PROC *_pSrv , int _parm_number , bool bInitialize ) : m_debug( _debug ) , m_srv( _pSrv ) , m_parm_number( 0 ) , m_data_type( 0 ) , m_max_len( 0 ) , m_actual_len( 0 ) , m_parm_data( NULL ) , m_is_null( FALSE ) , m_is_output( FALSE ) { if( bInitialize ) { load( _debug, _pSrv, _parm_number ) ; } //if } //ctor ABR::SqlServerParm::SqlServerParm( const SqlServerParm &o ) : m_debug( (const_cast(o)).debug() ) , m_srv( (const_cast(o)).srv() ) , m_parm_number( 0 ) , m_data_type( 0 ) , m_max_len( 0 ) , m_actual_len( 0 ) , m_parm_data( NULL ) , m_is_null( FALSE ) , m_is_output( FALSE ) { assign( o ) ; } //ctor ABR::SqlServerParm & ABR::SqlServerParm::operator=( const SqlServerParm &o ) { if( this != &o ) { // clean up and copy reset() ; assign( o ) ; } //if return *this ; } //= ABR::SqlServerParm::~SqlServerParm() { try { reset() ; } catch( ... ) {} m_srv = NULL ; } //reset void ABR::SqlServerParm::load( DebugUtil &_debug, abrSRV_PROC *pSrvIn, int _parm_number ) { ABR_STACK_TRACE( _debug, "SqlServerParm::load" ) ; SRV_PROC *pSrv = _srv( pSrvIn ) ; // ensure we're clean reset() ; // save parm number parm_number( _parm_number ) ; // get data info int i = srv_paramtype( pSrv, _parm_number ) ; if( -1 == i ) { ABR_EXCEPT_FATAL( _debug, debug_printf( "Error retrieving data type for parameter %d", _parm_number ) ) ; } //if m_data_type = static_cast(i) ; i = srv_parammaxlen( pSrv, _parm_number ) ; if( -1 == i ) { ABR_EXCEPT_FATAL( _debug, debug_printf( "Error retrieving max length for parameter %d", _parm_number ) ) ; } //if m_max_len = static_cast(i) ; i = srv_paramlen( pSrv, _parm_number ) ; if( -1 == i ) { ABR_EXCEPT_FATAL( _debug, debug_printf( "Error retrieving actual length for parameter %d", _parm_number ) ) ; } //if m_actual_len = static_cast(i) ; m_is_null = FALSE ; void *pData = srv_paramdata( pSrv, _parm_number ) ; if( !pData ) { m_is_null = TRUE ; } else if( actual_len() > 0 ) { // allocate memory and copy (with NULL-termination for safety) m_parm_data = new BYTE[actual_len() + 1] ; memcpy( m_parm_data, pData, actual_len() ) ; m_parm_data[actual_len()] = 0 ; } //if // get whether this is an output parm or not m_is_output = FALSE ; if( SRV_PARAMRETURN & srv_paramstatus( pSrv, _parm_number ) ) { m_is_output = TRUE ; } //if // parm name DBCHAR *pName ; int name_len ; pName = srv_paramname( pSrv, _parm_number, &name_len ) ; if( name_len > 0 ) { m_name = pName ; } //if /* try { ABR_DEBUG_INFO( _debug, MEDIUM, debug_printf( "Parm #%d (%s): type %d; max %lu; actual %lu; %s", parm_number(), name().empty()? "": name().c_str(), data_type(), max_len(), actual_len(), to_string().c_str() ) ) ; } catch( const Except & ) { // conversion error--show all but the data ABR_DEBUG_INFO( _debug, MEDIUM, debug_printf( "Parm #%d (%s): type %d; max %lu; actual %lu; %s", parm_number(), name().empty()? "": name().c_str(), data_type(), max_len(), actual_len(), "" ) ) ; } //try */ } //load void ABR::SqlServerParm::parm_data( const BYTE *x ) { // sanity if( m_parm_data == x ) return ; // release old data delete []m_parm_data ; m_parm_data = NULL ; // allocate new if( !is_null() ) { if( actual_len() > 0 ) { // the extra byte is so we can physically insert a NULL for safety m_parm_data = new BYTE[actual_len() + 1] ; memcpy( m_parm_data, x, actual_len() ) ; m_parm_data[actual_len()] = 0 ; } //if } //if } //parm_data std::string ABR::SqlServerParm::to_string() const { // handle NULL and empty if( is_null() ) { return "" ; } //if if( ( actual_len() == 0 ) || !parm_data() ) { return "" ; } //if int reqtype = SRVVARCHAR ; // anything to do? if( reqtype == data_type() ) { return reinterpret_cast(parm_data()) ; } //if // will this convert? (non-fatal upon failure) if( !srv_willconvert( data_type(), reqtype ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Error converting Parm #%d (%s) to VARCHAR", parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // do the conversion (account for long result strings) SRV_PROC *pSrv = _srv( m_srv ) ; DBCHAR buff[32768] ; memset( buff, 0, sizeof( buff ) ) ; srv_convert( pSrv, data_type(), m_parm_data, static_cast(actual_len()), reqtype, buff, -1 ) ; return buff ; } //to_string int ABR::SqlServerParm::to_int() const { // handle NULL and empty if( is_null() || ( actual_len() == 0 ) || !parm_data() ) { return 0 ; } //if int reqtype = SRVINT4 ; // anything to do? if( reqtype == data_type() ) { //lint -e{826} return *reinterpret_cast(parm_data()) ; } //if // will this convert? (non-fatal upon failure) if( !srv_willconvert( data_type(), reqtype ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Error converting Parm #%d (%s) to INT4", parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // do the conversion SRV_PROC *pSrv = _srv( m_srv ) ; int buff = 0 ; srv_convert( pSrv, data_type(), m_parm_data, static_cast(actual_len()) , reqtype, &buff, sizeof( buff ) ) ; return buff ; } //to_int double ABR::SqlServerParm::to_double() const { // handle NULL and empty if( is_null() || ( actual_len() == 0 ) || !parm_data() ) { return 0.0 ; } //if int reqtype = SRVFLT8 ; // anything to do? if( reqtype == data_type() ) { //lint -e826 return *reinterpret_cast(parm_data()) ; } //if // will this convert? (non-fatal upon failure) if( !srv_willconvert( data_type(), reqtype ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Error converting Parm #%d (%s) to FLT8", parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // do the conversion SRV_PROC *pSrv = _srv( m_srv ) ; double buff = 0.0 ; int i = srv_convert( pSrv, data_type(), m_parm_data, static_cast(actual_len()) , reqtype, &buff, sizeof( buff ) ) ; if( -1 == i ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Error converting Parm #%d (%s) to FLT8", parm_number(), name().empty()? "": name().c_str() ) ) ; } //if return buff ; } //to_double ABR::SqlServerDatetime ABR::SqlServerParm::to_datetime() const { // handle NULL and empty if( is_null() || ( actual_len() == 0 ) || !parm_data() ) { return SqlServerDatetime() ; } //if // must be a datetime if( !SqlServerUtils::is_datetime( data_type() ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): can only call to_date for DATETIME parms", parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // we convert these manually const BYTE *p = parm_data() ; return xlat_dbdatetime( *reinterpret_cast(p) ) ; } //to_datetime void ABR::SqlServerParm::set_output_data( const std::string &_data ) const { // must be OUTPUT parm if( !is_output() ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be an OUTPUT parm" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // verify the type if( !SqlServerUtils::is_varchar( data_type() ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be a VARCHAR field" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // verify the output parm size if( _data.size() > max_len() ) { ABR_DEBUG_PROBLEM( m_debug, WARNING, debug_printf( "Parm #%d (%s): Max output size is %d, trying to return %d chars" , parm_number(), name().empty()? "": name().c_str() , max_len(), _data.size() ) ) ; } //if // set the data char buff[256] ; if( _data.size() > 255 ) { memcpy( buff, _data.c_str(), 255 ) ; buff[255] = 0 ; } else { strcpy( buff, _data.c_str() ) ; } //if if( strlen( buff ) > max_len() ) { buff[max_len()] = 0 ; } //if SRV_PROC *pSrv = _srv( const_cast(this)->srv() ) ; int rc = srv_paramset( pSrv , parm_number() , buff , static_cast(strlen( buff )) ) ; if( FAIL == rc ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Error setting output data" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if } //set_output_data void ABR::SqlServerParm::set_output_data( int _data ) const { // must be OUTPUT parm if( !is_output() ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be an OUTPUT parm" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // verify the type if( !SqlServerUtils::is_intn( data_type() ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be an INTN field" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // set the data SRV_PROC *pSrv = _srv( const_cast(this)->srv() ) ; int buff = _data ; int rc = srv_paramset( pSrv, parm_number(), &buff, sizeof( buff ) ) ; if( FAIL == rc ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Error setting output data" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if } //set_output_data void ABR::SqlServerParm::set_output_data( const SqlServerDatetime &_data ) const { // must be OUTPUT parm if( !is_output() ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be an OUTPUT parm" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // verify the type if( !SqlServerUtils::is_datetime( data_type() ) ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Must be an DATETIME field" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if // set the data SRV_PROC *pSrv = _srv( const_cast(this)->srv() ) ; DBDATETIME buff = xlat_datetime( _data ) ; int rc = srv_paramset( pSrv, parm_number(), &buff, sizeof( buff ) ) ; if( FAIL == rc ) { ABR_EXCEPT_ERROR( m_debug, debug_printf( "Parm #%d (%s): Error setting output data" , parm_number(), name().empty()? "": name().c_str() ) ) ; } //if } //set_output_data void ABR::SqlServerParm::assign( const SqlServerParm &rhs ) { // simple assignment parm_number( rhs.parm_number() ) ; data_type( rhs.data_type() ) ; max_len( rhs.max_len() ) ; actual_len( rhs.actual_len() ) ; is_null( rhs.is_null() ) ; is_output( rhs.is_output() ) ; name( rhs.name().c_str() ) ; // set last parm_data( rhs.parm_data() ) ; } //assign void ABR::SqlServerParm::reset() { // reset all simple vars m_parm_number = 0 ; m_data_type = 0 ; m_max_len = 0 ; m_actual_len = 0 ; m_is_null = FALSE ; m_is_output = FALSE ; m_name = "" ; // clear out memory delete []m_parm_data ; m_parm_data = NULL ; } //reset //////////////////////////////////////////////////////////////////////// // IMPLEMENTATION: SqlServer // defined errors /// general error class #define MAX_SERVER_ERROR 20000 #define ABR_XP_MSGNUM_ERROR ((MAX_SERVER_ERROR) + 1) /// general informational class #define ABR_XP_CLASS_INFO 10 #define ABR_XP_CLASS_WARNING 14 #define ABR_XP_CLASS_ERROR 16 #define ABR_XP_CLASS_FATAL 17 ABR::SqlServer::SqlServer( DebugUtil &_debug, abrSRV_PROC *pSrv ) : m_debug( _debug ) , m_pSrv( pSrv ) , m_nDescribeCol( 1 ) { } //ctor ABR::SqlServer::~SqlServer() { m_pSrv = NULL ; } //dtor void ABR::SqlServer::init() { ABR_STACK_TRACE( debug(), "SqlServer::init" ) ; // empty data m_parms = SqlServerParms() ; SRV_PROC *pSrv = _srv( m_pSrv ) ; // load up parms--throws exception on error int nParms = srv_rpcparams( pSrv ) ; for( int i = 0 ; i < nParms ; ++i ) { m_parms.push_back( SqlServerParm( debug(), srv(), i + 1, true ) ) ; } //for } //init void ABR::SqlServer::senddone( abrSRVRETCODE result ) { SRV_PROC *pSrv = _srv( m_pSrv ) ; if( SRV_ERROR == result ) { srv_senddone( pSrv, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0 ) ; } else { srv_senddone( pSrv, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1 ) ; } //if } //senddone void ABR::SqlServer::notify( IDebugSink::ErrorLevel nErrorLevel , IDebugSink::Category , LPCSTR strFile , int nLine , const std::string &strMessage ) { // vars for msg int msgtype = 0 ; DBINT msgnum = 0 ; DBTINYINT msgclass = 0, msgstate = 0 ; DBCHAR *rpcname = NULL ; int rpcnamelen = 0; DBUSMALLINT linenum = 0 ; DBCHAR *message = NULL ; int msglen = 0 ; SRV_PROC *pSrv = _srv( m_pSrv ) ; // message type //msgtype = ( IDebugSink::ABRNone == nErrorLevel )? SRV_MSG_INFO: SRV_MSG_ERROR ; msgtype = SRV_MSG_INFO ; // message number msgnum = ABR_XP_MSGNUM_ERROR ; // message class switch( nErrorLevel ) { case IDebugSink::ABRNone : msgclass = ABR_XP_CLASS_INFO ; break ; case IDebugSink::ABRWarning : msgclass = ABR_XP_CLASS_WARNING ; break ; case IDebugSink::ABRError : msgclass = ABR_XP_CLASS_ERROR ; break ; case IDebugSink::ABRFatal : msgclass = ABR_XP_CLASS_FATAL ; break ; default : break ; } //switch msgclass = ABR_XP_CLASS_INFO ; // message state // TODO: base this on a "last error number..." msgstate = 1 ; // rcp info not supported now rpcname = NULL ; rpcnamelen = 0 ; // use the line number for the line number linenum = static_cast(nLine) ; // construct the message (max: 255 chars) std::string msg ; const char *p = strrchr( strFile, '\\' ) ; if( !p ) p = strrchr( strFile, '/' ) ; if( p ) ++p ; else p = strFile ; msg.append( p ) ; msg.append( ": " ) ; msg.append( strMessage ) ; if( strlen( msg.c_str() ) > 255 ) { char buff[256] ; strncpy( buff, msg.c_str(), 255 ) ; buff[255] = 0 ; msg = buff ; } //if message = reinterpret_cast(const_cast(msg.c_str())) ; msglen = SRV_NULLTERM ; // send to SQL server srv_sendmsg( pSrv, msgtype, msgnum, msgclass, msgstate, rpcname, rpcnamelen, linenum, message, msglen ) ; } //notify // for lint--it got confused (ODS_VERSION is really defined here) #ifndef ODS_VERSION #define ODS_VERSION 0 #endif ULONG ABR::SqlServer::version() { return ODS_VERSION ; } //version void ABR::SqlServer::rs_col_describe( const std::string &col_name, SqlServerCol::Types col_type, int col_len ) { // determine the type BYTE bType = 0 ; switch( col_type ) { case SqlServerCol::type_int: bType = SRVINT4 ; break ; case SqlServerCol::type_float: bType = SRVFLT8 ; break ; case SqlServerCol::type_string: bType = SRVVARCHAR ; break ; case SqlServerCol::type_char: bType = SRVCHAR ; break ; case SqlServerCol::type_binary: bType = SRVVARBINARY ; break ; case SqlServerCol::type_datetime: bType = SRVDATETIME ; break ; default: ABR_EXCEPT_FATAL( debug(), debug_printf( "Invalid data type %d for column %s", static_cast(col_type), col_name.c_str() ) ) ; break ; } //switch SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_describe( pSrv, m_nDescribeCol++, const_cast(col_name.c_str()), SRV_NULLTERM, bType, col_len, bType, 0, NULL ) ; if( !rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error describing column %s", col_name.c_str() ) ) ; } //if } //describe void ABR::SqlServer::rs_col_setdata( int col_number, const int &col_data ) { // the const_cast below is intentional; required by // srv_setcoldata (as if the data was mutable!) SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_setcoldata( pSrv, col_number, &(const_cast(col_data)) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting column data: %d, %d", col_number, col_data ) ) ; } //if rc = srv_setcollen( pSrv, col_number, sizeof( col_data ) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting column length: %d, %d", col_number, col_data ) ) ; } //if } //rs_col_setdata void ABR::SqlServer::rs_col_setdata( int col_number, const double &col_data ) { // the const_cast below is intentional; required by // srv_setcoldata (as if the data was mutable!) SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_setcoldata( pSrv, col_number, &(const_cast(col_data)) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting column data: %d, %d", col_number, col_data ) ) ; } //if rc = srv_setcollen( pSrv, col_number, sizeof( col_data ) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting column length: %d, %d", col_number, col_data ) ) ; } //if } //rs_col_setdata void ABR::SqlServer::rs_col_setdata( int col_number, const char *col_data, int col_len ) { // convenience wrapper for binary data send rs_col_setdata( col_number , reinterpret_cast(col_data) , col_len , "LPCTSTR" ) ; } //rs_col_setdata void ABR::SqlServer::rs_col_setdata( int col_number, const SqlServerDatetime_raw &col_data ) { // the const_cast below is intentional; required by // srv_setcoldata (as if the data was mutable!) SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_setcoldata( pSrv, col_number, const_cast(col_data.data()) ) ; rc = srv_setcollen( pSrv, col_number, static_cast(sizeof( DBDATETIME )) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting column length for DBDATETIME: %d", col_number ) ) ; } //if } //rs_col_setdata void ABR::SqlServer::rs_col_setdata( int col_number , const void *col_data , int col_len , const char *_type ) { // the const_cast below is intentional; required by // srv_setcoldata (as if the data was mutable!) SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_setcoldata( pSrv, col_number, const_cast(col_data) ) ; rc = srv_setcollen( pSrv, col_number, col_len ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error setting %s column length: %d, %d", _type, col_number, col_len ) ) ; } //if } //rs_col_setdata void ABR::SqlServer::rs_send() { SRV_PROC *pSrv = _srv( m_pSrv ) ; int rc = srv_sendrow( pSrv ) ; if( SUCCEED != rc ) { ABR_EXCEPT_FATAL( debug(), debug_printf( "Error sending row back to SQL Server" ) ) ; } //if } //rs_send ABR::SqlServer::ConnPtr ABR::SqlServer::make_conn( const std::string &server_name ) { // automatically managed connection object tied to this server ConnPtr result( new SqlServerConn( *this ) ) ; // now do the connection open_connection( server_name , result->dbprocess() , result->loginrec() , result->impersonated() , "abr_persistent_conn" ) ; // final result return result ; } //make_conn int ABR::SqlServer::issue_query( const std::string &server_name , const std::string &query , ISqlServerRowHandler &row_handler , ConnPtr *_conn ) { ABR_DEBUG_INFO( m_debug, LOW, "About to issue internal query" ) ; return issue_query_i( server_name, query, row_handler, _conn ) ; } //issue_query int ABR::SqlServer::issue_cmd( const std::string &server_name , const std::string &cmd , ConnPtr *_conn ) { return issue_cmd_i( server_name, cmd, _conn ) ; } //issue_cmd void ABR::SqlServer::open_connection( const std::string &server_name , void *&_dbproc , void *&_loginrec , int &bImpersonated , const char *szId ) { // NOTE: the caller should not catch exceptions thrown by this // method. the method ensures that it cleans up after itself in // the event of an error. // deref vars SRV_PROC *srvproc = _srv( m_pSrv ) ; DBPROCESS *&dbproc = reinterpret_cast(_dbproc) ; LOGINREC *&loginrec = reinterpret_cast(_loginrec) ; // Get a loginrec ABR_DEBUG_INFO( m_debug, LOW, "Allocating a login record" ) ; loginrec = dblogin() ; ABR_DEBUG_INFO( m_debug, LOW, "Allocated a login record" ) ; // Check for integrated security. if( !strcmp( srv_pfield( srvproc, SRV_LSECURE, NULL ), "TRUE" ) ) { // Client has accessed using some form of integrated security // Impersonate client and set DBSETLSECURE flag ABR_DEBUG_INFO( m_debug, LOW, "Connecting using integrated security" ) ; bImpersonated = srv_impersonate_client( srvproc ) ; DBSETLSECURE( loginrec ) ; } else { // Set the user name, password, and application name for the remote ABR_DEBUG_INFO( m_debug, LOW, "Connecting using impersonation" ) ; DBSETLUSER( loginrec, srv_pfield( srvproc, SRV_USER, NULL ) ); DBSETLPWD( loginrec, srv_pfield( srvproc, SRV_PWD, NULL ) ); } //if DBSETLAPP( loginrec, szId ) ; ABR_DEBUG_INFO( m_debug, LOW, "Login record set" ) ; // Since the servername parameter is set to NULL, the connection will be // opened to the local DBMS. std::string local_server_name = trim( server_name ) ; if( local_server_name.empty() ) { ABR_DEBUG_INFO( m_debug, LOW, "Connecting to local database" ) ; dbproc = dbopen( loginrec, NULL ) ; } else { ABR_DEBUG_INFO( m_debug, LOW, "Connecting to named database '" << local_server_name.c_str() << "'" ) ; dbproc = dbopen( loginrec, local_server_name.c_str() ) ; } //if if( !dbproc ) { // clear up the allocated objects and revert ABR_DEBUG_INFO( m_debug, LOW, "Connection failed" ) ; dbfreelogin( loginrec ) ; if( bImpersonated ) srv_revert_to_self( srvproc ) ; ABR_EXCEPT_FATAL( debug(), debug_printf( "Error opening connection to %s", local_server_name.c_str() ) ) ; } //if dbsetuserdata( dbproc, reinterpret_cast(srvproc) ) ; ABR_DEBUG_INFO( m_debug, LOW, "Connection complete" ) ; // Bind to the clients connection for shared transaction space. char szBindToken[256] ; srv_getbindtoken( srvproc, szBindToken ) ; dbfcmd( dbproc, "exec sp_bindsession \'%s\'", szBindToken ) ; dbsqlexec( dbproc ) ; ABR_DEBUG_INFO( m_debug, LOW, debug_printf( "Connection is bound to token '%s'", szBindToken ) ) ; // NOTE: after this point, it's crucial that the caller call // close_connection before returning to the ultimate caller. } //open_connection void ABR::SqlServer::run_query( void *&_dbproc , const std::string &sql ) { // deref vars DBPROCESS *&dbproc = reinterpret_cast(_dbproc) ; // Execute the SQL stmt dbcmd( dbproc, sql.c_str() ) ; RETCODE rc = dbsqlexec( dbproc ) ; ABR_DEBUG_INFO( m_debug, LOW, debug_printf( "dbsqlexec returned %d", static_cast(rc) ) ) ; if( SUCCEED != rc ) { ABR_EXCEPT_ERROR( debug(), debug_printf( "Error executing SQL '%s'", sql.c_str() ) ) ; } //if } //run_query void ABR::SqlServer::close_connection( void *&_dbproc , void *&_loginrec , int &bImpersonated ) { // deref vars SRV_PROC *srvproc = _srv( m_pSrv ) ; DBPROCESS *&dbproc = reinterpret_cast(_dbproc) ; LOGINREC *&loginrec = reinterpret_cast(_loginrec) ; if( dbproc ) { try { dbclose( dbproc ) ; } catch( ... ) {} dbproc = NULL ; } //if if( loginrec ) { try { dbfreelogin( loginrec ) ; } catch( ... ) {} loginrec = NULL ; } //if try { if( bImpersonated ) srv_revert_to_self( srvproc ) ; } catch( ... ) {} bImpersonated = 0 ; } //close_connection void ABR::SqlServer::build_row_headers( void *_dbproc , SqlServerRow &row , int &nCols , bool &bProcessRows ) { // deref DBPROCESS *dbproc = reinterpret_cast(_dbproc) ; nCols = dbnumcols( dbproc ) ; bProcessRows = true ; // Build the row description for the client return. for( int nCol = 1 ; nCol <= nCols ; ++nCol ) { // get the data information DBINT coltype = dbcoltype( dbproc, nCol ) ; //DBINT collen = dbcollen( dbproc, nCol ) ; SqlServerCol::Types our_type = SqlServerCol::type_none ; ABR_DEBUG_INFO( debug(), LOW, debug_printf( "Checking header column type %d", coltype ) ; ) ; // convert to our subset if( SqlServerUtils::is_char( coltype ) ) { our_type = SqlServerCol::type_char ; } else if( SqlServerUtils::is_string( coltype ) ) { our_type = SqlServerCol::type_string ; } else if( SqlServerUtils::is_int( coltype ) ) { our_type = SqlServerCol::type_int ; } else if( SqlServerUtils::is_float( coltype ) ) { our_type = SqlServerCol::type_float ; } else if( SqlServerUtils::is_binary( coltype ) ) { our_type = SqlServerCol::type_binary ; } else { // we have a problem, so rows can't be processed. // however, it's crucial that we fully iterate over // all resultsets anyway. bProcessRows = false ; ABR_DEBUG_INFO( debug(), HIGH, debug_printf( "Unsupported column type %d", coltype ) ) ; } //if // create the entry row.push_back( SqlServerCol( nCol, our_type, NULL, 0 ) ) ; } //for } //build_row_headers int ABR::SqlServer::handle_row( void *_dbproc , SqlServerRow &row , int nCols , ISqlServerRowHandler *_handler , int &nRowsFetched ) { // deref DBPROCESS *dbproc = reinterpret_cast(_dbproc) ; SRV_PROC *srvproc = _srv( m_pSrv ) ; ++nRowsFetched ; // load all current column data bool bColumnsOK = true ; for( int nCol = 1 ; nCol <= nCols ; ++nCol ) { // access information DBINT coltype = dbcoltype( dbproc, nCol ) ; DBINT collen = dbdatlen( dbproc, nCol ) ; SqlServerCol &our_col = row[static_cast(nCol - 1)] ; // do the conversion int iBuff = 0 ; double dBuff = 0.0 ; char cBuff[8001] ; memset( cBuff, 0, sizeof( cBuff ) ) ; DBINT our_dbtype = 0 ; void *dest = NULL ; int destlen = 0 ; bool bConvert = true ; if( our_col.is_double() ) { our_dbtype = SRVFLT8 ; dest = &dBuff ; destlen = sizeof( double ) ; } else if( our_col.is_int() ) { our_dbtype = SRVINT4 ; dest = &iBuff ; destlen = sizeof( int ) ; } else if( our_col.is_string() ) { our_dbtype = SRVVARCHAR ; dest = cBuff ; destlen = -1 ; } else if( our_col.is_char() ) { our_dbtype = SRVCHAR ; dest = cBuff ; destlen = -1 ; } else if( our_col.is_binary() ) { our_dbtype = SRVVARBINARY ; dest = cBuff ; destlen = -1 ; ABR_DEBUG_INFO( debug(), LOW, debug_printf( "R%dC%d converting varbinary;" " coltype=%d; collen=%d;" " our_dbtype=%d; destlen=%d;" , nRowsFetched, nCol , static_cast(coltype) , static_cast(collen) , static_cast(our_dbtype) , destlen ) ) ; switch( coltype ) { case SRVBINARY: //lint !e1924 case SRVVARBINARY: //lint !e1924 // no need to convert bConvert = false ; dest = dbdata( dbproc, nCol ) ; destlen = collen ; break ; default: break ; } //switch } else { ABR_DEBUG_INFO( debug(), HIGH, debug_printf( "R%dC%d unsupported column type %d" , nRowsFetched, nCol, our_col.col_type() ) ) ; } //if int _rc = collen ; if( bConvert ) { _rc = srv_convert( srvproc, coltype, dbdata( dbproc, nCol ), collen , our_dbtype, dest, destlen ) ; } //if if( -1 == _rc ) { ABR_DEBUG_PROBLEM( debug(), ERROR, debug_printf( "Error converting R%dC%d", nRowsFetched, nCol ) ) ; bColumnsOK = false ; continue ; } //if // "correct" the length of the data if( bConvert && ( our_col.is_string() || our_col.is_char() || our_col.is_binary() ) ) { destlen = _rc ; ABR_DEBUG_INFO( debug(), LOW, debug_printf( "R%dC%d len=%d", nRowsFetched, nCol, destlen ) ) ; } //if // set the data in the row our_col.set_data( dest, destlen ) ; } //for // now issue the call bool bResult = bColumnsOK ; if( _handler && bResult ) { bResult = _handler->notify( row ) ; } //if if( !bResult ) { return -1 ; } //if return 0 ; } //handle_row int ABR::SqlServer::process_rows( void *_dbproc , SqlServerRow &row , int nCols , ISqlServerRowHandler *_handler , bool bProcessRows ) { // deref DBPROCESS *dbproc = reinterpret_cast(_dbproc) ; // handle the rows bool bMoreRows = true ; int nRowsFetched = 0 ; while( bMoreRows ) { RETCODE rc = dbnextrow( dbproc ) ; int row_rc = 0 ; switch( rc ) { case REG_ROW: if( bProcessRows ) { row_rc = handle_row( _dbproc, row, nCols, _handler, nRowsFetched ) ; } //if break ; case NO_MORE_ROWS: case FAIL: bMoreRows = false ; break ; default: // computed row if( bProcessRows ) { row_rc = handle_row( _dbproc, row, nCols, _handler, nRowsFetched ) ; } //if break ; } //switch // -1 from the handler indicates that the caller wants // an early termination. if( -1 == row_rc ) { ABR_DEBUG_PROBLEM( debug(), WARNING, debug_printf( "Error processing rows; continuing to empty resultset" ) ) ; bProcessRows = false ; } //if } //while // non-termination is the actual rows processed return nRowsFetched ; } //process_rows int ABR::SqlServer::process_query( void *_dbproc , ISqlServerRowHandler *_handler ) { // deref DBPROCESS *dbproc = reinterpret_cast(_dbproc) ; // Process the results. int nRowsProcessed = 0 ; bool bMoreResults = true, bProcessRows = true, bWasFail = false, bIsFail = false ; while( bMoreResults ) { // vars for the iteration SqlServerRow row ; int nCols = 0 ; int rows_rc = 0 ; // iterate the results RETCODE rc = dbresults( dbproc ) ; ABR_DEBUG_INFO( m_debug, LOW, debug_printf( "dbresults returned %d", static_cast(rc) ) ) ; switch( rc ) { case SUCCEED: if( _handler ) { build_row_headers( dbproc, row, nCols, bProcessRows ) ; if( !bProcessRows ) { // we had a problem. we must iterate over the entire // set of results, but we won't do any processing. // we're basically cleaning out the SQL Server buffer. ABR_DEBUG_INFO( m_debug, MEDIUM, debug_printf( "build_row_headers failed" ) ) ; nRowsProcessed = -1 ; } //if } //if rows_rc = process_rows( dbproc, row, nCols, _handler, bProcessRows ) ; if( -1 == rows_rc ) { // we had an error, don't process more rows. // however, we must still run through and empty all // resultsets. bProcessRows = false ; ABR_DEBUG_INFO( m_debug, MEDIUM, debug_printf( "process_rows returned -1" ) ) ; // indicate that we had a problem or that the user terminated // the processing. nRowsProcessed = -1 ; } else { // only add the results if no previous resultset iterations // failed. if( nRowsProcessed != -1 ) { nRowsProcessed += rows_rc ; ABR_DEBUG_INFO( m_debug, LOW, debug_printf( "Processed %d rows so far", static_cast(nRowsProcessed) ) ) ; } //if } //if break ; case FAIL: // could be more results (non-fatal) bIsFail = true ; break ; default: // no more results bMoreResults = false ; break ; } //switch if( bIsFail && bWasFail ) { // fail on two back-to-back failures bMoreResults = false ; } else { bWasFail = bIsFail ; } //if } //while // total rows processed (-1 on error or early termination) return nRowsProcessed ; } //process_query int ABR::SqlServer::issue_query_i( const std::string &server_name , const std::string &query , ISqlServerRowHandler &row_handler , ConnPtr *_conn ) { // the connection handler and the reference to the connection to use ConnPtr *_connp ; ConnPtr conn_ptr ; if( !_conn ) { // allocate an auto-closing connection ABR_DEBUG_INFO( m_debug, LOW, "Allocating auto-closing connection" ) ; conn_ptr = ConnPtr( new Conn( *this, false ) ) ; _connp = &conn_ptr ; } else { ABR_DEBUG_INFO( m_debug, LOW, "Using existing connection" ) ; _connp = _conn ; } //if ConnPtr &conn = *_connp ; // open if necessary if( !conn->is_external() ) { ABR_DEBUG_INFO( m_debug, LOW , "Opening connection to '" << server_name.c_str() << "'" ) ; open_connection( server_name , conn->dbprocess() , conn->loginrec() , conn->impersonated() , "abr_issue_query" ) ; } //if // run the query now int nRowsFetched = 0 ; ABR_DEBUG_INFO( m_debug, LOW , "Running query " << query.c_str() ) ; run_query( conn->dbprocess(), query ) ; ABR_DEBUG_INFO( m_debug, LOW , "Processing query" ) ; nRowsFetched = process_query( conn->dbprocess(), &row_handler ) ; // final result return nRowsFetched ; } //issue_query_i int ABR::SqlServer::issue_cmd_i( const std::string &server_name , const std::string &cmd , ConnPtr *_conn ) { // allocate a self-closing handle if necessary ConnPtr *_connp ; ConnPtr conn_ptr ; if( !_conn ) { // this handle closes upon object going out of scope conn_ptr = ConnPtr( new Conn( *this, false ) ) ; _connp = &conn_ptr ; } else { // user passed in an existing connection; use it // without auto-closing it _connp = _conn ; } //if ConnPtr &conn = *_connp ; // open if necessary if( !conn->is_external() ) { open_connection( server_name , conn->dbprocess() , conn->loginrec() , conn->impersonated() , "abr_issue_cmd" ) ; } //if // run the query int nRowsFetched = 0 ; run_query( conn->dbprocess(), cmd ) ; nRowsFetched = process_query( conn->dbprocess(), 0 ) ; // final result return nRowsFetched ; } //issue_cmd_i