2016-01-30 03:00:20 +01:00
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_results.cpp
//
// Contents: Result sets
//
// Microsoft Drivers 4.0 for PHP for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
# include "core_sqlsrv.h"
# include <functional>
# include <iostream>
# include <sstream>
using namespace core ;
// conversion matrix
// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
// this is initialized the first time the buffered result set is created.
sqlsrv_buffered_result_set : : conv_matrix_t sqlsrv_buffered_result_set : : conv_matrix ;
namespace {
// *** internal types ***
# pragma warning(disable:4200)
// *** internal constants ***
const int INITIAL_FIELD_STRING_LEN = 256 ; // base allocation size when retrieving a string field
// *** internal functions ***
// return an integral type rounded up to a certain number
template < int align , typename T >
T align_to ( T number )
{
DEBUG_SQLSRV_ASSERT ( ( number + align ) > number , " Number to align overflowed " ) ;
return ( ( number % align ) = = 0 ) ? number : ( number + align - ( number % align ) ) ;
}
// return a pointer address aligned to a certain address boundary
template < int align , typename T >
T * align_to ( T * ptr )
{
size_t p_value = ( size_t ) ptr ;
return align_to < align , size_t > ( p_value ) ;
}
// set the nth bit of the bitstream starting at ptr
void set_bit ( void * ptr , unsigned int bit )
{
unsigned char * null_bits = reinterpret_cast < unsigned char * > ( ptr ) ;
null_bits + = bit > > 3 ;
* null_bits | = 1 < < ( 7 - ( bit & 0x7 ) ) ;
}
// retrieve the nth bit from the bitstream starting at ptr
bool get_bit ( void * ptr , unsigned int bit )
{
unsigned char * null_bits = reinterpret_cast < unsigned char * > ( ptr ) ;
null_bits + = bit > > 3 ;
return ( ( * null_bits & ( 1 < < ( 7 - ( bit & 0x07 ) ) ) ) ! = 0 ) ;
}
// read in LOB field during buffered result creation
SQLPOINTER read_lob_field ( sqlsrv_stmt * stmt , SQLUSMALLINT field_index , sqlsrv_buffered_result_set : : meta_data & meta ,
2016-03-15 04:09:46 +01:00
zend_long mem_used TSRMLS_DC ) ;
2016-01-30 03:00:20 +01:00
// dtor for each row in the cache
void cache_row_dtor ( zval * data ) ;
// convert a number to a string using locales
// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of
// subclassing a new streambuf just to avoid the copy, it's easier to do the copy
template < typename Char , typename Number >
SQLRETURN number_to_string ( Number * number_data , __out void * buffer , SQLLEN buffer_length , __out SQLLEN * out_buffer_length ,
sqlsrv_error_auto_ptr & last_error )
{
std : : basic_ostringstream < Char > os ;
std : : locale loc ;
os . imbue ( loc ) ;
std : : use_facet < std : : num_put < Char > > ( loc ) . put ( std : : basic_ostream < Char > : : _Iter ( os . rdbuf ( ) ) , os , ' ' , * number_data ) ;
std : : basic_string < Char > & str_num = os . str ( ) ;
if ( os . fail ( ) ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error (
( SQLCHAR * ) " IMSSP " , ( SQLCHAR * ) " Failed to convert number to string " , - 1 ) ;
return SQL_ERROR ;
}
if ( str_num . size ( ) * sizeof ( Char ) + sizeof ( Char ) > ( size_t ) buffer_length ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error (
( SQLCHAR * ) " HY090 " , ( SQLCHAR * ) " Buffer length too small to hold number as string " , - 1 ) ;
return SQL_ERROR ;
}
* out_buffer_length = str_num . size ( ) * sizeof ( Char ) + sizeof ( Char ) ; // include NULL terminator
memcpy ( buffer , str_num . c_str ( ) , * out_buffer_length ) ;
return SQL_SUCCESS ;
}
template < typename Number , typename Char >
SQLRETURN string_to_number ( Char * string_data , SQLLEN str_len , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length , sqlsrv_error_auto_ptr & last_error )
{
Number * number_data = reinterpret_cast < Number * > ( buffer ) ;
std : : locale loc ; // default locale should match system
std : : basic_istringstream < Char > is ;
is . str ( string_data ) ;
is . imbue ( loc ) ;
std : : ios_base : : iostate st = 0 ;
std : : use_facet < std : : num_get < Char > > ( loc ) . get ( std : : basic_istream < Char > : : _Iter ( is . rdbuf ( ) ) ,
std : : basic_istream < Char > : : _Iter ( 0 ) , is , st , * number_data ) ;
if ( st & std : : ios_base : : failbit ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error (
( SQLCHAR * ) " 22003 " , ( SQLCHAR * ) " Numeric value out of range " , 103 ) ;
return SQL_ERROR ;
}
* out_buffer_length = sizeof ( Number ) ;
return SQL_SUCCESS ;
}
// "closure" for the hash table destructor
struct row_dtor_closure {
sqlsrv_buffered_result_set * results ;
BYTE * row_data ;
row_dtor_closure ( sqlsrv_buffered_result_set * st , BYTE * row ) :
results ( st ) , row_data ( row )
{
}
} ;
sqlsrv_error * odbc_get_diag_rec ( sqlsrv_stmt * odbc , SQLSMALLINT record_number )
{
SQLWCHAR wsql_state [ SQL_SQLSTATE_BUFSIZE ] ;
SQLWCHAR wnative_message [ SQL_MAX_MESSAGE_LENGTH + 1 ] ;
SQLINTEGER native_code ;
SQLSMALLINT wnative_message_len = 0 ;
SQLRETURN r = SQLGetDiagRecW ( SQL_HANDLE_STMT , odbc - > handle ( ) , record_number , wsql_state , & native_code , wnative_message ,
SQL_MAX_MESSAGE_LENGTH + 1 , & wnative_message_len ) ;
if ( ! SQL_SUCCEEDED ( r ) | | r = = SQL_NO_DATA ) {
return NULL ;
}
// convert the error into the encoding of the context
SQLSRV_ENCODING enc = odbc - > encoding ( ) ;
if ( enc = = SQLSRV_ENCODING_DEFAULT ) {
enc = odbc - > conn - > encoding ( ) ;
}
// convert the error into the encoding of the context
sqlsrv_malloc_auto_ptr < SQLCHAR > sql_state ;
2016-03-15 04:09:46 +01:00
SQLLEN sql_state_len = 0 ;
2016-01-30 03:00:20 +01:00
if ( ! convert_string_from_utf16 ( enc , wsql_state , sizeof ( wsql_state ) , ( char * * ) & sql_state , sql_state_len ) ) {
return NULL ;
}
sqlsrv_malloc_auto_ptr < SQLCHAR > native_message ;
2016-03-15 04:09:46 +01:00
SQLLEN native_message_len = 0 ;
2016-01-30 03:00:20 +01:00
if ( ! convert_string_from_utf16 ( enc , wnative_message , wnative_message_len , ( char * * ) & native_message , native_message_len ) ) {
return NULL ;
}
return new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error ( ( SQLCHAR * ) sql_state , ( SQLCHAR * ) native_message ,
native_code ) ;
}
} // namespace
// base class result set
sqlsrv_result_set : : sqlsrv_result_set ( sqlsrv_stmt * stmt ) :
odbc ( stmt )
{
}
// ODBC result set
// This object simply wraps ODBC function calls
sqlsrv_odbc_result_set : : sqlsrv_odbc_result_set ( sqlsrv_stmt * stmt ) :
sqlsrv_result_set ( stmt )
{
}
sqlsrv_odbc_result_set : : ~ sqlsrv_odbc_result_set ( void )
{
}
SQLRETURN sqlsrv_odbc_result_set : : fetch ( SQLSMALLINT orientation , SQLLEN offset TSRMLS_DC )
{
SQLSRV_ASSERT ( odbc ! = NULL , " Invalid statement handle " ) ;
return core : : SQLFetchScroll ( odbc , orientation , offset TSRMLS_CC ) ;
}
SQLRETURN sqlsrv_odbc_result_set : : get_data ( SQLUSMALLINT field_index , SQLSMALLINT target_type ,
__out SQLPOINTER buffer , SQLLEN buffer_length , __out SQLLEN * out_buffer_length ,
bool handle_warning TSRMLS_DC )
{
SQLSRV_ASSERT ( odbc ! = NULL , " Invalid statement handle " ) ;
return core : : SQLGetData ( odbc , field_index , target_type , buffer , buffer_length , out_buffer_length , handle_warning TSRMLS_CC ) ;
}
SQLRETURN sqlsrv_odbc_result_set : : get_diag_field ( SQLSMALLINT record_number , SQLSMALLINT diag_identifier ,
__out SQLPOINTER diag_info_buffer , SQLSMALLINT buffer_length ,
__out SQLSMALLINT * out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT ( odbc ! = NULL , " Invalid statement handle " ) ;
return core : : SQLGetDiagField ( odbc , record_number , diag_identifier , diag_info_buffer , buffer_length ,
out_buffer_length TSRMLS_CC ) ;
}
sqlsrv_error * sqlsrv_odbc_result_set : : get_diag_rec ( SQLSMALLINT record_number )
{
SQLSRV_ASSERT ( odbc ! = NULL , " Invalid statement handle " ) ;
return odbc_get_diag_rec ( odbc , record_number ) ;
}
SQLLEN sqlsrv_odbc_result_set : : row_count ( TSRMLS_D )
{
SQLSRV_ASSERT ( odbc ! = NULL , " Invalid statement handle " ) ;
return core : : SQLRowCount ( odbc TSRMLS_CC ) ;
}
// Buffered result set
// This class holds a result set in memory
sqlsrv_buffered_result_set : : sqlsrv_buffered_result_set ( sqlsrv_stmt * stmt TSRMLS_DC ) :
sqlsrv_result_set ( stmt ) ,
cache ( NULL ) ,
col_count ( 0 ) ,
meta ( NULL ) ,
current ( 0 ) ,
last_field_index ( - 1 ) ,
read_so_far ( 0 )
{
// 10 is an arbitrary number for now for the initial size of the cache
ALLOC_HASHTABLE ( cache ) ;
core : : sqlsrv_zend_hash_init ( * stmt , cache , 10 /* # of buckets */ , cache_row_dtor /*dtor*/ , 0 /*persistent*/ TSRMLS_CC ) ;
col_count = core : : SQLNumResultCols ( stmt TSRMLS_CC ) ;
// there is no result set to buffer
if ( col_count = = 0 ) {
return ;
}
SQLULEN null_bytes = ( col_count / 8 ) + 1 ; // number of bits to reserve at the beginning of each row for NULL flags
meta = static_cast < sqlsrv_buffered_result_set : : meta_data * > ( sqlsrv_malloc ( col_count *
sizeof ( sqlsrv_buffered_result_set : : meta_data ) ) ) ;
// set up the conversion matrix if this is the first time we're called
if ( conv_matrix . size ( ) = = 0 ) {
conv_matrix [ SQL_C_CHAR ] [ SQL_C_CHAR ] = & sqlsrv_buffered_result_set : : to_same_string ;
conv_matrix [ SQL_C_CHAR ] [ SQL_C_WCHAR ] = & sqlsrv_buffered_result_set : : system_to_wide_string ;
conv_matrix [ SQL_C_CHAR ] [ SQL_C_BINARY ] = & sqlsrv_buffered_result_set : : to_binary_string ;
conv_matrix [ SQL_C_CHAR ] [ SQL_C_DOUBLE ] = & sqlsrv_buffered_result_set : : string_to_double ;
conv_matrix [ SQL_C_CHAR ] [ SQL_C_LONG ] = & sqlsrv_buffered_result_set : : string_to_long ;
conv_matrix [ SQL_C_WCHAR ] [ SQL_C_WCHAR ] = & sqlsrv_buffered_result_set : : to_same_string ;
conv_matrix [ SQL_C_WCHAR ] [ SQL_C_BINARY ] = & sqlsrv_buffered_result_set : : to_binary_string ;
conv_matrix [ SQL_C_WCHAR ] [ SQL_C_CHAR ] = & sqlsrv_buffered_result_set : : wide_to_system_string ;
conv_matrix [ SQL_C_WCHAR ] [ SQL_C_DOUBLE ] = & sqlsrv_buffered_result_set : : wstring_to_double ;
conv_matrix [ SQL_C_WCHAR ] [ SQL_C_LONG ] = & sqlsrv_buffered_result_set : : wstring_to_long ;
conv_matrix [ SQL_C_BINARY ] [ SQL_C_BINARY ] = & sqlsrv_buffered_result_set : : to_same_string ;
conv_matrix [ SQL_C_BINARY ] [ SQL_C_CHAR ] = & sqlsrv_buffered_result_set : : binary_to_system_string ;
conv_matrix [ SQL_C_BINARY ] [ SQL_C_WCHAR ] = & sqlsrv_buffered_result_set : : binary_to_wide_string ;
conv_matrix [ SQL_C_LONG ] [ SQL_C_DOUBLE ] = & sqlsrv_buffered_result_set : : long_to_double ;
conv_matrix [ SQL_C_LONG ] [ SQL_C_LONG ] = & sqlsrv_buffered_result_set : : to_long ;
conv_matrix [ SQL_C_LONG ] [ SQL_C_BINARY ] = & sqlsrv_buffered_result_set : : to_long ;
conv_matrix [ SQL_C_LONG ] [ SQL_C_CHAR ] = & sqlsrv_buffered_result_set : : long_to_system_string ;
conv_matrix [ SQL_C_LONG ] [ SQL_C_WCHAR ] = & sqlsrv_buffered_result_set : : long_to_wide_string ;
conv_matrix [ SQL_C_DOUBLE ] [ SQL_C_DOUBLE ] = & sqlsrv_buffered_result_set : : to_double ;
conv_matrix [ SQL_C_DOUBLE ] [ SQL_C_BINARY ] = & sqlsrv_buffered_result_set : : to_double ;
conv_matrix [ SQL_C_DOUBLE ] [ SQL_C_CHAR ] = & sqlsrv_buffered_result_set : : double_to_system_string ;
conv_matrix [ SQL_C_DOUBLE ] [ SQL_C_LONG ] = & sqlsrv_buffered_result_set : : double_to_long ;
conv_matrix [ SQL_C_DOUBLE ] [ SQL_C_WCHAR ] = & sqlsrv_buffered_result_set : : double_to_wide_string ;
}
// get the meta data and calculate the size of a row buffer
SQLULEN offset = null_bytes ;
for ( SQLSMALLINT i = 0 ; i < col_count ; + + i ) {
core : : SQLDescribeCol ( stmt , i + 1 , NULL , 0 , NULL , & meta [ i ] . type , & meta [ i ] . length , & meta [ i ] . scale , NULL TSRMLS_CC ) ;
offset = align_to < 4 > ( offset ) ;
meta [ i ] . offset = offset ;
switch ( meta [ i ] . type ) {
// these types are the display size
case SQL_BIGINT :
case SQL_DECIMAL :
case SQL_GUID :
case SQL_NUMERIC :
core : : SQLColAttribute ( stmt , i + 1 , SQL_DESC_DISPLAY_SIZE , NULL , 0 , NULL ,
reinterpret_cast < SQLLEN * > ( & meta [ i ] . length ) TSRMLS_CC ) ;
meta [ i ] . length + = sizeof ( char ) + sizeof ( SQLULEN ) ; // null terminator space
offset + = meta [ i ] . length ;
break ;
// these types are the column size
case SQL_BINARY :
case SQL_CHAR :
case SQL_SS_UDT :
case SQL_VARBINARY :
case SQL_VARCHAR :
// var* field types are length prefixed
if ( meta [ i ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
offset + = sizeof ( void * ) ;
}
else {
meta [ i ] . length + = sizeof ( SQLULEN ) + sizeof ( char ) ; // length plus null terminator space
offset + = meta [ i ] . length ;
}
break ;
case SQL_WCHAR :
case SQL_WVARCHAR :
if ( meta [ i ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
offset + = sizeof ( void * ) ;
}
else {
meta [ i ] . length * = sizeof ( WCHAR ) ;
meta [ i ] . length + = sizeof ( SQLULEN ) + sizeof ( WCHAR ) ; // length plus null terminator space
offset + = meta [ i ] . length ;
}
break ;
// these types are LOBs
case SQL_LONGVARBINARY :
case SQL_LONGVARCHAR :
case SQL_WLONGVARCHAR :
case SQL_SS_XML :
meta [ i ] . length = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ;
offset + = sizeof ( void * ) ;
break ;
// these types are the ISO date size
case SQL_DATETIME :
case SQL_TYPE_DATE :
case SQL_SS_TIME2 :
case SQL_SS_TIMESTAMPOFFSET :
case SQL_TYPE_TIMESTAMP :
core : : SQLColAttribute ( stmt , i + 1 , SQL_DESC_DISPLAY_SIZE , NULL , 0 , NULL ,
reinterpret_cast < SQLLEN * > ( & meta [ i ] . length ) TSRMLS_CC ) ;
meta [ i ] . length + = sizeof ( char ) + sizeof ( SQLULEN ) ; // null terminator space
offset + = meta [ i ] . length ;
break ;
// these types are the native size
case SQL_BIT :
case SQL_INTEGER :
case SQL_SMALLINT :
case SQL_TINYINT :
meta [ i ] . length = sizeof ( long ) ;
offset + = meta [ i ] . length ;
break ;
case SQL_REAL :
case SQL_FLOAT :
meta [ i ] . length = sizeof ( double ) ;
offset + = meta [ i ] . length ;
break ;
default :
SQLSRV_ASSERT ( false , " Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query " ) ;
break ;
}
switch ( meta [ i ] . type ) {
case SQL_BIGINT :
case SQL_CHAR :
case SQL_DATETIME :
case SQL_DECIMAL :
case SQL_GUID :
case SQL_NUMERIC :
case SQL_LONGVARCHAR :
case SQL_TYPE_DATE :
case SQL_SS_TIME2 :
case SQL_SS_TIMESTAMPOFFSET :
case SQL_SS_XML :
case SQL_TYPE_TIMESTAMP :
case SQL_VARCHAR :
meta [ i ] . c_type = SQL_C_CHAR ;
break ;
case SQL_SS_UDT :
case SQL_LONGVARBINARY :
case SQL_BINARY :
case SQL_VARBINARY :
meta [ i ] . c_type = SQL_C_BINARY ;
break ;
case SQL_WLONGVARCHAR :
case SQL_WCHAR :
case SQL_WVARCHAR :
meta [ i ] . c_type = SQL_C_WCHAR ;
break ;
case SQL_BIT :
case SQL_INTEGER :
case SQL_SMALLINT :
case SQL_TINYINT :
meta [ i ] . c_type = SQL_C_LONG ;
break ;
case SQL_REAL :
case SQL_FLOAT :
meta [ i ] . c_type = SQL_C_DOUBLE ;
break ;
default :
SQLSRV_ASSERT ( false , " Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query " ) ;
break ;
}
}
// read the data into the cache
// (offset from the above loop has the size of the row buffer necessary)
2016-03-15 04:09:46 +01:00
zend_ulong mem_used = 0 ;
2016-01-30 03:00:20 +01:00
unsigned long row_count = 0 ;
while ( core : : SQLFetchScroll ( stmt , SQL_FETCH_NEXT , 0 TSRMLS_CC ) ! = SQL_NO_DATA ) {
// allocate the row buffer
unsigned char * row = static_cast < unsigned char * > ( sqlsrv_malloc ( offset ) ) ;
memset ( row , 0 , offset ) ;
// read the fields into the row buffer
for ( SQLSMALLINT i = 0 ; i < col_count ; + + i ) {
SQLLEN out_buffer_temp = SQL_NULL_DATA ;
SQLPOINTER buffer ;
SQLLEN * out_buffer_length = & out_buffer_temp ;
switch ( meta [ i ] . c_type ) {
case SQL_C_CHAR :
case SQL_C_WCHAR :
case SQL_C_BINARY :
if ( meta [ i ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
out_buffer_length = & out_buffer_temp ;
SQLPOINTER * lob_addr = reinterpret_cast < SQLPOINTER * > ( & row [ meta [ i ] . offset ] ) ;
* lob_addr = read_lob_field ( stmt , i , meta [ i ] , mem_used TSRMLS_CC ) ;
// a NULL pointer means NULL field
if ( * lob_addr = = NULL ) {
* out_buffer_length = SQL_NULL_DATA ;
}
else {
* out_buffer_length = * * reinterpret_cast < SQLLEN * * > ( lob_addr ) ;
mem_used + = * out_buffer_length ;
}
}
else {
2016-03-15 04:09:46 +01:00
mem_used + = meta [ i ] . length ;
2016-01-30 03:00:20 +01:00
CHECK_CUSTOM_ERROR ( mem_used > stmt - > buffered_query_limit * 1024 , stmt ,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED , stmt - > buffered_query_limit ) {
throw core : : CoreException ( ) ;
}
buffer = row + meta [ i ] . offset + sizeof ( SQLULEN ) ;
out_buffer_length = reinterpret_cast < SQLLEN * > ( row + meta [ i ] . offset ) ;
core : : SQLGetData ( stmt , i + 1 , meta [ i ] . c_type , buffer , meta [ i ] . length , out_buffer_length ,
false TSRMLS_CC ) ;
}
break ;
case SQL_C_LONG :
case SQL_C_DOUBLE :
{
mem_used + = meta [ i ] . length ;
CHECK_CUSTOM_ERROR ( mem_used > stmt - > buffered_query_limit * 1024 , stmt ,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED , stmt - > buffered_query_limit ) {
throw core : : CoreException ( ) ;
}
buffer = row + meta [ i ] . offset ;
out_buffer_length = & out_buffer_temp ;
core : : SQLGetData ( stmt , i + 1 , meta [ i ] . c_type , buffer , meta [ i ] . length , out_buffer_length ,
false TSRMLS_CC ) ;
}
break ;
default :
SQLSRV_ASSERT ( false , " Unknown C type " ) ;
break ;
}
if ( * out_buffer_length = = SQL_NULL_DATA ) {
unsigned char * null_bits = reinterpret_cast < unsigned char * > ( row ) ;
set_bit ( row , i ) ;
}
}
SQLSRV_ASSERT ( row_count < LONG_MAX , " Hard maximum of 2 billion rows exceeded in a buffered query " ) ;
// add it to the cache
row_dtor_closure cl ( this , row ) ;
sqlsrv_zend_hash_next_index_insert_mem ( * stmt , cache , & cl , sizeof ( row_dtor_closure ) TSRMLS_CC ) ;
}
}
sqlsrv_buffered_result_set : : ~ sqlsrv_buffered_result_set ( void )
{
// free the rows
if ( cache ) {
zend_hash_destroy ( cache ) ;
FREE_HASHTABLE ( cache ) ;
cache = NULL ;
}
// free the meta data
if ( meta ) {
efree ( meta ) ;
meta = NULL ;
}
}
SQLRETURN sqlsrv_buffered_result_set : : fetch ( SQLSMALLINT orientation , SQLLEN offset TSRMLS_DC )
{
last_error = NULL ;
last_field_index = - 1 ;
read_so_far = 0 ;
switch ( orientation ) {
case SQL_FETCH_NEXT :
offset = 1 ;
orientation = SQL_FETCH_RELATIVE ;
break ;
case SQL_FETCH_PRIOR :
offset = - 1 ;
orientation = SQL_FETCH_RELATIVE ;
break ;
}
switch ( orientation ) {
case SQL_FETCH_FIRST :
current = 1 ;
break ;
case SQL_FETCH_LAST :
current = row_count ( TSRMLS_C ) ;
break ;
case SQL_FETCH_ABSOLUTE :
current = offset ;
break ;
case SQL_FETCH_RELATIVE :
current + = offset ;
break ;
default :
SQLSRV_ASSERT ( false , " Invalid fetch orientation. Should have been caught before here. " ) ;
break ;
}
// check validity of current row
// the cursor can never get further away than just before the first row
if ( current < = 0 & & ( offset < 0 | | orientation ! = SQL_FETCH_RELATIVE ) ) {
current = 0 ;
return SQL_NO_DATA ;
}
// the cursor can never get further away than just after the last row
if ( current > row_count ( TSRMLS_C ) | | ( current < = 0 & & offset > 0 ) /*overflow condition*/ ) {
current = row_count ( TSRMLS_C ) + 1 ;
return SQL_NO_DATA ;
}
return SQL_SUCCESS ;
}
SQLRETURN sqlsrv_buffered_result_set : : get_data ( SQLUSMALLINT field_index , SQLSMALLINT target_type ,
__out SQLPOINTER buffer , SQLLEN buffer_length , __out SQLLEN * out_buffer_length ,
bool handle_warning TSRMLS_DC )
{
last_error = NULL ;
field_index - - ; // convert from 1 based to 0 based
SQLSRV_ASSERT ( field_index < column_count ( ) , " Invalid field index requested " ) ;
if ( field_index ! = last_field_index ) {
last_field_index = field_index ;
read_so_far = 0 ;
}
unsigned char * row = get_row ( ) ;
// if the field is null, then return SQL_NULL_DATA
if ( get_bit ( row , field_index ) ) {
* out_buffer_length = SQL_NULL_DATA ;
return SQL_SUCCESS ;
}
// check to make sure the conversion type is valid
if ( conv_matrix . find ( meta [ field_index ] . c_type ) = = conv_matrix . end ( ) | |
conv_matrix . find ( meta [ field_index ] . c_type ) - > second . find ( target_type ) = =
conv_matrix . find ( meta [ field_index ] . c_type ) - > second . end ( ) ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " 07006 " ,
( SQLCHAR * ) " Restricted data type attribute violation " , 0 ) ;
return SQL_ERROR ;
}
return ( ( this ) - > * ( conv_matrix [ meta [ field_index ] . c_type ] [ target_type ] ) ) ( field_index , buffer , buffer_length ,
out_buffer_length ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : get_diag_field ( SQLSMALLINT record_number , SQLSMALLINT diag_identifier ,
__out SQLPOINTER diag_info_buffer , SQLSMALLINT buffer_length ,
__out SQLSMALLINT * out_buffer_length TSRMLS_DC )
{
SQLSRV_ASSERT ( record_number = = 1 , " Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field " ) ;
SQLSRV_ASSERT ( diag_identifier = = SQL_DIAG_SQLSTATE ,
" Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field " ) ;
SQLSRV_ASSERT ( buffer_length > = SQL_SQLSTATE_BUFSIZE ,
" Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field " ) ;
if ( last_error = = NULL ) {
return SQL_NO_DATA ;
}
SQLSRV_ASSERT ( last_error - > sqlstate ! = NULL ,
" Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field " ) ;
memcpy ( diag_info_buffer , last_error - > sqlstate , min ( buffer_length , SQL_SQLSTATE_BUFSIZE ) ) ;
return SQL_SUCCESS ;
}
unsigned char * sqlsrv_buffered_result_set : : get_row ( void )
{
row_dtor_closure * cl_ptr ;
cl_ptr = reinterpret_cast < row_dtor_closure * > ( zend_hash_index_find_ptr ( cache , static_cast < zend_ulong > ( current - 1 ) ) ) ;
SQLSRV_ASSERT ( cl_ptr ! = NULL , " Failed to find row %1!d! in the cache " , current ) ;
return cl_ptr - > row_data ;
}
sqlsrv_error * sqlsrv_buffered_result_set : : get_diag_rec ( SQLSMALLINT record_number )
{
// we only hold a single error if there is one, otherwise return the ODBC error(s)
if ( last_error = = NULL ) {
return odbc_get_diag_rec ( odbc , record_number ) ;
}
if ( record_number > 1 ) {
return NULL ;
}
return new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( last_error - > sqlstate , last_error - > native_message , last_error - > native_code ) ;
}
SQLLEN sqlsrv_buffered_result_set : : row_count ( TSRMLS_D )
{
last_error = NULL ;
return zend_hash_num_elements ( cache ) ;
}
// private functions
template < typename Char >
SQLRETURN binary_to_string ( SQLCHAR * field_data , SQLLEN & read_so_far , __out void * buffer ,
SQLLEN buffer_length , __out SQLLEN * out_buffer_length ,
sqlsrv_error_auto_ptr & out_error )
{
// hex characters for the conversion loop below
static char hex_chars [ ] = " 0123456789ABCDEF " ;
SQLSRV_ASSERT ( out_error = = NULL , " Pending error for sqlsrv_buffered_results_set::binary_to_string " ) ;
SQLRETURN r = SQL_ERROR ;
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = sizeof ( Char ) ;
SQLSRV_ASSERT ( ( ( buffer_length - extra ) % ( extra * 2 ) ) = = 0 , " Must be multiple of 2 for binary to system string or "
" multiple of 4 for binary to wide string " ) ;
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
// *2 is for each byte to hex conversion and * extra is for either system or wide string allocation
* out_buffer_length = ( * reinterpret_cast < SQLLEN * > ( field_data - sizeof ( SQLULEN ) ) - read_so_far ) * 2 * extra ;
// copy as much as we can into the buffer
SQLLEN to_copy ;
if ( buffer_length < * out_buffer_length + extra ) {
to_copy = ( buffer_length - extra ) ;
out_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " 01004 " , ( SQLCHAR * ) " String data, right truncated " , - 1 ) ;
r = SQL_SUCCESS_WITH_INFO ;
}
else {
r = SQL_SUCCESS ;
to_copy = * out_buffer_length ;
}
// if there are bytes to copy as hex
if ( to_copy > 0 ) {
// quick hex conversion routine
Char * h = reinterpret_cast < Char * > ( buffer ) ;
BYTE * b = reinterpret_cast < BYTE * > ( field_data ) ;
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
// to get the number of hex digits we can copy
SQLLEN to_copy_hex = to_copy / ( 2 * extra ) ;
for ( int i = 0 ; i < to_copy_hex ; + + i ) {
* h = hex_chars [ ( * b & 0xf0 ) > > 4 ] ;
h + + ;
* h = hex_chars [ ( * b + + & 0x0f ) ] ;
h + + ;
}
read_so_far + = to_copy_hex ;
* h = static_cast < Char > ( 0 ) ;
}
else {
reinterpret_cast < char * > ( buffer ) [ 0 ] = ' \0 ' ;
}
return r ;
}
SQLRETURN sqlsrv_buffered_result_set : : binary_to_system_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLCHAR * row = get_row ( ) ;
SQLCHAR * field_data = NULL ;
if ( meta [ field_index ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
field_data = * reinterpret_cast < SQLCHAR * * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) ;
}
else {
field_data = & row [ meta [ field_index ] . offset ] + sizeof ( SQLULEN ) ;
}
return binary_to_string < char > ( field_data , read_so_far , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : binary_to_wide_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLCHAR * row = get_row ( ) ;
SQLCHAR * field_data = NULL ;
if ( meta [ field_index ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
field_data = * reinterpret_cast < SQLCHAR * * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) ;
}
else {
field_data = & row [ meta [ field_index ] . offset ] + sizeof ( SQLULEN ) ;
}
return binary_to_string < WCHAR > ( field_data , read_so_far , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : double_to_long ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_DOUBLE , " Invalid conversion to long " ) ;
2016-03-15 04:09:46 +01:00
SQLSRV_ASSERT ( buffer_length > = sizeof ( SQLLEN ) , " Buffer length must be able to find a long in "
2016-01-30 03:00:20 +01:00
" sqlsrv_buffered_result_set::double_to_long " ) ;
unsigned char * row = get_row ( ) ;
double * double_data = reinterpret_cast < double * > ( & row [ meta [ field_index ] . offset ] ) ;
LONG * long_data = reinterpret_cast < LONG * > ( buffer ) ;
if ( * double_data < double ( LONG_MIN ) | | * double_data > double ( LONG_MAX ) ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error ( ( SQLCHAR * ) " 22003 " ,
( SQLCHAR * ) " Numeric value out of range " , 0 ) ;
return SQL_ERROR ;
}
if ( * double_data ! = floor ( * double_data ) ) {
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) ) sqlsrv_error ( ( SQLCHAR * ) " 01S07 " ,
( SQLCHAR * ) " Fractional truncation " , 0 ) ;
return SQL_SUCCESS_WITH_INFO ;
}
* long_data = static_cast < LONG > ( * double_data ) ;
* out_buffer_length = sizeof ( LONG ) ;
return SQL_SUCCESS ;
}
SQLRETURN sqlsrv_buffered_result_set : : double_to_system_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_DOUBLE , " Invalid conversion to system string " ) ;
SQLSRV_ASSERT ( buffer_length > 0 , " Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string " ) ;
unsigned char * row = get_row ( ) ;
double * double_data = reinterpret_cast < double * > ( & row [ meta [ field_index ] . offset ] ) ;
return number_to_string < char > ( double_data , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : double_to_wide_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_DOUBLE , " Invalid conversion to wide string " ) ;
SQLSRV_ASSERT ( buffer_length > 0 , " Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string " ) ;
unsigned char * row = get_row ( ) ;
double * double_data = reinterpret_cast < double * > ( & row [ meta [ field_index ] . offset ] ) ;
return number_to_string < WCHAR > ( double_data , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : long_to_double ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_LONG , " Invalid conversion to long " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( double ) , " Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long " ) ;
unsigned char * row = get_row ( ) ;
double * double_data = reinterpret_cast < double * > ( buffer ) ;
LONG * long_data = reinterpret_cast < LONG * > ( & row [ meta [ field_index ] . offset ] ) ;
* double_data = static_cast < LONG > ( * long_data ) ;
* out_buffer_length = sizeof ( double ) ;
return SQL_SUCCESS ;
}
SQLRETURN sqlsrv_buffered_result_set : : long_to_system_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_LONG , " Invalid conversion to system string " ) ;
SQLSRV_ASSERT ( buffer_length > 0 , " Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string " ) ;
unsigned char * row = get_row ( ) ;
LONG * long_data = reinterpret_cast < LONG * > ( & row [ meta [ field_index ] . offset ] ) ;
return number_to_string < char > ( long_data , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : long_to_wide_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_LONG , " Invalid conversion to wide string " ) ;
SQLSRV_ASSERT ( buffer_length > 0 , " Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string " ) ;
unsigned char * row = get_row ( ) ;
LONG * long_data = reinterpret_cast < LONG * > ( & row [ meta [ field_index ] . offset ] ) ;
return number_to_string < WCHAR > ( long_data , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : string_to_double ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_CHAR , " Invalid conversion from string to double " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( double ) , " Buffer needs to be big enough to hold a double " ) ;
unsigned char * row = get_row ( ) ;
char * string_data = reinterpret_cast < char * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) ;
return string_to_number < double > ( string_data , meta [ field_index ] . length , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : wstring_to_double ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_WCHAR , " Invalid conversion from wide string to double " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( double ) , " Buffer needs to be big enough to hold a double " ) ;
unsigned char * row = get_row ( ) ;
SQLWCHAR * string_data = reinterpret_cast < SQLWCHAR * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) / sizeof ( SQLWCHAR ) ;
return string_to_number < double > ( string_data , meta [ field_index ] . length , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : string_to_long ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_CHAR , " Invalid conversion from string to long " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( LONG ) , " Buffer needs to be big enough to hold a long " ) ;
unsigned char * row = get_row ( ) ;
char * string_data = reinterpret_cast < char * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) ;
return string_to_number < LONG > ( string_data , meta [ field_index ] . length , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : wstring_to_long ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_WCHAR , " Invalid conversion from wide string to long " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( LONG ) , " Buffer needs to be big enough to hold a long " ) ;
unsigned char * row = get_row ( ) ;
SQLWCHAR * string_data = reinterpret_cast < SQLWCHAR * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) / sizeof ( SQLWCHAR ) ;
return string_to_number < LONG > ( string_data , meta [ field_index ] . length , buffer , buffer_length , out_buffer_length , last_error ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : system_to_wide_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( last_error = = NULL , " Pending error for sqlsrv_buffered_results_set::system_to_wide_string " ) ;
SQLSRV_ASSERT ( buffer_length % 2 = = 0 , " Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string " ) ;
SQLRETURN r = SQL_ERROR ;
unsigned char * row = get_row ( ) ;
SQLCHAR * field_data = NULL ;
SQLULEN field_len = NULL ;
if ( meta [ field_index ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
field_len = * * reinterpret_cast < SQLLEN * * > ( & row [ meta [ field_index ] . offset ] ) ;
field_data = * reinterpret_cast < SQLCHAR * * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) + read_so_far ;
}
else {
field_len = * reinterpret_cast < SQLLEN * > ( & row [ meta [ field_index ] . offset ] ) ;
field_data = & row [ meta [ field_index ] . offset ] + sizeof ( SQLULEN ) + read_so_far ;
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
* out_buffer_length = ( * reinterpret_cast < SQLLEN * > ( field_data - sizeof ( SQLULEN ) ) - read_so_far ) * sizeof ( WCHAR ) ;
// to_copy is the number of characters to copy, not including the null terminator
// supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair.
SQLLEN to_copy ;
if ( ( size_t ) buffer_length < ( field_len - read_so_far + sizeof ( char ) ) * sizeof ( WCHAR ) ) {
to_copy = ( buffer_length - sizeof ( WCHAR ) ) / sizeof ( WCHAR ) ; // to_copy is the number of characters
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " 01004 " , ( SQLCHAR * ) " String data, right truncated " , - 1 ) ;
r = SQL_SUCCESS_WITH_INFO ;
}
else {
r = SQL_SUCCESS ;
to_copy = field_len - read_so_far ;
}
if ( to_copy > 0 ) {
bool tried_again = false ;
do {
2016-03-15 04:09:46 +01:00
if ( to_copy > INT_MAX ) {
LOG ( SEV_ERROR , " MultiByteToWideChar: Buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
int ch_space = MultiByteToWideChar ( CP_ACP , MB_ERR_INVALID_CHARS , ( LPCSTR ) field_data , static_cast < int > ( to_copy ) ,
static_cast < LPWSTR > ( buffer ) , static_cast < int > ( to_copy ) ) ;
2016-01-30 03:00:20 +01:00
if ( ch_space = = 0 ) {
switch ( GetLastError ( ) ) {
case ERROR_NO_UNICODE_TRANSLATION :
// the theory here is the conversion failed because the end of the buffer we provided contained only
// half a character at the end
if ( ! tried_again ) {
to_copy - - ;
tried_again = true ;
continue ;
}
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " IMSSP " , ( SQLCHAR * ) " Invalid Unicode translation " , - 1 ) ;
break ;
default :
SQLSRV_ASSERT ( false , " Severe error translating Unicode " ) ;
break ;
}
return SQL_ERROR ;
}
( ( WCHAR * ) buffer ) [ to_copy ] = L ' \0 ' ;
read_so_far + = to_copy ;
break ;
} while ( true ) ;
}
else {
reinterpret_cast < WCHAR * > ( buffer ) [ 0 ] = L ' \0 ' ;
}
return r ;
}
SQLRETURN sqlsrv_buffered_result_set : : to_same_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( last_error = = NULL , " Pending error for sqlsrv_buffered_results_set::to_same_string " ) ;
SQLRETURN r = SQL_ERROR ;
unsigned char * row = get_row ( ) ;
// Set the amount of space necessary for null characters at the end of the data.
SQLSMALLINT extra = 0 ;
switch ( meta [ field_index ] . c_type ) {
case SQL_C_WCHAR :
extra = sizeof ( SQLWCHAR ) ;
break ;
case SQL_C_BINARY :
extra = 0 ;
break ;
case SQL_C_CHAR :
extra = sizeof ( SQLCHAR ) ;
break ;
default :
SQLSRV_ASSERT ( false , " Invalid type in get_string_data " ) ;
break ;
}
SQLCHAR * field_data = NULL ;
if ( meta [ field_index ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
field_data = * reinterpret_cast < SQLCHAR * * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) ;
}
else {
field_data = & row [ meta [ field_index ] . offset ] + sizeof ( SQLULEN ) ;
}
// all fields will be treated as ODBC returns varchar(max) fields:
// the entire length of the string is returned the first
// call in out_buffer_len. Successive calls return how much is
// left minus how much has already been read by previous reads
* out_buffer_length = * reinterpret_cast < SQLLEN * > ( field_data - sizeof ( SQLULEN ) ) - read_so_far ;
// copy as much as we can into the buffer
SQLLEN to_copy ;
if ( buffer_length < * out_buffer_length + extra ) {
to_copy = buffer_length - extra ;
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " 01004 " , ( SQLCHAR * ) " String data, right truncated " , - 1 ) ;
r = SQL_SUCCESS_WITH_INFO ;
}
else {
r = SQL_SUCCESS ;
to_copy = * out_buffer_length ;
}
SQLSRV_ASSERT ( to_copy > = 0 , " Negative field length calculated in buffered result set " ) ;
if ( to_copy > 0 ) {
memcpy ( buffer , field_data + read_so_far , to_copy ) ;
read_so_far + = to_copy ;
}
if ( extra ) {
OACR_WARNING_SUPPRESS ( 26001 , " Buffer length verified above " ) ;
memcpy ( reinterpret_cast < SQLCHAR * > ( buffer ) + to_copy , L " \0 " , extra ) ;
}
return r ;
}
SQLRETURN sqlsrv_buffered_result_set : : wide_to_system_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( last_error = = NULL , " Pending error for sqlsrv_buffered_results_set::wide_to_system_string " ) ;
SQLRETURN r = SQL_ERROR ;
unsigned char * row = get_row ( ) ;
SQLCHAR * field_data = NULL ;
2016-03-15 04:09:46 +01:00
SQLLEN field_len = NULL ;
2016-01-30 03:00:20 +01:00
// if this is the first time called for this field, just convert the entire string to system first then
// use that to read from instead of converting chunk by chunk. This is because it's impossible to know
// the total length of the string for output_buffer_length without doing the conversion and returning
// SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and
// to_same_string).
if ( read_so_far = = 0 ) {
if ( meta [ field_index ] . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
field_len = * * reinterpret_cast < SQLLEN * * > ( & row [ meta [ field_index ] . offset ] ) ;
field_data = * reinterpret_cast < SQLCHAR * * > ( & row [ meta [ field_index ] . offset ] ) + sizeof ( SQLULEN ) + read_so_far ;
}
else {
field_len = * reinterpret_cast < SQLLEN * > ( & row [ meta [ field_index ] . offset ] ) ;
field_data = & row [ meta [ field_index ] . offset ] + sizeof ( SQLULEN ) + read_so_far ;
}
BOOL default_char_used = FALSE ;
char default_char = ' ? ' ;
// allocate enough to handle WC -> DBCS conversion if it happens
2016-03-15 04:09:46 +01:00
temp_string = reinterpret_cast < SQLCHAR * > ( sqlsrv_malloc ( field_len , sizeof ( char ) , sizeof ( char ) ) ) ;
temp_length = WideCharToMultiByte ( CP_ACP , 0 , ( LPCWSTR ) field_data , static_cast < int > ( field_len / sizeof ( WCHAR ) ) ,
( LPSTR ) temp_string . get ( ) , static_cast < int > ( field_len ) , & default_char , & default_char_used ) ;
2016-01-30 03:00:20 +01:00
if ( temp_length = = 0 ) {
switch ( GetLastError ( ) ) {
case ERROR_NO_UNICODE_TRANSLATION :
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " IMSSP " , ( SQLCHAR * ) " Invalid Unicode translation " , - 1 ) ;
break ;
default :
SQLSRV_ASSERT ( false , " Severe error translating Unicode " ) ;
break ;
}
return SQL_ERROR ;
}
}
* out_buffer_length = ( temp_length - read_so_far ) ;
SQLLEN to_copy = 0 ;
if ( ( size_t ) buffer_length < ( temp_length - read_so_far + sizeof ( char ) ) ) {
to_copy = buffer_length - sizeof ( char ) ;
last_error = new ( sqlsrv_malloc ( sizeof ( sqlsrv_error ) ) )
sqlsrv_error ( ( SQLCHAR * ) " 01004 " , ( SQLCHAR * ) " String data, right truncated " , - 1 ) ;
r = SQL_SUCCESS_WITH_INFO ;
}
else {
to_copy = ( temp_length - read_so_far ) ;
r = SQL_SUCCESS ;
}
if ( to_copy > 0 ) {
memcpy ( buffer , temp_string . get ( ) + read_so_far , to_copy ) ;
}
SQLSRV_ASSERT ( to_copy > = 0 , " Invalid field copy length " ) ;
OACR_WARNING_SUPPRESS ( BUFFER_UNDERFLOW , " Buffer length verified above " ) ;
( ( SQLCHAR * ) buffer ) [ to_copy ] = ' \0 ' ;
read_so_far + = to_copy ;
return r ;
}
SQLRETURN sqlsrv_buffered_result_set : : to_binary_string ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
return to_same_string ( field_index , buffer , buffer_length , out_buffer_length ) ;
}
SQLRETURN sqlsrv_buffered_result_set : : to_long ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_LONG , " Invlid conversion to long " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( LONG ) , " Buffer too small for SQL_C_LONG " ) ; // technically should ignore this
unsigned char * row = get_row ( ) ;
LONG * long_data = reinterpret_cast < LONG * > ( & row [ meta [ field_index ] . offset ] ) ;
memcpy ( buffer , long_data , sizeof ( LONG ) ) ;
* out_buffer_length = sizeof ( LONG ) ;
return SQL_SUCCESS ;
}
SQLRETURN sqlsrv_buffered_result_set : : to_double ( SQLSMALLINT field_index , __out void * buffer , SQLLEN buffer_length ,
__out SQLLEN * out_buffer_length )
{
SQLSRV_ASSERT ( meta [ field_index ] . c_type = = SQL_C_DOUBLE , " Invlid conversion to double " ) ;
SQLSRV_ASSERT ( buffer_length > = sizeof ( double ) , " Buffer too small for SQL_C_DOUBLE " ) ; // technically should ignore this
unsigned char * row = get_row ( ) ;
double * double_data = reinterpret_cast < double * > ( & row [ meta [ field_index ] . offset ] ) ;
memcpy ( buffer , double_data , sizeof ( double ) ) ;
* out_buffer_length = sizeof ( double ) ;
return SQL_SUCCESS ;
}
namespace {
// called for each row in the cache when the cache is destroyed in the destructor
void cache_row_dtor ( zval * data )
{
row_dtor_closure * cl = reinterpret_cast < row_dtor_closure * > ( Z_PTR_P ( data ) ) ;
BYTE * row = cl - > row_data ;
// don't release this here, since this is called from the destructor of the result_set
sqlsrv_buffered_result_set * result_set = cl - > results ;
for ( SQLSMALLINT i = 0 ; i < result_set - > column_count ( ) ; + + i ) {
if ( result_set - > col_meta_data ( i ) . length = = sqlsrv_buffered_result_set : : meta_data : : SIZE_UNKNOWN ) {
void * out_of_row_data = * reinterpret_cast < void * * > ( & row [ result_set - > col_meta_data ( i ) . offset ] ) ;
sqlsrv_free ( out_of_row_data ) ;
}
}
sqlsrv_free ( row ) ;
}
SQLPOINTER read_lob_field ( sqlsrv_stmt * stmt , SQLUSMALLINT field_index , sqlsrv_buffered_result_set : : meta_data & meta ,
2016-03-15 04:09:46 +01:00
zend_long mem_used TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
SQLSMALLINT extra = 0 ;
2016-03-15 04:09:46 +01:00
SQLULEN * output_buffer_len = NULL ;
2016-01-30 03:00:20 +01:00
// Set the amount of space necessary for null characters at the end of the data.
switch ( meta . c_type ) {
case SQL_C_WCHAR :
extra = sizeof ( SQLWCHAR ) ;
break ;
case SQL_C_BINARY :
extra = 0 ;
break ;
case SQL_C_CHAR :
extra = sizeof ( SQLCHAR ) ;
break ;
default :
SQLSRV_ASSERT ( false , " Invalid type in read_lob_field " ) ;
break ;
}
2016-03-15 04:09:46 +01:00
SQLULEN already_read = 0 ;
SQLULEN to_read = INITIAL_FIELD_STRING_LEN ;
2016-01-30 03:00:20 +01:00
sqlsrv_malloc_auto_ptr < char > buffer ;
buffer = static_cast < char * > ( sqlsrv_malloc ( INITIAL_FIELD_STRING_LEN + extra + sizeof ( SQLULEN ) ) ) ;
SQLRETURN r = SQL_SUCCESS ;
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] ;
SQLLEN last_field_len = 0 ;
bool full_length_returned = false ;
do {
2016-03-15 04:09:46 +01:00
output_buffer_len = reinterpret_cast < SQLULEN * > ( buffer . get ( ) ) ;
2016-01-30 03:00:20 +01:00
r = core : : SQLGetData ( stmt , field_index + 1 , meta . c_type , buffer . get ( ) + already_read + sizeof ( SQLULEN ) ,
to_read - already_read + extra , & last_field_len , false /*handle_warning*/ TSRMLS_CC ) ;
// if the field is NULL, then return a NULL pointer
if ( last_field_len = = SQL_NULL_DATA ) {
return NULL ;
}
// if the last read was successful, we're done
if ( r = = SQL_SUCCESS ) {
// check to make sure we haven't overflown our memory limit
CHECK_CUSTOM_ERROR ( mem_used + last_field_len > stmt - > buffered_query_limit * 1024 , stmt ,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED , stmt - > buffered_query_limit ) {
throw core : : CoreException ( ) ;
}
break ;
}
// else if it wasn't the truncated warning (01004) then we're done
else if ( r = = SQL_SUCCESS_WITH_INFO ) {
SQLSMALLINT len ;
core : : SQLGetDiagField ( stmt , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len
TSRMLS_CC ) ;
if ( ! is_truncated_warning ( state ) ) {
break ;
}
}
SQLSRV_ASSERT ( SQL_SUCCEEDED ( r ) , " Unknown SQL error not triggered " ) ;
// if the type of the field returns the total to be read, we use that and preallocate the buffer
if ( last_field_len ! = SQL_NO_TOTAL ) {
CHECK_CUSTOM_ERROR ( mem_used + last_field_len > stmt - > buffered_query_limit * 1024 , stmt ,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED , stmt - > buffered_query_limit ) {
throw core : : CoreException ( ) ;
}
already_read + = to_read - already_read ;
to_read = last_field_len ;
buffer . resize ( to_read + extra + sizeof ( SQLULEN ) ) ;
2016-03-15 04:09:46 +01:00
output_buffer_len = reinterpret_cast < SQLULEN * > ( buffer . get ( ) ) ;
2016-01-30 03:00:20 +01:00
// record the size of the field since we have it available
* output_buffer_len = last_field_len ;
full_length_returned = true ;
}
// otherwise allocate another chunk of memory to read in
else {
already_read + = to_read - already_read ;
to_read * = 2 ;
CHECK_CUSTOM_ERROR ( mem_used + to_read > stmt - > buffered_query_limit * 1024 , stmt ,
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED , stmt - > buffered_query_limit ) {
throw core : : CoreException ( ) ;
}
buffer . resize ( to_read + extra + sizeof ( SQLULEN ) ) ;
2016-03-15 04:09:46 +01:00
output_buffer_len = reinterpret_cast < SQLULEN * > ( buffer . get ( ) ) ;
2016-01-30 03:00:20 +01:00
}
} while ( true ) ;
SQLSRV_ASSERT ( output_buffer_len ! = NULL , " Output buffer not allocated properly " ) ;
// most LOB field types return the total length in the last_field_len, but some field types such as XML
// only return the amount read on the last read
if ( ! full_length_returned ) {
* output_buffer_len = already_read + last_field_len ;
}
char * return_buffer = buffer ;
buffer . transferred ( ) ;
return return_buffer ;
}
}