deleted pdo_sqlsrv obsolete folder
This commit is contained in:
parent
4c42a9ad6a
commit
a8415715b9
|
@ -1,1391 +0,0 @@
|
||||||
//---------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
// File: core_results.cpp
|
|
||||||
//
|
|
||||||
// Contents: Result sets
|
|
||||||
//
|
|
||||||
// Microsoft Drivers 4.1 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,
|
|
||||||
zend_long mem_used TSRMLS_DC );
|
|
||||||
|
|
||||||
// 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 )
|
|
||||||
{
|
|
||||||
// get to display size by removing the null terminator from buffer length
|
|
||||||
size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char );
|
|
||||||
|
|
||||||
std::basic_ostringstream<Char> os;
|
|
||||||
// use the display size to determine the sql type. And if it is a double, set the precision accordingly
|
|
||||||
// the display sizes are set by the ODBC driver based on the precision of the sql type
|
|
||||||
// otherwise we can just use the default precision as long will not be truncated
|
|
||||||
size_t real_display_size = 14;
|
|
||||||
size_t float_display_size = 24;
|
|
||||||
size_t real_precision = 7;
|
|
||||||
size_t float_precision = 15;
|
|
||||||
// this is the case of sql type float(24) or real
|
|
||||||
if ( display_size == real_display_size ) {
|
|
||||||
os.precision( real_precision );
|
|
||||||
}
|
|
||||||
// this is the case of sql type float(53)
|
|
||||||
else if ( display_size == float_display_size ) {
|
|
||||||
os.precision( float_precision );
|
|
||||||
}
|
|
||||||
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) > (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 ); // str_num.size() already include the NULL terminator
|
|
||||||
memcpy_s( buffer, buffer_length, 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;
|
|
||||||
SQLLEN sql_state_len = 0;
|
|
||||||
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;
|
|
||||||
SQLLEN native_message_len = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
|
|
||||||
stmt->encoding());
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
if ( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
|
|
||||||
offset += sizeof( void* );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If encoding is set to UTF-8, the following types are not necessarily column size.
|
|
||||||
// We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly.
|
|
||||||
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
|
|
||||||
meta[i].length *= sizeof( WCHAR );
|
|
||||||
meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space
|
|
||||||
offset += meta[i].length;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space
|
|
||||||
offset += meta[i].length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// these types are the column size
|
|
||||||
case SQL_BINARY:
|
|
||||||
case SQL_SS_UDT:
|
|
||||||
case SQL_VARBINARY:
|
|
||||||
// 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_DATETIME:
|
|
||||||
case SQL_DECIMAL:
|
|
||||||
case SQL_GUID:
|
|
||||||
case SQL_NUMERIC:
|
|
||||||
case SQL_TYPE_DATE:
|
|
||||||
case SQL_SS_TIME2:
|
|
||||||
case SQL_SS_TIMESTAMPOFFSET:
|
|
||||||
case SQL_SS_XML:
|
|
||||||
case SQL_TYPE_TIMESTAMP:
|
|
||||||
meta[i].c_type = SQL_C_CHAR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
case SQL_LONGVARCHAR:
|
|
||||||
// If encoding is set to UTF-8, the following types are not necessarily column size.
|
|
||||||
// We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly.
|
|
||||||
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
|
|
||||||
meta[i].c_type = SQL_C_WCHAR;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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)
|
|
||||||
zend_long mem_used = 0;
|
|
||||||
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 {
|
|
||||||
|
|
||||||
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 + 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_s( diag_info_buffer, buffer_length, 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" );
|
|
||||||
SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "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*>( &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 {
|
|
||||||
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));
|
|
||||||
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_s( buffer, buffer_length, field_data + read_so_far, to_copy );
|
|
||||||
read_so_far += to_copy;
|
|
||||||
}
|
|
||||||
if( extra ) {
|
|
||||||
OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
|
|
||||||
memcpy_s( reinterpret_cast<SQLCHAR*>( buffer ) + to_copy, buffer_length, 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;
|
|
||||||
SQLLEN field_len = NULL;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
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 );
|
|
||||||
|
|
||||||
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_s( buffer, buffer_length, 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_s( buffer, buffer_length, 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_s( buffer, buffer_length, 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 );
|
|
||||||
sqlsrv_free( cl );
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
|
|
||||||
zend_long mem_used TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLSMALLINT extra = 0;
|
|
||||||
SQLULEN* output_buffer_len = NULL;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLLEN already_read = 0;
|
|
||||||
SQLLEN to_read = INITIAL_FIELD_STRING_LEN;
|
|
||||||
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 {
|
|
||||||
|
|
||||||
|
|
||||||
output_buffer_len = reinterpret_cast<SQLULEN*>( buffer.get() );
|
|
||||||
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 ));
|
|
||||||
output_buffer_len = reinterpret_cast<SQLULEN*>( buffer.get() );
|
|
||||||
// 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 ));
|
|
||||||
output_buffer_len = reinterpret_cast<SQLULEN*>( buffer.get() );
|
|
||||||
}
|
|
||||||
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,2471 +0,0 @@
|
||||||
//---------------------------------------------------------------------------------------------------------------------------------
|
|
||||||
// File: core_stmt.cpp
|
|
||||||
//
|
|
||||||
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
|
|
||||||
//
|
|
||||||
// Microsoft Drivers 4.1 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"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// certain drivers using this layer will call for repeated or out of order field retrievals. To allow this, we cache the
|
|
||||||
// results of every field request, and if it is out of order, we cache those for preceding fields.
|
|
||||||
struct field_cache {
|
|
||||||
|
|
||||||
void* value;
|
|
||||||
SQLLEN len;
|
|
||||||
sqlsrv_phptype type;
|
|
||||||
|
|
||||||
field_cache( void* field_value, SQLLEN field_len, sqlsrv_phptype t )
|
|
||||||
: type( t )
|
|
||||||
{
|
|
||||||
// if the value is NULL, then just record a NULL pointer
|
|
||||||
if( field_value != NULL ) {
|
|
||||||
value = sqlsrv_malloc( field_len );
|
|
||||||
memcpy_s( value, field_len, field_value, field_len );
|
|
||||||
len = field_len;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value = NULL;
|
|
||||||
len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no destructor because we don't want to release the memory when it goes out of scope, but instead we
|
|
||||||
// rely on the hash table destructor to free the memory
|
|
||||||
};
|
|
||||||
|
|
||||||
const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
|
|
||||||
|
|
||||||
// UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads
|
|
||||||
const unsigned int UTF8_MIDBYTE_MASK = 0xc0;
|
|
||||||
const unsigned int UTF8_MIDBYTE_TAG = 0x80;
|
|
||||||
const unsigned int UTF8_2BYTESEQ_TAG1 = 0xc0;
|
|
||||||
const unsigned int UTF8_2BYTESEQ_TAG2 = 0xd0;
|
|
||||||
const unsigned int UTF8_3BYTESEQ_TAG = 0xe0;
|
|
||||||
const unsigned int UTF8_4BYTESEQ_TAG = 0xf0;
|
|
||||||
const unsigned int UTF8_NBYTESEQ_MASK = 0xf0;
|
|
||||||
|
|
||||||
// constants used to convert from a DateTime object to a string which is sent to the server.
|
|
||||||
// Using the format defined by the ODBC documentation at http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx
|
|
||||||
namespace DateTime {
|
|
||||||
|
|
||||||
const char DATETIME_CLASS_NAME[] = "DateTime";
|
|
||||||
const size_t DATETIME_CLASS_NAME_LEN = sizeof( DATETIME_CLASS_NAME ) - 1;
|
|
||||||
const char DATETIMEOFFSET_FORMAT[] = "Y-m-d H:i:s.u P";
|
|
||||||
const size_t DATETIMEOFFSET_FORMAT_LEN = sizeof( DATETIMEOFFSET_FORMAT );
|
|
||||||
const char DATETIME_FORMAT[] = "Y-m-d H:i:s.u";
|
|
||||||
const size_t DATETIME_FORMAT_LEN = sizeof( DATETIME_FORMAT );
|
|
||||||
const char DATE_FORMAT[] = "Y-m-d";
|
|
||||||
const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// *** internal functions ***
|
|
||||||
// Only declarations are put here. Functions contain the documentation they need at their definition sites.
|
|
||||||
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC );
|
|
||||||
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC );
|
|
||||||
bool check_for_next_stream_parameter( sqlsrv_stmt* stmt TSRMLS_DC );
|
|
||||||
bool convert_input_param_to_utf16( zval* input_param_z, zval* convert_param_z );
|
|
||||||
void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
|
|
||||||
sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC);
|
|
||||||
// returns the ODBC C type constant that matches the PHP type and encoding given
|
|
||||||
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC );
|
|
||||||
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
|
|
||||||
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC );
|
|
||||||
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
|
|
||||||
void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding,
|
|
||||||
_Out_ SQLSMALLINT& sql_type TSRMLS_DC );
|
|
||||||
void field_cache_dtor( zval* data_z );
|
|
||||||
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC );
|
|
||||||
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
|
|
||||||
_Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC );
|
|
||||||
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC );
|
|
||||||
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type );
|
|
||||||
// assure there is enough space for the output parameter string
|
|
||||||
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding,
|
|
||||||
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
|
|
||||||
SQLLEN& buffer_len TSRMLS_DC );
|
|
||||||
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC );
|
|
||||||
// send all the stream data
|
|
||||||
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC );
|
|
||||||
// called when a bound output string parameter is to be destroyed
|
|
||||||
void sqlsrv_output_param_dtor( zval* data );
|
|
||||||
// called when a bound stream parameter is to be destroyed.
|
|
||||||
void sqlsrv_stream_dtor( zval* data );
|
|
||||||
bool is_streamable_type( SQLINTEGER sql_type );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
|
|
||||||
sqlsrv_stmt::sqlsrv_stmt( sqlsrv_conn* c, SQLHANDLE handle, error_callback e, void* drv TSRMLS_DC ) :
|
|
||||||
sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ),
|
|
||||||
conn( c ),
|
|
||||||
executed( false ),
|
|
||||||
past_fetch_end( false ),
|
|
||||||
current_results( NULL ),
|
|
||||||
cursor_type( SQL_CURSOR_FORWARD_ONLY ),
|
|
||||||
has_rows( false ),
|
|
||||||
fetch_called( false ),
|
|
||||||
last_field_index( -1 ),
|
|
||||||
past_next_result_end( false ),
|
|
||||||
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
|
|
||||||
send_streams_at_exec( true ),
|
|
||||||
current_stream( NULL, SQLSRV_ENCODING_DEFAULT ),
|
|
||||||
current_stream_read( 0 ),
|
|
||||||
query_timeout( QUERY_TIMEOUT_INVALID ),
|
|
||||||
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID )
|
|
||||||
{
|
|
||||||
ZVAL_UNDEF( &active_stream );
|
|
||||||
// initialize the input string parameters array (which holds zvals)
|
|
||||||
core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC );
|
|
||||||
|
|
||||||
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
|
|
||||||
ZVAL_NEW_ARR( ¶m_streams );
|
|
||||||
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC);
|
|
||||||
|
|
||||||
// initialize the (input only) datetime parameters of converted date time objects to strings
|
|
||||||
array_init( ¶m_datetime_buffers );
|
|
||||||
|
|
||||||
// initialize the output string parameters (which holds sqlsrv_output_param structures)
|
|
||||||
ZVAL_NEW_ARR( &output_params );
|
|
||||||
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC);
|
|
||||||
|
|
||||||
// initialize the field cache
|
|
||||||
ZVAL_NEW_ARR( &field_cache );
|
|
||||||
core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// desctructor for sqlsrv statement.
|
|
||||||
sqlsrv_stmt::~sqlsrv_stmt( void )
|
|
||||||
{
|
|
||||||
if( Z_TYPE( active_stream ) != IS_UNDEF ) {
|
|
||||||
TSRMLS_FETCH();
|
|
||||||
close_active_stream( this TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete any current results
|
|
||||||
if( current_results ) {
|
|
||||||
current_results->~sqlsrv_result_set();
|
|
||||||
efree( current_results );
|
|
||||||
current_results = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidate();
|
|
||||||
zval_ptr_dtor( ¶m_input_strings );
|
|
||||||
zval_ptr_dtor( &output_params );
|
|
||||||
zval_ptr_dtor( ¶m_streams );
|
|
||||||
zval_ptr_dtor( ¶m_datetime_buffers );
|
|
||||||
zval_ptr_dtor( &field_cache );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// centralized place to release (without destroying the hash tables
|
|
||||||
// themselves) all the parameter data that accrues during the
|
|
||||||
// execution phase.
|
|
||||||
void sqlsrv_stmt::free_param_data( TSRMLS_D )
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY,
|
|
||||||
"sqlsrv_stmt::free_param_data: Param zvals aren't arrays." );
|
|
||||||
zend_hash_clean( Z_ARRVAL( param_input_strings ));
|
|
||||||
zend_hash_clean( Z_ARRVAL( output_params ));
|
|
||||||
zend_hash_clean( Z_ARRVAL( param_streams ));
|
|
||||||
zend_hash_clean( Z_ARRVAL( param_datetime_buffers ));
|
|
||||||
zend_hash_clean( Z_ARRVAL( field_cache ));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// to be called whenever a new result set is created, such as after an
|
|
||||||
// execute or next_result. Resets the state variables.
|
|
||||||
|
|
||||||
void sqlsrv_stmt::new_result_set( TSRMLS_D )
|
|
||||||
{
|
|
||||||
this->fetch_called = false;
|
|
||||||
this->has_rows = false;
|
|
||||||
this->past_next_result_end = false;
|
|
||||||
this->past_fetch_end = false;
|
|
||||||
this->last_field_index = -1;
|
|
||||||
|
|
||||||
// delete any current results
|
|
||||||
if( current_results ) {
|
|
||||||
current_results->~sqlsrv_result_set();
|
|
||||||
efree( current_results );
|
|
||||||
current_results = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new result set
|
|
||||||
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
|
|
||||||
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ))) sqlsrv_buffered_result_set( this TSRMLS_CC );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
current_results = new (sqlsrv_malloc( sizeof( sqlsrv_odbc_result_set ))) sqlsrv_odbc_result_set( this );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// core_sqlsrv_create_stmt
|
|
||||||
// Common code to allocate a statement from either driver. Returns a valid driver statement object or
|
|
||||||
// throws an exception if an error occurs.
|
|
||||||
// Parameters:
|
|
||||||
// conn - The connection resource by which the client and server are connected.
|
|
||||||
// stmt_factory - factory method to create a statement.
|
|
||||||
// options_ht - A HashTable of user provided options to be set on the statement.
|
|
||||||
// valid_stmt_opts - An array of valid driver supported statement options.
|
|
||||||
// err - callback for error handling
|
|
||||||
// driver - reference to caller
|
|
||||||
// Return
|
|
||||||
// Returns the created statement
|
|
||||||
|
|
||||||
sqlsrv_stmt* core_sqlsrv_create_stmt( sqlsrv_conn* conn, driver_stmt_factory stmt_factory, HashTable* options_ht,
|
|
||||||
const stmt_option valid_stmt_opts[], error_callback const err, void* driver TSRMLS_DC )
|
|
||||||
{
|
|
||||||
sqlsrv_malloc_auto_ptr<sqlsrv_stmt> stmt;
|
|
||||||
SQLHANDLE stmt_h = SQL_NULL_HANDLE;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC );
|
|
||||||
|
|
||||||
stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC );
|
|
||||||
|
|
||||||
stmt->conn = conn;
|
|
||||||
|
|
||||||
// handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free
|
|
||||||
// in the catch block below.
|
|
||||||
stmt_h = SQL_NULL_HANDLE;
|
|
||||||
|
|
||||||
// process the options array given to core_sqlsrv_prepare.
|
|
||||||
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
|
|
||||||
zend_ulong index = -1;
|
|
||||||
zend_string *key = NULL;
|
|
||||||
zval* value_z = NULL;
|
|
||||||
|
|
||||||
ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) {
|
|
||||||
|
|
||||||
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
|
||||||
|
|
||||||
// The driver layer should ensure a valid key.
|
|
||||||
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." );
|
|
||||||
|
|
||||||
const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC );
|
|
||||||
|
|
||||||
// if the key didn't match, then return the error to the script.
|
|
||||||
// The driver layer should ensure that the key is valid.
|
|
||||||
DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." );
|
|
||||||
|
|
||||||
// perform the actions the statement option needs done.
|
|
||||||
(*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC );
|
|
||||||
} ZEND_HASH_FOREACH_END();
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlsrv_stmt* return_stmt = stmt;
|
|
||||||
stmt.transferred();
|
|
||||||
|
|
||||||
return return_stmt;
|
|
||||||
}
|
|
||||||
catch( core::CoreException& )
|
|
||||||
{
|
|
||||||
if( stmt ) {
|
|
||||||
|
|
||||||
conn->set_last_error( stmt->last_error() );
|
|
||||||
stmt->~sqlsrv_stmt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if allocating the handle failed before the statement was allocated, free the handle
|
|
||||||
if( stmt_h != SQL_NULL_HANDLE) {
|
|
||||||
::SQLFreeHandle( SQL_HANDLE_STMT, stmt_h );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch( ... ) {
|
|
||||||
|
|
||||||
DIE( "core_sqlsrv_allocate_stmt: Unknown exception caught." );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_bind_param
|
|
||||||
// Binds a parameter using SQLBindParameter. It allocates memory and handles other details
|
|
||||||
// in translating between the driver and ODBC.
|
|
||||||
// Parameters:
|
|
||||||
// param_num - number of the parameter, 0 based
|
|
||||||
// param_z - zval of the parameter
|
|
||||||
// php_out_type - type to return for output parameter
|
|
||||||
// sql_type - ODBC constant for the SQL Server type (SQL_UNKNOWN_TYPE = 0 means not known, so infer defaults)
|
|
||||||
// column_size - length of the field on the server (SQLSRV_UKNOWN_SIZE means not known, so infer defaults)
|
|
||||||
// decimal_digits - if column_size is valid and the type contains a scale, this contains the scale
|
|
||||||
// Return:
|
|
||||||
// Nothing, though an exception is thrown if an error occurs
|
|
||||||
// The php type of the parameter is taken from the zval.
|
|
||||||
// The sql type is given as a hint if the driver provides it.
|
|
||||||
|
|
||||||
void core_sqlsrv_bind_param( sqlsrv_stmt* stmt, SQLUSMALLINT param_num, SQLSMALLINT direction, zval* param_z,
|
|
||||||
SQLSRV_PHPTYPE php_out_type, SQLSRV_ENCODING encoding, SQLSMALLINT sql_type, SQLULEN column_size,
|
|
||||||
SQLSMALLINT decimal_digits TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLSMALLINT c_type;
|
|
||||||
SQLPOINTER buffer = NULL;
|
|
||||||
SQLLEN buffer_len = 0;
|
|
||||||
|
|
||||||
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT,
|
|
||||||
"core_sqlsrv_bind_param: Invalid parameter direction." );
|
|
||||||
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT || php_out_type != SQLSRV_PHPTYPE_INVALID,
|
|
||||||
"core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param." );
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// check is only < because params are 0 based
|
|
||||||
CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// resize the statements array of int_ptrs if the parameter isn't already set.
|
|
||||||
if( stmt->param_ind_ptrs.size() < static_cast<size_t>(param_num + 1) ) {
|
|
||||||
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
|
|
||||||
}
|
|
||||||
SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ];
|
|
||||||
|
|
||||||
zval* param_ref = param_z;
|
|
||||||
if ( Z_ISREF_P( param_z ) ) {
|
|
||||||
ZVAL_DEREF( param_z );
|
|
||||||
}
|
|
||||||
bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL );
|
|
||||||
bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE );
|
|
||||||
// if the user asks for for a specific type for input and output, make sure the data type we send matches the data we
|
|
||||||
// type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so
|
|
||||||
// we always let that match if they want a string back.
|
|
||||||
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
|
|
||||||
bool match = false;
|
|
||||||
switch( php_out_type ) {
|
|
||||||
case SQLSRV_PHPTYPE_INT:
|
|
||||||
if( zval_was_null || zval_was_bool ) {
|
|
||||||
convert_to_long( param_z );
|
|
||||||
}
|
|
||||||
match = Z_TYPE_P( param_z ) == IS_LONG;
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_FLOAT:
|
|
||||||
if( zval_was_null ) {
|
|
||||||
convert_to_double( param_z );
|
|
||||||
}
|
|
||||||
match = Z_TYPE_P( param_z ) == IS_DOUBLE;
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_STRING:
|
|
||||||
// anything can be converted to a string
|
|
||||||
convert_to_string( param_z );
|
|
||||||
match = true;
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_NULL:
|
|
||||||
case SQLSRV_PHPTYPE_DATETIME:
|
|
||||||
case SQLSRV_PHPTYPE_STREAM:
|
|
||||||
SQLSRV_ASSERT( false, "Invalid type for an output parameter." );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so
|
|
||||||
// when the buffer is filled, the type is correct
|
|
||||||
if( direction == SQL_PARAM_OUTPUT ) {
|
|
||||||
switch( php_out_type ) {
|
|
||||||
case SQLSRV_PHPTYPE_INT:
|
|
||||||
convert_to_long( param_z );
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_FLOAT:
|
|
||||||
convert_to_double( param_z );
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_STRING:
|
|
||||||
convert_to_string( param_z );
|
|
||||||
break;
|
|
||||||
case SQLSRV_PHPTYPE_NULL:
|
|
||||||
case SQLSRV_PHPTYPE_DATETIME:
|
|
||||||
case SQLSRV_PHPTYPE_STREAM:
|
|
||||||
SQLSRV_ASSERT( false, "Invalid type for an output parameter" );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SQLSRV_ASSERT( false, "Uknown SQLSRV_PHPTYPE_* constant given" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLSRV_ASSERT(( Z_TYPE_P( param_z ) != IS_STRING && Z_TYPE_P( param_z ) != IS_RESOURCE ) ||
|
|
||||||
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
|
|
||||||
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
|
|
||||||
|
|
||||||
// if the sql type is unknown, then set the default based on the PHP type passed in
|
|
||||||
if( sql_type == SQL_UNKNOWN_TYPE ) {
|
|
||||||
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the size is unknown, then set the default based on the PHP type passed in
|
|
||||||
if( column_size == SQLSRV_UNKNOWN_SIZE ) {
|
|
||||||
default_sql_size_and_scale( stmt, static_cast<unsigned int>( param_num ), param_z, encoding, column_size, decimal_digits TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine the ODBC C type
|
|
||||||
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
|
|
||||||
|
|
||||||
// set the buffer based on the PHP parameter type
|
|
||||||
switch( Z_TYPE_P( param_z )) {
|
|
||||||
|
|
||||||
case IS_NULL:
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
|
|
||||||
ind_ptr = SQL_NULL_DATA;
|
|
||||||
buffer = NULL;
|
|
||||||
buffer_len = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_TRUE:
|
|
||||||
case IS_FALSE:
|
|
||||||
case IS_LONG:
|
|
||||||
{
|
|
||||||
// if it is boolean, set the lval to 0 or 1
|
|
||||||
convert_to_long( param_z );
|
|
||||||
buffer = ¶m_z->value;
|
|
||||||
buffer_len = sizeof( Z_LVAL_P( param_z ));
|
|
||||||
ind_ptr = buffer_len;
|
|
||||||
if( direction != SQL_PARAM_INPUT ) {
|
|
||||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
|
||||||
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool );
|
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_DOUBLE:
|
|
||||||
{
|
|
||||||
buffer = ¶m_z->value;
|
|
||||||
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
|
||||||
ind_ptr = buffer_len;
|
|
||||||
if( direction != SQL_PARAM_INPUT ) {
|
|
||||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
|
||||||
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), false );
|
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_STRING:
|
|
||||||
buffer = Z_STRVAL_P( param_z );
|
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
|
||||||
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
|
|
||||||
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
|
|
||||||
|
|
||||||
zval wbuffer_z;
|
|
||||||
ZVAL_NULL( &wbuffer_z );
|
|
||||||
|
|
||||||
bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z );
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
|
||||||
param_num + 1, get_last_error_message() ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
buffer = Z_STRVAL_P( &wbuffer_z );
|
|
||||||
buffer_len = Z_STRLEN_P( &wbuffer_z );
|
|
||||||
core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC);
|
|
||||||
}
|
|
||||||
ind_ptr = buffer_len;
|
|
||||||
if( direction != SQL_PARAM_INPUT ) {
|
|
||||||
// PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion,
|
|
||||||
// we reallocate the string if it's interned
|
|
||||||
if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) {
|
|
||||||
core::sqlsrv_zval_stringl( param_z, static_cast<const char*>(buffer), buffer_len );
|
|
||||||
buffer = Z_STRVAL_P( param_z );
|
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
|
|
||||||
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
|
|
||||||
// convert it to wchar first
|
|
||||||
if( direction == SQL_PARAM_INPUT_OUTPUT &&
|
|
||||||
( c_type == SQL_C_WCHAR ||
|
|
||||||
( c_type == SQL_C_BINARY &&
|
|
||||||
( sql_type == SQL_WCHAR ||
|
|
||||||
sql_type == SQL_WVARCHAR ||
|
|
||||||
sql_type == SQL_WLONGVARCHAR )))) {
|
|
||||||
|
|
||||||
bool converted = convert_input_param_to_utf16( param_z, param_z );
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
|
||||||
param_num + 1, get_last_error_message() ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
buffer = Z_STRVAL_P( param_z );
|
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
|
||||||
ind_ptr = buffer_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// since this is an output string, assure there is enough space to hold the requested size and
|
|
||||||
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
|
|
||||||
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size,
|
|
||||||
buffer, buffer_len TSRMLS_CC );
|
|
||||||
|
|
||||||
// save the parameter to be adjusted and/or converted after the results are processed
|
|
||||||
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ));
|
|
||||||
|
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
|
||||||
|
|
||||||
// For output parameters, if we set the column_size to be same as the buffer_len,
|
|
||||||
// then if there is a truncation due to the data coming from the server being
|
|
||||||
// greater than the column_size, we don't get any truncation error. In order to
|
|
||||||
// avoid this silent truncation, we set the column_size to be "MAX" size for
|
|
||||||
// string types. This will guarantee that there is no silent truncation for
|
|
||||||
// output parameters.
|
|
||||||
if( direction == SQL_PARAM_OUTPUT ) {
|
|
||||||
|
|
||||||
switch( sql_type ) {
|
|
||||||
|
|
||||||
case SQL_VARBINARY:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
case SQL_WVARCHAR:
|
|
||||||
column_size = SQL_SS_LENGTH_UNLIMITED;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_RESOURCE:
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
|
|
||||||
sqlsrv_stream stream_encoding( param_z, encoding );
|
|
||||||
HashTable* streams_ht = Z_ARRVAL( stmt->param_streams );
|
|
||||||
core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC );
|
|
||||||
buffer = reinterpret_cast<SQLPOINTER>( param_num );
|
|
||||||
Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it
|
|
||||||
buffer_len = 0;
|
|
||||||
ind_ptr = SQL_DATA_AT_EXEC;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_OBJECT:
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." );
|
|
||||||
zval function_z;
|
|
||||||
zval buffer_z;
|
|
||||||
zval format_z;
|
|
||||||
zval params[1];
|
|
||||||
ZVAL_UNDEF( &function_z );
|
|
||||||
ZVAL_UNDEF( &buffer_z );
|
|
||||||
ZVAL_UNDEF( &format_z );
|
|
||||||
ZVAL_UNDEF( params );
|
|
||||||
|
|
||||||
bool valid_class_name_found = false;
|
|
||||||
|
|
||||||
zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC );
|
|
||||||
|
|
||||||
while( class_entry != NULL ) {
|
|
||||||
|
|
||||||
if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
|
|
||||||
stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) {
|
|
||||||
valid_class_name_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Check the parent
|
|
||||||
class_entry = class_entry->parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
|
|
||||||
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
|
||||||
// sql type, it lacks the timezone.
|
|
||||||
if( sql_type == SQL_SS_TIMESTAMPOFFSET ) {
|
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
|
|
||||||
DateTime::DATETIMEOFFSET_FORMAT_LEN );
|
|
||||||
}
|
|
||||||
else if( sql_type == SQL_TYPE_DATE ) {
|
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN );
|
|
||||||
}
|
|
||||||
// call the DateTime::format member function to convert the object to a string that SQL Server understands
|
|
||||||
core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 );
|
|
||||||
params[0] = format_z;
|
|
||||||
// This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the
|
|
||||||
// DateTime object and $format_z is the format string.
|
|
||||||
int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC );
|
|
||||||
zend_string_release( Z_STR( format_z ));
|
|
||||||
zend_string_release( Z_STR( function_z ));
|
|
||||||
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
buffer = Z_STRVAL( buffer_z );
|
|
||||||
zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z );
|
|
||||||
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
buffer_len = Z_STRLEN( buffer_z ) - 1;
|
|
||||||
ind_ptr = buffer_len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IS_ARRAY:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DIE( "core_sqlsrv_bind_param: Unsupported PHP type. Only string, float, int, and streams (resource) are supported. "
|
|
||||||
"It is the responsibilty of the driver layer to convert a parameter to one of these types." );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( zval_was_null ) {
|
|
||||||
ind_ptr = SQL_NULL_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
core::SQLBindParameter( stmt, param_num + 1, direction,
|
|
||||||
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
stmt->free_param_data( TSRMLS_C );
|
|
||||||
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_execute
|
|
||||||
// Executes the statement previously prepared
|
|
||||||
// Parameters:
|
|
||||||
// stmt - the core sqlsrv_stmt structure that contains the ODBC handle
|
|
||||||
// Return:
|
|
||||||
// true if there is data, false if there is not
|
|
||||||
|
|
||||||
void core_sqlsrv_execute( sqlsrv_stmt* stmt TSRMLS_DC, const char* sql, int sql_len )
|
|
||||||
{
|
|
||||||
SQLRETURN r;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// close the stream to release the resource
|
|
||||||
close_active_stream( stmt TSRMLS_CC );
|
|
||||||
|
|
||||||
if( sql ) {
|
|
||||||
|
|
||||||
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
|
|
||||||
unsigned int wsql_len = 0;
|
|
||||||
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
|
|
||||||
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
|
|
||||||
wsql_string[0] = L'\0';
|
|
||||||
wsql_len = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
|
|
||||||
stmt->encoding() );
|
|
||||||
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
|
|
||||||
sql_len, &wsql_len );
|
|
||||||
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
|
|
||||||
get_last_error_message() ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
r = core::SQLExecute( stmt TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if data is needed (streams were bound) and they should be sent at execute time, then do so now
|
|
||||||
if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) {
|
|
||||||
|
|
||||||
send_param_streams( stmt TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt->new_result_set( TSRMLS_C );
|
|
||||||
stmt->executed = true;
|
|
||||||
|
|
||||||
// if all the data has been sent and no data was returned then finalize the output parameters
|
|
||||||
if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) {
|
|
||||||
|
|
||||||
finalize_output_parameters( stmt TSRMLS_CC );
|
|
||||||
}
|
|
||||||
// stream parameters are sent, clean the Hashtable
|
|
||||||
if ( stmt->send_streams_at_exec ) {
|
|
||||||
zend_hash_clean( Z_ARRVAL( stmt->param_streams ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
|
|
||||||
// if the statement executed but failed in a subsequent operation before returning,
|
|
||||||
// we need to cancel the statement and deref the output and stream parameters
|
|
||||||
if ( stmt->send_streams_at_exec ) {
|
|
||||||
zend_hash_clean( Z_ARRVAL( stmt->output_params ));
|
|
||||||
zend_hash_clean( Z_ARRVAL( stmt->param_streams ));
|
|
||||||
}
|
|
||||||
if( stmt->executed ) {
|
|
||||||
SQLCancel( stmt->handle() );
|
|
||||||
// stmt->executed = false; should this be reset if something fails?
|
|
||||||
}
|
|
||||||
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_fetch
|
|
||||||
// Moves the cursor according to the parameters (by default, moves to the next row)
|
|
||||||
// Parameters:
|
|
||||||
// stmt - the sqlsrv_stmt of the cursor
|
|
||||||
// fetch_orientation - method to move the cursor
|
|
||||||
// fetch_offset - if the method has a parameter (such as number of rows to move or literal row number)
|
|
||||||
// Returns:
|
|
||||||
// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the
|
|
||||||
// user scrolls past a non-scrollable result set
|
|
||||||
|
|
||||||
bool core_sqlsrv_fetch( sqlsrv_stmt* stmt, SQLSMALLINT fetch_orientation, SQLULEN fetch_offset TSRMLS_DC )
|
|
||||||
{
|
|
||||||
// pre-condition check
|
|
||||||
SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE,
|
|
||||||
"core_sqlsrv_fetch: Invalid value provided for fetch_orientation parameter." );
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// clear the field cache of the previous fetch
|
|
||||||
zend_hash_clean( Z_ARRVAL( stmt->field_cache ));
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// close the stream to release the resource
|
|
||||||
close_active_stream( stmt TSRMLS_CC );
|
|
||||||
|
|
||||||
// if the statement has rows and is not scrollable but doesn't yet have
|
|
||||||
// fetch_called, this must be the first time we've called sqlsrv_fetch.
|
|
||||||
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY && stmt->has_rows && !stmt->fetch_called ) {
|
|
||||||
stmt->fetch_called = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
|
|
||||||
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided.
|
|
||||||
SQLRETURN r = stmt->current_results->fetch( fetch_orientation,
|
|
||||||
( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1
|
|
||||||
TSRMLS_CC );
|
|
||||||
if( r == SQL_NO_DATA ) {
|
|
||||||
// if this is a forward only cursor, mark that we've passed the end so future calls result in an error
|
|
||||||
if( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY ) {
|
|
||||||
stmt->past_fetch_end = true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark that we called fetch (which get_field, et. al. uses) and reset our last field retrieved
|
|
||||||
stmt->fetch_called = true;
|
|
||||||
stmt->last_field_index = -1;
|
|
||||||
stmt->has_rows = true; // since we made it this far, we must have at least one row
|
|
||||||
}
|
|
||||||
catch (core::CoreException& e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
catch ( ... ) {
|
|
||||||
DIE( "core_sqlsrv_fetch: Unexpected exception occurred." );
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Retrieves metadata for a field of a prepared statement.
|
|
||||||
// Parameters:
|
|
||||||
// colno - the index of the field for which to return the metadata. columns are 0 based in PDO
|
|
||||||
// Return:
|
|
||||||
// A field_meta_data* consisting of the field metadata.
|
|
||||||
|
|
||||||
field_meta_data* core_sqlsrv_field_metadata( sqlsrv_stmt* stmt, SQLSMALLINT colno TSRMLS_DC )
|
|
||||||
{
|
|
||||||
// pre-condition check
|
|
||||||
SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." );
|
|
||||||
|
|
||||||
sqlsrv_malloc_auto_ptr<field_meta_data> meta_data;
|
|
||||||
SQLSMALLINT field_name_len = 0;
|
|
||||||
|
|
||||||
meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data();
|
|
||||||
meta_data->field_name = static_cast<SQLCHAR*>( sqlsrv_malloc( SS_MAXCOLNAMELEN + 1 ));
|
|
||||||
|
|
||||||
try {
|
|
||||||
core::SQLDescribeCol( stmt, colno + 1, meta_data->field_name.get(), SS_MAXCOLNAMELEN, &field_name_len,
|
|
||||||
&(meta_data->field_type), &(meta_data->field_size), &(meta_data->field_scale),
|
|
||||||
&(meta_data->field_is_nullable) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// depending on field type, we add the values into size or precision/scale.
|
|
||||||
switch( meta_data->field_type ) {
|
|
||||||
case SQL_DECIMAL:
|
|
||||||
case SQL_NUMERIC:
|
|
||||||
case SQL_TYPE_TIMESTAMP:
|
|
||||||
case SQL_TYPE_DATE:
|
|
||||||
case SQL_SS_TIME2:
|
|
||||||
case SQL_SS_TIMESTAMPOFFSET:
|
|
||||||
case SQL_BIT:
|
|
||||||
case SQL_TINYINT:
|
|
||||||
case SQL_SMALLINT:
|
|
||||||
case SQL_INTEGER:
|
|
||||||
case SQL_BIGINT:
|
|
||||||
case SQL_REAL:
|
|
||||||
case SQL_FLOAT:
|
|
||||||
case SQL_DOUBLE:
|
|
||||||
{
|
|
||||||
meta_data->field_precision = meta_data->field_size;
|
|
||||||
meta_data->field_size = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the field name lenth
|
|
||||||
meta_data->field_name_len = field_name_len;
|
|
||||||
|
|
||||||
field_meta_data* result_field_meta_data = meta_data;
|
|
||||||
meta_data.transferred();
|
|
||||||
return result_field_meta_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_get_field
|
|
||||||
// Return the value of a column from ODBC
|
|
||||||
// Parameters:
|
|
||||||
// stmt - the sqlsrv_stmt from which to retrieve the column
|
|
||||||
// field_index - 0 based index for the column to retrieve
|
|
||||||
// sqlsrv_php_type_in - sqlsrv_php_type structure that tells what format to return the data in
|
|
||||||
// field_value - pointer to the data retrieved
|
|
||||||
// field_len - length of the data in the field_value buffer
|
|
||||||
// Returns:
|
|
||||||
// Nothing, excpetion thrown if an error occurs
|
|
||||||
|
|
||||||
void core_sqlsrv_get_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type_in, bool prefer_string,
|
|
||||||
_Out_ void*& field_value, _Out_ SQLLEN* field_len, bool cache_field,
|
|
||||||
_Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
// close the stream to release the resource
|
|
||||||
close_active_stream(stmt TSRMLS_CC);
|
|
||||||
|
|
||||||
// if the field has been retrieved before, return the previous result
|
|
||||||
field_cache* cached = NULL;
|
|
||||||
if (NULL != ( cached = static_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast<zend_ulong>( field_index ))))) {
|
|
||||||
// the field value is NULL
|
|
||||||
if( cached->value == NULL ) {
|
|
||||||
field_value = NULL;
|
|
||||||
*field_len = 0;
|
|
||||||
if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 );
|
|
||||||
memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len );
|
|
||||||
if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) {
|
|
||||||
// prevent the 'string not null terminated' warning
|
|
||||||
reinterpret_cast<char*>( field_value )[ cached->len ] = '\0';
|
|
||||||
}
|
|
||||||
*field_len = cached->len;
|
|
||||||
if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>(cached->type.typeinfo.type); }
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
|
|
||||||
|
|
||||||
SQLLEN sql_field_type = 0;
|
|
||||||
SQLLEN sql_field_len = 0;
|
|
||||||
|
|
||||||
// Make sure that the statement was executed and not just prepared.
|
|
||||||
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
|
|
||||||
// may also be retrieved.
|
|
||||||
if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) {
|
|
||||||
sqlsrv_phptype invalid;
|
|
||||||
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
|
||||||
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
|
|
||||||
SQLSRV_ASSERT((cached = reinterpret_cast<field_cache*>(zend_hash_index_find_ptr(Z_ARRVAL(stmt->field_cache), i))) == NULL,
|
|
||||||
"Field already cached." );
|
|
||||||
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field,
|
|
||||||
sqlsrv_php_type_out TSRMLS_CC );
|
|
||||||
// delete the value returned since we only want it cached, not the actual value
|
|
||||||
if( field_value ) {
|
|
||||||
efree( field_value );
|
|
||||||
field_value = NULL;
|
|
||||||
*field_len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the php type was not specified set the php type to be the default type.
|
|
||||||
if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) {
|
|
||||||
|
|
||||||
// Get the SQL type of the field.
|
|
||||||
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
|
|
||||||
|
|
||||||
// Get the length of the field.
|
|
||||||
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC );
|
|
||||||
|
|
||||||
// Get the corresponding php type from the sql type.
|
|
||||||
sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast<SQLINTEGER>( sql_field_type ), static_cast<SQLUINTEGER>( sql_field_len ), prefer_string );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that we have an acceptable type to convert.
|
|
||||||
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sqlsrv_php_type_out != NULL )
|
|
||||||
*sqlsrv_php_type_out = static_cast<SQLSRV_PHPTYPE>( sqlsrv_php_type.typeinfo.type );
|
|
||||||
|
|
||||||
// Retrieve the data
|
|
||||||
core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
|
||||||
|
|
||||||
// if the user wants us to cache the field, we'll do it
|
|
||||||
if( cache_field ) {
|
|
||||||
field_cache cache( field_value, *field_len, sqlsrv_php_type );
|
|
||||||
core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// core_sqlsrv_has_any_result
|
|
||||||
// return if any result set or rows affected message is waiting
|
|
||||||
// to be consumed and moved over by sqlsrv_next_result.
|
|
||||||
// Parameters:
|
|
||||||
// stmt - The statement object on which to check for results.
|
|
||||||
// Return:
|
|
||||||
// true if any results are present, false otherwise.
|
|
||||||
|
|
||||||
bool core_sqlsrv_has_any_result( sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
// Use SQLNumResultCols to determine if we have rows or not.
|
|
||||||
SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC );
|
|
||||||
// use SQLRowCount to determine if there is a rows status waiting
|
|
||||||
SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC );
|
|
||||||
return (num_cols != 0) || (rows_affected > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// core_sqlsrv_next_result
|
|
||||||
// Advances to the next result set from the last executed query
|
|
||||||
// Parameters
|
|
||||||
// stmt - the sqlsrv_stmt structure
|
|
||||||
// Returns
|
|
||||||
// Nothing, exception thrown if problem occurs
|
|
||||||
|
|
||||||
void core_sqlsrv_next_result( sqlsrv_stmt* stmt TSRMLS_DC, bool finalize_output_params, bool throw_on_errors )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
// make sure that the statement has been executed.
|
|
||||||
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( stmt->past_next_result_end, stmt, SQLSRV_ERROR_NEXT_RESULT_PAST_END ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
close_active_stream( stmt TSRMLS_CC );
|
|
||||||
|
|
||||||
SQLRETURN r;
|
|
||||||
if( throw_on_errors ) {
|
|
||||||
r = core::SQLMoreResults( stmt TSRMLS_CC );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
r = SQLMoreResults( stmt->handle() );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r == SQL_NO_DATA ) {
|
|
||||||
|
|
||||||
if( &(stmt->output_params) && finalize_output_params ) {
|
|
||||||
// if we're finished processing result sets, handle the output parameters
|
|
||||||
finalize_output_parameters( stmt TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark we are past the end of all results
|
|
||||||
stmt->past_next_result_end = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt->new_result_set( TSRMLS_C );
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
|
|
||||||
SQLCancel( stmt->handle() );
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_post_param
|
|
||||||
// Performs any actions post execution for each parameter. For now it cleans up input parameters memory from the statement
|
|
||||||
// Parameters:
|
|
||||||
// stmt - the sqlsrv_stmt structure
|
|
||||||
// param_num - 0 based index of the parameter
|
|
||||||
// param_z - parameter value itself.
|
|
||||||
// Returns:
|
|
||||||
// Nothing, exception thrown if problem occurs
|
|
||||||
|
|
||||||
void core_sqlsrv_post_param( sqlsrv_stmt* stmt, zend_ulong param_num, zval* param_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." );
|
|
||||||
SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." );
|
|
||||||
|
|
||||||
// if the parameter was an input string, delete it from the array holding input parameter strings
|
|
||||||
if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) {
|
|
||||||
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams
|
|
||||||
// PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it
|
|
||||||
// with sqlsrv_send_stream_data.
|
|
||||||
if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) {
|
|
||||||
sqlsrv_stream* stream_encoding = NULL;
|
|
||||||
stream_encoding = reinterpret_cast<sqlsrv_stream*>(zend_hash_index_find_ptr(Z_ARRVAL(stmt->param_streams), param_num));
|
|
||||||
core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Calls SQLSetStmtAttr to set a cursor.
|
|
||||||
void core_sqlsrv_set_scrollable( sqlsrv_stmt* stmt, unsigned long cursor_type TSRMLS_DC )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
switch( cursor_type ) {
|
|
||||||
|
|
||||||
case SQL_CURSOR_STATIC:
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
|
|
||||||
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SQL_CURSOR_DYNAMIC:
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
|
|
||||||
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SQL_CURSOR_KEYSET_DRIVEN:
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
|
|
||||||
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SQL_CURSOR_FORWARD_ONLY:
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
|
|
||||||
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SQLSRV_CURSOR_BUFFERED:
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE,
|
|
||||||
reinterpret_cast<SQLPOINTER>( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt->cursor_type = cursor_type;
|
|
||||||
|
|
||||||
}
|
|
||||||
catch( core::CoreException& ) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
if( Z_TYPE_P( value_z ) != IS_LONG ) {
|
|
||||||
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
|
|
||||||
}
|
|
||||||
|
|
||||||
core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC )
|
|
||||||
{
|
|
||||||
if( limit <= 0 ) {
|
|
||||||
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT );
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt->buffered_query_limit = limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout
|
|
||||||
// which accepts timeout parameter as a long. If the zval is not of type long
|
|
||||||
// than throws error.
|
|
||||||
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
// validate the value
|
|
||||||
if( Z_TYPE_P( value_z ) != IS_LONG || Z_LVAL_P( value_z ) < 0 ) {
|
|
||||||
|
|
||||||
convert_to_string( value_z );
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
core_sqlsrv_set_query_timeout( stmt, static_cast<long>( Z_LVAL_P( value_z )) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
catch( core::CoreException& ) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overloaded. Accepts the timeout as a long.
|
|
||||||
void core_sqlsrv_set_query_timeout( sqlsrv_stmt* stmt, long timeout TSRMLS_DC )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." );
|
|
||||||
|
|
||||||
// set the statement attribute
|
|
||||||
core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast<SQLPOINTER>( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC );
|
|
||||||
|
|
||||||
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
|
|
||||||
// is represented by -1.
|
|
||||||
long lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ );
|
|
||||||
|
|
||||||
// set the LOCK_TIMEOUT on the server.
|
|
||||||
char lock_timeout_sql[ 32 ];
|
|
||||||
int written = sprintf_s( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d",
|
|
||||||
lock_timeout );
|
|
||||||
|
|
||||||
SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )),
|
|
||||||
"stmt_option_query_timeout: sprintf_s failed. Shouldn't ever fail." );
|
|
||||||
|
|
||||||
core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC );
|
|
||||||
|
|
||||||
stmt->query_timeout = timeout;
|
|
||||||
}
|
|
||||||
catch( core::CoreException& ) {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
TSRMLS_C;
|
|
||||||
|
|
||||||
// zend_is_true does not fail. It either returns true or false.
|
|
||||||
stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// core_sqlsrv_send_stream_packet
|
|
||||||
// send a single packet from a stream parameter to the database using
|
|
||||||
// ODBC. This will also handle the transition between parameters. It
|
|
||||||
// returns true if it is not done sending, false if it is finished.
|
|
||||||
// return_value is what should be returned to the script if it is
|
|
||||||
// given. Any errors that occur are posted here.
|
|
||||||
// Parameters:
|
|
||||||
// stmt - query to send the next packet for
|
|
||||||
// Returns:
|
|
||||||
// true if more data remains to be sent, false if all data processed
|
|
||||||
|
|
||||||
bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLRETURN r = SQL_SUCCESS;
|
|
||||||
|
|
||||||
// if there no current parameter to process, get the next one
|
|
||||||
// (probably because this is the first call to sqlsrv_send_stream_data)
|
|
||||||
if( stmt->current_stream.stream_z == NULL ) {
|
|
||||||
|
|
||||||
if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) {
|
|
||||||
|
|
||||||
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
|
|
||||||
stmt->current_stream_read = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
// get the stream from the zval we bound
|
|
||||||
php_stream* param_stream = NULL;
|
|
||||||
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC );
|
|
||||||
|
|
||||||
// if we're at the end, then release our current parameter
|
|
||||||
if( php_stream_eof( param_stream )) {
|
|
||||||
// if no data was actually sent prior, then send a NULL
|
|
||||||
if( stmt->current_stream_read == 0 ) {
|
|
||||||
// send an empty string, which is what a 0 length does.
|
|
||||||
char buff[1]; // temp storage to hand to SQLPutData
|
|
||||||
core::SQLPutData( stmt, buff, 0 TSRMLS_CC );
|
|
||||||
}
|
|
||||||
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
|
|
||||||
stmt->current_stream_read = 0;
|
|
||||||
}
|
|
||||||
// read the data from the stream, send it via SQLPutData and track how much we've sent.
|
|
||||||
else {
|
|
||||||
char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
|
|
||||||
std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character
|
|
||||||
std::size_t read = php_stream_read( param_stream, buffer, buffer_size );
|
|
||||||
|
|
||||||
if (read > UINT_MAX)
|
|
||||||
{
|
|
||||||
LOG(SEV_ERROR, "PHP stream: buffer length exceeded.");
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt->current_stream_read += static_cast<unsigned int>( read );
|
|
||||||
if( read > 0 ) {
|
|
||||||
// if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
|
|
||||||
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
|
|
||||||
// twice.
|
|
||||||
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
|
|
||||||
// since all other MBCS supported by SQL Server are 2 byte maximum size.
|
|
||||||
if( stmt->current_stream.encoding == CP_UTF8 ) {
|
|
||||||
|
|
||||||
// the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a
|
|
||||||
// expansion of 2x the UTF-8 size.
|
|
||||||
wchar_t wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ];
|
|
||||||
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
|
|
||||||
int wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
|
|
||||||
buffer, static_cast<int>( read ), wbuffer, static_cast<int>( sizeof( wbuffer ) / sizeof( wchar_t )));
|
|
||||||
if( wsize == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION ) {
|
|
||||||
|
|
||||||
// this will calculate how many bytes were cut off from the last UTF-8 character and read that many more
|
|
||||||
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
|
|
||||||
size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC );
|
|
||||||
// read the missing bytes
|
|
||||||
size_t new_read = php_stream_read( param_stream, static_cast<char*>( buffer ) + read,
|
|
||||||
need_to_read );
|
|
||||||
// if the bytes couldn't be read, then we return an error
|
|
||||||
CHECK_CUSTOM_ERROR( new_read != need_to_read, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
|
|
||||||
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
// try the conversion again with the complete character
|
|
||||||
wsize = MultiByteToWideChar( stmt->current_stream.encoding, MB_ERR_INVALID_CHARS,
|
|
||||||
buffer, static_cast<int>( read + new_read ), wbuffer, static_cast<int>( sizeof( wbuffer ) / sizeof( wchar_t )));
|
|
||||||
// something else must be wrong if it failed
|
|
||||||
CHECK_CUSTOM_ERROR( wsize == 0, stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
|
|
||||||
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION )) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
core::SQLPutData( stmt, wbuffer, wsize * sizeof( wchar_t ) TSRMLS_CC );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core::SQLPutData( stmt, buffer, read TSRMLS_CC );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
stmt->free_param_data( TSRMLS_C );
|
|
||||||
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
|
|
||||||
SQLCancel( stmt->handle() );
|
|
||||||
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT );
|
|
||||||
stmt->current_stream_read = 0;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC )
|
|
||||||
{
|
|
||||||
TSRMLS_C;
|
|
||||||
|
|
||||||
// This implementation should never get called.
|
|
||||||
DIE( "Not implemented." );
|
|
||||||
}
|
|
||||||
|
|
||||||
void stmt_option_query_timeout:: operator()( sqlsrv_stmt* stmt, stmt_option const* /**/, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
void stmt_option_send_at_exec:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
void stmt_option_buffered_query_limit:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC )
|
|
||||||
{
|
|
||||||
core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// internal function to release the active stream. Called by each main API function
|
|
||||||
// that will alter the statement and cancel any retrieval of data from a stream.
|
|
||||||
void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
// if there is no active stream, return
|
|
||||||
if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
php_stream* stream = NULL;
|
|
||||||
|
|
||||||
// we use no verify since verify would return immediately and we want to assert, not return.
|
|
||||||
php_stream_from_zval_no_verify( stream, &( stmt->active_stream ));
|
|
||||||
|
|
||||||
SQLSRV_ASSERT(( stream != NULL ), "close_active_stream: Unknown resource type as our active stream." );
|
|
||||||
|
|
||||||
php_stream_close( stream ); // this will NULL out the active stream in the statement. We don't check for errors here.
|
|
||||||
|
|
||||||
SQLSRV_ASSERT( Z_TYPE( stmt->active_stream ) == IS_UNDEF, "close_active_stream: Active stream not closed." );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// local routines not shared by other files (arranged alphabetically)
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
bool is_streamable_type( SQLLEN sql_type )
|
|
||||||
{
|
|
||||||
switch( sql_type ) {
|
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_WCHAR:
|
|
||||||
case SQL_BINARY:
|
|
||||||
case SQL_VARBINARY:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
case SQL_WVARCHAR:
|
|
||||||
case SQL_SS_XML:
|
|
||||||
case SQL_LONGVARBINARY:
|
|
||||||
case SQL_LONGVARCHAR:
|
|
||||||
case SQL_WLONGVARCHAR:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void calc_string_size( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, SQLLEN sql_type, _Out_ SQLLEN& size TSRMLS_DC )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
switch( sql_type ) {
|
|
||||||
// for types that are fixed in size or for which the size is unknown, return the display size.
|
|
||||||
case SQL_BIGINT:
|
|
||||||
case SQL_BIT:
|
|
||||||
case SQL_INTEGER:
|
|
||||||
case SQL_SMALLINT:
|
|
||||||
case SQL_TINYINT:
|
|
||||||
case SQL_GUID:
|
|
||||||
case SQL_FLOAT:
|
|
||||||
case SQL_DOUBLE:
|
|
||||||
case SQL_REAL:
|
|
||||||
case SQL_DECIMAL:
|
|
||||||
case SQL_NUMERIC:
|
|
||||||
case SQL_TYPE_TIMESTAMP:
|
|
||||||
case SQL_LONGVARBINARY:
|
|
||||||
case SQL_LONGVARCHAR:
|
|
||||||
case SQL_BINARY:
|
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_VARBINARY:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
case SQL_SS_XML:
|
|
||||||
case SQL_SS_UDT:
|
|
||||||
case SQL_WLONGVARCHAR:
|
|
||||||
case SQL_DATETIME:
|
|
||||||
case SQL_TYPE_DATE:
|
|
||||||
case SQL_SS_TIME2:
|
|
||||||
case SQL_SS_TIMESTAMPOFFSET:
|
|
||||||
{
|
|
||||||
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for wide char types for which the size is known, return the octet length instead, since it will include the
|
|
||||||
// the number of bytes necessary for the string, not just the characters
|
|
||||||
case SQL_WCHAR:
|
|
||||||
case SQL_WVARCHAR:
|
|
||||||
{
|
|
||||||
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
DIE ( "Unexpected SQL type encountered in calc_string_size." );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// calculates how many characters were cut off from the end of a buffer when reading
|
|
||||||
// in UTF-8 encoded text
|
|
||||||
|
|
||||||
size_t calc_utf8_missing( sqlsrv_stmt* stmt, const char* buffer, size_t buffer_end TSRMLS_DC )
|
|
||||||
{
|
|
||||||
const char* last_char = buffer + buffer_end - 1;
|
|
||||||
size_t need_to_read = 0;
|
|
||||||
|
|
||||||
// rewind until we are at the byte that starts the cut off character
|
|
||||||
while( (*last_char & UTF8_MIDBYTE_MASK ) == UTF8_MIDBYTE_TAG ) {
|
|
||||||
--last_char;
|
|
||||||
++need_to_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine how many bytes we need to read in based on the number of bytes in the character
|
|
||||||
// (# of high bits set) versus the number of bytes we've already read.
|
|
||||||
switch( *last_char & UTF8_NBYTESEQ_MASK ) {
|
|
||||||
case UTF8_2BYTESEQ_TAG1:
|
|
||||||
case UTF8_2BYTESEQ_TAG2:
|
|
||||||
need_to_read = 1 - need_to_read;
|
|
||||||
break;
|
|
||||||
case UTF8_3BYTESEQ_TAG:
|
|
||||||
need_to_read = 2 - need_to_read;
|
|
||||||
break;
|
|
||||||
case UTF8_4BYTESEQ_TAG:
|
|
||||||
need_to_read = 3 - need_to_read;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE,
|
|
||||||
get_last_error_message( ERROR_NO_UNICODE_TRANSLATION ));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return need_to_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Caller is responsible for freeing the memory allocated for the field_value.
|
|
||||||
// The memory allocation has to happen in the core layer because otherwise
|
|
||||||
// the driver layer would have to calculate size of the field_value
|
|
||||||
// to decide the amount of memory allocation.
|
|
||||||
void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype
|
|
||||||
sqlsrv_php_type, _Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC )
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
|
|
||||||
close_active_stream( stmt TSRMLS_CC );
|
|
||||||
|
|
||||||
// make sure that fetch is called before trying to retrieve.
|
|
||||||
CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that fields are not retrieved incorrectly.
|
|
||||||
CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index,
|
|
||||||
stmt->last_field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( sqlsrv_php_type.typeinfo.type ) {
|
|
||||||
|
|
||||||
case SQLSRV_PHPTYPE_INT:
|
|
||||||
{
|
|
||||||
sqlsrv_malloc_auto_ptr<long> field_value_temp;
|
|
||||||
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
|
|
||||||
|
|
||||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
|
|
||||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
|
||||||
|
|
||||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( *field_len == SQL_NULL_DATA ) {
|
|
||||||
field_value = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
field_value = field_value_temp;
|
|
||||||
field_value_temp.transferred();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SQLSRV_PHPTYPE_FLOAT:
|
|
||||||
{
|
|
||||||
sqlsrv_malloc_auto_ptr<double> field_value_temp;
|
|
||||||
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
|
|
||||||
|
|
||||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
|
|
||||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
|
||||||
|
|
||||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( *field_len == SQL_NULL_DATA ) {
|
|
||||||
field_value = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
field_value = field_value_temp;
|
|
||||||
field_value_temp.transferred();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SQLSRV_PHPTYPE_STRING:
|
|
||||||
{
|
|
||||||
get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
|
|
||||||
// convert it to a DateTime object and return the created object
|
|
||||||
case SQLSRV_PHPTYPE_DATETIME:
|
|
||||||
{
|
|
||||||
char field_value_temp[ MAX_DATETIME_STRING_LEN ];
|
|
||||||
zval params[1];
|
|
||||||
zval field_value_temp_z;
|
|
||||||
zval function_z;
|
|
||||||
|
|
||||||
ZVAL_UNDEF( &field_value_temp_z );
|
|
||||||
ZVAL_UNDEF( &function_z );
|
|
||||||
ZVAL_UNDEF( params );
|
|
||||||
|
|
||||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp,
|
|
||||||
MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
zval_auto_ptr return_value_z;
|
|
||||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
|
||||||
ZVAL_UNDEF( return_value_z );
|
|
||||||
|
|
||||||
if( *field_len == SQL_NULL_DATA ) {
|
|
||||||
ZVAL_NULL( return_value_z );
|
|
||||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
|
||||||
return_value_z.transferred();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the string date to a DateTime object
|
|
||||||
core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len );
|
|
||||||
core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 );
|
|
||||||
params[0] = field_value_temp_z;
|
|
||||||
|
|
||||||
if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1,
|
|
||||||
params TSRMLS_CC ) == FAILURE) {
|
|
||||||
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
|
||||||
return_value_z.transferred();
|
|
||||||
zend_string_free( Z_STR( field_value_temp_z ));
|
|
||||||
zend_string_free( Z_STR( function_z ));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
|
|
||||||
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
|
|
||||||
// for how these fields are used.
|
|
||||||
case SQLSRV_PHPTYPE_STREAM:
|
|
||||||
{
|
|
||||||
|
|
||||||
php_stream* stream = NULL;
|
|
||||||
sqlsrv_stream* ss = NULL;
|
|
||||||
SQLLEN sql_type;
|
|
||||||
|
|
||||||
SQLRETURN r = SQLColAttribute( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
|
|
||||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
ss = static_cast<sqlsrv_stream*>( stream->abstract );
|
|
||||||
ss->stmt = stmt;
|
|
||||||
ss->field_index = field_index;
|
|
||||||
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
|
|
||||||
ss->encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding );
|
|
||||||
|
|
||||||
zval_auto_ptr return_value_z;
|
|
||||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
|
||||||
ZVAL_UNDEF( return_value_z );
|
|
||||||
|
|
||||||
// turn our stream into a zval to be returned
|
|
||||||
php_stream_to_zval( stream, return_value_z );
|
|
||||||
|
|
||||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
|
||||||
return_value_z.transferred();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SQLSRV_PHPTYPE_NULL:
|
|
||||||
field_value = NULL;
|
|
||||||
*field_len = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sucessfully retrieved the field, so update our last retrieved field
|
|
||||||
if( stmt->last_field_index < field_index ) {
|
|
||||||
stmt->last_field_index = field_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( core::CoreException& e ) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// check_for_next_stream_parameter
|
|
||||||
// see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise
|
|
||||||
// returns false
|
|
||||||
bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
int stream_index = 0;
|
|
||||||
SQLRETURN r = SQL_SUCCESS;
|
|
||||||
sqlsrv_stream* stream_encoding = NULL;
|
|
||||||
zval* param_z = NULL;
|
|
||||||
|
|
||||||
// get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param
|
|
||||||
r = core::SQLParamData( stmt, reinterpret_cast<SQLPOINTER*>( &stream_index ) TSRMLS_CC );
|
|
||||||
// if no more data, we've exhausted the bound parameters, so return that we're done
|
|
||||||
if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
|
|
||||||
|
|
||||||
// we're all done, so return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashTable* streams_ht = Z_ARRVAL( stmt->param_streams );
|
|
||||||
|
|
||||||
// pull out the sqlsrv_encoding struct
|
|
||||||
stream_encoding = reinterpret_cast<sqlsrv_stream*>(zend_hash_index_find_ptr(streams_ht, stream_index));
|
|
||||||
SQLSRV_ASSERT(stream_encoding != NULL, "Stream parameter does not exist"); // if the index isn't in the hash, that's a serious error
|
|
||||||
|
|
||||||
param_z = stream_encoding->stream_z;
|
|
||||||
|
|
||||||
// make the next stream current
|
|
||||||
stmt->current_stream = sqlsrv_stream( param_z, stream_encoding->encoding );
|
|
||||||
stmt->current_stream_read = 0;
|
|
||||||
|
|
||||||
// there are more parameters
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// utility routine to convert an input parameter from UTF-8 to UTF-16
|
|
||||||
|
|
||||||
bool convert_input_param_to_utf16( zval* input_param_z, zval* converted_param_z )
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( input_param_z == converted_param_z || Z_TYPE_P( converted_param_z ) == IS_NULL,
|
|
||||||
"convert_input_param_z called with invalid parameter states" );
|
|
||||||
|
|
||||||
const char* buffer = Z_STRVAL_P( input_param_z );
|
|
||||||
std::size_t buffer_len = Z_STRLEN_P( input_param_z );
|
|
||||||
int wchar_size;
|
|
||||||
|
|
||||||
if (buffer_len > INT_MAX)
|
|
||||||
{
|
|
||||||
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the string is empty, then just return that the conversion succeeded as
|
|
||||||
// MultiByteToWideChar will "fail" on an empty string.
|
|
||||||
if( buffer_len == 0 ) {
|
|
||||||
core::sqlsrv_zval_stringl( converted_param_z, "", 0 );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string
|
|
||||||
wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS,
|
|
||||||
reinterpret_cast<LPCSTR>( buffer ), static_cast<int>( buffer_len ), NULL, 0 );
|
|
||||||
|
|
||||||
// if there was a problem determining the size of the string, return false
|
|
||||||
if( wchar_size == 0 ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sqlsrv_malloc_auto_ptr<wchar_t> wbuffer;
|
|
||||||
wbuffer = reinterpret_cast<wchar_t*>( sqlsrv_malloc( (wchar_size + 1) * sizeof( wchar_t ) ));
|
|
||||||
// convert the utf-8 string to a wchar string in the new buffer
|
|
||||||
int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast<LPCSTR>( buffer ),
|
|
||||||
static_cast<int>( buffer_len ), wbuffer, wchar_size );
|
|
||||||
// if there was a problem converting the string, then free the memory and return false
|
|
||||||
if( r == 0 ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// null terminate the string, set the size within the zval, and return success
|
|
||||||
wbuffer[ wchar_size ] = L'\0';
|
|
||||||
core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast<char*>( wbuffer.get() ),
|
|
||||||
wchar_size * sizeof( wchar_t ) );
|
|
||||||
sqlsrv_free(wbuffer);
|
|
||||||
wbuffer.transferred();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the ODBC C type constant that matches the PHP type and encoding given
|
|
||||||
|
|
||||||
SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* param_z, SQLSRV_ENCODING encoding TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE;
|
|
||||||
int php_type = Z_TYPE_P( param_z );
|
|
||||||
|
|
||||||
switch( php_type ) {
|
|
||||||
|
|
||||||
case IS_NULL:
|
|
||||||
switch( encoding ) {
|
|
||||||
// The c type is set to match to the corresponding sql_type. For NULL cases, if the server type
|
|
||||||
// is a binary type, than the server expects the sql_type to be binary type as well, otherwise
|
|
||||||
// an error stating "Implicit conversion not allowed.." is thrown by the server.
|
|
||||||
// For all other server types, setting the sql_type to sql_char works fine.
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
sql_c_type = SQL_C_BINARY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sql_c_type = SQL_C_CHAR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_TRUE:
|
|
||||||
case IS_FALSE:
|
|
||||||
case IS_LONG:
|
|
||||||
//ODBC 64-bit long and integer type are 4 byte values.
|
|
||||||
if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) )
|
|
||||||
{
|
|
||||||
sql_c_type = SQL_C_SBIGINT;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sql_c_type = SQL_C_SLONG;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_DOUBLE:
|
|
||||||
sql_c_type = SQL_C_DOUBLE;
|
|
||||||
break;
|
|
||||||
case IS_STRING:
|
|
||||||
case IS_RESOURCE:
|
|
||||||
switch( encoding ) {
|
|
||||||
case SQLSRV_ENCODING_CHAR:
|
|
||||||
sql_c_type = SQL_C_CHAR;
|
|
||||||
break;
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
sql_c_type = SQL_C_BINARY;
|
|
||||||
break;
|
|
||||||
case CP_UTF8:
|
|
||||||
sql_c_type = SQL_C_WCHAR;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// it is assumed that an object is a DateTime since it's the only thing we support.
|
|
||||||
// verification that it's a real DateTime object occurs in core_sqlsrv_bind_param.
|
|
||||||
// we convert the DateTime to a string before sending it to the server.
|
|
||||||
case IS_OBJECT:
|
|
||||||
sql_c_type = SQL_C_CHAR;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql_c_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// given a zval and encoding, determine the appropriate sql type
|
|
||||||
|
|
||||||
void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV_ENCODING encoding,
|
|
||||||
_Out_ SQLSMALLINT& sql_type TSRMLS_DC )
|
|
||||||
{
|
|
||||||
sql_type = SQL_UNKNOWN_TYPE;
|
|
||||||
int php_type = Z_TYPE_P(param_z);
|
|
||||||
switch( php_type ) {
|
|
||||||
|
|
||||||
case IS_NULL:
|
|
||||||
switch( encoding ) {
|
|
||||||
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
|
|
||||||
// if the server type is a binary type, than the server expects the sql_type to be binary type
|
|
||||||
// as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the
|
|
||||||
// server. For all other server types, setting the sql_type to sql_char works fine.
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
sql_type = SQL_BINARY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sql_type = SQL_CHAR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_TRUE:
|
|
||||||
case IS_FALSE:
|
|
||||||
case IS_LONG:
|
|
||||||
//ODBC 64-bit long and integer type are 4 byte values.
|
|
||||||
if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) )
|
|
||||||
{
|
|
||||||
sql_type = SQL_BIGINT;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sql_type = SQL_INTEGER;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case IS_DOUBLE:
|
|
||||||
sql_type = SQL_FLOAT;
|
|
||||||
break;
|
|
||||||
case IS_RESOURCE:
|
|
||||||
case IS_STRING:
|
|
||||||
switch( encoding ) {
|
|
||||||
case SQLSRV_ENCODING_CHAR:
|
|
||||||
sql_type = SQL_VARCHAR;
|
|
||||||
break;
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
sql_type = SQL_VARBINARY;
|
|
||||||
break;
|
|
||||||
case CP_UTF8:
|
|
||||||
sql_type = SQL_WVARCHAR;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// it is assumed that an object is a DateTime since it's the only thing we support.
|
|
||||||
// verification that it's a real DateTime object occurs in the calling function.
|
|
||||||
// we convert the DateTime to a string before sending it to the server.
|
|
||||||
case IS_OBJECT:
|
|
||||||
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
|
|
||||||
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
|
|
||||||
// since these are the date types of the highest precision for their respective server versions
|
|
||||||
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
|
|
||||||
sql_type = SQL_TYPE_TIMESTAMP;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sql_type = SQL_SS_TIMESTAMPOFFSET;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate)
|
|
||||||
|
|
||||||
void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* param_z, SQLSRV_ENCODING encoding,
|
|
||||||
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC )
|
|
||||||
{
|
|
||||||
int php_type = Z_TYPE_P( param_z );
|
|
||||||
column_size = 0;
|
|
||||||
decimal_digits = 0;
|
|
||||||
|
|
||||||
switch( php_type ) {
|
|
||||||
|
|
||||||
case IS_NULL:
|
|
||||||
column_size = 1;
|
|
||||||
break;
|
|
||||||
// size is not necessary for these types, they are inferred by ODBC
|
|
||||||
case IS_TRUE:
|
|
||||||
case IS_FALSE:
|
|
||||||
case IS_LONG:
|
|
||||||
case IS_DOUBLE:
|
|
||||||
case IS_RESOURCE:
|
|
||||||
break;
|
|
||||||
case IS_STRING:
|
|
||||||
{
|
|
||||||
size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char );
|
|
||||||
SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size;
|
|
||||||
if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) {
|
|
||||||
column_size = SQL_SERVER_MAX_TYPE_SIZE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// it is assumed that an object is a DateTime since it's the only thing we support.
|
|
||||||
// verification that it's a real DateTime object occurs in the calling function.
|
|
||||||
// we convert the DateTime to a string before sending it to the server.
|
|
||||||
case IS_OBJECT:
|
|
||||||
// if the user is sending this type to SQL Server 2005 or earlier, make it appear
|
|
||||||
// as a SQLSRV_SQLTYPE_DATETIME, otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET
|
|
||||||
// since these are the date types of the highest precision for their respective server versions
|
|
||||||
if( stmt->conn->server_version <= SERVER_VERSION_2005 ) {
|
|
||||||
column_size = SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION;
|
|
||||||
decimal_digits = SQL_SERVER_2005_DEFAULT_DATETIME_SCALE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
column_size = SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION;
|
|
||||||
decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, paramno );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void field_cache_dtor( zval* data_z )
|
|
||||||
{
|
|
||||||
field_cache* cache = static_cast<field_cache*>( Z_PTR_P( data_z ));
|
|
||||||
if( cache->value )
|
|
||||||
{
|
|
||||||
sqlsrv_free( cache->value );
|
|
||||||
}
|
|
||||||
sqlsrv_free( cache );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// To be called after all results are processed. ODBC and SQL Server do not guarantee that all output
|
|
||||||
// parameters will be present until all results are processed (since output parameters can depend on results
|
|
||||||
// while being processed). This function updates the lengths of output parameter strings from the ind_ptr
|
|
||||||
// parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
|
|
||||||
// For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server
|
|
||||||
|
|
||||||
void finalize_output_parameters( sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
if( Z_ISUNDEF(stmt->output_params) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool converted = true;
|
|
||||||
HashTable* params_ht = Z_ARRVAL( stmt->output_params );
|
|
||||||
zend_ulong index = -1;
|
|
||||||
zend_string* key = NULL;
|
|
||||||
void* output_param_temp = NULL;
|
|
||||||
|
|
||||||
ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) {
|
|
||||||
sqlsrv_output_param* output_param = static_cast<sqlsrv_output_param*>( output_param_temp );
|
|
||||||
zval* value_z = Z_REFVAL_P( output_param->param_z );
|
|
||||||
switch( Z_TYPE_P( value_z )) {
|
|
||||||
case IS_STRING:
|
|
||||||
{
|
|
||||||
// adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter
|
|
||||||
char* str = Z_STRVAL_P( value_z );
|
|
||||||
SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ];
|
|
||||||
if( str_len == SQL_NULL_DATA || str_len == 0 ) {
|
|
||||||
zend_string_release( Z_STR_P( value_z ));
|
|
||||||
ZVAL_NULL( value_z );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there was more to output than buffer size to hold it, then throw a truncation error
|
|
||||||
int null_size = 0;
|
|
||||||
switch( output_param->encoding ) {
|
|
||||||
case SQLSRV_ENCODING_UTF8:
|
|
||||||
null_size = sizeof( wchar_t ); // string isn't yet converted to UTF-8, still UTF-16
|
|
||||||
break;
|
|
||||||
case SQLSRV_ENCODING_SYSTEM:
|
|
||||||
null_size = 1;
|
|
||||||
break;
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
null_size = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt,
|
|
||||||
SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's not in the 8 bit encodings, then it's in UTF-16
|
|
||||||
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
|
|
||||||
bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len);
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
|
|
||||||
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
|
|
||||||
// so we do that here if the length of the returned data is less than the original allocation. The
|
|
||||||
// original allocation null terminates the buffer already.
|
|
||||||
str[ str_len ] = '\0';
|
|
||||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_LONG:
|
|
||||||
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
|
|
||||||
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
|
|
||||||
ZVAL_NULL( value_z );
|
|
||||||
}
|
|
||||||
else if( output_param->is_bool ) {
|
|
||||||
convert_to_boolean( value_z );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ZVAL_LONG( value_z, static_cast<int>( Z_LVAL_P( value_z )));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IS_DOUBLE:
|
|
||||||
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
|
|
||||||
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
|
|
||||||
ZVAL_NULL( value_z );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
value_z = NULL;
|
|
||||||
} ZEND_HASH_FOREACH_END();
|
|
||||||
|
|
||||||
// empty the hash table since it's been processed
|
|
||||||
zend_hash_clean( Z_ARRVAL( stmt->output_params ));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_phptype sqlsrv_php_type,
|
|
||||||
_Out_ void*& field_value, _Out_ SQLLEN* field_len TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLRETURN r;
|
|
||||||
SQLSMALLINT c_type;
|
|
||||||
SQLLEN sql_field_type = 0;
|
|
||||||
SQLSMALLINT extra = 0;
|
|
||||||
SQLLEN field_len_temp;
|
|
||||||
SQLLEN sql_display_size = 0;
|
|
||||||
char* field_value_temp = NULL;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING,
|
|
||||||
"Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" );
|
|
||||||
|
|
||||||
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
|
||||||
sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the C type and account for null characters at the end of the data.
|
|
||||||
switch( sqlsrv_php_type.typeinfo.encoding ) {
|
|
||||||
case CP_UTF8:
|
|
||||||
c_type = SQL_C_WCHAR;
|
|
||||||
extra = sizeof( SQLWCHAR );
|
|
||||||
break;
|
|
||||||
case SQLSRV_ENCODING_BINARY:
|
|
||||||
c_type = SQL_C_BINARY;
|
|
||||||
extra = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
c_type = SQL_C_CHAR;
|
|
||||||
extra = sizeof( SQLCHAR );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the SQL type of the field.
|
|
||||||
core::SQLColAttribute( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC );
|
|
||||||
|
|
||||||
// Calculate the field size.
|
|
||||||
calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC );
|
|
||||||
|
|
||||||
// if this is a large type, then read the first few bytes to get the actual length from SQLGetData
|
|
||||||
if( sql_display_size == 0 || sql_display_size == INT_MAX ||
|
|
||||||
sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) {
|
|
||||||
|
|
||||||
field_len_temp = INITIAL_FIELD_STRING_LEN;
|
|
||||||
|
|
||||||
field_value_temp = static_cast<char*>( sqlsrv_malloc( field_len_temp + extra + 1 ));
|
|
||||||
|
|
||||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ),
|
|
||||||
&field_len_temp, false /*handle_warning*/ TSRMLS_CC );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( field_len_temp == SQL_NULL_DATA ) {
|
|
||||||
field_value = NULL;
|
|
||||||
sqlsrv_free( field_value_temp );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r == SQL_SUCCESS_WITH_INFO ) {
|
|
||||||
|
|
||||||
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
|
|
||||||
SQLSMALLINT len;
|
|
||||||
|
|
||||||
stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC );
|
|
||||||
|
|
||||||
if( is_truncated_warning( state )) {
|
|
||||||
|
|
||||||
SQLLEN dummy_field_len;
|
|
||||||
|
|
||||||
// for XML (and possibly other conditions) the field length returned is not the real field length, so
|
|
||||||
// in every pass, we double the allocation size to retrieve all the contents.
|
|
||||||
if( field_len_temp == SQL_NO_TOTAL ) {
|
|
||||||
|
|
||||||
// reset the field_len_temp
|
|
||||||
field_len_temp = INITIAL_FIELD_STRING_LEN;
|
|
||||||
|
|
||||||
do {
|
|
||||||
SQLLEN initial_field_len = field_len_temp;
|
|
||||||
// Double the size.
|
|
||||||
field_len_temp *= 2;
|
|
||||||
|
|
||||||
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
|
|
||||||
|
|
||||||
field_len_temp -= initial_field_len;
|
|
||||||
|
|
||||||
// Get the rest of the data.
|
|
||||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len,
|
|
||||||
field_len_temp + extra, &dummy_field_len,
|
|
||||||
false /*handle_warning*/ TSRMLS_CC );
|
|
||||||
|
|
||||||
// the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL
|
|
||||||
// so we calculate the actual length of the string with that.
|
|
||||||
if( dummy_field_len != SQL_NO_TOTAL )
|
|
||||||
field_len_temp += dummy_field_len;
|
|
||||||
else
|
|
||||||
field_len_temp += initial_field_len;
|
|
||||||
|
|
||||||
if( r == SQL_SUCCESS_WITH_INFO ) {
|
|
||||||
core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
|
|
||||||
TSRMLS_CC );
|
|
||||||
}
|
|
||||||
|
|
||||||
} while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state ));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// We got the field_len_temp from SQLGetData call.
|
|
||||||
field_value_temp = static_cast<char*>( sqlsrv_realloc( field_value_temp, field_len_temp + extra + 1 ));
|
|
||||||
|
|
||||||
// We have already recieved INITIAL_FIELD_STRING_LEN size data.
|
|
||||||
field_len_temp -= INITIAL_FIELD_STRING_LEN;
|
|
||||||
|
|
||||||
// Get the rest of the data.
|
|
||||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + INITIAL_FIELD_STRING_LEN,
|
|
||||||
field_len_temp + extra, &dummy_field_len,
|
|
||||||
true /*handle_warning*/ TSRMLS_CC );
|
|
||||||
|
|
||||||
if( dummy_field_len == SQL_NULL_DATA ) {
|
|
||||||
field_value = NULL;
|
|
||||||
sqlsrv_free( field_value_temp );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
field_len_temp += INITIAL_FIELD_STRING_LEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // if( is_truncation_warning ( state ) )
|
|
||||||
else {
|
|
||||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // if( r == SQL_SUCCESS_WITH_INFO )
|
|
||||||
|
|
||||||
if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) {
|
|
||||||
|
|
||||||
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
|
|
||||||
&field_value_temp, field_len_temp );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
|
|
||||||
|
|
||||||
else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) {
|
|
||||||
|
|
||||||
// only allow binary retrievals for char and binary types. All others get a string converted
|
|
||||||
// to the encoding type they asked for.
|
|
||||||
|
|
||||||
// null terminator
|
|
||||||
if( c_type == SQL_C_CHAR ) {
|
|
||||||
sql_display_size += sizeof( SQLCHAR );
|
|
||||||
}
|
|
||||||
|
|
||||||
// For WCHAR multiply by sizeof(WCHAR) and include the null terminator
|
|
||||||
else if( c_type == SQL_C_WCHAR ) {
|
|
||||||
sql_display_size = (sql_display_size * sizeof(WCHAR)) + sizeof(WCHAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
field_value_temp = static_cast<char*>( sqlsrv_malloc( sql_display_size + extra + 1 ));
|
|
||||||
|
|
||||||
// get the data
|
|
||||||
r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size,
|
|
||||||
&field_len_temp, true /*handle_warning*/ TSRMLS_CC );
|
|
||||||
CHECK_SQL_ERROR( r, stmt ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( field_len_temp == SQL_NULL_DATA ) {
|
|
||||||
field_value = NULL;
|
|
||||||
sqlsrv_free( field_value_temp );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) {
|
|
||||||
|
|
||||||
bool converted = convert_string_from_utf16_inplace( static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding ),
|
|
||||||
&field_value_temp, field_len_temp );
|
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) {
|
|
||||||
throw core::CoreException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
|
|
||||||
|
|
||||||
else {
|
|
||||||
|
|
||||||
DIE( "Invalid sql_display_size" );
|
|
||||||
return; // to eliminate a warning
|
|
||||||
}
|
|
||||||
|
|
||||||
field_value = field_value_temp;
|
|
||||||
*field_len = field_len_temp;
|
|
||||||
|
|
||||||
// prevent a warning in debug mode about strings not being NULL terminated. Even though nulls are not necessary, the PHP
|
|
||||||
// runtime checks to see if a string is null terminated and issues a warning about it if running in debug mode.
|
|
||||||
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and use the ternary
|
|
||||||
// operator to set add 1 to fill the null terminator
|
|
||||||
field_value_temp[field_len_temp] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
catch( core::CoreException& ) {
|
|
||||||
|
|
||||||
field_value = NULL;
|
|
||||||
*field_len = 0;
|
|
||||||
sqlsrv_free( field_value_temp );
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch ( ... ) {
|
|
||||||
|
|
||||||
field_value = NULL;
|
|
||||||
*field_len = 0;
|
|
||||||
sqlsrv_free( field_value_temp );
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// return the option from the stmt_opts array that matches the key. If no option found,
|
|
||||||
// NULL is returned.
|
|
||||||
|
|
||||||
stmt_option const* get_stmt_option( sqlsrv_conn const* conn, zend_ulong key, const stmt_option stmt_opts[] TSRMLS_DC )
|
|
||||||
{
|
|
||||||
for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) {
|
|
||||||
|
|
||||||
// if we find the key we're looking for, return it
|
|
||||||
if( key == stmt_opts[ i ].key ) {
|
|
||||||
return &stmt_opts[ i ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL; // no option found
|
|
||||||
}
|
|
||||||
|
|
||||||
// is_fixed_size_type
|
|
||||||
// returns true if the SQL data type is a fixed length, as opposed to a variable length data type such as varchar or varbinary
|
|
||||||
|
|
||||||
bool is_fixed_size_type( SQLINTEGER sql_type )
|
|
||||||
{
|
|
||||||
switch( sql_type ) {
|
|
||||||
|
|
||||||
case SQL_BINARY:
|
|
||||||
case SQL_CHAR:
|
|
||||||
case SQL_WCHAR:
|
|
||||||
case SQL_VARCHAR:
|
|
||||||
case SQL_WVARCHAR:
|
|
||||||
case SQL_LONGVARCHAR:
|
|
||||||
case SQL_WLONGVARCHAR:
|
|
||||||
case SQL_VARBINARY:
|
|
||||||
case SQL_LONGVARBINARY:
|
|
||||||
case SQL_SS_XML:
|
|
||||||
case SQL_SS_UDT:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_valid_sqlsrv_phptype( sqlsrv_phptype type )
|
|
||||||
{
|
|
||||||
switch( type.typeinfo.type ) {
|
|
||||||
|
|
||||||
case SQLSRV_PHPTYPE_NULL:
|
|
||||||
case SQLSRV_PHPTYPE_INT:
|
|
||||||
case SQLSRV_PHPTYPE_FLOAT:
|
|
||||||
case SQLSRV_PHPTYPE_DATETIME:
|
|
||||||
return true;
|
|
||||||
case SQLSRV_PHPTYPE_STRING:
|
|
||||||
case SQLSRV_PHPTYPE_STREAM:
|
|
||||||
{
|
|
||||||
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|
|
||||||
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// verify there is enough space to hold the output string parameter, and allocate it if needed. The param_z
|
|
||||||
// is updated to have the new buffer with the correct size and its reference is incremented. The output
|
|
||||||
// string is place in the stmt->output_params. param_z is modified to hold the new buffer, and buffer, buffer_len and
|
|
||||||
// stmt->param_ind_ptrs are modified to hold the correct values for SQLBindParameter
|
|
||||||
|
|
||||||
void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULEN paramno, SQLSRV_ENCODING encoding,
|
|
||||||
SQLSMALLINT c_type, SQLSMALLINT sql_type, SQLULEN column_size, SQLPOINTER& buffer,
|
|
||||||
SQLLEN& buffer_len TSRMLS_DC )
|
|
||||||
{
|
|
||||||
SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." );
|
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
|
||||||
SQLLEN expected_len;
|
|
||||||
SQLLEN buffer_null_extra;
|
|
||||||
SQLLEN elem_size;
|
|
||||||
SQLLEN without_null_len;
|
|
||||||
|
|
||||||
// calculate the size of each 'element' represented by column_size. WCHAR is of course 2,
|
|
||||||
// as is a n(var)char/ntext field being returned as a binary field.
|
|
||||||
elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1;
|
|
||||||
|
|
||||||
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
|
|
||||||
expected_len = column_size * elem_size + elem_size;
|
|
||||||
|
|
||||||
// binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
|
|
||||||
buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0;
|
|
||||||
|
|
||||||
// this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter
|
|
||||||
without_null_len = column_size * elem_size;
|
|
||||||
|
|
||||||
// increment to include the null terminator since the Zend length doesn't include the null terminator
|
|
||||||
buffer_len += elem_size;
|
|
||||||
|
|
||||||
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
|
|
||||||
// length.
|
|
||||||
if( buffer_len < expected_len ) {
|
|
||||||
SQLSRV_ASSERT( expected_len >= expected_len - buffer_null_extra,
|
|
||||||
"Integer overflow/underflow caused a corrupt field length." );
|
|
||||||
|
|
||||||
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
|
|
||||||
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
|
|
||||||
// not having a NULL terminator on a string.
|
|
||||||
zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 );
|
|
||||||
|
|
||||||
// A zval string len doesn't include the null. This calculates the length it should be
|
|
||||||
// regardless of whether the ODBC type contains the NULL or not.
|
|
||||||
|
|
||||||
// null terminate the string to avoid a warning in debug PHP builds
|
|
||||||
ZSTR_VAL(param_z_string)[without_null_len] = '\0';
|
|
||||||
ZVAL_NEW_STR(param_z, param_z_string);
|
|
||||||
|
|
||||||
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
|
|
||||||
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
|
|
||||||
buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra;
|
|
||||||
|
|
||||||
// Zend string length doesn't include the null terminator
|
|
||||||
ZSTR_LEN(Z_STR_P(param_z)) -= elem_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer = Z_STRVAL_P(param_z);
|
|
||||||
|
|
||||||
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
|
|
||||||
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
|
|
||||||
// than the error 22001 is returned by ODBC.
|
|
||||||
if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) {
|
|
||||||
stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// output parameters have their reference count incremented so that they do not disappear
|
|
||||||
// while the query is executed and processed. They are saved in the statement so that
|
|
||||||
// their reference count may be decremented later (after results are processed)
|
|
||||||
|
|
||||||
void save_output_param_for_later( sqlsrv_stmt* stmt, sqlsrv_output_param& param TSRMLS_DC )
|
|
||||||
{
|
|
||||||
HashTable* param_ht = Z_ARRVAL( stmt->output_params );
|
|
||||||
zend_ulong paramno = static_cast<zend_ulong>( param.param_num );
|
|
||||||
core::sqlsrv_zend_hash_index_update_mem(*stmt, param_ht, paramno, ¶m, sizeof( sqlsrv_output_param ));
|
|
||||||
Z_TRY_ADDREF_P( param.param_z ); // we have a reference to the param
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// send all the stream data
|
|
||||||
|
|
||||||
void send_param_streams( sqlsrv_stmt* stmt TSRMLS_DC )
|
|
||||||
{
|
|
||||||
while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// called by Zend for each parameter in the sqlsrv_stmt::output_params hash table when it is cleaned/destroyed
|
|
||||||
void sqlsrv_output_param_dtor( zval* data )
|
|
||||||
{
|
|
||||||
sqlsrv_output_param *output_param = static_cast<sqlsrv_output_param*>( Z_PTR_P( data ));
|
|
||||||
zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold
|
|
||||||
sqlsrv_free( output_param );
|
|
||||||
}
|
|
||||||
|
|
||||||
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
|
|
||||||
void sqlsrv_stream_dtor( zval* data )
|
|
||||||
{
|
|
||||||
sqlsrv_stream* stream_encoding = static_cast<sqlsrv_stream*>( Z_PTR_P( data ));
|
|
||||||
zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold
|
|
||||||
sqlsrv_free( stream_encoding );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue