2016-01-30 03:00:20 +01:00
//---------------------------------------------------------------------------------------------------------------------------------
// File: core_stmt.cpp
//
// Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv
//
2019-01-16 19:19:01 +01:00
// Microsoft Drivers 5.6 for PHP for SQL Server
2016-01-30 03:00:20 +01:00
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
2017-12-09 01:15:24 +01:00
// 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,
2016-01-30 03:00:20 +01:00
// 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.
2017-12-09 01:15:24 +01:00
// 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
2016-01-30 03:00:20 +01:00
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
# include "core_sqlsrv.h"
2017-11-16 01:38:27 +01:00
# include <sstream>
# include <vector>
2016-01-30 03:00:20 +01:00
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 {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
void * value ;
SQLLEN len ;
sqlsrv_phptype type ;
2017-06-22 23:04:34 +02:00
field_cache ( _In_reads_bytes_opt_ ( field_len ) void * field_value , _In_ SQLLEN field_len , _In_ sqlsrv_phptype t )
2016-01-30 03:00:20 +01:00
: type ( t )
{
// if the value is NULL, then just record a NULL pointer
2018-09-18 01:24:52 +02:00
// field_len may be equal to SQL_NULL_DATA even when field_value is not null
if ( field_value ! = NULL & & field_len ! = SQL_NULL_DATA ) {
2016-01-30 03:00:20 +01:00
value = sqlsrv_malloc ( field_len ) ;
2016-07-06 20:47:05 +02:00
memcpy_s ( value , field_len , field_value , field_len ) ;
2016-01-30 03:00:20 +01:00
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
} ;
2017-03-24 23:46:53 +01:00
// Used to cache display size and SQL type of a column in get_field_as_string()
struct col_cache {
2017-03-25 00:01:45 +01:00
SQLLEN sql_type ;
SQLLEN display_size ;
2017-03-24 23:46:53 +01:00
2017-06-22 23:04:34 +02:00
col_cache ( _In_ SQLLEN col_sql_type , _In_ SQLLEN col_display_size )
2017-03-25 00:01:45 +01:00
{
sql_type = col_sql_type ;
2017-03-24 23:46:53 +01:00
display_size = col_display_size ;
2017-03-25 00:01:45 +01:00
}
2017-03-24 23:46:53 +01:00
} ;
2017-11-15 20:00:56 +01:00
const int INITIAL_FIELD_STRING_LEN = 2048 ; // base allocation size when retrieving a string field
2016-01-30 03:00:20 +01:00
// 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.
2017-06-22 23:04:34 +02:00
void calc_string_size ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _In_ SQLLEN sql_type , _Inout_ SQLLEN & size TSRMLS_DC ) ;
size_t calc_utf8_missing ( _Inout_ sqlsrv_stmt * stmt , _In_reads_ ( buffer_end ) const char * buffer , _In_ size_t buffer_end TSRMLS_DC ) ;
bool check_for_next_stream_parameter ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC ) ;
bool convert_input_param_to_utf16 ( _In_ zval * input_param_z , _Inout_ zval * convert_param_z ) ;
void core_get_field_common ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype
2018-12-04 22:00:34 +01:00
sqlsrv_php_type , _Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len TSRMLS_DC ) ;
2016-01-30 03:00:20 +01:00
// returns the ODBC C type constant that matches the PHP type and encoding given
2017-06-22 23:04:34 +02:00
SQLSMALLINT default_c_type ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ SQLULEN paramno , _In_ zval const * param_z , _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) ;
2017-12-09 01:15:24 +01:00
void default_sql_size_and_scale ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ unsigned int paramno , _In_ zval * param_z , _In_ SQLSRV_ENCODING encoding ,
2016-07-06 20:47:05 +02:00
_Out_ SQLULEN & column_size , _Out_ SQLSMALLINT & decimal_digits TSRMLS_DC ) ;
2016-01-30 03:00:20 +01:00
// given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate)
2017-12-09 01:15:24 +01:00
void default_sql_type ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ SQLULEN paramno , _In_ zval * param_z , _In_ SQLSRV_ENCODING encoding ,
2016-07-06 20:47:05 +02:00
_Out_ SQLSMALLINT & sql_type TSRMLS_DC ) ;
2017-06-22 23:04:34 +02:00
void col_cache_dtor ( _Inout_ zval * data_z ) ;
void field_cache_dtor ( _Inout_ zval * data_z ) ;
2018-11-28 02:18:38 +01:00
void format_decimal_numbers ( _In_ SQLSMALLINT decimals_places , _In_ SQLSMALLINT field_scale , _Inout_updates_bytes_ ( * field_len ) char * & field_value , _Inout_ SQLLEN * field_len ) ;
2017-06-22 23:04:34 +02:00
void finalize_output_parameters ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC ) ;
void get_field_as_string ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype sqlsrv_php_type ,
2018-12-04 22:00:34 +01:00
_Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len TSRMLS_DC ) ;
2017-06-22 23:04:34 +02:00
stmt_option const * get_stmt_option ( sqlsrv_conn const * conn , _In_ zend_ulong key , _In_ const stmt_option stmt_opts [ ] TSRMLS_DC ) ;
bool is_valid_sqlsrv_phptype ( _In_ sqlsrv_phptype type ) ;
2016-01-30 03:00:20 +01:00
// assure there is enough space for the output parameter string
2017-06-22 23:04:34 +02:00
void resize_output_buffer_if_necessary ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z , _In_ SQLULEN paramno , SQLSRV_ENCODING encoding ,
2017-09-06 01:51:40 +02:00
_In_ SQLSMALLINT c_type , _In_ SQLSMALLINT sql_type , _In_ SQLULEN column_size , _In_ SQLSMALLINT decimal_digits ,
_Out_writes_ ( buffer_len ) SQLPOINTER & buffer , _Out_ SQLLEN & buffer_len TSRMLS_DC ) ;
2017-11-16 01:36:10 +01:00
void adjustInputPrecision ( _Inout_ zval * param_z , _In_ SQLSMALLINT decimal_digits ) ;
2017-06-22 23:04:34 +02:00
void save_output_param_for_later ( _Inout_ sqlsrv_stmt * stmt , _Inout_ sqlsrv_output_param & param TSRMLS_DC ) ;
2017-12-09 01:15:24 +01:00
// send all the stream data
2017-06-22 23:04:34 +02:00
void send_param_streams ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC ) ;
2016-01-30 03:00:20 +01:00
// called when a bound output string parameter is to be destroyed
2017-06-22 23:04:34 +02:00
void sqlsrv_output_param_dtor ( _Inout_ zval * data ) ;
2016-01-30 03:00:20 +01:00
// called when a bound stream parameter is to be destroyed.
2017-06-22 23:04:34 +02:00
void sqlsrv_stream_dtor ( _Inout_ zval * data ) ;
2016-01-30 03:00:20 +01:00
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
2017-06-22 23:04:34 +02:00
sqlsrv_stmt : : sqlsrv_stmt ( _In_ sqlsrv_conn * c , _In_ SQLHANDLE handle , _In_ error_callback e , _In_opt_ void * drv TSRMLS_DC ) :
2016-01-30 03:00:20 +01:00
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 ) ,
2019-04-13 05:49:03 +02:00
column_count ( ACTIVE_NUM_COLS_INVALID ) ,
row_count ( ACTIVE_NUM_ROWS_INVALID ) ,
2017-01-20 23:43:49 +01:00
query_timeout ( QUERY_TIMEOUT_INVALID ) ,
2018-09-18 01:25:02 +02:00
date_as_string ( false ) ,
2018-11-28 02:18:38 +01:00
format_decimals ( false ) , // no formatting needed
decimal_places ( NO_CHANGE_DECIMAL_PLACES ) , // the default is no formatting to resultset required
2019-05-01 17:03:33 +02:00
data_classification ( false ) ,
2017-01-20 23:43:49 +01:00
buffered_query_limit ( sqlsrv_buffered_result_set : : BUFFERED_QUERY_LIMIT_INVALID ) ,
2018-10-13 00:22:27 +02:00
param_ind_ptrs ( 10 ) , // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
2016-01-30 03:00:20 +01:00
send_streams_at_exec ( true ) ,
current_stream ( NULL , SQLSRV_ENCODING_DEFAULT ) ,
2017-01-20 23:43:49 +01:00
current_stream_read ( 0 )
2016-01-30 03:00:20 +01:00
{
2018-12-04 22:00:34 +01:00
ZVAL_UNDEF ( & active_stream ) ;
2016-01-30 03:00:20 +01:00
// initialize the input string parameters array (which holds zvals)
core : : sqlsrv_array_init ( * conn , & param_input_strings TSRMLS_CC ) ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
// initialize the (input only) stream parameters (which holds sqlsrv_stream structures)
ZVAL_NEW_ARR ( & param_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 ( & param_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 ) ;
2017-12-09 01:15:24 +01:00
2017-03-25 00:01:45 +01:00
// initialize the col cache
ZVAL_NEW_ARR ( & col_cache ) ;
core : : sqlsrv_zend_hash_init ( * conn , Z_ARRVAL ( col_cache ) , 5 /* # of buckets */ , col_cache_dtor , 0 /*persistent*/ TSRMLS_CC ) ;
2017-03-24 23:46:53 +01:00
2016-01-30 03:00:20 +01:00
// 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 )
{
2016-06-13 23:44:53 +02:00
if ( Z_TYPE ( active_stream ) ! = IS_UNDEF ) {
2016-01-30 03:00:20 +01:00
TSRMLS_FETCH ( ) ;
close_active_stream ( this TSRMLS_CC ) ;
2017-12-09 01:15:24 +01:00
}
2016-01-30 03:00:20 +01:00
// delete any current results
if ( current_results ) {
current_results - > ~ sqlsrv_result_set ( ) ;
efree ( current_results ) ;
current_results = NULL ;
}
2017-12-09 01:15:24 +01:00
2019-05-01 17:03:33 +02:00
// delete sensivity data
clean_up_sensitivity_metadata ( ) ;
2017-12-09 01:15:24 +01:00
invalidate ( ) ;
2016-01-30 03:00:20 +01:00
zval_ptr_dtor ( & param_input_strings ) ;
zval_ptr_dtor ( & output_params ) ;
zval_ptr_dtor ( & param_streams ) ;
zval_ptr_dtor ( & param_datetime_buffers ) ;
2017-03-25 00:01:45 +01:00
zval_ptr_dtor ( & col_cache ) ;
2016-01-30 03:00:20 +01:00
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 ) ) ;
2017-03-25 00:01:45 +01:00
zend_hash_clean ( Z_ARRVAL ( col_cache ) ) ;
2016-01-30 03:00:20 +01:00
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 ;
2019-04-13 05:49:03 +02:00
this - > column_count = ACTIVE_NUM_COLS_INVALID ;
this - > row_count = ACTIVE_NUM_ROWS_INVALID ;
2016-01-30 03:00:20 +01:00
// delete any current results
if ( current_results ) {
current_results - > ~ sqlsrv_result_set ( ) ;
efree ( current_results ) ;
current_results = NULL ;
}
2019-05-01 17:03:33 +02:00
// delete sensivity data
clean_up_sensitivity_metadata ( ) ;
2016-01-30 03:00:20 +01:00
// create a new result set
if ( cursor_type = = SQLSRV_CURSOR_BUFFERED ) {
2017-12-09 01:15:24 +01:00
sqlsrv_malloc_auto_ptr < sqlsrv_buffered_result_set > result ;
2017-02-24 22:29:09 +01:00
result = reinterpret_cast < sqlsrv_buffered_result_set * > ( sqlsrv_malloc ( sizeof ( sqlsrv_buffered_result_set ) ) ) ;
new ( result . get ( ) ) sqlsrv_buffered_result_set ( this TSRMLS_CC ) ;
2017-12-09 01:15:24 +01:00
current_results = result . get ( ) ;
2017-02-24 22:29:09 +01:00
result . transferred ( ) ;
2016-01-30 03:00:20 +01:00
}
else {
current_results = new ( sqlsrv_malloc ( sizeof ( sqlsrv_odbc_result_set ) ) ) sqlsrv_odbc_result_set ( this ) ;
}
}
2019-05-01 17:03:33 +02:00
// free sensitivity classification metadata
void sqlsrv_stmt : : clean_up_sensitivity_metadata ( )
{
if ( current_sensitivity_metadata ) {
current_sensitivity_metadata - > ~ sensitivity_metadata ( ) ;
2019-05-09 00:18:15 +02:00
current_sensitivity_metadata . reset ( ) ;
2019-05-01 17:03:33 +02:00
}
}
2016-01-30 03:00:20 +01:00
// core_sqlsrv_create_stmt
2017-12-09 01:15:24 +01:00
// Common code to allocate a statement from either driver. Returns a valid driver statement object or
2016-01-30 03:00:20 +01:00
// 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
2017-12-09 01:15:24 +01:00
// Returns the created statement
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
sqlsrv_stmt * core_sqlsrv_create_stmt ( _Inout_ sqlsrv_conn * conn , _In_ driver_stmt_factory stmt_factory , _In_opt_ HashTable * options_ht ,
2017-06-22 23:04:34 +02:00
_In_opt_ const stmt_option valid_stmt_opts [ ] , _In_ error_callback const err , _In_opt_ void * driver TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2018-12-04 22:00:34 +01:00
sqlsrv_malloc_auto_ptr < sqlsrv_stmt > stmt ;
2016-01-30 03:00:20 +01:00
SQLHANDLE stmt_h = SQL_NULL_HANDLE ;
2017-01-30 22:05:38 +01:00
sqlsrv_stmt * return_stmt = NULL ;
2016-01-30 03:00:20 +01:00
try {
core : : SQLAllocHandle ( SQL_HANDLE_STMT , * conn , & stmt_h TSRMLS_CC ) ;
stmt = stmt_factory ( conn , stmt_h , err , driver TSRMLS_CC ) ;
stmt - > conn = conn ;
2017-12-09 01:15:24 +01:00
// handle has been set in the constructor of ss_sqlsrv_stmt, so we set it to NULL to prevent a double free
2016-01-30 03:00:20 +01:00
// in the catch block below.
stmt_h = SQL_NULL_HANDLE ;
2017-12-09 01:15:24 +01:00
// process the options array given to core_sqlsrv_prepare.
2017-06-22 23:04:34 +02:00
if ( options_ht & & zend_hash_num_elements ( options_ht ) > 0 & & valid_stmt_opts ) {
2018-12-04 22:00:34 +01:00
zend_ulong index = - 1 ;
zend_string * key = NULL ;
zval * value_z = NULL ;
2016-01-30 03:00:20 +01:00
2018-12-04 22:00:34 +01:00
ZEND_HASH_FOREACH_KEY_VAL ( options_ht , index , key , value_z ) {
2016-01-30 03:00:20 +01:00
2018-12-04 22:00:34 +01:00
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
// The driver layer should ensure a valid key.
DEBUG_SQLSRV_ASSERT ( ( type = = HASH_KEY_IS_LONG ) , " allocate_stmt: Invalid statment option key provided. " ) ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
const stmt_option * stmt_opt = get_stmt_option ( stmt - > conn , index , valid_stmt_opts TSRMLS_CC ) ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
// 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. " ) ;
2016-01-30 03:00:20 +01:00
2018-12-04 22:00:34 +01:00
// perform the actions the statement option needs done.
( * stmt_opt - > func ) ( stmt , stmt_opt , value_z TSRMLS_CC ) ;
} ZEND_HASH_FOREACH_END ( ) ;
2016-01-30 03:00:20 +01:00
}
2017-01-20 23:43:49 +01:00
return_stmt = stmt ;
2016-01-30 03:00:20 +01:00
stmt . transferred ( ) ;
}
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. " ) ;
}
2017-01-20 23:43:49 +01:00
return return_stmt ;
2016-01-30 03:00:20 +01:00
}
// 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
2017-12-09 01:15:24 +01:00
// The php type of the parameter is taken from the zval.
// The sql type is given as a hint if the driver provides it.
2016-01-30 03:00:20 +01:00
2017-06-22 23:04:34 +02:00
void core_sqlsrv_bind_param ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT param_num , _In_ SQLSMALLINT direction , _Inout_ zval * param_z ,
2017-10-18 00:37:59 +02:00
_In_ SQLSRV_PHPTYPE php_out_type , _Inout_ SQLSRV_ENCODING encoding , _Inout_ SQLSMALLINT sql_type , _Inout_ SQLULEN column_size ,
2017-06-22 23:04:34 +02:00
_Inout_ SQLSMALLINT decimal_digits TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
SQLSMALLINT c_type ;
SQLPOINTER buffer = NULL ;
SQLLEN buffer_len = 0 ;
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( direction = = SQL_PARAM_INPUT | | direction = = SQL_PARAM_OUTPUT | | direction = = SQL_PARAM_INPUT_OUTPUT ,
2016-01-30 03:00:20 +01:00
" core_sqlsrv_bind_param: Invalid parameter direction. " ) ;
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( direction = = SQL_PARAM_INPUT | | php_out_type ! = SQLSRV_PHPTYPE_INVALID ,
2016-01-30 03:00:20 +01:00
" core_sqlsrv_bind_param: php_out_type not set before calling core_sqlsrv_bind_param. " ) ;
try {
// check is only < because params are 0 based
2017-10-21 00:10:56 +02:00
CHECK_CUSTOM_ERROR ( param_num > = SQL_SERVER_MAX_PARAMS , stmt , SQLSRV_ERROR_MAX_PARAMS_EXCEEDED , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
// resize the statements array of int_ptrs if the parameter isn't already set.
2017-10-21 00:10:56 +02:00
if ( stmt - > param_ind_ptrs . size ( ) < static_cast < size_t > ( param_num + 1 ) ) {
2016-01-30 03:00:20 +01:00
stmt - > param_ind_ptrs . resize ( param_num + 1 , SQL_NULL_DATA ) ;
}
2018-08-01 02:22:56 +02:00
SQLLEN & ind_ptr = stmt - > param_ind_ptrs [ param_num ] ;
2016-01-30 03:00:20 +01:00
2016-03-15 04:09:46 +01:00
zval * param_ref = param_z ;
2017-10-21 00:10:56 +02:00
if ( Z_ISREF_P ( param_z ) ) {
2016-03-15 04:09:46 +01:00
ZVAL_DEREF ( param_z ) ;
}
2016-07-06 20:47:05 +02:00
bool zval_was_null = ( Z_TYPE_P ( param_z ) = = IS_NULL ) ;
2016-05-04 05:05:41 +02:00
bool zval_was_bool = ( Z_TYPE_P ( param_z ) = = IS_TRUE | | Z_TYPE_P ( param_z ) = = IS_FALSE ) ;
2016-01-30 03:00:20 +01:00
// 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 ;
2017-10-21 00:10:56 +02:00
switch ( php_out_type ) {
2016-01-30 03:00:20 +01:00
case SQLSRV_PHPTYPE_INT :
2017-10-21 00:10:56 +02:00
if ( zval_was_null | | zval_was_bool ) {
2016-01-30 03:00:20 +01:00
convert_to_long ( param_z ) ;
}
2018-04-10 02:15:58 +02:00
match = Z_TYPE_P ( param_z ) = = IS_LONG ;
2016-01-30 03:00:20 +01:00
break ;
case SQLSRV_PHPTYPE_FLOAT :
2017-10-21 00:10:56 +02:00
if ( zval_was_null ) {
2016-01-30 03:00:20 +01:00
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 ;
}
2017-10-21 00:10:56 +02:00
CHECK_CUSTOM_ERROR ( ! match , stmt , SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
}
2017-12-09 01:15:24 +01:00
// If the user specifies a certain type for an output parameter, we have to convert the zval
2017-11-03 19:14:55 +01:00
// to that type so that when the buffer is filled, the type is correct. But first,
2017-12-09 01:15:24 +01:00
// should check if a LOB type is specified.
CHECK_CUSTOM_ERROR ( direction ! = SQL_PARAM_INPUT & & ( sql_type = = SQL_LONGVARCHAR
| | sql_type = = SQL_WLONGVARCHAR | | sql_type = = SQL_LONGVARBINARY ) ,
2017-11-03 18:00:08 +01:00
stmt , SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED ) {
throw core : : CoreException ( ) ;
}
2017-10-21 00:10:56 +02:00
if ( direction = = SQL_PARAM_OUTPUT ) {
2016-01-30 03:00:20 +01:00
switch ( php_out_type ) {
case SQLSRV_PHPTYPE_INT :
2018-04-10 02:15:58 +02:00
convert_to_long ( param_z ) ;
2016-01-30 03:00:20 +01:00
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 ) | |
2017-12-09 01:15:24 +01:00
( encoding = = SQLSRV_ENCODING_SYSTEM | | encoding = = SQLSRV_ENCODING_UTF8 | |
2016-01-30 03:00:20 +01:00
encoding = = SQLSRV_ENCODING_BINARY ) , " core_sqlsrv_bind_param: invalid encoding " ) ;
2017-10-21 00:10:56 +02:00
if ( stmt - > conn - > ce_option . enabled & & ( sql_type = = SQL_UNKNOWN_TYPE | | column_size = = SQLSRV_UNKNOWN_SIZE ) ) {
2017-11-16 19:30:44 +01:00
// use the meta data only if the user has not specified the sql type or column size
2017-11-22 01:41:18 +01:00
SQLSRV_ASSERT ( param_num < stmt - > param_descriptions . size ( ) , " Invalid param_num passed in core_sqlsrv_bind_param! " ) ;
sql_type = stmt - > param_descriptions [ param_num ] . get_sql_type ( ) ;
column_size = stmt - > param_descriptions [ param_num ] . get_column_size ( ) ;
decimal_digits = stmt - > param_descriptions [ param_num ] . get_decimal_digits ( ) ;
2017-11-16 19:30:44 +01:00
2017-09-19 21:48:21 +02:00
// change long to double if the sql type is decimal
2017-10-21 00:10:56 +02:00
if ( ( sql_type = = SQL_DECIMAL | | sql_type = = SQL_NUMERIC ) & & Z_TYPE_P ( param_z ) = = IS_LONG )
2017-10-19 03:10:30 +02:00
convert_to_double ( param_z ) ;
2016-01-30 03:00:20 +01:00
}
2017-10-21 00:10:56 +02:00
else {
2017-09-06 01:51:40 +02:00
// if the sql type is unknown, then set the default based on the PHP type passed in
2017-10-21 00:10:56 +02:00
if ( sql_type = = SQL_UNKNOWN_TYPE ) {
2017-09-06 01:51:40 +02:00
default_sql_type ( stmt , param_num , param_z , encoding , sql_type TSRMLS_CC ) ;
}
2016-01-30 03:00:20 +01:00
2017-09-06 01:51:40 +02:00
// if the size is unknown, then set the default based on the PHP type passed in
2017-10-21 00:10:56 +02:00
if ( column_size = = SQLSRV_UNKNOWN_SIZE ) {
2017-09-06 01:51:40 +02:00
default_sql_size_and_scale ( stmt , static_cast < unsigned int > ( param_num ) , param_z , encoding , column_size , decimal_digits TSRMLS_CC ) ;
}
2016-01-30 03:00:20 +01:00
}
// 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
2017-10-21 00:10:56 +02:00
switch ( Z_TYPE_P ( param_z ) ) {
2016-01-30 03:00:20 +01:00
case IS_NULL :
{
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( direction = = SQL_PARAM_INPUT , " Invalid output param type. The driver layer should catch this. " ) ;
2016-01-30 03:00:20 +01:00
ind_ptr = SQL_NULL_DATA ;
buffer = NULL ;
buffer_len = 0 ;
}
break ;
case IS_TRUE :
case IS_FALSE :
case IS_LONG :
2017-12-09 01:15:24 +01:00
{
2017-10-19 03:10:30 +02:00
// if it is boolean, set the lval to 0 or 1
convert_to_long ( param_z ) ;
2016-01-30 03:00:20 +01:00
buffer = & param_z - > value ;
2018-05-15 23:28:26 +02:00
buffer_len = sizeof ( Z_LVAL_P ( param_z ) ) ;
2016-01-30 03:00:20 +01:00
ind_ptr = buffer_len ;
2017-10-21 00:10:56 +02:00
if ( direction ! = SQL_PARAM_INPUT ) {
2016-01-30 03:00:20 +01:00
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
2018-12-04 22:00:34 +01:00
sqlsrv_output_param output_param ( param_ref , static_cast < int > ( param_num ) , zval_was_bool , php_out_type ) ;
2016-01-30 03:00:20 +01:00
save_output_param_for_later ( stmt , output_param TSRMLS_CC ) ;
}
}
break ;
case IS_DOUBLE :
{
buffer = & param_z - > value ;
2018-12-04 22:00:34 +01:00
buffer_len = sizeof ( Z_DVAL_P ( param_z ) ) ;
2016-01-30 03:00:20 +01:00
ind_ptr = buffer_len ;
2017-10-21 00:10:56 +02:00
if ( direction ! = SQL_PARAM_INPUT ) {
2016-01-30 03:00:20 +01:00
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
2018-12-04 22:00:34 +01:00
sqlsrv_output_param output_param ( param_ref , static_cast < int > ( param_num ) , zval_was_bool , php_out_type ) ;
2016-01-30 03:00:20 +01:00
save_output_param_for_later ( stmt , output_param TSRMLS_CC ) ;
}
}
break ;
case IS_STRING :
2017-10-19 03:10:30 +02:00
{
2017-11-17 19:21:03 +01:00
if ( sql_type = = SQL_DECIMAL | | sql_type = = SQL_NUMERIC ) {
2017-11-16 01:36:10 +01:00
adjustInputPrecision ( param_z , decimal_digits ) ;
2016-01-30 03:00:20 +01:00
}
2017-11-16 01:36:10 +01:00
2017-10-19 03:10:30 +02:00
buffer = Z_STRVAL_P ( param_z ) ;
buffer_len = Z_STRLEN_P ( param_z ) ;
2017-12-09 01:15:24 +01:00
2017-10-19 03:10:30 +02:00
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
2017-10-21 00:10:56 +02:00
if ( direction = = SQL_PARAM_INPUT & & encoding = = CP_UTF8 ) {
2017-10-19 03:10:30 +02:00
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 ,
2017-10-21 00:10:56 +02:00
param_num + 1 , get_last_error_message ( ) ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
2017-10-19 03:10:30 +02:00
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 ) ;
2016-01-30 03:00:20 +01:00
}
2017-10-19 03:10:30 +02:00
ind_ptr = buffer_len ;
2017-10-21 00:10:56 +02:00
if ( direction ! = SQL_PARAM_INPUT ) {
2017-10-19 03:10:30 +02:00
// 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
2017-10-21 00:10:56 +02:00
if ( ZSTR_IS_INTERNED ( Z_STR_P ( param_z ) ) ) {
2017-10-19 03:10:30 +02:00
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 ) ;
}
2016-01-30 03:00:20 +01:00
2017-10-19 03:10:30 +02:00
// 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 | |
2017-10-21 00:10:56 +02:00
sql_type = = SQL_WLONGVARCHAR ) ) ) ) {
2017-10-19 03:10:30 +02:00
bool converted = convert_input_param_to_utf16 ( param_z , param_z ) ;
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE ,
2018-04-10 02:15:58 +02:00
param_num + 1 , get_last_error_message ( ) ) {
2017-10-19 03:10:30 +02:00
throw core : : CoreException ( ) ;
}
buffer = Z_STRVAL_P ( param_z ) ;
buffer_len = Z_STRLEN_P ( param_z ) ;
ind_ptr = buffer_len ;
}
2016-01-30 03:00:20 +01:00
2017-10-19 03:10:30 +02:00
// 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 , decimal_digits ,
2018-04-10 02:15:58 +02:00
buffer , buffer_len TSRMLS_CC ) ;
2017-10-19 03:10:30 +02:00
// save the parameter to be adjusted and/or converted after the results are processed
2018-04-10 02:15:58 +02:00
sqlsrv_output_param output_param ( param_ref , encoding , param_num , static_cast < SQLUINTEGER > ( buffer_len ) ) ;
2017-10-19 03:10:30 +02:00
2018-10-13 00:22:27 +02:00
output_param . saveMetaData ( sql_type , column_size , decimal_digits ) ;
2017-10-19 03:10:30 +02:00
save_output_param_for_later ( stmt , output_param TSRMLS_CC ) ;
2016-03-15 04:09:46 +01:00
2017-12-09 01:15:24 +01:00
// 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
2017-10-19 03:10:30 +02:00
// greater than the column_size, we don't get any truncation error. In order to
2017-12-09 01:15:24 +01:00
// 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
2017-10-19 03:10:30 +02:00
// output parameters.
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
2017-10-21 00:10:56 +02:00
if ( direction = = SQL_PARAM_OUTPUT & & ! stmt - > conn - > ce_option . enabled ) {
2016-01-30 03:00:20 +01:00
2017-10-21 00:10:56 +02:00
switch ( sql_type ) {
2016-01-30 03:00:20 +01:00
case SQL_VARBINARY :
case SQL_VARCHAR :
2017-10-19 03:10:30 +02:00
case SQL_WVARCHAR :
2016-01-30 03:00:20 +01:00
column_size = SQL_SS_LENGTH_UNLIMITED ;
break ;
default :
break ;
2017-10-19 03:10:30 +02:00
}
2016-01-30 03:00:20 +01:00
}
}
}
break ;
case IS_RESOURCE :
{
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( direction = = SQL_PARAM_INPUT , " Invalid output param type. The driver layer should catch this. " ) ;
2016-01-30 03:00:20 +01:00
sqlsrv_stream stream_encoding ( param_z , encoding ) ;
HashTable * streams_ht = Z_ARRVAL ( stmt - > param_streams ) ;
2016-07-06 20:47:05 +02:00
core : : sqlsrv_zend_hash_index_update_mem ( * stmt , streams_ht , param_num , & stream_encoding , sizeof ( stream_encoding ) TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
buffer = reinterpret_cast < SQLPOINTER > ( param_num ) ;
2016-05-04 05:05:41 +02:00
Z_TRY_ADDREF_P ( param_z ) ; // so that it doesn't go away while we're using it
2016-01-30 03:00:20 +01:00
buffer_len = 0 ;
ind_ptr = SQL_DATA_AT_EXEC ;
}
break ;
case IS_OBJECT :
{
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( direction = = SQL_PARAM_INPUT , " Invalid output param type. The driver layer should catch this. " ) ;
2016-01-30 03:00:20 +01:00
zval function_z ;
zval buffer_z ;
zval format_z ;
zval params [ 1 ] ;
2018-12-04 22:00:34 +01:00
ZVAL_UNDEF ( & function_z ) ;
ZVAL_UNDEF ( & buffer_z ) ;
ZVAL_UNDEF ( & format_z ) ;
ZVAL_UNDEF ( params ) ;
2016-04-12 23:43:46 +02:00
2016-01-30 03:00:20 +01:00
bool valid_class_name_found = false ;
2017-12-09 01:15:24 +01:00
2016-07-06 20:47:05 +02:00
zend_class_entry * class_entry = Z_OBJCE_P ( param_z TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
2017-10-21 00:10:56 +02:00
while ( class_entry ! = NULL ) {
2017-06-22 23:04:34 +02:00
SQLSRV_ASSERT ( class_entry - > name ! = NULL , " core_sqlsrv_bind_param: class_entry->name is NULL. " ) ;
2017-12-09 01:15:24 +01:00
if ( class_entry - > name - > len = = DateTime : : DATETIME_CLASS_NAME_LEN & & class_entry - > name ! = NULL & &
2017-10-21 00:10:56 +02:00
stricmp ( class_entry - > name - > val , DateTime : : DATETIME_CLASS_NAME ) = = 0 ) {
2016-01-30 03:00:20 +01:00
valid_class_name_found = true ;
break ;
}
2017-10-21 00:10:56 +02:00
else {
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
// Check the parent
2016-01-30 03:00:20 +01:00
class_entry = class_entry - > parent ;
}
}
2017-10-21 00:10:56 +02:00
CHECK_CUSTOM_ERROR ( ! valid_class_name_found , stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
// 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.
2017-10-21 00:10:56 +02:00
if ( sql_type = = SQL_SS_TIMESTAMPOFFSET ) {
2018-12-04 22:00:34 +01:00
core : : sqlsrv_zval_stringl ( & format_z , const_cast < char * > ( DateTime : : DATETIMEOFFSET_FORMAT ) ,
2016-07-06 20:47:05 +02:00
DateTime : : DATETIMEOFFSET_FORMAT_LEN ) ;
2016-01-30 03:00:20 +01:00
}
2017-10-21 00:10:56 +02:00
else if ( sql_type = = SQL_TYPE_DATE ) {
2018-12-04 22:00:34 +01:00
core : : sqlsrv_zval_stringl ( & format_z , const_cast < char * > ( DateTime : : DATE_FORMAT ) , DateTime : : DATE_FORMAT_LEN ) ;
2016-01-30 03:00:20 +01:00
}
2017-10-21 00:10:56 +02:00
else {
2018-12-04 22:00:34 +01:00
core : : sqlsrv_zval_stringl ( & format_z , const_cast < char * > ( DateTime : : DATETIME_FORMAT ) , DateTime : : DATETIME_FORMAT_LEN ) ;
2016-01-30 03:00:20 +01:00
}
// call the DateTime::format member function to convert the object to a string that SQL Server understands
2018-12-04 22:00:34 +01:00
core : : sqlsrv_zval_stringl ( & function_z , " format " , sizeof ( " format " ) - 1 ) ;
2016-01-30 03:00:20 +01:00
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 ) ;
2018-12-04 22:00:34 +01:00
zend_string_release ( Z_STR ( format_z ) ) ;
zend_string_release ( Z_STR ( function_z ) ) ;
2017-10-21 00:10:56 +02:00
CHECK_CUSTOM_ERROR ( zr = = FAILURE , stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
2016-02-24 03:06:51 +01:00
buffer = Z_STRVAL ( buffer_z ) ;
2016-07-06 20:47:05 +02:00
zr = add_next_index_zval ( & ( stmt - > param_datetime_buffers ) , & buffer_z ) ;
2017-10-21 00:10:56 +02:00
CHECK_CUSTOM_ERROR ( zr = = FAILURE , stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
2016-02-24 03:06:51 +01:00
buffer_len = Z_STRLEN ( buffer_z ) - 1 ;
2016-01-30 03:00:20 +01:00
ind_ptr = buffer_len ;
break ;
2017-12-09 01:15:24 +01:00
}
2016-01-30 03:00:20 +01:00
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 ;
}
2017-10-21 00:10:56 +02:00
if ( zval_was_null ) {
2016-01-30 03:00:20 +01:00
ind_ptr = SQL_NULL_DATA ;
}
2017-12-09 01:15:24 +01:00
core : : SQLBindParameter ( stmt , param_num + 1 , direction ,
2018-12-04 22:00:34 +01:00
c_type , sql_type , column_size , decimal_digits , buffer , buffer_len , & ind_ptr TSRMLS_CC ) ;
2017-09-06 01:51:40 +02:00
if ( stmt - > conn - > ce_option . enabled & & sql_type = = SQL_TYPE_TIMESTAMP )
{
2017-10-21 00:10:56 +02:00
if ( decimal_digits = = 3 )
2018-01-30 01:34:57 +01:00
core : : SQLSetDescField ( stmt , param_num + 1 , SQL_CA_SS_SERVER_TYPE , ( SQLPOINTER ) SQL_SS_TYPE_DATETIME , SQL_IS_INTEGER ) ;
2017-09-06 01:51:40 +02:00
else if ( decimal_digits = = 0 )
2018-01-30 01:34:57 +01:00
core : : SQLSetDescField ( stmt , param_num + 1 , SQL_CA_SS_SERVER_TYPE , ( SQLPOINTER ) SQL_SS_TYPE_SMALLDATETIME , SQL_IS_INTEGER ) ;
2017-09-06 01:51:40 +02:00
}
2016-01-30 03:00:20 +01:00
}
2017-10-21 00:10:56 +02:00
catch ( core : : CoreException & e ) {
2016-01-30 03:00:20 +01:00
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
2017-06-22 23:04:34 +02:00
SQLRETURN core_sqlsrv_execute ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC , _In_reads_bytes_ ( sql_len ) const char * sql , _In_ int sql_len )
2016-01-30 03:00:20 +01:00
{
2017-04-04 20:10:56 +02:00
SQLRETURN r = SQL_ERROR ;
2016-06-13 23:44:53 +02:00
2016-01-30 03:00:20 +01:00
try {
// close the stream to release the resource
close_active_stream ( stmt TSRMLS_CC ) ;
if ( sql ) {
2017-01-20 23:43:49 +01:00
sqlsrv_malloc_auto_ptr < SQLWCHAR > wsql_string ;
2016-01-30 03:00:20 +01:00
unsigned int wsql_len = 0 ;
if ( sql_len = = 0 | | ( sql [ 0 ] = = ' \0 ' & & sql_len = = 1 ) ) {
2017-01-20 23:43:49 +01:00
wsql_string = reinterpret_cast < SQLWCHAR * > ( sqlsrv_malloc ( sizeof ( SQLWCHAR ) ) ) ;
2016-01-30 03:00:20 +01:00
wsql_string [ 0 ] = L ' \0 ' ;
wsql_len = 0 ;
}
else {
2017-01-26 23:59:23 +01:00
SQLSRV_ENCODING encoding = ( ( stmt - > encoding ( ) = = SQLSRV_ENCODING_DEFAULT ) ? stmt - > conn - > encoding ( ) : stmt - > encoding ( ) ) ;
2016-01-30 03:00:20 +01:00
wsql_string = utf16_string_from_mbcs_string ( encoding , reinterpret_cast < const char * > ( sql ) ,
sql_len , & wsql_len ) ;
2017-01-20 23:43:49 +01:00
CHECK_CUSTOM_ERROR ( wsql_string = = 0 , stmt , SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE ,
2016-01-30 03:00:20 +01:00
get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
}
r = core : : SQLExecDirectW ( stmt , wsql_string TSRMLS_CC ) ;
}
else {
r = core : : SQLExecute ( stmt TSRMLS_CC ) ;
}
2017-04-01 00:48:22 +02:00
// if data is needed (streams were bound) and they should be sent at execute time, then do so now
2016-01-30 03:00:20 +01:00
if ( r = = SQL_NEED_DATA & & stmt - > send_streams_at_exec ) {
send_param_streams ( stmt TSRMLS_CC ) ;
}
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
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
2016-07-06 20:47:05 +02:00
if ( stmt - > send_streams_at_exec & & ( r = = SQL_NO_DATA | | ! core_sqlsrv_has_any_result ( stmt TSRMLS_CC ) ) ) {
2016-01-30 03:00:20 +01:00
finalize_output_parameters ( stmt TSRMLS_CC ) ;
}
2017-04-01 00:48:22 +02:00
// stream parameters are sent, clean the Hashtable
if ( stmt - > send_streams_at_exec ) {
zend_hash_clean ( Z_ARRVAL ( stmt - > param_streams ) ) ;
}
return r ;
2016-01-30 03:00:20 +01:00
}
catch ( core : : CoreException & e ) {
2017-12-09 01:15:24 +01:00
// if the statement executed but failed in a subsequent operation before returning,
2016-07-06 20:47:05 +02:00
// we need to cancel the statement and deref the output and stream parameters
2017-04-01 00:48:22 +02:00
if ( stmt - > send_streams_at_exec ) {
2017-05-03 23:41:19 +02:00
finalize_output_parameters ( stmt TSRMLS_CC ) ;
2017-04-01 00:48:22 +02:00
zend_hash_clean ( Z_ARRVAL ( stmt - > param_streams ) ) ;
}
if ( stmt - > executed ) {
2016-01-30 03:00:20 +01:00
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:
2017-12-09 01:15:24 +01:00
// Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the
2016-01-30 03:00:20 +01:00
// user scrolls past a non-scrollable result set
2017-06-22 23:04:34 +02:00
bool core_sqlsrv_fetch ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLSMALLINT fetch_orientation , _In_ SQLULEN fetch_offset TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
// 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. " ) ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
try {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
// 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 ( ) ;
}
2019-04-15 22:48:48 +02:00
2017-03-25 01:12:10 +01:00
// First time only
2017-03-25 00:01:45 +01:00
if ( ! stmt - > fetch_called ) {
2019-04-05 21:34:33 +02:00
SQLSMALLINT has_fields ;
2019-04-13 05:49:03 +02:00
if ( stmt - > column_count ! = ACTIVE_NUM_COLS_INVALID ) {
2019-04-05 21:34:33 +02:00
has_fields = stmt - > column_count ;
} else {
has_fields = core : : SQLNumResultCols ( stmt TSRMLS_CC ) ;
2019-04-15 22:48:48 +02:00
stmt - > column_count = has_fields ;
2019-04-05 21:34:33 +02:00
}
2017-03-25 01:03:24 +01:00
CHECK_CUSTOM_ERROR ( has_fields = = 0 , stmt , SQLSRV_ERROR_NO_FIELDS ) {
2017-03-25 00:01:45 +01:00
throw core : : CoreException ( ) ;
}
}
2016-01-30 03:00:20 +01:00
// 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
2017-12-09 01:15:24 +01:00
// fetch_called, this must be the first time we've called sqlsrv_fetch.
2016-01-30 03:00:20 +01:00
if ( stmt - > cursor_type = = SQL_CURSOR_FORWARD_ONLY & & stmt - > has_rows & & ! stmt - > fetch_called ) {
stmt - > fetch_called = true ;
return true ;
}
2017-12-09 01:15:24 +01:00
// move to the record requested. For absolute records, we use a 0 based offset, so +1 since
2016-01-30 03:00:20 +01:00
// SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided.
2017-01-26 23:59:23 +01:00
SQLRETURN r = stmt - > current_results - > fetch ( fetch_orientation , ( fetch_orientation = = SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ) ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
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 ;
}
2017-02-09 19:44:33 +01:00
stmt - > fetch_called = false ; // reset this flag
2016-01-30 03:00:20 +01:00
return false ;
}
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
// 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
2017-12-09 01:15:24 +01:00
}
2016-01-30 03:00:20 +01:00
catch ( core : : CoreException & e ) {
throw e ;
}
catch ( . . . ) {
DIE ( " core_sqlsrv_fetch: Unexpected exception occurred. " ) ;
}
return true ;
}
2017-12-09 01:15:24 +01:00
// Retrieves metadata for a field of a prepared statement.
2016-01-30 03:00:20 +01:00
// 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.
2017-06-22 23:04:34 +02:00
field_meta_data * core_sqlsrv_field_metadata ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLSMALLINT colno TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
// pre-condition check
SQLSRV_ASSERT ( colno > = 0 , " core_sqlsrv_field_metadata: Invalid column number provided. " ) ;
sqlsrv_malloc_auto_ptr < field_meta_data > meta_data ;
2017-01-17 21:38:31 +01:00
sqlsrv_malloc_auto_ptr < SQLWCHAR > field_name_temp ;
SQLSMALLINT field_len_temp = 0 ;
SQLLEN field_name_len = 0 ;
2016-01-30 03:00:20 +01:00
meta_data = new ( sqlsrv_malloc ( sizeof ( field_meta_data ) ) ) field_meta_data ( ) ;
2017-02-01 03:10:37 +01:00
field_name_temp = static_cast < SQLWCHAR * > ( sqlsrv_malloc ( ( SS_MAXCOLNAMELEN + 1 ) * sizeof ( SQLWCHAR ) ) ) ;
2017-01-17 23:19:45 +01:00
SQLSRV_ENCODING encoding = ( ( stmt - > encoding ( ) = = SQLSRV_ENCODING_DEFAULT ) ? stmt - > conn - > encoding ( ) : stmt - > encoding ( ) ) ;
2018-12-04 22:00:34 +01:00
try {
2017-02-02 23:20:50 +01:00
core : : SQLDescribeColW ( stmt , colno + 1 , field_name_temp , SS_MAXCOLNAMELEN + 1 , & field_len_temp ,
2017-01-17 23:19:45 +01:00
& ( meta_data - > field_type ) , & ( meta_data - > field_size ) , & ( meta_data - > field_scale ) ,
& ( meta_data - > field_is_nullable ) TSRMLS_CC ) ;
2018-12-04 22:00:34 +01:00
}
catch ( core : : CoreException & e ) {
throw e ;
}
2017-01-17 21:38:31 +01:00
2017-01-17 23:19:45 +01:00
bool converted = convert_string_from_utf16 ( encoding , field_name_temp , field_len_temp , ( char * * ) & ( meta_data - > field_name ) , field_name_len ) ;
2017-12-09 01:15:24 +01:00
2017-01-17 21:38:31 +01:00
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
2016-01-30 03:00:20 +01:00
}
// 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 :
2017-12-09 01:15:24 +01:00
case SQL_SS_TIMESTAMPOFFSET :
2016-01-30 03:00:20 +01:00
case SQL_BIT :
case SQL_TINYINT :
case SQL_SMALLINT :
case SQL_INTEGER :
case SQL_BIGINT :
case SQL_REAL :
case SQL_FLOAT :
2017-12-09 01:15:24 +01:00
case SQL_DOUBLE :
2016-01-30 03:00:20 +01:00
{
meta_data - > field_precision = meta_data - > field_size ;
meta_data - > field_size = 0 ;
break ;
}
default : {
break ;
}
}
2018-11-28 02:18:38 +01:00
if ( meta_data - > field_type = = SQL_DECIMAL ) {
// Check if it is money type -- get the name of the data type
char field_type_name [ SS_MAXCOLNAMELEN ] = { ' \0 ' } ;
SQLSMALLINT out_buff_len ;
SQLLEN not_used ;
core : : SQLColAttribute ( stmt , colno + 1 , SQL_DESC_TYPE_NAME , field_type_name ,
sizeof ( field_type_name ) , & out_buff_len , & not_used TSRMLS_CC ) ;
if ( ! strcmp ( field_type_name , " money " ) | | ! strcmp ( field_type_name , " smallmoney " ) ) {
meta_data - > field_is_money_type = true ;
}
}
2019-05-09 00:18:15 +02:00
// Set the field name length
2017-06-22 23:04:34 +02:00
meta_data - > field_name_len = static_cast < SQLSMALLINT > ( field_name_len ) ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
field_meta_data * result_field_meta_data = meta_data ;
2017-12-09 01:15:24 +01:00
meta_data . transferred ( ) ;
2016-01-30 03:00:20 +01:00
return result_field_meta_data ;
}
2019-05-01 17:03:33 +02:00
void core_sqlsrv_sensitivity_metadata ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
{
sqlsrv_malloc_auto_ptr < unsigned char > dcbuf ;
SQLINTEGER dclen = 0 ;
SQLINTEGER dclenout = 0 ;
SQLHANDLE ird ;
SQLRETURN r ;
try {
if ( ! stmt - > data_classification ) {
return ;
}
if ( stmt - > current_sensitivity_metadata ! = NULL ) {
// Already cached, so return
return ;
}
CHECK_CUSTOM_ERROR ( ! stmt - > executed , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION ) {
throw core : : CoreException ( ) ;
}
// Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification
// To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by
// calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute
r = : : SQLGetStmtAttr ( stmt - > handle ( ) , SQL_ATTR_IMP_ROW_DESC , ( SQLPOINTER ) & ird , SQL_IS_POINTER , 0 ) ;
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
LOG ( SEV_ERROR , " core_sqlsrv_sensitivity_metadata: failed in getting Implementation Row Descriptor handle. " ) ;
throw core : : CoreException ( ) ;
}
// First call to get dclen
r = : : SQLGetDescFieldW ( ird , 0 , SQL_CA_SS_DATA_CLASSIFICATION , dcbuf , 0 , & dclen ) ;
if ( r ! = SQL_SUCCESS | | dclen = = 0 ) {
// log the error first
LOG ( SEV_ERROR , " core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW first time. " ) ;
// If this fails, check if it is the "Invalid Descriptor Field error"
SQLRETURN rc ;
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] = { ' \0 ' } ;
SQLSMALLINT len ;
rc = : : SQLGetDiagField ( SQL_HANDLE_DESC , ird , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len TSRMLS_CC ) ;
CHECK_SQL_ERROR_OR_WARNING ( rc , stmt ) {
throw core : : CoreException ( ) ;
}
CHECK_CUSTOM_ERROR ( ! strcmp ( " HY091 " , reinterpret_cast < char * > ( state ) ) , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE ) {
throw core : : CoreException ( ) ;
}
CHECK_CUSTOM_ERROR ( true , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED , " Unexpected SQL Error state " ) {
throw core : : CoreException ( ) ;
}
}
// Call again to read SQL_CA_SS_DATA_CLASSIFICATION data
dcbuf = static_cast < unsigned char * > ( sqlsrv_malloc ( dclen * sizeof ( char ) ) ) ;
r = : : SQLGetDescFieldW ( ird , 0 , SQL_CA_SS_DATA_CLASSIFICATION , dcbuf , dclen , & dclenout ) ;
if ( r ! = SQL_SUCCESS ) {
LOG ( SEV_ERROR , " core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW again. " ) ;
CHECK_CUSTOM_ERROR ( true , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED , " SQLGetDescFieldW failed unexpectedly " ) {
throw core : : CoreException ( ) ;
}
}
// Start parsing the data (blob)
using namespace data_classification ;
unsigned char * dcptr = dcbuf ;
sqlsrv_malloc_auto_ptr < sensitivity_metadata > sensitivity_meta ;
sensitivity_meta = new ( sqlsrv_malloc ( sizeof ( sensitivity_metadata ) ) ) sensitivity_metadata ( ) ;
// Parse the name id pairs for labels first then info types
2019-05-09 00:18:15 +02:00
parse_sensitivity_name_id_pairs ( stmt , sensitivity_meta - > num_labels , & sensitivity_meta - > labels , & dcptr ) ;
parse_sensitivity_name_id_pairs ( stmt , sensitivity_meta - > num_infotypes , & sensitivity_meta - > infotypes , & dcptr ) ;
2019-05-01 17:03:33 +02:00
// Next parse the sensitivity properties
parse_column_sensitivity_props ( sensitivity_meta , & dcptr ) ;
unsigned char * dcend = dcbuf ;
dcend + = dclen ;
CHECK_CUSTOM_ERROR ( dcptr ! = dcend , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED , " Metadata parsing ends unexpectedly " ) {
throw core : : CoreException ( ) ;
}
stmt - > current_sensitivity_metadata = sensitivity_meta ;
sensitivity_meta . transferred ( ) ;
} catch ( core : : CoreException & e ) {
throw e ;
}
}
2016-01-30 03:00:20 +01:00
// 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
2017-06-22 23:04:34 +02:00
void core_sqlsrv_get_field ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _In_ sqlsrv_phptype sqlsrv_php_type_in , _In_ bool prefer_string ,
2018-12-04 22:00:34 +01:00
_Outref_result_bytebuffer_maybenull_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len , _In_ bool cache_field ,
_Out_ SQLSRV_PHPTYPE * sqlsrv_php_type_out TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2018-12-04 22:00:34 +01:00
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 ) {
2017-01-26 23:59:23 +01:00
sqlsrv_phptype invalid ;
invalid . typeinfo . type = SQLSRV_PHPTYPE_INVALID ;
for ( int i = stmt - > last_field_index + 1 ; i < field_index ; + + i ) {
SQLSRV_ASSERT ( 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 ;
2018-10-06 00:01:18 +02:00
}
}
}
2016-01-30 03:00:20 +01:00
2018-10-06 00:01:18 +02:00
// 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 ) {
SQLSRV_ASSERT ( stmt - > current_meta_data . size ( ) > field_index , " core_sqlsrv_get_field - meta data vector not in sync " ) ;
sql_field_type = stmt - > current_meta_data [ field_index ] - > field_type ;
if ( stmt - > current_meta_data [ field_index ] - > field_precision > 0 ) {
sql_field_len = stmt - > current_meta_data [ field_index ] - > field_precision ;
}
else {
sql_field_len = stmt - > current_meta_data [ field_index ] - > field_size ;
}
2016-06-13 23:44:53 +02:00
2018-10-06 00:01:18 +02:00
// 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 ) ;
}
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
// 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 ( ) ;
}
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
if ( sqlsrv_php_type_out ! = NULL )
* sqlsrv_php_type_out = static_cast < SQLSRV_PHPTYPE > ( sqlsrv_php_type . typeinfo . type ) ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
// Retrieve the data
core_get_field_common ( stmt , field_index , sqlsrv_php_type , field_value , field_len TSRMLS_CC ) ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
// 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 ) ;
}
}
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
catch ( core : : CoreException & e ) {
throw e ;
}
2016-01-30 03:00:20 +01:00
}
// core_sqlsrv_has_any_result
// return if any result set or rows affected message is waiting
2017-12-09 01:15:24 +01:00
// to be consumed and moved over by sqlsrv_next_result.
2016-01-30 03:00:20 +01:00
// Parameters:
// stmt - The statement object on which to check for results.
// Return:
// true if any results are present, false otherwise.
2017-06-22 23:04:34 +02:00
bool core_sqlsrv_has_any_result ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2019-04-13 05:49:03 +02:00
SQLSMALLINT num_cols ;
SQLLEN rows_affected ;
if ( stmt - > column_count ! = ACTIVE_NUM_COLS_INVALID ) {
2019-04-05 21:34:33 +02:00
num_cols = stmt - > column_count ;
}
2019-04-13 05:49:03 +02:00
else {
2019-04-05 21:34:33 +02:00
// Use SQLNumResultCols to determine if we have rows or not
num_cols = core : : SQLNumResultCols ( stmt TSRMLS_CC ) ;
stmt - > column_count = num_cols ;
2019-04-13 05:49:03 +02:00
}
if ( stmt - > row_count ! = ACTIVE_NUM_ROWS_INVALID ) {
rows_affected = stmt - > row_count ;
}
else {
2019-04-05 21:34:33 +02:00
// Use SQLRowCount to determine if there is a rows status waiting
rows_affected = core : : SQLRowCount ( stmt TSRMLS_CC ) ;
stmt - > row_count = rows_affected ;
}
2016-01-30 03:00:20 +01:00
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
2017-06-22 23:04:34 +02:00
void core_sqlsrv_next_result ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC , _In_ bool finalize_output_params , _In_ bool throw_on_errors )
2016-01-30 03:00:20 +01:00
{
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 ( ) ;
}
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
close_active_stream ( stmt TSRMLS_CC ) ;
2017-03-28 00:24:41 +02:00
//Clear column sql types and sql display sizes.
2017-12-09 01:15:24 +01:00
zend_hash_clean ( Z_ARRVAL ( stmt - > col_cache ) ) ;
2016-01-30 03:00:20 +01:00
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
2017-06-22 23:04:34 +02:00
void core_sqlsrv_post_param ( _Inout_ sqlsrv_stmt * stmt , _In_ zend_ulong param_num , zval * param_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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 ) ) {
core : : sqlsrv_zend_hash_index_del ( * stmt , Z_ARRVAL ( stmt - > param_streams ) , param_num TSRMLS_CC ) ;
}
}
//Calls SQLSetStmtAttr to set a cursor.
2017-12-09 01:15:24 +01:00
void core_sqlsrv_set_scrollable ( _Inout_ sqlsrv_stmt * stmt , _In_ unsigned long cursor_type TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
try {
switch ( cursor_type ) {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
case SQL_CURSOR_STATIC :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2016-01-30 03:00:20 +01:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_STATIC ) , SQL_IS_UINTEGER TSRMLS_CC ) ;
break ;
case SQL_CURSOR_DYNAMIC :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2016-01-30 03:00:20 +01:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_DYNAMIC ) , SQL_IS_UINTEGER TSRMLS_CC ) ;
break ;
case SQL_CURSOR_KEYSET_DRIVEN :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2016-01-30 03:00:20 +01:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_KEYSET_DRIVEN ) , SQL_IS_UINTEGER TSRMLS_CC ) ;
break ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
case SQL_CURSOR_FORWARD_ONLY :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2016-01-30 03:00:20 +01:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_FORWARD_ONLY ) , SQL_IS_UINTEGER TSRMLS_CC ) ;
break ;
case SQLSRV_CURSOR_BUFFERED :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2016-01-30 03:00:20 +01:00
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 ;
}
}
2017-06-22 23:04:34 +02:00
void core_sqlsrv_set_buffered_query_limit ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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 ) ;
}
2017-06-22 23:04:34 +02:00
void core_sqlsrv_set_buffered_query_limit ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLLEN limit TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
if ( limit < = 0 ) {
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INVALID_BUFFER_LIMIT ) ;
}
2016-04-12 23:43:46 +02:00
stmt - > buffered_query_limit = limit ;
2016-01-30 03:00:20 +01:00
}
// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout
2017-12-09 01:15:24 +01:00
// which accepts timeout parameter as a long. If the zval is not of type long
2016-01-30 03:00:20 +01:00
// than throws error.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_set_query_timeout ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
try {
// validate the value
if ( Z_TYPE_P ( value_z ) ! = IS_LONG | | Z_LVAL_P ( value_z ) < 0 ) {
2017-12-09 01:15:24 +01:00
convert_to_string ( value_z ) ;
2016-01-30 03:00:20 +01:00
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE , Z_STRVAL_P ( value_z ) ) ;
}
2016-05-04 05:05:41 +02:00
core_sqlsrv_set_query_timeout ( stmt , static_cast < long > ( Z_LVAL_P ( value_z ) ) TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
}
catch ( core : : CoreException & ) {
throw ;
}
}
// Overloaded. Accepts the timeout as a long.
2017-06-22 23:04:34 +02:00
void core_sqlsrv_set_query_timeout ( _Inout_ sqlsrv_stmt * stmt , _In_ long timeout TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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
2016-03-15 04:09:46 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_QUERY_TIMEOUT , reinterpret_cast < SQLPOINTER > ( ( SQLLEN ) timeout ) , SQL_IS_UINTEGER TSRMLS_CC ) ;
2017-12-09 01:15:24 +01:00
// a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which
2016-01-30 03:00:20 +01:00
// is represented by -1.
2017-01-20 23:43:49 +01:00
int lock_timeout = ( ( timeout = = 0 ) ? - 1 : timeout * 1000 /*convert to milliseconds*/ ) ;
2016-01-30 03:00:20 +01:00
// set the LOCK_TIMEOUT on the server.
2018-08-01 02:22:56 +02:00
char lock_timeout_sql [ 32 ] = { ' \0 ' } ;
2017-12-09 01:15:24 +01:00
2017-01-30 22:05:38 +01:00
int written = snprintf ( lock_timeout_sql , sizeof ( lock_timeout_sql ) , " SET LOCK_TIMEOUT %d " , lock_timeout ) ;
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( ( written ! = - 1 & & written ! = sizeof ( lock_timeout_sql ) ) ,
2017-01-20 23:43:49 +01:00
" stmt_option_query_timeout: snprintf failed. Shouldn't ever fail. " ) ;
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
core : : SQLExecDirect ( stmt , lock_timeout_sql TSRMLS_CC ) ;
stmt - > query_timeout = timeout ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
2018-11-28 02:18:38 +01:00
void core_sqlsrv_set_decimal_places ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z TSRMLS_DC )
2018-11-02 22:34:27 +01:00
{
try {
// first check if the input is an integer
2018-11-28 02:18:38 +01:00
CHECK_CUSTOM_ERROR ( Z_TYPE_P ( value_z ) ! = IS_LONG , stmt , SQLSRV_ERROR_INVALID_DECIMAL_PLACES ) {
2018-11-02 22:34:27 +01:00
throw core : : CoreException ( ) ;
}
2018-11-28 02:18:38 +01:00
zend_long decimal_places = Z_LVAL_P ( value_z ) ;
if ( decimal_places < 0 | | decimal_places > SQL_SERVER_MAX_MONEY_SCALE ) {
// ignore decimal_places because it is out of range
decimal_places = NO_CHANGE_DECIMAL_PLACES ;
2018-11-02 22:34:27 +01:00
}
2018-11-28 02:18:38 +01:00
stmt - > decimal_places = static_cast < short > ( decimal_places ) ;
2018-11-02 22:34:27 +01:00
}
catch ( core : : CoreException & ) {
throw ;
}
}
2017-06-22 23:04:34 +02:00
void core_sqlsrv_set_send_at_exec ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
TSRMLS_C ;
// zend_is_true does not fail. It either returns true or false.
2017-12-09 01:15:24 +01:00
stmt - > send_streams_at_exec = ( zend_is_true ( value_z ) ) ? true : false ;
2016-01-30 03:00:20 +01:00
}
// 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
2017-06-22 23:04:34 +02:00
bool core_sqlsrv_send_stream_packet ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2017-12-09 01:15:24 +01:00
// if there no current parameter to process, get the next one
2016-01-30 03:00:20 +01:00
// (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 ) ;
2018-10-19 23:48:21 +02:00
// if we're at the end, then reset both current_stream and current_stream_read
if ( php_stream_eof ( param_stream ) ) {
// yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again
int ret = php_stream_seek ( param_stream , 0 , SEEK_SET ) ;
if ( ret ! = 0 ) {
LOG ( SEV_ERROR , " PHP stream: stream seek failed. " ) ;
throw core : : CoreException ( ) ;
2016-01-30 03:00:20 +01:00
}
2018-10-19 23:48:21 +02:00
stmt - > current_stream = sqlsrv_stream ( NULL , SQLSRV_ENCODING_CHAR ) ;
2016-01-30 03:00:20 +01:00
stmt - > current_stream_read = 0 ;
}
2017-12-09 01:15:24 +01:00
// read the data from the stream, send it via SQLPutData and track how much we've sent.
2016-01-30 03:00:20 +01:00
else {
2018-08-01 02:22:56 +02:00
char buffer [ PHP_STREAM_BUFFER_SIZE + 1 ] = { ' \0 ' } ;
2018-12-04 22:00:34 +01:00
std : : size_t buffer_size = sizeof ( buffer ) - 3 ; // -3 to preserve enough space for a cut off UTF-8 character
2016-03-15 04:09:46 +01:00
std : : size_t read = php_stream_read ( param_stream , buffer , buffer_size ) ;
2018-12-04 22:00:34 +01:00
if ( read > UINT_MAX )
{
LOG ( SEV_ERROR , " PHP stream: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
2016-03-15 04:09:46 +01:00
2016-05-04 05:05:41 +02:00
stmt - > current_stream_read + = static_cast < unsigned int > ( read ) ;
2018-10-19 23:48:21 +02:00
if ( 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 ) ;
}
else if ( read > 0 ) {
2016-01-30 03:00:20 +01:00
// 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 ) {
2017-12-09 01:15:24 +01:00
// the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a
2016-01-30 03:00:20 +01:00
// expansion of 2x the UTF-8 size.
2018-08-01 02:22:56 +02:00
SQLWCHAR wbuffer [ PHP_STREAM_BUFFER_SIZE + 1 ] = { L ' \0 ' } ;
2017-01-30 22:05:38 +01:00
int wbuffer_size = static_cast < int > ( sizeof ( wbuffer ) / sizeof ( SQLWCHAR ) ) ;
2018-12-04 22:00:34 +01:00
DWORD last_error_code = ERROR_SUCCESS ;
// buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2017-01-30 22:05:38 +01:00
int wsize = SystemLocale : : ToUtf16Strict ( stmt - > current_stream . encoding , buffer , static_cast < int > ( read ) , wbuffer , wbuffer_size , & last_error_code ) ;
2017-01-20 23:43:49 +01:00
# else
2017-01-30 22:05:38 +01:00
int wsize = MultiByteToWideChar ( stmt - > current_stream . encoding , MB_ERR_INVALID_CHARS , buffer , static_cast < int > ( read ) , wbuffer , wbuffer_size ) ;
2017-01-20 23:43:49 +01:00
last_error_code = GetLastError ( ) ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2017-01-20 23:43:49 +01:00
2018-12-04 22:00:34 +01:00
if ( wsize = = 0 & & last_error_code = = ERROR_NO_UNICODE_TRANSLATION ) {
2016-01-30 03:00:20 +01:00
// 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
2017-12-09 01:15:24 +01:00
size_t new_read = php_stream_read ( param_stream , static_cast < char * > ( buffer ) + read ,
2016-01-30 03:00:20 +01:00
need_to_read ) ;
// if the bytes couldn't be read, then we return an error
2017-01-20 23:43:49 +01:00
CHECK_CUSTOM_ERROR ( new_read ! = need_to_read , stmt , SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE , get_last_error_message ( ERROR_NO_UNICODE_TRANSLATION ) ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
// try the conversion again with the complete character
2017-12-09 01:15:24 +01:00
# ifndef _WIN32
2017-01-27 02:06:17 +01:00
wsize = SystemLocale : : ToUtf16Strict ( stmt - > current_stream . encoding , buffer , static_cast < int > ( read + new_read ) , wbuffer , static_cast < int > ( sizeof ( wbuffer ) / sizeof ( SQLWCHAR ) ) ) ;
2017-01-20 23:43:49 +01:00
# else
2017-01-27 02:06:17 +01:00
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 ) ) ) ;
# endif //!_WIN32
// 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 ) ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
}
2017-01-20 23:43:49 +01:00
core : : SQLPutData ( stmt , wbuffer , wsize * sizeof ( SQLWCHAR ) TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
}
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 ;
}
2017-06-22 23:04:34 +02:00
void stmt_option_functor : : operator ( ) ( _Inout_ sqlsrv_stmt * /*stmt*/ , stmt_option const * /*opt*/ , _In_ zval * /*value_z*/ TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
TSRMLS_C ;
// This implementation should never get called.
DIE ( " Not implemented. " ) ;
}
2017-06-22 23:04:34 +02:00
void stmt_option_query_timeout : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
core_sqlsrv_set_query_timeout ( stmt , value_z TSRMLS_CC ) ;
}
2017-06-22 23:04:34 +02:00
void stmt_option_send_at_exec : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /*opt*/ , _In_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2017-12-09 01:15:24 +01:00
core_sqlsrv_set_send_at_exec ( stmt , value_z TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
}
2017-06-22 23:04:34 +02:00
void stmt_option_buffered_query_limit : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /*opt*/ , _In_ zval * value_z TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
core_sqlsrv_set_buffered_query_limit ( stmt , value_z TSRMLS_CC ) ;
}
2018-09-18 01:25:02 +02:00
void stmt_option_date_as_string : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z TSRMLS_DC )
{
if ( zend_is_true ( value_z ) ) {
stmt - > date_as_string = true ;
}
else {
stmt - > date_as_string = false ;
}
}
2016-01-30 03:00:20 +01:00
2018-10-13 00:22:27 +02:00
void stmt_option_format_decimals : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z TSRMLS_DC )
{
2018-11-28 02:18:38 +01:00
if ( zend_is_true ( value_z ) ) {
stmt - > format_decimals = true ;
}
else {
stmt - > format_decimals = false ;
}
}
void stmt_option_decimal_places : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z TSRMLS_DC )
{
core_sqlsrv_set_decimal_places ( stmt , value_z TSRMLS_CC ) ;
2018-10-13 00:22:27 +02:00
}
2019-05-01 17:03:33 +02:00
void stmt_option_data_classification : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z TSRMLS_DC )
{
if ( zend_is_true ( value_z ) ) {
stmt - > data_classification = true ;
}
else {
stmt - > data_classification = false ;
}
}
2016-01-30 03:00:20 +01:00
// 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.
2016-07-06 20:47:05 +02:00
void close_active_stream ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
// if there is no active stream, return
2016-06-13 23:44:53 +02:00
if ( Z_TYPE ( stmt - > active_stream ) = = IS_UNDEF ) {
2016-01-30 03:00:20 +01:00
return ;
}
php_stream * stream = NULL ;
// we use no verify since verify would return immediately and we want to assert, not return.
2016-06-13 23:44:53 +02:00
php_stream_from_zval_no_verify ( stream , & ( stmt - > active_stream ) ) ;
2016-01-30 03:00:20 +01:00
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.
2016-06-13 23:44:53 +02:00
SQLSRV_ASSERT ( Z_TYPE ( stmt - > active_stream ) = = IS_UNDEF , " close_active_stream: Active stream not closed. " ) ;
2016-01-30 03:00:20 +01:00
}
// local routines not shared by other files (arranged alphabetically)
namespace {
2018-10-06 00:01:18 +02:00
bool is_streamable_type ( _In_ SQLSMALLINT sql_type )
2016-01-30 03:00:20 +01:00
{
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 ;
}
2018-10-06 00:01:18 +02:00
bool is_a_numeric_type ( _In_ SQLSMALLINT sql_type )
{
switch ( sql_type ) {
case SQL_BIGINT :
case SQL_BIT :
case SQL_INTEGER :
case SQL_SMALLINT :
case SQL_TINYINT :
case SQL_FLOAT :
case SQL_DOUBLE :
case SQL_REAL :
case SQL_DECIMAL :
case SQL_NUMERIC :
return true ;
}
return false ;
}
2017-06-22 23:04:34 +02:00
void calc_string_size ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _In_ SQLLEN sql_type , _Inout_ SQLLEN & size TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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 :
2017-05-15 23:19:36 +02:00
case SQL_SS_VARIANT :
2016-01-30 03:00:20 +01:00
{
2017-02-01 03:10:37 +01:00
// unixODBC 2.3.1 requires wide calls to support pooling
core : : SQLColAttributeW ( stmt , field_index + 1 , SQL_DESC_DISPLAY_SIZE , NULL , 0 , NULL , & size TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
break ;
2017-12-09 01:15:24 +01:00
}
2016-01-30 03:00:20 +01:00
// 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 :
2017-12-09 01:15:24 +01:00
case SQL_WVARCHAR :
2016-01-30 03:00:20 +01:00
{
2017-02-01 03:10:37 +01:00
// unixODBC 2.3.1 requires wide calls to support pooling
core : : SQLColAttributeW ( stmt , field_index + 1 , SQL_DESC_OCTET_LENGTH , NULL , 0 , NULL , & size TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
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
2017-06-22 23:04:34 +02:00
size_t calc_utf8_missing ( _Inout_ sqlsrv_stmt * stmt , _In_reads_ ( buffer_end ) const char * buffer , _In_ size_t buffer_end TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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 ;
}
2017-12-09 01:15:24 +01:00
// determine how many bytes we need to read in based on the number of bytes in the character
2016-01-30 03:00:20 +01:00
// (# 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 :
2017-12-09 01:15:24 +01:00
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE ,
2016-01-30 03:00:20 +01:00
get_last_error_message ( ERROR_NO_UNICODE_TRANSLATION ) ) ;
break ;
}
return need_to_read ;
}
2017-12-09 01:15:24 +01:00
// Caller is responsible for freeing the memory allocated for the field_value.
2016-01-30 03:00:20 +01:00
// The memory allocation has to happen in the core layer because otherwise
2017-12-09 01:15:24 +01:00
// the driver layer would have to calculate size of the field_value
2016-01-30 03:00:20 +01:00
// to decide the amount of memory allocation.
2017-06-22 23:04:34 +02:00
void core_get_field_common ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype
2017-12-09 01:15:24 +01:00
sqlsrv_php_type , _Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len TSRMLS_DC )
2016-06-13 23:44:53 +02:00
{
2017-12-09 01:15:24 +01:00
try {
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
close_active_stream ( stmt TSRMLS_CC ) ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
// 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 ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
// 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 ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
switch ( sqlsrv_php_type . typeinfo . type ) {
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
case SQLSRV_PHPTYPE_INT :
{
sqlsrv_malloc_auto_ptr < long > field_value_temp ;
field_value_temp = static_cast < long * > ( sqlsrv_malloc ( sizeof ( long ) ) ) ;
* field_value_temp = 0 ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
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 ) ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
CHECK_CUSTOM_ERROR ( ( r = = SQL_NO_DATA ) , stmt , SQLSRV_ERROR_NO_DATA , field_index ) {
throw core : : CoreException ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
if ( * field_len = = SQL_NULL_DATA ) {
field_value = NULL ;
break ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
field_value = field_value_temp ;
field_value_temp . transferred ( ) ;
break ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
case SQLSRV_PHPTYPE_FLOAT :
{
sqlsrv_malloc_auto_ptr < double > field_value_temp ;
field_value_temp = static_cast < double * > ( sqlsrv_malloc ( sizeof ( double ) ) ) ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
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 ) ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
CHECK_CUSTOM_ERROR ( ( r = = SQL_NO_DATA ) , stmt , SQLSRV_ERROR_NO_DATA , field_index ) {
throw core : : CoreException ( ) ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
if ( * field_len = = SQL_NULL_DATA ) {
field_value = NULL ;
break ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
field_value = field_value_temp ;
field_value_temp . transferred ( ) ;
break ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
case SQLSRV_PHPTYPE_STRING :
{
get_field_as_string ( stmt , field_index , sqlsrv_php_type , field_value , field_len TSRMLS_CC ) ;
break ;
}
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
// 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 :
{
2018-08-01 02:22:56 +02:00
char field_value_temp [ MAX_DATETIME_STRING_LEN ] = { ' \0 ' } ;
2017-12-09 01:15:24 +01:00
zval params [ 1 ] ;
zval field_value_temp_z ;
zval function_z ;
2016-01-30 03:00:20 +01:00
2017-12-09 01:15:24 +01:00
ZVAL_UNDEF ( & field_value_temp_z ) ;
ZVAL_UNDEF ( & function_z ) ;
ZVAL_UNDEF ( params ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
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 ) ;
2016-07-06 20:47:05 +02:00
2017-12-09 01:15:24 +01:00
CHECK_CUSTOM_ERROR ( ( r = = SQL_NO_DATA ) , stmt , SQLSRV_ERROR_NO_DATA , field_index ) {
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
zval_auto_ptr return_value_z ;
return_value_z = ( zval * ) sqlsrv_malloc ( sizeof ( zval ) ) ;
ZVAL_UNDEF ( return_value_z ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
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 ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
// 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 ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
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 ;
2018-10-06 00:01:18 +02:00
SQLSMALLINT sql_type ;
2016-06-13 23:44:53 +02:00
2018-10-06 00:01:18 +02:00
SQLSRV_ASSERT ( stmt - > current_meta_data . size ( ) > field_index , " core_get_field_common - meta data vector not in sync " ) ;
sql_type = stmt - > current_meta_data [ field_index ] - > field_type ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
CHECK_CUSTOM_ERROR ( ! is_streamable_type ( sql_type ) , stmt , SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
stream = php_stream_open_wrapper ( " sqlsrv://sqlncli10 " , " r " , 0 , NULL ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
CHECK_CUSTOM_ERROR ( ! stream , stmt , SQLSRV_ERROR_STREAM_CREATE ) {
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
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 ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
zval_auto_ptr return_value_z ;
return_value_z = ( zval * ) sqlsrv_malloc ( sizeof ( zval ) ) ;
ZVAL_UNDEF ( return_value_z ) ;
2016-07-06 20:47:05 +02:00
2017-12-09 01:15:24 +01:00
// turn our stream into a zval to be returned
php_stream_to_zval ( stream , return_value_z ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
field_value = reinterpret_cast < void * > ( return_value_z . get ( ) ) ;
return_value_z . transferred ( ) ;
break ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
case SQLSRV_PHPTYPE_NULL :
field_value = NULL ;
* field_len = 0 ;
break ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
default :
DIE ( " core_get_field_common: Unexpected sqlsrv_phptype provided " ) ;
break ;
}
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
// 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 ;
}
2016-01-30 03:00:20 +01:00
}
// 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
2016-07-06 20:47:05 +02:00
bool check_for_next_stream_parameter ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2017-01-20 23:43:49 +01:00
zend_ulong stream_index = 0 ;
2016-01-30 03:00:20 +01:00
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
2017-06-22 23:04:34 +02:00
bool convert_input_param_to_utf16 ( _In_ zval * input_param_z , _Inout_ zval * converted_param_z )
2016-01-30 03:00:20 +01:00
{
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 ) ;
2016-03-15 04:09:46 +01:00
int wchar_size ;
2018-12-04 22:00:34 +01:00
if ( buffer_len > INT_MAX )
{
LOG ( SEV_ERROR , " Convert input parameter to utf16: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
2016-01-30 03:00:20 +01:00
// if the string is empty, then just return that the conversion succeeded as
// MultiByteToWideChar will "fail" on an empty string.
if ( buffer_len = = 0 ) {
2018-12-04 22:00:34 +01:00
core : : sqlsrv_zval_stringl ( converted_param_z , " " , 0 ) ;
2016-01-30 03:00:20 +01:00
return true ;
}
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2018-12-04 22:00:34 +01:00
// Declare wchar_size to be the largest possible number of UTF-16 characters after
// conversion, to avoid the performance penalty of calling ToUtf16
wchar_size = buffer_len ;
2017-01-20 23:43:49 +01:00
# else
2018-12-04 22:00:34 +01:00
// Calculate the size of the necessary buffer from the length of the string -
// no performance penalty because MultiByteToWidechar is highly optimised
2017-12-09 01:15:24 +01:00
wchar_size = MultiByteToWideChar ( CP_UTF8 , MB_ERR_INVALID_CHARS , reinterpret_cast < LPCSTR > ( buffer ) , static_cast < int > ( buffer_len ) , NULL , 0 ) ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2016-03-15 04:09:46 +01:00
2016-01-30 03:00:20 +01:00
// if there was a problem determining the size of the string, return false
if ( wchar_size = = 0 ) {
return false ;
}
2017-01-20 23:43:49 +01:00
sqlsrv_malloc_auto_ptr < SQLWCHAR > wbuffer ;
wbuffer = reinterpret_cast < SQLWCHAR * > ( sqlsrv_malloc ( ( wchar_size + 1 ) * sizeof ( SQLWCHAR ) ) ) ;
2016-01-30 03:00:20 +01:00
// convert the utf-8 string to a wchar string in the new buffer
2017-01-27 02:06:17 +01:00
# ifndef _WIN32
2018-12-04 22:00:34 +01:00
int rc = SystemLocale : : ToUtf16Strict ( CP_UTF8 , reinterpret_cast < LPCSTR > ( buffer ) , static_cast < int > ( buffer_len ) , wbuffer , wchar_size ) ;
2017-01-20 23:43:49 +01:00
# else
2018-12-04 22:00:34 +01:00
int rc = MultiByteToWideChar ( CP_UTF8 , MB_ERR_INVALID_CHARS , reinterpret_cast < LPCSTR > ( buffer ) , static_cast < int > ( buffer_len ) , wbuffer , wchar_size ) ;
2017-01-27 02:06:17 +01:00
# endif // !_WIN32
2016-01-30 03:00:20 +01:00
// if there was a problem converting the string, then free the memory and return false
2018-12-04 22:00:34 +01:00
if ( rc = = 0 ) {
2016-01-30 03:00:20 +01:00
return false ;
}
2018-12-04 22:00:34 +01:00
wchar_size = rc ;
2016-01-30 03:00:20 +01:00
// null terminate the string, set the size within the zval, and return success
2018-12-04 22:00:34 +01:00
wbuffer [ wchar_size ] = L ' \0 ' ;
2017-01-20 23:43:49 +01:00
core : : sqlsrv_zval_stringl ( converted_param_z , reinterpret_cast < char * > ( wbuffer . get ( ) ) , wchar_size * sizeof ( SQLWCHAR ) ) ;
2016-01-30 03:00:20 +01:00
sqlsrv_free ( wbuffer ) ;
wbuffer . transferred ( ) ;
return true ;
}
// returns the ODBC C type constant that matches the PHP type and encoding given
2017-06-22 23:04:34 +02:00
SQLSMALLINT default_c_type ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ SQLULEN paramno , _In_ zval const * param_z , _In_ SQLSRV_ENCODING encoding TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE ;
int php_type = Z_TYPE_P ( param_z ) ;
switch ( php_type ) {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
case IS_NULL :
switch ( encoding ) {
2017-12-09 01:15:24 +01:00
// The c type is set to match to the corresponding sql_type. For NULL cases, if the server type
2016-01-30 03:00:20 +01:00
// is a binary type, than the server expects the sql_type to be binary type as well, otherwise
2017-12-09 01:15:24 +01:00
// an error stating "Implicit conversion not allowed.." is thrown by the server.
2016-01-30 03:00:20 +01:00
// 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 :
2018-05-16 01:13:36 +02:00
sql_c_type = SQL_C_SLONG ;
break ;
2016-01-30 03:00:20 +01:00
case IS_LONG :
2018-05-16 01:13:36 +02:00
//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 {
2018-05-15 23:28:26 +02:00
sql_c_type = SQL_C_SLONG ;
}
2016-01-30 03:00:20 +01:00
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 ) ;
2017-12-09 01:15:24 +01:00
break ;
2016-01-30 03:00:20 +01:00
}
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 ;
}
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
return sql_c_type ;
}
// given a zval and encoding, determine the appropriate sql type
2017-12-09 01:15:24 +01:00
void default_sql_type ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ SQLULEN paramno , _In_ zval * param_z , _In_ SQLSRV_ENCODING encoding ,
2016-07-06 20:47:05 +02:00
_Out_ SQLSMALLINT & sql_type TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
sql_type = SQL_UNKNOWN_TYPE ;
2018-12-04 22:00:34 +01:00
int php_type = Z_TYPE_P ( param_z ) ;
2016-01-30 03:00:20 +01:00
switch ( php_type ) {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
case IS_NULL :
2017-12-09 01:15:24 +01:00
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
2016-01-30 03:00:20 +01:00
// 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 :
2017-01-20 23:43:49 +01:00
sql_type = SQL_INTEGER ;
break ;
2016-01-30 03:00:20 +01:00
case IS_LONG :
2018-05-16 01:13:36 +02:00
//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 ;
2018-05-15 23:28:26 +02:00
}
2016-01-30 03:00:20 +01:00
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)
2017-12-09 01:15:24 +01:00
void default_sql_size_and_scale ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ unsigned int paramno , _In_ zval * param_z , _In_ SQLSRV_ENCODING encoding ,
2016-07-06 20:47:05 +02:00
_Out_ SQLULEN & column_size , _Out_ SQLSMALLINT & decimal_digits TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
int php_type = Z_TYPE_P ( param_z ) ;
column_size = 0 ;
decimal_digits = 0 ;
switch ( php_type ) {
2017-12-09 01:15:24 +01:00
2016-01-30 03:00:20 +01:00
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 :
{
2017-01-20 23:43:49 +01:00
size_t char_size = ( encoding = = SQLSRV_ENCODING_UTF8 ) ? sizeof ( SQLWCHAR ) : sizeof ( char ) ;
2017-01-26 23:59:23 +01:00
SQLULEN byte_len = Z_STRLEN_P ( param_z ) * char_size ;
2016-01-30 03:00:20 +01:00
if ( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) {
column_size = SQL_SERVER_MAX_TYPE_SIZE ;
}
else {
2017-01-26 23:59:23 +01:00
column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size ;
2016-01-30 03:00:20 +01:00
}
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 ;
}
}
2017-06-22 23:04:34 +02:00
void col_cache_dtor ( _Inout_ zval * data_z )
2017-03-24 23:46:53 +01:00
{
2017-03-25 00:01:45 +01:00
col_cache * cache = static_cast < col_cache * > ( Z_PTR_P ( data_z ) ) ;
sqlsrv_free ( cache ) ;
2017-03-24 23:46:53 +01:00
}
2017-06-22 23:04:34 +02:00
void field_cache_dtor ( _Inout_ zval * data_z )
2016-01-30 03:00:20 +01:00
{
2016-07-06 20:47:05 +02:00
field_cache * cache = static_cast < field_cache * > ( Z_PTR_P ( data_z ) ) ;
2017-12-09 01:15:24 +01:00
if ( cache - > value )
2016-01-30 03:00:20 +01:00
{
sqlsrv_free ( cache - > value ) ;
}
2018-12-04 22:00:34 +01:00
sqlsrv_free ( cache ) ;
2016-01-30 03:00:20 +01:00
}
2018-10-13 00:22:27 +02:00
// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string()
2018-11-28 02:18:38 +01:00
void format_decimal_numbers ( _In_ SQLSMALLINT decimals_places , _In_ SQLSMALLINT field_scale , _Inout_updates_bytes_ ( * field_len ) char * & field_value , _Inout_ SQLLEN * field_len )
2018-10-13 00:22:27 +02:00
{
// In SQL Server, the default maximum precision of numeric and decimal data types is 38
//
2018-11-28 02:18:38 +01:00
// Note: decimals_places is NO_CHANGE_DECIMAL_PLACES by default, which means no formatting on decimal data is necessary
// This function assumes stmt->format_decimals is true, so it first checks if it is necessary to add the leading zero.
//
// Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the
// number of decimals adheres to the column field scale. If smaller, the output value may be rounded up.
2018-10-13 00:22:27 +02:00
//
2018-11-28 02:18:38 +01:00
// Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0.
// Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of
// format_decimals and decimals_places
2018-10-13 00:22:27 +02:00
//
std : : string str = field_value ;
size_t pos = str . find_first_of ( ' . ' ) ;
2018-11-28 02:18:38 +01:00
// The decimal dot is not found, simply return
if ( pos = = std : : string : : npos ) {
2018-10-13 00:22:27 +02:00
return ;
}
2018-11-28 02:18:38 +01:00
SQLSMALLINT num_decimals = decimals_places ;
2018-10-13 00:22:27 +02:00
if ( num_decimals > field_scale ) {
num_decimals = field_scale ;
}
// We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php
// as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is
// followed by 5 or above.
bool isNegative = false ;
// If negative, remove the minus sign for now so as not to complicate the rounding process
if ( str [ 0 ] = = ' - ' ) {
isNegative = true ;
std : : ostringstream oss ;
oss < < str . substr ( 1 ) ;
str = oss . str ( ) ;
pos = str . find_first_of ( ' . ' ) ;
}
// Adds the leading zero if not exists
if ( pos = = 0 ) {
std : : ostringstream oss ;
oss < < ' 0 ' < < str ;
str = oss . str ( ) ;
pos + + ;
}
2018-11-28 02:18:38 +01:00
if ( num_decimals = = NO_CHANGE_DECIMAL_PLACES ) {
// Add the minus sign back if negative
if ( isNegative ) {
std : : ostringstream oss ;
oss < < ' - ' < < str . substr ( 0 ) ;
str = oss . str ( ) ;
}
} else {
// Start formatting
size_t last = 0 ;
if ( num_decimals = = 0 ) {
// Chop all decimal digits, including the decimal dot
size_t pos2 = pos + 1 ;
2018-10-13 00:22:27 +02:00
short n = str [ pos2 ] - ' 0 ' ;
if ( n > = 5 ) {
2018-11-28 02:18:38 +01:00
// Start rounding up - starting from the digit left of the dot all the way to the first digit
2018-10-13 00:22:27 +02:00
bool carry_over = true ;
2018-11-28 02:18:38 +01:00
for ( short p = pos - 1 ; p > = 0 & & carry_over ; p - - ) {
2018-10-13 00:22:27 +02:00
n = str [ p ] - ' 0 ' ;
if ( n = = 9 ) {
str [ p ] = ' 0 ' ;
carry_over = true ;
}
else {
n + + ;
carry_over = false ;
str [ p ] = ' 0 ' + n ;
}
}
if ( carry_over ) {
std : : ostringstream oss ;
2018-11-28 02:18:38 +01:00
oss < < ' 1 ' < < str . substr ( 0 , pos ) ;
2018-10-13 00:22:27 +02:00
str = oss . str ( ) ;
2018-11-28 02:18:38 +01:00
pos + + ;
2018-10-13 00:22:27 +02:00
}
}
2018-11-28 02:18:38 +01:00
last = pos ;
}
else {
size_t pos2 = pos + num_decimals + 1 ;
// No need to check if rounding is necessary when pos2 has passed the last digit in the input string
if ( pos2 < str . length ( ) ) {
short n = str [ pos2 ] - ' 0 ' ;
if ( n > = 5 ) {
// Start rounding up - starting from the digit left of pos2 all the way to the first digit
bool carry_over = true ;
for ( short p = pos2 - 1 ; p > = 0 & & carry_over ; p - - ) {
if ( str [ p ] = = ' . ' ) { // Skip the dot
continue ;
}
n = str [ p ] - ' 0 ' ;
if ( n = = 9 ) {
str [ p ] = ' 0 ' ;
carry_over = true ;
}
else {
n + + ;
carry_over = false ;
str [ p ] = ' 0 ' + n ;
}
}
if ( carry_over ) {
std : : ostringstream oss ;
oss < < ' 1 ' < < str . substr ( 0 , pos2 ) ;
str = oss . str ( ) ;
pos2 + + ;
}
}
}
last = pos2 ;
}
// Add the minus sign back if negative
if ( isNegative ) {
std : : ostringstream oss ;
oss < < ' - ' < < str . substr ( 0 , last ) ;
str = oss . str ( ) ;
} else {
str = str . substr ( 0 , last ) ;
2018-10-13 00:22:27 +02:00
}
}
size_t len = str . length ( ) ;
str . copy ( field_value , len ) ;
field_value [ len ] = ' \0 ' ;
* field_len = len ;
}
2016-01-30 03:00:20 +01:00
// 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
2017-06-22 23:04:34 +02:00
void finalize_output_parameters ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2017-12-09 01:15:24 +01:00
if ( Z_ISUNDEF ( stmt - > output_params ) )
2016-01-30 03:00:20 +01:00
return ;
HashTable * params_ht = Z_ARRVAL ( stmt - > output_params ) ;
2018-12-04 22:00:34 +01:00
zend_ulong index = - 1 ;
zend_string * key = NULL ;
void * output_param_temp = NULL ;
2016-06-13 23:44:53 +02:00
2018-12-04 22:00:34 +01:00
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 ) ;
2016-03-15 04:09:46 +01:00
switch ( Z_TYPE_P ( value_z ) ) {
2016-01-30 03:00:20 +01:00
case IS_STRING :
{
// adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter
2016-03-15 04:09:46 +01:00
char * str = Z_STRVAL_P ( value_z ) ;
2018-08-01 02:22:56 +02:00
SQLLEN str_len = stmt - > param_ind_ptrs [ output_param - > param_num ] ;
2017-02-28 19:08:46 +01:00
if ( str_len = = 0 ) {
2017-03-01 00:27:13 +01:00
core : : sqlsrv_zval_stringl ( value_z , " " , 0 ) ;
2017-02-28 19:08:46 +01:00
continue ;
}
2017-02-28 19:04:59 +01:00
if ( str_len = = SQL_NULL_DATA ) {
2016-12-15 23:06:47 +01:00
zend_string_release ( Z_STR_P ( value_z ) ) ;
2016-03-15 04:09:46 +01:00
ZVAL_NULL ( value_z ) ;
2016-01-30 03:00:20 +01:00
continue ;
}
2017-10-18 21:17:52 +02:00
// 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 ( SQLWCHAR ) ; // 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 ( ) ;
}
// For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx
// A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to
2017-12-09 01:15:24 +01:00
// output_param->original_buffer_len data and is NULL terminated.
2017-10-18 21:17:52 +02:00
// The IF statement can be true when using connection pooling with unixODBC 2.3.4.
if ( str_len = = SQL_NO_TOTAL )
{
str_len = output_param - > original_buffer_len - null_size ;
}
2018-11-02 22:34:27 +01:00
if ( output_param - > encoding = = SQLSRV_ENCODING_BINARY ) {
2017-10-18 21:17:52 +02:00
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
2017-12-09 01:15:24 +01:00
// so we do that here if the length of the returned data is less than the original allocation. The
2017-10-18 21:17:52 +02:00
// original allocation null terminates the buffer already.
2018-11-02 22:34:27 +01:00
if ( str_len < output_param - > original_buffer_len ) {
str [ str_len ] = ' \0 ' ;
}
2017-10-18 21:17:52 +02:00
core : : sqlsrv_zval_stringl ( value_z , str , str_len ) ;
}
else {
2018-11-28 02:18:38 +01:00
param_meta_data metaData = output_param - > getMetaData ( ) ;
2018-11-02 22:34:27 +01:00
if ( output_param - > encoding ! = SQLSRV_ENCODING_CHAR ) {
char * outString = NULL ;
SQLLEN outLen = 0 ;
bool result = convert_string_from_utf16 ( output_param - > encoding , reinterpret_cast < const SQLWCHAR * > ( str ) , int ( str_len / sizeof ( SQLWCHAR ) ) , & outString , outLen ) ;
CHECK_CUSTOM_ERROR ( ! result , stmt , SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
2018-11-28 02:18:38 +01:00
if ( stmt - > format_decimals & & ( metaData . sql_type = = SQL_DECIMAL | | metaData . sql_type = = SQL_NUMERIC ) ) {
format_decimal_numbers ( NO_CHANGE_DECIMAL_PLACES , metaData . decimal_digits , outString , & outLen ) ;
2018-11-02 22:34:27 +01:00
}
2018-11-28 02:18:38 +01:00
2018-11-02 22:34:27 +01:00
core : : sqlsrv_zval_stringl ( value_z , outString , outLen ) ;
sqlsrv_free ( outString ) ;
2018-10-13 00:22:27 +02:00
}
2018-11-02 22:34:27 +01:00
else {
2018-11-28 02:18:38 +01:00
if ( stmt - > format_decimals & & ( metaData . sql_type = = SQL_DECIMAL | | metaData . sql_type = = SQL_NUMERIC ) ) {
format_decimal_numbers ( NO_CHANGE_DECIMAL_PLACES , metaData . decimal_digits , str , & str_len ) ;
2018-11-02 22:34:27 +01:00
}
2018-10-13 00:22:27 +02:00
2018-11-02 22:34:27 +01:00
core : : sqlsrv_zval_stringl ( value_z , str , str_len ) ;
}
2017-10-18 21:17:52 +02:00
}
2016-01-30 03:00:20 +01:00
}
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
2018-08-01 02:22:56 +02:00
if ( stmt - > param_ind_ptrs [ output_param - > param_num ] = = SQL_NULL_DATA ) {
2016-03-15 04:09:46 +01:00
ZVAL_NULL ( value_z ) ;
2016-01-30 03:00:20 +01:00
}
else if ( output_param - > is_bool ) {
2016-03-15 04:09:46 +01:00
convert_to_boolean ( value_z ) ;
2016-01-30 03:00:20 +01:00
}
2017-01-26 23:59:23 +01:00
else {
ZVAL_LONG ( value_z , static_cast < int > ( Z_LVAL_P ( value_z ) ) ) ;
}
2016-01-30 03:00:20 +01:00
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
2018-04-26 00:43:56 +02:00
if ( stmt - > param_ind_ptrs [ output_param - > param_num ] = = SQL_NULL_DATA ) {
ZVAL_NULL ( value_z ) ;
}
else if ( output_param - > php_out_type = = SQLSRV_PHPTYPE_INT ) {
2018-04-26 17:57:22 +02:00
// first check if its value is out of range
double dval = Z_DVAL_P ( value_z ) ;
if ( dval > INT_MAX | | dval < INT_MIN ) {
2018-04-26 00:43:56 +02:00
CHECK_CUSTOM_ERROR ( true , stmt , SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED ) {
throw core : : CoreException ( ) ;
}
}
2018-04-26 17:57:22 +02:00
// if the output param is a boolean, still convert to
// a long integer first to take care of rounding
2018-04-26 00:43:56 +02:00
convert_to_long ( value_z ) ;
if ( output_param - > is_bool ) {
convert_to_boolean ( value_z ) ;
}
2016-01-30 03:00:20 +01:00
}
break ;
default :
DIE ( " Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter. " ) ;
break ;
}
2018-12-04 22:00:34 +01:00
value_z = NULL ;
2016-06-13 23:44:53 +02:00
} ZEND_HASH_FOREACH_END ( ) ;
2016-01-30 03:00:20 +01:00
// empty the hash table since it's been processed
zend_hash_clean ( Z_ARRVAL ( stmt - > output_params ) ) ;
return ;
}
2017-06-22 23:04:34 +02:00
void get_field_as_string ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype sqlsrv_php_type ,
_Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2017-08-14 20:44:54 +02:00
SQLRETURN r ;
SQLSMALLINT c_type ;
2018-10-13 00:22:27 +02:00
SQLSMALLINT sql_field_type = 0 ;
2017-08-14 20:44:54 +02:00
SQLSMALLINT extra = 0 ;
SQLLEN field_len_temp = 0 ;
SQLLEN sql_display_size = 0 ;
char * field_value_temp = NULL ;
2017-12-09 01:15:24 +01:00
unsigned int intial_field_len = INITIAL_FIELD_STRING_LEN ;
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
try {
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
DEBUG_SQLSRV_ASSERT ( sqlsrv_php_type . typeinfo . type = = SQLSRV_PHPTYPE_STRING ,
" Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string " ) ;
2016-01-30 03:00:20 +01:00
2018-10-06 00:01:18 +02:00
col_cache * cached = NULL ;
if ( NULL ! = ( cached = static_cast < col_cache * > ( zend_hash_index_find_ptr ( Z_ARRVAL ( stmt - > col_cache ) , static_cast < zend_ulong > ( field_index ) ) ) ) ) {
sql_field_type = cached - > sql_type ;
sql_display_size = cached - > display_size ;
}
else {
SQLSRV_ASSERT ( stmt - > current_meta_data . size ( ) > field_index , " get_field_as_string - meta data vector not in sync " ) ;
sql_field_type = stmt - > current_meta_data [ field_index ] - > field_type ;
// Calculate the field size.
calc_string_size ( stmt , field_index , sql_field_type , sql_display_size TSRMLS_CC ) ;
col_cache cache ( sql_field_type , sql_display_size ) ;
core : : sqlsrv_zend_hash_index_update_mem ( * stmt , Z_ARRVAL ( stmt - > col_cache ) , field_index , & cache , sizeof ( col_cache ) TSRMLS_CC ) ;
}
// Determine the correct encoding
2017-08-14 20:44:54 +02:00
if ( sqlsrv_php_type . typeinfo . encoding = = SQLSRV_ENCODING_DEFAULT ) {
sqlsrv_php_type . typeinfo . encoding = stmt - > conn - > encoding ( ) ;
}
2018-10-06 00:01:18 +02:00
// For numbers, no need to convert
if ( is_a_numeric_type ( sql_field_type ) ) {
sqlsrv_php_type . typeinfo . encoding = SQLSRV_ENCODING_CHAR ;
}
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
// 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 ;
}
2017-03-24 23:46:53 +01:00
2017-08-14 20:44:54 +02:00
// 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 ) {
2017-12-09 01:15:24 +01:00
2017-11-15 20:00:56 +01:00
field_len_temp = intial_field_len ;
2016-06-13 23:44:53 +02:00
2017-02-01 03:10:37 +01:00
SQLLEN initiallen = field_len_temp + extra ;
2017-08-14 20:44:54 +02:00
field_value_temp = static_cast < char * > ( sqlsrv_malloc ( field_len_temp + extra + 1 ) ) ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
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 ) ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
CHECK_CUSTOM_ERROR ( ( r = = SQL_NO_DATA ) , stmt , SQLSRV_ERROR_NO_DATA , field_index ) {
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
if ( field_len_temp = = SQL_NULL_DATA ) {
field_value = NULL ;
sqlsrv_free ( field_value_temp ) ;
return ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
if ( r = = SQL_SUCCESS_WITH_INFO ) {
2016-06-13 23:44:53 +02:00
2018-07-31 23:58:21 +02:00
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] = { L ' \0 ' } ;
2017-08-14 20:44:54 +02:00
SQLSMALLINT len = 0 ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
stmt - > current_results - > get_diag_field ( 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len TSRMLS_CC ) ;
2016-06-13 23:44:53 +02:00
2017-12-09 01:15:24 +01:00
// with Linux connection pooling may not get a truncated warning back but the actual field_len_temp
2017-02-01 03:10:37 +01:00
// can be greater than the initallen value.
2017-02-02 02:36:41 +01:00
# ifndef _WIN32
2017-02-01 03:10:37 +01:00
if ( is_truncated_warning ( state ) | | initiallen < field_len_temp ) {
# else
if ( is_truncated_warning ( state ) ) {
2017-12-09 01:15:24 +01:00
# endif // !_WIN32
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
SQLLEN dummy_field_len = 0 ;
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
// 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 ) {
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
// reset the field_len_temp
2017-11-15 20:00:56 +01:00
field_len_temp = intial_field_len ;
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
do {
SQLLEN initial_field_len = field_len_temp ;
2017-12-09 01:15:24 +01:00
// Double the size.
2017-08-14 20:44:54 +02:00
field_len_temp * = 2 ;
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
field_value_temp = static_cast < char * > ( sqlsrv_realloc ( field_value_temp , field_len_temp + extra + 1 ) ) ;
2016-01-30 03:00:20 +01:00
2018-01-15 20:57:52 +01:00
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 ,
2018-01-17 01:50:17 +01:00
field_len_temp + extra , & dummy_field_len , false /*handle_warning*/ TSRMLS_CC ) ;
2018-01-15 20:57:52 +01:00
// 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 ;
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
if ( r = = SQL_SUCCESS_WITH_INFO ) {
core : : SQLGetDiagField ( stmt , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len
TSRMLS_CC ) ;
}
2016-01-30 03:00:20 +01:00
2017-08-14 20:44:54 +02:00
} while ( r = = SQL_SUCCESS_WITH_INFO & & is_truncated_warning ( state ) ) ;
}
else {
2017-08-15 02:20:11 +02:00
// the real field length is returned here, thus no need to double the allocation size here, just have to
// allocate field_len_temp (which is the field length retrieved from the first SQLGetData
2017-08-14 20:44:54 +02:00
field_value_temp = static_cast < char * > ( sqlsrv_realloc ( field_value_temp , field_len_temp + extra + 1 ) ) ;
2016-01-30 03:00:20 +01:00
2018-01-17 19:12:31 +01:00
// We have already received intial_field_len size data.
2018-01-15 20:57:52 +01:00
field_len_temp - = intial_field_len ;
2017-08-08 21:03:51 +02:00
2018-01-15 20:57:52 +01:00
// Get the rest of the data.
r = stmt - > current_results - > get_data ( field_index + 1 , c_type , field_value_temp + intial_field_len ,
2018-01-17 01:50:17 +01:00
field_len_temp + extra , & dummy_field_len , true /*handle_warning*/ TSRMLS_CC ) ;
2018-01-15 20:57:52 +01:00
field_len_temp + = intial_field_len ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
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 ( ) ;
}
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
} // if( is_truncation_warning ( state ) )
else {
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
}
} // if( r == SQL_SUCCESS_WITH_INFO )
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
if ( sqlsrv_php_type . typeinfo . encoding = = SQLSRV_ENCODING_UTF8 ) {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
bool converted = convert_string_from_utf16_inplace ( static_cast < SQLSRV_ENCODING > ( sqlsrv_php_type . typeinfo . encoding ) ,
& field_value_temp , field_len_temp ) ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
}
2017-12-09 01:15:24 +01:00
} // if ( sql_display_size == 0 || sql_display_size == LONG_MAX .. )
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
else if ( sql_display_size > = 1 & & sql_display_size < = SQL_SERVER_MAX_FIELD_SIZE ) {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
// only allow binary retrievals for char and binary types. All others get a string converted
// to the encoding type they asked for.
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
// null terminator
if ( c_type = = SQL_C_CHAR ) {
sql_display_size + = sizeof ( SQLCHAR ) ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
// 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 ) ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
field_value_temp = static_cast < char * > ( sqlsrv_malloc ( sql_display_size + extra + 1 ) ) ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
// 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 ( ) ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
if ( field_len_temp = = SQL_NULL_DATA ) {
field_value = NULL ;
sqlsrv_free ( field_value_temp ) ;
return ;
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
if ( sqlsrv_php_type . typeinfo . encoding = = CP_UTF8 ) {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
bool converted = convert_string_from_utf16_inplace ( static_cast < SQLSRV_ENCODING > ( sqlsrv_php_type . typeinfo . encoding ) ,
& field_value_temp , field_len_temp ) ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
}
2018-10-13 00:22:27 +02:00
2018-11-28 02:18:38 +01:00
if ( stmt - > format_decimals & & ( sql_field_type = = SQL_DECIMAL | | sql_field_type = = SQL_NUMERIC ) ) {
// number of decimal places only affect money / smallmoney fields
SQLSMALLINT decimal_places = ( stmt - > current_meta_data [ field_index ] - > field_is_money_type ) ? stmt - > decimal_places : NO_CHANGE_DECIMAL_PLACES ;
format_decimal_numbers ( decimal_places , stmt - > current_meta_data [ field_index ] - > field_scale , field_value_temp , & field_len_temp ) ;
2018-10-13 00:22:27 +02:00
}
2017-12-09 01:15:24 +01:00
} // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE )
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
else {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
DIE ( " Invalid sql_display_size " ) ;
return ; // to eliminate a warning
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
field_value = field_value_temp ;
* field_len = field_len_temp ;
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
// 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
2017-02-01 03:10:37 +01:00
// with unixODBC connection pooling sometimes field_len_temp can be SQL_NO_DATA.
// In that cause do not set null terminator and set length to 0.
if ( field_len_temp > 0 )
2017-12-09 01:15:24 +01:00
{
2017-02-01 03:10:37 +01:00
field_value_temp [ field_len_temp ] = ' \0 ' ;
}
else
{
2017-12-09 01:15:24 +01:00
* field_len = 0 ;
2017-02-01 03:10:37 +01:00
}
2017-08-14 20:44:54 +02:00
}
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
catch ( core : : CoreException & ) {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
field_value = NULL ;
* field_len = 0 ;
sqlsrv_free ( field_value_temp ) ;
throw ;
}
catch ( . . . ) {
2016-06-13 23:44:53 +02:00
2017-08-14 20:44:54 +02:00
field_value = NULL ;
* field_len = 0 ;
sqlsrv_free ( field_value_temp ) ;
throw ;
}
2016-01-30 03:00:20 +01:00
}
// return the option from the stmt_opts array that matches the key. If no option found,
// NULL is returned.
2017-06-22 23:04:34 +02:00
stmt_option const * get_stmt_option ( sqlsrv_conn const * conn , _In_ zend_ulong key , _In_ const stmt_option stmt_opts [ ] TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
2018-08-01 02:22:56 +02:00
for ( int i = 0 ; stmt_opts [ i ] . key ! = SQLSRV_STMT_OPTION_INVALID ; + + i ) {
2016-01-30 03:00:20 +01:00
// if we find the key we're looking for, return it
2018-08-01 02:22:56 +02:00
if ( key = = stmt_opts [ i ] . key ) {
return & stmt_opts [ i ] ;
2016-01-30 03:00:20 +01:00
}
}
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
2017-06-22 23:04:34 +02:00
bool is_fixed_size_type ( _In_ SQLINTEGER sql_type )
2016-01-30 03:00:20 +01:00
{
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 ;
}
2017-06-22 23:04:34 +02:00
bool is_valid_sqlsrv_phptype ( _In_ sqlsrv_phptype type )
2016-01-30 03:00:20 +01:00
{
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 :
{
2017-12-09 01:15:24 +01:00
if ( type . typeinfo . encoding = = SQLSRV_ENCODING_BINARY | | type . typeinfo . encoding = = SQLSRV_ENCODING_CHAR
2016-01-30 03:00:20 +01:00
| | 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
2017-12-09 01:15:24 +01:00
void resize_output_buffer_if_necessary ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z , _In_ SQLULEN paramno , SQLSRV_ENCODING encoding ,
_In_ SQLSMALLINT c_type , _In_ SQLSMALLINT sql_type , _In_ SQLULEN column_size , _In_ SQLSMALLINT decimal_digits ,
2017-09-06 01:51:40 +02:00
_Out_writes_ ( buffer_len ) SQLPOINTER & buffer , _Out_ SQLLEN & buffer_len TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
SQLSRV_ASSERT ( column_size ! = SQLSRV_UNKNOWN_SIZE , " column size should be set to a known value. " ) ;
buffer_len = Z_STRLEN_P ( param_z ) ;
2018-12-17 21:25:37 +01:00
SQLLEN original_len = buffer_len ;
2016-01-30 03:00:20 +01:00
SQLLEN expected_len ;
SQLLEN buffer_null_extra ;
SQLLEN elem_size ;
// 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.
2017-02-21 00:24:41 +01:00
elem_size = ( c_type = = SQL_C_WCHAR | | ( c_type = = SQL_C_BINARY & & ( sql_type = = SQL_WCHAR | | sql_type = = SQL_WVARCHAR | | sql_type = = SQL_WLONGVARCHAR ) ) ) ? 2 : 1 ;
2016-01-30 03:00:20 +01:00
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
2017-02-21 00:24:41 +01:00
SQLULEN field_size = column_size ;
2018-05-17 22:06:22 +02:00
// with AE on, when column_size is retrieved from SQLDescribeParam, column_size
// does not include the negative sign or decimal place for numeric values
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
if ( sql_type = = SQL_DECIMAL | | sql_type = = SQL_NUMERIC | | sql_type = = SQL_BIGINT | | sql_type = = SQL_INTEGER | | sql_type = = SQL_SMALLINT ) {
2018-04-03 00:41:54 +02:00
// include the possible negative sign
2017-09-06 01:51:40 +02:00
field_size + = elem_size ;
2018-04-03 00:41:54 +02:00
// include the decimal for output params by adding elem_size
if ( decimal_digits > 0 ) {
field_size + = elem_size ;
}
2017-09-06 01:51:40 +02:00
}
2017-02-21 00:24:41 +01:00
if ( column_size = = SQL_SS_LENGTH_UNLIMITED ) {
field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size ;
}
expected_len = field_size * elem_size + elem_size ;
2016-01-30 03:00:20 +01:00
// 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 ;
// 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 ) {
2017-12-09 01:15:24 +01:00
SQLSRV_ASSERT ( expected_len > = expected_len - buffer_null_extra ,
2016-01-30 03:00:20 +01:00
" Integer overflow/underflow caused a corrupt field length. " ) ;
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
2017-12-09 01:15:24 +01:00
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
2016-01-30 03:00:20 +01:00
// not having a NULL terminator on a string.
2018-12-04 22:00:34 +01:00
zend_string * param_z_string = zend_string_realloc ( Z_STR_P ( param_z ) , expected_len , 0 ) ;
2016-01-30 03:00:20 +01:00
// 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.
2017-12-09 01:15:24 +01:00
2018-12-17 21:25:37 +01:00
// initialize the newly allocated space
char * p = ZSTR_VAL ( param_z_string ) ;
p = p + original_len ;
memset ( p , ' \0 ' , expected_len - original_len ) ;
2018-12-04 22:00:34 +01:00
ZVAL_NEW_STR ( param_z , param_z_string ) ;
2016-04-12 23:43:46 +02:00
2018-12-04 22:00:34 +01:00
// 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 ;
2016-01-30 03:00:20 +01:00
2018-12-04 22:00:34 +01:00
// Zend string length doesn't include the null terminator
ZSTR_LEN ( Z_STR_P ( param_z ) ) - = elem_size ;
2017-12-09 01:15:24 +01:00
}
2016-04-12 23:43:46 +02:00
2018-12-04 22:00:34 +01:00
buffer = Z_STRVAL_P ( param_z ) ;
2016-01-30 03:00:20 +01:00
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
2017-12-09 01:15:24 +01:00
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
2016-01-30 03:00:20 +01:00
// than the error 22001 is returned by ODBC.
2018-08-01 02:22:56 +02:00
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 ) ;
2016-01-30 03:00:20 +01:00
}
}
2017-11-16 01:36:10 +01:00
void adjustInputPrecision ( _Inout_ zval * param_z , _In_ SQLSMALLINT decimal_digits ) {
2018-01-17 19:12:31 +01:00
// 38 is the maximum length of a stringified decimal number
2017-11-17 19:21:03 +01:00
size_t maxDecimalPrecision = 38 ;
// 6 is derived from: 1 for '.'; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation);
// 1 for sign of scientific exponent; 2 for length of scientific exponent
2017-11-17 22:27:45 +01:00
// if the length is greater than maxDecimalStrLen, do not change the string
2017-11-17 19:21:03 +01:00
size_t maxDecimalStrLen = maxDecimalPrecision + 6 ;
2017-11-17 22:27:45 +01:00
if ( Z_STRLEN_P ( param_z ) > maxDecimalStrLen ) {
return ;
}
2017-11-16 01:36:10 +01:00
std : : vector < size_t > digits ;
2017-12-13 22:33:32 +01:00
unsigned char * ptr = reinterpret_cast < unsigned char * > ( ZSTR_VAL ( Z_STR_P ( param_z ) ) ) ;
2017-11-16 01:36:10 +01:00
bool isNeg = false ;
2017-11-21 22:17:39 +01:00
bool isScientificNot = false ;
2017-11-16 01:36:10 +01:00
char scientificChar = ' ' ;
2017-11-17 19:21:03 +01:00
short scientificExp = 0 ;
2017-12-13 22:33:32 +01:00
if ( strchr ( reinterpret_cast < char * > ( ptr ) , ' e ' ) | | strchr ( reinterpret_cast < char * > ( ptr ) , ' E ' ) ) {
2017-11-21 22:17:39 +01:00
isScientificNot = true ;
}
2017-11-16 01:36:10 +01:00
// parse digits in param_z into the vector digits
if ( * ptr = = ' + ' | | * ptr = = ' - ' ) {
2017-11-17 19:21:03 +01:00
if ( * ptr = = ' - ' ) {
2017-11-16 01:36:10 +01:00
isNeg = true ;
}
ptr + + ;
}
2017-11-17 19:21:03 +01:00
short numInt = 0 ;
short numDec = 0 ;
2017-11-16 01:36:10 +01:00
while ( isdigit ( * ptr ) ) {
digits . push_back ( * ptr - ' 0 ' ) ;
ptr + + ;
numInt + + ;
}
if ( * ptr = = ' . ' ) {
ptr + + ;
2017-11-21 22:17:39 +01:00
if ( ! isScientificNot ) {
while ( isdigit ( * ptr ) & & numDec < decimal_digits + 1 ) {
digits . push_back ( * ptr - ' 0 ' ) ;
ptr + + ;
numDec + + ;
}
// make sure the rest of the number are digits
while ( isdigit ( * ptr ) ) {
ptr + + ;
}
}
else {
while ( isdigit ( * ptr ) ) {
digits . push_back ( * ptr - ' 0 ' ) ;
ptr + + ;
numDec + + ;
}
2017-11-16 01:36:10 +01:00
}
}
2017-11-21 22:17:39 +01:00
if ( isScientificNot ) {
if ( * ptr = = ' e ' | | * ptr = = ' E ' ) {
scientificChar = * ptr ;
}
2017-11-16 01:36:10 +01:00
ptr + + ;
bool isNegExp = false ;
if ( * ptr = = ' + ' | | * ptr = = ' - ' ) {
if ( * ptr = = ' - ' ) {
isNegExp = true ;
}
ptr + + ;
}
while ( isdigit ( * ptr ) ) {
2017-11-17 19:21:03 +01:00
scientificExp = scientificExp * 10 + ( * ptr - ' 0 ' ) ;
2017-11-16 01:36:10 +01:00
ptr + + ;
}
2017-11-17 19:21:03 +01:00
SQLSRV_ASSERT ( scientificExp < = maxDecimalPrecision , " Input decimal overflow: sql decimal type only supports up to a precision of 38. " ) ;
2017-11-16 01:36:10 +01:00
if ( isNegExp ) {
scientificExp = scientificExp * - 1 ;
}
}
2017-11-17 21:30:14 +01:00
// if ptr is not pointing to a null terminator at this point, that means the decimal string input is invalid
2017-11-17 22:27:45 +01:00
// do not change the string and let SQL Server handle the invalid decimal string
2017-11-21 22:17:39 +01:00
if ( * ptr ! = ' \0 ' ) {
return ;
}
2017-11-16 01:36:10 +01:00
// if number of decimal is less than the exponent, that means the number is a whole number, so no need to adjust the precision
2017-11-21 22:17:39 +01:00
if ( numDec > scientificExp ) {
2017-11-17 21:30:14 +01:00
int decToRemove = numDec - scientificExp - decimal_digits ;
2017-11-16 01:36:10 +01:00
if ( decToRemove > 0 ) {
bool carryOver = false ;
2017-11-17 19:21:03 +01:00
short backInd = 0 ;
2017-11-16 01:36:10 +01:00
// pop digits from the vector until there is only 1 more decimal place than required decimal_digits
while ( decToRemove ! = 1 & & ! digits . empty ( ) ) {
digits . pop_back ( ) ;
decToRemove - - ;
}
if ( ! digits . empty ( ) ) {
2017-11-17 19:21:03 +01:00
// check if the last digit to be popped is greater than 5, if so, the digit before it needs to round up
2017-11-16 01:36:10 +01:00
carryOver = digits . back ( ) > = 5 ;
digits . pop_back ( ) ;
2017-12-13 22:33:32 +01:00
backInd = static_cast < short > ( digits . size ( ) - 1 ) ;
2017-11-16 01:36:10 +01:00
// round up from the end until no more carry over
while ( carryOver & & backInd > = 0 ) {
if ( digits . at ( backInd ) ! = 9 ) {
digits . at ( backInd ) + + ;
carryOver = false ;
}
else {
digits . at ( backInd ) = 0 ;
}
backInd - - ;
}
}
std : : ostringstream oss ;
if ( isNeg ) {
oss < < ' - ' ;
}
2017-12-09 01:15:24 +01:00
// insert 1 if carry over persist all the way to the beginning of the number
2017-11-16 01:36:10 +01:00
if ( carryOver & & backInd = = - 1 ) {
oss < < 1 ;
}
if ( digits . empty ( ) & & ! carryOver ) {
oss < < 0 ;
}
else {
2017-11-17 19:21:03 +01:00
short i = 0 ;
2017-11-16 01:36:10 +01:00
for ( i ; i < numInt & & i < digits . size ( ) ; i + + ) {
oss < < digits [ i ] ;
}
// fill string with 0 if the number of digits in digits is less then numInt
if ( i < numInt ) {
for ( i ; i < numInt ; i + + ) {
oss < < 0 ;
}
}
if ( numInt < digits . size ( ) ) {
oss < < ' . ' ;
for ( i ; i < digits . size ( ) ; i + + ) {
oss < < digits [ i ] ;
}
}
if ( scientificExp ! = 0 ) {
oss < < scientificChar < < std : : to_string ( scientificExp ) ;
}
}
std : : string str = oss . str ( ) ;
zend_string * zstr = zend_string_init ( str . c_str ( ) , str . length ( ) , 0 ) ;
zend_string_release ( Z_STR_P ( param_z ) ) ;
ZVAL_NEW_STR ( param_z , zstr ) ;
}
}
}
2016-01-30 03:00:20 +01:00
// 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)
2017-06-22 23:04:34 +02:00
void save_output_param_for_later ( _Inout_ sqlsrv_stmt * stmt , _Inout_ sqlsrv_output_param & param TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
HashTable * param_ht = Z_ARRVAL ( stmt - > output_params ) ;
2016-05-04 05:05:41 +02:00
zend_ulong paramno = static_cast < zend_ulong > ( param . param_num ) ;
core : : sqlsrv_zend_hash_index_update_mem ( * stmt , param_ht , paramno , & param , sizeof ( sqlsrv_output_param ) ) ;
Z_TRY_ADDREF_P ( param . param_z ) ; // we have a reference to the param
2016-01-30 03:00:20 +01:00
}
// send all the stream data
2017-06-22 23:04:34 +02:00
void send_param_streams ( _Inout_ sqlsrv_stmt * stmt TSRMLS_DC )
2016-01-30 03:00:20 +01:00
{
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
2017-06-22 23:04:34 +02:00
void sqlsrv_output_param_dtor ( _Inout_ zval * data )
2016-01-30 03:00:20 +01:00
{
2016-05-04 05:05:41 +02:00
sqlsrv_output_param * output_param = static_cast < sqlsrv_output_param * > ( Z_PTR_P ( data ) ) ;
2016-01-30 03:00:20 +01:00
zval_ptr_dtor ( output_param - > param_z ) ; // undo the reference to the string we will no longer hold
2018-12-04 22:00:34 +01:00
sqlsrv_free ( output_param ) ;
2016-01-30 03:00:20 +01:00
}
// called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed
2017-06-22 23:04:34 +02:00
void sqlsrv_stream_dtor ( _Inout_ zval * data )
2016-01-30 03:00:20 +01:00
{
2016-05-04 05:05:41 +02:00
sqlsrv_stream * stream_encoding = static_cast < sqlsrv_stream * > ( Z_PTR_P ( data ) ) ;
2016-01-30 03:00:20 +01:00
zval_ptr_dtor ( stream_encoding - > stream_z ) ; // undo the reference to the stream we will no longer hold
2018-12-04 22:00:34 +01:00
sqlsrv_free ( stream_encoding ) ;
2016-01-30 03:00:20 +01:00
}
}