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
//
2021-09-08 02:38:17 +02:00
// Microsoft Drivers 5.10 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
2020-06-19 23:45:13 +02:00
const char DECIMAL_POINT = ' . ' ;
const int SQL_SERVER_DECIMAL_MAXIMUM_PRECISION = 38 ; // 38 is the maximum length of a stringified decimal number
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 ***
2021-05-11 01:33:14 +02:00
// Only declarations are put here. Functions contain more explanations they need in their definitions
2020-04-21 00:17:21 +02:00
void calc_string_size ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _In_ SQLLEN sql_type , _Inout_ SQLLEN & size ) ;
size_t calc_utf8_missing ( _Inout_ sqlsrv_stmt * stmt , _In_reads_ ( buffer_end ) const char * buffer , _In_ size_t buffer_end ) ;
2017-06-22 23:04:34 +02:00
void core_get_field_common ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype
2020-04-21 00:17:21 +02:00
sqlsrv_php_type , _Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len ) ;
2017-06-22 23:04:34 +02:00
void col_cache_dtor ( _Inout_ zval * data_z ) ;
void field_cache_dtor ( _Inout_ zval * data_z ) ;
2020-10-17 00:17:58 +02:00
int round_up_decimal_numbers ( _Inout_ char * buffer , _In_ int decimal_pos , _In_ int decimals_places , _In_ int offset , _In_ int lastpos ) ;
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 get_field_as_string ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _Inout_ sqlsrv_phptype sqlsrv_php_type ,
2020-04-21 00:17:21 +02:00
_Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len ) ;
stmt_option const * get_stmt_option ( sqlsrv_conn const * conn , _In_ zend_ulong key , _In_ const stmt_option stmt_opts [ ] ) ;
2017-06-22 23:04:34 +02:00
bool is_valid_sqlsrv_phptype ( _In_ sqlsrv_phptype type ) ;
2020-06-19 23:45:13 +02:00
void adjustDecimalPrecision ( _Inout_ zval * param_z , _In_ SQLSMALLINT decimal_digits ) ;
bool is_a_numeric_type ( _In_ SQLSMALLINT sql_type ) ;
2021-05-26 00:36:01 +02:00
bool is_a_string_type ( _In_ SQLSMALLINT sql_type ) ;
2016-01-30 03:00:20 +01:00
}
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
2020-04-21 00:17:21 +02:00
sqlsrv_stmt : : sqlsrv_stmt ( _In_ sqlsrv_conn * c , _In_ SQLHANDLE handle , _In_ error_callback e , _In_opt_ void * drv ) :
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 ) ,
2021-05-11 01:33:14 +02:00
send_streams_at_exec ( true )
2016-01-30 03:00:20 +01:00
{
2018-12-04 22:00:34 +01:00
ZVAL_UNDEF ( & active_stream ) ;
2017-12-09 01:15:24 +01:00
2017-03-25 00:01:45 +01:00
// initialize the col cache
2021-06-09 22:00:04 +02:00
array_init ( & col_cache ) ;
2020-04-21 00:17:21 +02:00
core : : sqlsrv_zend_hash_init ( * conn , Z_ARRVAL ( col_cache ) , 5 /* # of buckets */ , col_cache_dtor , 0 /*persistent*/ ) ;
2017-03-24 23:46:53 +01:00
2016-01-30 03:00:20 +01:00
// initialize the field cache
2021-06-09 22:00:04 +02:00
array_init ( & field_cache ) ;
2020-04-21 00:17:21 +02:00
core : : sqlsrv_zend_hash_init ( * conn , Z_ARRVAL ( field_cache ) , 5 /* # of buckets */ , field_cache_dtor , 0 /*persistent*/ ) ;
2016-01-30 03:00:20 +01:00
}
// desctructor for sqlsrv statement.
sqlsrv_stmt : : ~ sqlsrv_stmt ( void )
{
2016-06-13 23:44:53 +02:00
if ( Z_TYPE ( active_stream ) ! = IS_UNDEF ) {
2020-04-21 00:17:21 +02:00
close_active_stream ( this ) ;
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 ( ) ;
2021-02-05 05:03:46 +01:00
// clean up metadata
clean_up_results_metadata ( ) ;
2017-12-09 01:15:24 +01:00
invalidate ( ) ;
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.
2020-04-21 00:17:21 +02:00
void sqlsrv_stmt : : free_param_data ( void )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
params_container . clean_up_param_data ( ) ;
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.
2020-04-21 00:17:21 +02:00
void sqlsrv_stmt : : new_result_set ( void )
2016-01-30 03:00:20 +01:00
{
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 ( ) ;
2019-11-06 22:14:28 +01:00
// reset sqlsrv php type in meta data
size_t num_fields = this - > current_meta_data . size ( ) ;
for ( size_t f = 0 ; f < num_fields ; f + + ) {
this - > current_meta_data [ f ] - > reset_php_type ( ) ;
}
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 ) ) ) ;
2020-04-21 00:17:21 +02:00
new ( result . get ( ) ) sqlsrv_buffered_result_set ( this ) ;
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
}
}
2021-02-05 05:03:46 +01:00
// internal helper function to free meta data structures allocated
void meta_data_free ( _Inout_ field_meta_data * meta )
{
2021-05-11 01:33:14 +02:00
meta - > field_name . reset ( ) ;
2021-02-05 05:03:46 +01:00
sqlsrv_free ( meta ) ;
}
void sqlsrv_stmt : : clean_up_results_metadata ( )
{
std : : for_each ( current_meta_data . begin ( ) , current_meta_data . end ( ) , meta_data_free ) ;
current_meta_data . clear ( ) ;
column_count = ACTIVE_NUM_COLS_INVALID ;
row_count = ACTIVE_NUM_ROWS_INVALID ;
}
2020-07-24 01:07:41 +02:00
void sqlsrv_stmt : : set_query_timeout ( )
{
if ( query_timeout = = QUERY_TIMEOUT_INVALID | | query_timeout < 0 ) {
return ;
}
core : : SQLSetStmtAttr ( this , SQL_ATTR_QUERY_TIMEOUT , reinterpret_cast < SQLPOINTER > ( ( SQLLEN ) query_timeout ) , SQL_IS_UINTEGER ) ;
}
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 ,
2020-04-21 00:17:21 +02:00
_In_opt_ const stmt_option valid_stmt_opts [ ] , _In_ error_callback const err , _In_opt_ void * driver )
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 {
2020-04-21 00:17:21 +02:00
core : : SQLAllocHandle ( SQL_HANDLE_STMT , * conn , & stmt_h ) ;
2016-01-30 03:00:20 +01:00
2020-04-21 00:17:21 +02:00
stmt = stmt_factory ( conn , stmt_h , err , driver ) ;
2016-01-30 03:00:20 +01:00
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
2020-04-21 00:17:21 +02:00
const stmt_option * stmt_opt = get_stmt_option ( stmt - > conn , index , valid_stmt_opts ) ;
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.
2020-04-21 00:17:21 +02:00
( * stmt_opt - > func ) ( stmt , stmt_opt , value_z ) ;
2018-12-04 22:00:34 +01:00
} ZEND_HASH_FOREACH_END ( ) ;
2016-01-30 03:00:20 +01:00
}
2019-09-18 16:49:14 +02:00
// The query timeout setting is inherited from the corresponding connection attribute, but
2019-12-18 01:25:57 +01:00
// the user may override that the query timeout setting using the statement option.
2019-09-18 16:49:14 +02:00
// In any case, set query timeout using the latest value
stmt - > set_query_timeout ( ) ;
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 ,
2021-05-26 00:36:01 +02:00
_Inout_ SQLSMALLINT decimal_digits )
2016-01-30 03:00:20 +01:00
{
// check is only < because params are 0 based
2021-05-11 01:33:14 +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 ( ) ;
}
2021-05-11 01:33:14 +02:00
// Dereference the parameter if necessary
2016-03-15 04:09:46 +01:00
zval * param_ref = param_z ;
2021-05-11 01:33:14 +02:00
if ( Z_ISREF_P ( param_z ) ) {
ZVAL_DEREF ( param_z ) ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
sqlsrv_param * param_ptr = stmt - > params_container . find_param ( param_num , ( direction = = SQL_PARAM_INPUT ) ) ;
try {
if ( param_ptr = = NULL ) {
sqlsrv_malloc_auto_ptr < sqlsrv_param > new_param ;
if ( direction = = SQL_PARAM_INPUT ) {
2021-05-26 00:36:01 +02:00
// Check if it's a Table-Valued Parameter first
if ( Z_TYPE_P ( param_z ) = = IS_ARRAY ) {
new_param = new ( sqlsrv_malloc ( sizeof ( sqlsrv_param_tvp ) ) ) sqlsrv_param_tvp ( param_num , encoding , SQL_SS_TABLE , 0 , 0 , NULL ) ;
} else {
new_param = new ( sqlsrv_malloc ( sizeof ( sqlsrv_param ) ) ) sqlsrv_param ( param_num , direction , encoding , sql_type , column_size , decimal_digits ) ;
}
2021-05-11 01:33:14 +02:00
} else if ( direction = = SQL_PARAM_OUTPUT | | direction = = SQL_PARAM_INPUT_OUTPUT ) {
new_param = new ( sqlsrv_malloc ( sizeof ( sqlsrv_param_inout ) ) ) sqlsrv_param_inout ( param_num , direction , encoding , sql_type , column_size , decimal_digits , php_out_type ) ;
} else {
SQLSRV_ASSERT ( false , " sqlsrv_params_container::insert_param - Invalid parameter direction. " ) ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
stmt - > params_container . insert_param ( param_num , new_param ) ;
param_ptr = new_param ;
new_param . transferred ( ) ;
2021-11-22 22:59:32 +01:00
} else if ( direction = = SQL_PARAM_INPUT
& & param_ptr - > sql_data_type ! = SQL_SS_TABLE
& & param_ptr - > strlen_or_indptr = = SQL_NULL_DATA ) {
// reset the followings for regular input parameters if it was bound as a null param before
param_ptr - > sql_data_type = sql_type ;
param_ptr - > column_size = column_size ;
param_ptr - > strlen_or_indptr = 0 ;
2021-05-11 01:33:14 +02:00
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
SQLSRV_ASSERT ( param_ptr ! = NULL , " core_sqlsrv_bind_param: param_ptr is null. Something went wrong. " ) ;
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
bool result = param_ptr - > prepare_param ( param_ref , param_z ) ;
if ( ! result & & direction = = SQL_PARAM_INPUT_OUTPUT ) {
CHECK_CUSTOM_ERROR ( ! result , stmt , SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH , param_num + 1 ) {
2016-01-30 03:00:20 +01:00
throw core : : CoreException ( ) ;
}
2021-05-11 01:33:14 +02:00
}
2017-12-09 01:15:24 +01:00
2021-05-11 01:33:14 +02:00
// If Always Encrypted is enabled, transfer the known param meta data if applicable, which might alter param_z for decimal types
if ( stmt - > conn - > ce_option . enabled ) {
if ( param_ptr - > sql_data_type = = SQL_UNKNOWN_TYPE | | param_ptr - > column_size = = SQLSRV_UNKNOWN_SIZE ) {
// meta data parameters are always sorted based on parameter number
param_ptr - > copy_param_meta_ae ( param_z , stmt - > params_container . params_meta_ae [ param_num ] ) ;
2016-01-30 03:00:20 +01:00
}
2017-12-09 01:15:24 +01:00
}
2020-08-13 02:24:39 +02:00
2021-05-11 01:33:14 +02:00
// Get all necessary values to prepare for SQLBindParameter
param_ptr - > process_param ( stmt , param_z ) ;
param_ptr - > bind_param ( stmt ) ;
2020-08-13 02:24:39 +02:00
2021-05-11 01:33:14 +02:00
// When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
// For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
// This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
// To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
// Note this must be called after SQLBindParameter() or SQLSetDescField() may fail.
// VSO BUG 2693: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size
if ( stmt - > conn - > ce_option . enabled & & param_ptr - > sql_data_type = = SQL_TYPE_TIMESTAMP ) {
if ( param_ptr - > decimal_digits = = 3 ) {
core : : SQLSetDescField ( stmt , param_num + 1 , SQL_CA_SS_SERVER_TYPE , ( SQLPOINTER ) SQL_SS_TYPE_DATETIME , SQL_IS_INTEGER ) ;
} else if ( param_ptr - > decimal_digits = = 0 & & param_ptr - > column_size = = 16 ) {
core : : SQLSetDescField ( stmt , param_num + 1 , SQL_CA_SS_SERVER_TYPE , ( SQLPOINTER ) SQL_SS_TYPE_SMALLDATETIME , SQL_IS_INTEGER ) ;
}
2020-08-08 03:33:12 +02:00
}
2017-09-06 01:51:40 +02:00
}
2017-10-21 00:10:56 +02:00
catch ( core : : CoreException & e ) {
2020-04-21 00:17:21 +02:00
stmt - > free_param_data ( ) ;
2016-01-30 03:00:20 +01:00
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
2020-04-21 00:17:21 +02:00
SQLRETURN core_sqlsrv_execute ( _Inout_ sqlsrv_stmt * stmt , _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
2020-04-21 00:17:21 +02:00
close_active_stream ( stmt ) ;
2016-01-30 03:00:20 +01:00
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 ( ) ;
}
}
2020-04-21 00:17:21 +02:00
r = core : : SQLExecDirectW ( stmt , wsql_string ) ;
2016-01-30 03:00:20 +01:00
}
else {
2020-04-21 00:17:21 +02:00
r = core : : SQLExecute ( stmt ) ;
2016-01-30 03:00:20 +01:00
}
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 ) {
2021-05-11 01:33:14 +02:00
core_sqlsrv_send_stream_packet ( stmt , true ) ;
2016-01-30 03:00:20 +01:00
}
2017-12-09 01:15:24 +01:00
2020-04-21 00:17:21 +02:00
stmt - > new_result_set ( ) ;
2016-01-30 03:00:20 +01:00
stmt - > executed = true ;
// if all the data has been sent and no data was returned then finalize the output parameters
2020-04-21 00:17:21 +02:00
if ( stmt - > send_streams_at_exec & & ( r = = SQL_NO_DATA | | ! core_sqlsrv_has_any_result ( stmt ) ) ) {
2021-05-11 01:33:14 +02:00
stmt - > params_container . finalize_output_parameters ( ) ;
2017-04-01 00:48:22 +02:00
}
2021-05-11 01:33:14 +02:00
2017-04-01 00:48:22 +02:00
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,
2021-05-11 01:33:14 +02:00
// we need to remove all the parameters and cancel the statement
stmt - > params_container . clean_up_param_data ( ) ;
2017-04-01 00:48:22 +02:00
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
2020-04-21 00:17:21 +02:00
bool core_sqlsrv_fetch ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLSMALLINT fetch_orientation , _In_ SQLULEN fetch_offset )
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 {
2021-02-05 05:03:46 +01:00
// first check if the end of all results has been reached
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
// 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-12-18 01:25:57 +01: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 {
2020-04-21 00:17:21 +02:00
has_fields = core : : SQLNumResultCols ( stmt ) ;
2019-04-15 22:48:48 +02:00
stmt - > column_count = has_fields ;
2019-04-05 21:34:33 +02:00
}
2019-12-18 01:25:57 +01: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
2020-04-21 00:17:21 +02:00
close_active_stream ( stmt ) ;
2016-01-30 03:00:20 +01:00
// 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.
2020-04-21 00:17:21 +02:00
SQLRETURN r = stmt - > current_results - > fetch ( fetch_orientation , ( fetch_orientation = = SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 ) ;
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.
2020-04-21 00:17:21 +02:00
field_meta_data * core_sqlsrv_field_metadata ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLSMALLINT colno )
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 ) ,
2020-04-21 00:17:21 +02:00
& ( meta_data - > field_is_nullable ) ) ;
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 ,
2020-04-21 00:17:21 +02:00
sizeof ( field_type_name ) , & out_buff_len , & not_used ) ;
2018-11-28 02:18:38 +01:00
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 ;
}
2020-04-21 00:17:21 +02:00
void core_sqlsrv_sensitivity_metadata ( _Inout_ sqlsrv_stmt * stmt )
2019-05-01 17:03:33 +02:00
{
sqlsrv_malloc_auto_ptr < unsigned char > dcbuf ;
2020-09-02 19:39:54 +02:00
DWORD dcVersion = 0 ;
SQLINTEGER dclen = 0 , dcIRD = 0 ;
2019-05-01 17:03:33 +02:00
SQLINTEGER dclenout = 0 ;
SQLHANDLE ird ;
SQLRETURN r ;
try {
if ( ! stmt - > data_classification ) {
return ;
}
2019-07-29 17:02:50 +02:00
if ( stmt - > current_sensitivity_metadata ) {
2019-05-01 17:03:33 +02:00
// 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
2019-12-18 01:25:57 +01:00
// To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by
2019-05-01 17:03:33 +02:00
// calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute
2020-09-02 19:39:54 +02:00
r = : : SQLGetStmtAttr ( stmt - > handle ( ) , SQL_ATTR_IMP_ROW_DESC , reinterpret_cast < SQLPOINTER * > ( & ird ) , SQL_IS_POINTER , 0 ) ;
2019-05-01 17:03:33 +02:00
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
2020-09-02 19:39:54 +02:00
r = : : SQLGetDescFieldW ( ird , 0 , SQL_CA_SS_DATA_CLASSIFICATION , reinterpret_cast < SQLPOINTER > ( dcbuf . get ( ) ) , 0 , & dclen ) ;
2019-05-01 17:03:33 +02:00
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 ;
2020-04-21 00:17:21 +02:00
rc = : : SQLGetDiagField ( SQL_HANDLE_DESC , ird , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
2019-05-01 17:03:33 +02:00
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 ( ) ;
}
2019-05-17 20:36:24 +02:00
CHECK_CUSTOM_ERROR ( true , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED , " Check if ODBC driver or the server supports the Data Classification feature. " ) {
2019-05-01 17:03:33 +02:00
throw core : : CoreException ( ) ;
}
}
// Call again to read SQL_CA_SS_DATA_CLASSIFICATION data
dcbuf = static_cast < unsigned char * > ( sqlsrv_malloc ( dclen * sizeof ( char ) ) ) ;
2020-09-02 19:39:54 +02:00
r = : : SQLGetDescFieldW ( ird , 0 , SQL_CA_SS_DATA_CLASSIFICATION , reinterpret_cast < SQLPOINTER > ( dcbuf . get ( ) ) , dclen , & dclenout ) ;
2019-05-01 17:03:33 +02:00
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 ;
2020-09-02 19:39:54 +02:00
// If make it this far, must be using ODBC 17.2 or above. Prior to ODBC 17.4, checking Data Classification version will fail.
// When the function is successful and the version is right, rank info is available for retrieval
bool getRankInfo = false ;
r = : : SQLGetDescFieldW ( ird , 0 , SQL_CA_SS_DATA_CLASSIFICATION_VERSION , reinterpret_cast < SQLPOINTER > ( & dcVersion ) , SQL_IS_INTEGER , & dcIRD ) ;
if ( r = = SQL_SUCCESS & & dcVersion > = VERSION_RANK_AVAILABLE ) {
getRankInfo = true ;
}
// Start parsing the data (blob)
2019-05-01 17:03:33 +02:00
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
2020-09-02 19:39:54 +02:00
parse_column_sensitivity_props ( sensitivity_meta , & dcptr , getRankInfo ) ;
2019-05-01 17:03:33 +02:00
unsigned char * dcend = dcbuf ;
dcend + = dclen ;
CHECK_CUSTOM_ERROR ( dcptr ! = dcend , stmt , SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED , " Metadata parsing ends unexpectedly " ) {
throw core : : CoreException ( ) ;
}
2019-12-18 01:25:57 +01:00
2019-05-01 17:03:33 +02:00
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 ,
2020-04-21 00:17:21 +02:00
_Out_ SQLSRV_PHPTYPE * sqlsrv_php_type_out )
2016-01-30 03:00:20 +01:00
{
2018-12-04 22:00:34 +01:00
try {
// close the stream to release the resource
2020-04-21 00:17:21 +02:00
close_active_stream ( stmt ) ;
2018-12-04 22:00:34 +01:00
// 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 ;
// 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 ) {
2019-11-06 22:14:28 +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. " ) ;
2020-04-21 00:17:21 +02:00
core_sqlsrv_get_field ( stmt , i , invalid , prefer_string , field_value , field_len , cache_field , sqlsrv_php_type_out ) ;
2019-11-06 22:14:28 +01:00
// 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 " ) ;
2019-11-06 22:14:28 +01:00
// Get the corresponding php type from the sql type and then save the result for later
if ( stmt - > current_meta_data [ field_index ] - > sqlsrv_php_type . typeinfo . type = = SQLSRV_PHPTYPE_INVALID ) {
SQLLEN sql_field_type = 0 ;
SQLLEN sql_field_len = 0 ;
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 ;
}
sqlsrv_php_type = stmt - > sql_type_to_php_type ( static_cast < SQLINTEGER > ( sql_field_type ) , static_cast < SQLUINTEGER > ( sql_field_len ) , prefer_string ) ;
stmt - > current_meta_data [ field_index ] - > sqlsrv_php_type = sqlsrv_php_type ;
2018-10-06 00:01:18 +02:00
}
else {
2019-11-06 22:14:28 +01:00
// use the previously saved php type
sqlsrv_php_type = stmt - > current_meta_data [ field_index ] - > sqlsrv_php_type ;
2018-10-06 00:01:18 +02:00
}
2019-12-18 01:25:57 +01:00
}
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.
2019-11-06 22:14:28 +01:00
CHECK_CUSTOM_ERROR ( ! is_valid_sqlsrv_phptype ( sqlsrv_php_type ) , stmt , SQLSRV_ERROR_INVALID_TYPE ) {
2018-12-04 22:00:34 +01:00
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
2020-04-21 00:17:21 +02:00
core_get_field_common ( stmt , field_index , sqlsrv_php_type , field_value , field_len ) ;
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 ) ;
2020-04-21 00:17:21 +02:00
core : : sqlsrv_zend_hash_index_update_mem ( * stmt , Z_ARRVAL ( stmt - > field_cache ) , field_index , & cache , sizeof ( field_cache ) ) ;
2018-12-04 22:00:34 +01:00
}
}
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.
2020-04-21 00:17:21 +02:00
bool core_sqlsrv_has_any_result ( _Inout_ sqlsrv_stmt * stmt )
2016-01-30 03:00:20 +01:00
{
2019-04-13 05:49:03 +02:00
SQLSMALLINT num_cols ;
SQLLEN rows_affected ;
2019-12-18 01:25:57 +01:00
2019-04-13 05:49:03 +02:00
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
2020-04-21 00:17:21 +02:00
num_cols = core : : SQLNumResultCols ( stmt ) ;
2019-04-05 21:34:33 +02:00
stmt - > column_count = num_cols ;
2019-04-13 05:49:03 +02:00
}
2019-12-18 01:25:57 +01:00
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
2020-04-21 00:17:21 +02:00
rows_affected = core : : SQLRowCount ( stmt ) ;
2019-04-05 21:34:33 +02:00
stmt - > row_count = rows_affected ;
}
2019-12-18 01:25:57 +01:00
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
2020-04-21 00:17:21 +02:00
void core_sqlsrv_next_result ( _Inout_ sqlsrv_stmt * stmt , _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
2020-04-21 00:17:21 +02:00
close_active_stream ( stmt ) ;
2016-01-30 03:00:20 +01:00
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 ) {
2020-04-21 00:17:21 +02:00
r = core : : SQLMoreResults ( stmt ) ;
2016-01-30 03:00:20 +01:00
}
else {
r = SQLMoreResults ( stmt - > handle ( ) ) ;
}
if ( r = = SQL_NO_DATA ) {
2019-12-18 01:25:57 +01:00
if ( finalize_output_params ) {
2016-01-30 03:00:20 +01:00
// if we're finished processing result sets, handle the output parameters
2021-05-11 01:33:14 +02:00
stmt - > params_container . finalize_output_parameters ( ) ;
2016-01-30 03:00:20 +01:00
}
// mark we are past the end of all results
stmt - > past_next_result_end = true ;
return ;
}
2020-04-21 00:17:21 +02:00
stmt - > new_result_set ( ) ;
2016-01-30 03:00:20 +01:00
}
catch ( core : : CoreException & e ) {
SQLCancel ( stmt - > handle ( ) ) ;
throw e ;
}
}
//Calls SQLSetStmtAttr to set a cursor.
2020-04-21 00:17:21 +02:00
void core_sqlsrv_set_scrollable ( _Inout_ sqlsrv_stmt * stmt , _In_ unsigned long cursor_type )
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 ,
2020-04-21 00:17:21 +02:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_STATIC ) , SQL_IS_UINTEGER ) ;
2016-01-30 03:00:20 +01:00
break ;
case SQL_CURSOR_DYNAMIC :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2020-04-21 00:17:21 +02:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_DYNAMIC ) , SQL_IS_UINTEGER ) ;
2016-01-30 03:00:20 +01:00
break ;
case SQL_CURSOR_KEYSET_DRIVEN :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2020-04-21 00:17:21 +02:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_KEYSET_DRIVEN ) , SQL_IS_UINTEGER ) ;
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
case SQL_CURSOR_FORWARD_ONLY :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2020-04-21 00:17:21 +02:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_FORWARD_ONLY ) , SQL_IS_UINTEGER ) ;
2016-01-30 03:00:20 +01:00
break ;
case SQLSRV_CURSOR_BUFFERED :
2017-12-09 01:15:24 +01:00
core : : SQLSetStmtAttr ( stmt , SQL_ATTR_CURSOR_TYPE ,
2020-04-21 00:17:21 +02:00
reinterpret_cast < SQLPOINTER > ( SQL_CURSOR_FORWARD_ONLY ) , SQL_IS_UINTEGER ) ;
2016-01-30 03:00:20 +01:00
break ;
default :
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) ;
break ;
}
stmt - > cursor_type = cursor_type ;
}
catch ( core : : CoreException & ) {
throw ;
}
}
2020-04-21 00:17:21 +02:00
void core_sqlsrv_set_buffered_query_limit ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z )
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 ) ;
}
2020-04-21 00:17:21 +02:00
core_sqlsrv_set_buffered_query_limit ( stmt , Z_LVAL_P ( value_z ) ) ;
2016-01-30 03:00:20 +01:00
}
2020-04-21 00:17:21 +02:00
void core_sqlsrv_set_buffered_query_limit ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLLEN limit )
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
}
2019-09-18 16:49:14 +02:00
// 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.
2020-04-21 00:17:21 +02:00
void core_sqlsrv_set_query_timeout ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * value_z )
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 ) ) ;
}
2019-09-18 16:49:14 +02:00
// Save the query timeout setting for processing later
stmt - > query_timeout = static_cast < long > ( Z_LVAL_P ( value_z ) ) ;
2016-01-30 03:00:20 +01:00
}
catch ( core : : CoreException & ) {
throw ;
}
}
2020-04-21 00:17:21 +02:00
void core_sqlsrv_set_decimal_places ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z )
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 ) ;
2019-12-18 01:25:57 +01:00
}
2018-11-02 22:34:27 +01:00
catch ( core : : CoreException & ) {
throw ;
}
}
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
2021-05-11 01:33:14 +02:00
// given. Any errors that occur will be thrown.
2016-01-30 03:00:20 +01:00
// Parameters:
// stmt - query to send the next packet for
2021-05-11 01:33:14 +02:00
// get_all - send stream data all at once (false by default)
2016-01-30 03:00:20 +01:00
// Returns:
// true if more data remains to be sent, false if all data processed
2021-05-11 01:33:14 +02:00
bool core_sqlsrv_send_stream_packet ( _Inout_ sqlsrv_stmt * stmt , _In_opt_ bool get_all /*= false*/ )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
bool bMore = false ;
2016-01-30 03:00:20 +01:00
try {
2021-05-11 01:33:14 +02:00
if ( get_all ) {
// send stream data all at once (so no more after this)
stmt - > params_container . send_all_packets ( stmt ) ;
} else {
bMore = stmt - > params_container . send_next_packet ( stmt ) ;
2018-10-19 23:48:21 +02:00
}
2017-01-20 23:43:49 +01:00
2021-05-11 01:33:14 +02:00
if ( ! bMore ) {
// All resources parameters are sent, so it's time to clean up
stmt - > params_container . clean_up_param_data ( true ) ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
} catch ( core : : CoreException & e ) {
2020-04-21 00:17:21 +02:00
stmt - > free_param_data ( ) ;
2021-05-11 01:33:14 +02:00
SQLFreeStmt ( stmt - > handle ( ) , SQL_RESET_PARAMS ) ;
SQLCancel ( stmt - > handle ( ) ) ;
2016-01-30 03:00:20 +01:00
throw e ;
}
2021-05-11 01:33:14 +02:00
return bMore ;
2016-01-30 03:00:20 +01:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_functor : : operator ( ) ( _Inout_ sqlsrv_stmt * /*stmt*/ , stmt_option const * /*opt*/ , _In_ zval * /*value_z*/ )
2016-01-30 03:00:20 +01:00
{
// This implementation should never get called.
DIE ( " Not implemented. " ) ;
}
2020-04-21 00:17:21 +02:00
void stmt_option_query_timeout : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z )
2016-01-30 03:00:20 +01:00
{
2020-04-21 00:17:21 +02:00
core_sqlsrv_set_query_timeout ( stmt , value_z ) ;
2016-01-30 03:00:20 +01:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_send_at_exec : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /*opt*/ , _In_ zval * value_z )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
// zend_is_true does not fail. It either returns true or false.
stmt - > send_streams_at_exec = ( zend_is_true ( value_z ) ) ;
2016-01-30 03:00:20 +01:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_buffered_query_limit : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /*opt*/ , _In_ zval * value_z )
2016-01-30 03:00:20 +01:00
{
2020-04-21 00:17:21 +02:00
core_sqlsrv_set_buffered_query_limit ( stmt , value_z ) ;
2016-01-30 03:00:20 +01:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_date_as_string : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z )
2018-09-18 01:25:02 +02:00
{
2021-05-19 01:56:49 +02:00
stmt - > date_as_string = zend_is_true ( value_z ) ;
2018-09-18 01:25:02 +02:00
}
2016-01-30 03:00:20 +01:00
2020-04-21 00:17:21 +02:00
void stmt_option_format_decimals : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z )
2018-10-13 00:22:27 +02:00
{
2021-05-19 01:56:49 +02:00
stmt - > format_decimals = zend_is_true ( value_z ) ;
2018-11-28 02:18:38 +01:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_decimal_places : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z )
2018-11-28 02:18:38 +01:00
{
2020-04-21 00:17:21 +02:00
core_sqlsrv_set_decimal_places ( stmt , value_z ) ;
2018-10-13 00:22:27 +02:00
}
2020-04-21 00:17:21 +02:00
void stmt_option_data_classification : : operator ( ) ( _Inout_ sqlsrv_stmt * stmt , stmt_option const * /**/ , _In_ zval * value_z )
2019-05-01 17:03:33 +02:00
{
2021-05-19 01:56:49 +02:00
stmt - > data_classification = zend_is_true ( value_z ) ;
2019-05-01 17:03:33 +02:00
}
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.
2020-04-21 00:17:21 +02:00
void close_active_stream ( _Inout_ sqlsrv_stmt * stmt )
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 ;
}
2021-05-26 00:36:01 +02:00
bool is_a_string_type ( _In_ SQLSMALLINT sql_type )
{
switch ( sql_type ) {
case SQL_BIGINT :
case SQL_DECIMAL :
case SQL_NUMERIC :
case SQL_SS_VARIANT :
case SQL_SS_UDT :
case SQL_GUID :
case SQL_SS_XML :
case SQL_CHAR :
case SQL_WCHAR :
case SQL_VARCHAR :
case SQL_WVARCHAR :
case SQL_LONGVARCHAR :
case SQL_WLONGVARCHAR :
return true ;
}
return false ;
}
2020-04-21 00:17:21 +02:00
void calc_string_size ( _Inout_ sqlsrv_stmt * stmt , _In_ SQLUSMALLINT field_index , _In_ SQLLEN sql_type , _Inout_ SQLLEN & size )
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
2020-04-21 00:17:21 +02:00
core : : SQLColAttributeW ( stmt , field_index + 1 , SQL_DESC_DISPLAY_SIZE , NULL , 0 , NULL , & size ) ;
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
2020-04-21 00:17:21 +02:00
core : : SQLColAttributeW ( stmt , field_index + 1 , SQL_DESC_OCTET_LENGTH , NULL , 0 , NULL , & size ) ;
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
2020-04-21 00:17:21 +02:00
size_t calc_utf8_missing ( _Inout_ sqlsrv_stmt * stmt , _In_reads_ ( buffer_end ) const char * buffer , _In_ size_t buffer_end )
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
2020-04-21 00:17:21 +02:00
sqlsrv_php_type , _Inout_updates_bytes_ ( * field_len ) void * & field_value , _Inout_ SQLLEN * field_len )
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
2020-04-21 00:17:21 +02:00
close_active_stream ( stmt ) ;
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 :
{
2020-02-19 23:27:36 +01:00
sqlsrv_malloc_auto_ptr < SQLLEN > field_value_temp ;
field_value_temp = static_cast < SQLLEN * > ( sqlsrv_malloc ( sizeof ( SQLLEN ) ) ) ;
2017-12-09 01:15:24 +01:00
* field_value_temp = 0 ;
2016-01-30 03:00:20 +01:00
2020-02-19 23:27:36 +01:00
SQLRETURN r = stmt - > current_results - > get_data ( field_index + 1 , SQL_C_LONG , field_value_temp , sizeof ( SQLLEN ) ,
2020-04-21 00:17:21 +02:00
field_len , true /*handle_warning*/ ) ;
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 ) ) ) ;
2020-07-30 22:58:50 +02:00
* field_value_temp = 0.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_DOUBLE , field_value_temp , sizeof ( double ) ,
2020-04-21 00:17:21 +02:00
field_len , true /*handle_warning*/ ) ;
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 :
{
2020-04-21 00:17:21 +02:00
get_field_as_string ( stmt , field_index , sqlsrv_php_type , field_value , field_len ) ;
2017-12-09 01:15:24 +01:00
break ;
}
2016-01-30 03:00:20 +01:00
2019-07-15 23:21:54 +02:00
// Reference: https://docs.microsoft.com/sql/odbc/reference/appendixes/sql-to-c-timestamp
// Retrieve the datetime data as a string, which may be cached for later use.
2019-12-18 01:25:57 +01:00
// The string is converted to a DateTime object only when it is required to
2019-07-15 23:21:54 +02:00
// be returned as a zval.
2017-12-09 01:15:24 +01:00
case SQLSRV_PHPTYPE_DATETIME :
{
2019-07-29 17:02:50 +02:00
sqlsrv_malloc_auto_ptr < char > field_value_temp ;
2019-07-15 23:21:54 +02:00
SQLLEN field_len_temp = 0 ;
2016-01-30 03:00:20 +01:00
2019-07-15 23:21:54 +02:00
field_value_temp = static_cast < char * > ( sqlsrv_malloc ( MAX_DATETIME_STRING_LEN ) ) ;
memset ( field_value_temp , ' \0 ' , MAX_DATETIME_STRING_LEN ) ;
2016-06-13 23:44:53 +02:00
2020-04-21 00:17:21 +02:00
SQLRETURN r = stmt - > current_results - > get_data ( field_index + 1 , SQL_C_CHAR , field_value_temp , MAX_DATETIME_STRING_LEN , & field_len_temp , true ) ;
2016-07-06 20:47:05 +02:00
2019-07-15 23:21:54 +02:00
if ( r = = SQL_NO_DATA | | field_len_temp = = SQL_NULL_DATA ) {
2019-07-29 17:02:50 +02:00
field_value_temp . reset ( ) ;
2019-07-15 23:21:54 +02:00
field_len_temp = 0 ;
2017-12-09 01:15:24 +01:00
}
2016-06-13 23:44:53 +02:00
2019-07-15 23:21:54 +02:00
CHECK_CUSTOM_ERROR ( ( r = = SQL_NO_DATA ) , stmt , SQLSRV_ERROR_NO_DATA , field_index ) {
throw core : : CoreException ( ) ;
2017-12-09 01:15:24 +01:00
}
2016-06-13 23:44:53 +02:00
2019-07-15 23:21:54 +02:00
field_value = field_value_temp ;
2019-07-29 17:02:50 +02:00
field_value_temp . transferred ( ) ;
2019-07-15 23:21:54 +02:00
* field_len = field_len_temp ;
2017-12-09 01:15:24 +01:00
break ;
}
2019-12-18 01:25:57 +01:00
2017-12-09 01:15:24 +01:00
// 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
2020-09-30 02:53:58 +02:00
// For a sqlsrv stream, only REPORT_ERRORS may be used. For "mode", the 'b' option
// is ignored on POSIX systems, which treat text and binary files the same. Yet, the
// 'b' option might be important in other systems.
// For details check https://www.php.net/manual/en/internals2.ze1.streams.php
stream = php_stream_open_wrapper ( " sqlsrv://sqlncli10 " , " rb " , REPORT_ERRORS , 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
}
2021-05-11 01:33:14 +02:00
void col_cache_dtor ( _Inout_ zval * data_z )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
col_cache * cache = static_cast < col_cache * > ( Z_PTR_P ( data_z ) ) ;
sqlsrv_free ( cache ) ;
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
void field_cache_dtor ( _Inout_ zval * data_z )
{
field_cache * cache = static_cast < field_cache * > ( Z_PTR_P ( data_z ) ) ;
if ( cache - > value )
{
sqlsrv_free ( cache - > value ) ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
sqlsrv_free ( cache ) ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string()
void format_decimal_numbers ( _In_ SQLSMALLINT decimals_places , _In_ SQLSMALLINT field_scale , _Inout_updates_bytes_ ( * field_len ) char * & field_value , _Inout_ SQLLEN * field_len )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
// In SQL Server, the default maximum precision of numeric and decimal data types is 38
//
// 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.
//
// Note: it's possible that the decimal data does not contain a decimal point because the field scale is 0.
// Thus, first check if the decimal point exists. If not, no formatting necessary, regardless of
// format_decimals and decimals_places
//
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
// Check if it's a negative number and if necessary to add the leading zero
short is_negative = ( * field_value = = ' - ' ) ? 1 : 0 ;
char * src = field_value + is_negative ;
bool add_leading_zero = false ;
2016-03-15 04:09:46 +01:00
2021-05-11 01:33:14 +02:00
// If the decimal point is not found, simply return
char * pt = strchr ( src , DECIMAL_POINT ) ;
if ( pt = = NULL ) {
return ;
}
else if ( pt = = src ) {
add_leading_zero = true ;
2018-12-04 22:00:34 +01:00
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
SQLSMALLINT scale = decimals_places ;
if ( scale > field_scale ) {
scale = field_scale ;
2016-01-30 03:00:20 +01:00
}
2021-05-11 01:33:14 +02:00
char buffer [ 50 ] = " " ; // A buffer with TWO blank spaces, as leeway
int offset = 1 + is_negative ; // for cases like 9.* to 10.* and the minus sign if needed
int src_length = strnlen_s ( src ) ;
2018-10-13 00:22:27 +02:00
2020-06-19 23:45:13 +02:00
if ( add_leading_zero ) {
2021-01-05 03:28:07 +01:00
buffer [ offset + + ] = ' 0 ' ; // leading zero added
2018-10-13 00:22:27 +02:00
}
2020-06-19 23:45:13 +02:00
// Copy the original numerical value to the buffer
memcpy_s ( buffer + offset , src_length , src , src_length ) ;
2018-10-13 00:22:27 +02:00
2020-06-19 23:45:13 +02:00
int last_pos = src_length + offset ;
2019-12-18 01:25:57 +01:00
2020-06-19 23:45:13 +02:00
// If no need to adjust decimal places, skip formatting
if ( decimals_places ! = NO_CHANGE_DECIMAL_PLACES ) {
2020-10-17 00:17:58 +02:00
int num_decimals = src_length - ( pt - src ) - 1 ;
2020-06-19 23:45:13 +02:00
if ( num_decimals > scale ) {
last_pos = round_up_decimal_numbers ( buffer , ( pt - src ) + offset , scale , offset , last_pos ) ;
2018-10-13 00:22:27 +02:00
}
2020-06-19 23:45:13 +02:00
}
2018-10-13 00:22:27 +02:00
2021-01-05 03:28:07 +01:00
// Remove the extra white space if not used. For a negative number,
// the first pos is always a space
offset = is_negative ;
char * p = buffer + offset ;
while ( * p + + = = ' ' ) {
2020-06-19 23:45:13 +02:00
offset + + ;
}
if ( is_negative ) {
buffer [ - - offset ] = ' - ' ;
}
2020-10-17 00:17:58 +02:00
int len = last_pos - offset ;
2020-06-19 23:45:13 +02:00
memcpy_s ( field_value , len , buffer + offset , len ) ;
2018-10-13 00:22:27 +02:00
field_value [ len ] = ' \0 ' ;
* field_len = len ;
}
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +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 )
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 ;
2021-06-08 21:37:28 +02:00
unsigned int initial_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.
2020-04-21 00:17:21 +02:00
calc_string_size ( stmt , field_index , sql_field_type , sql_display_size ) ;
2018-10-06 00:01:18 +02:00
col_cache cache ( sql_field_type , sql_display_size ) ;
2020-04-21 00:17:21 +02:00
core : : sqlsrv_zend_hash_index_update_mem ( * stmt , Z_ARRVAL ( stmt - > col_cache ) , field_index , & cache , sizeof ( col_cache ) ) ;
2018-10-06 00:01:18 +02:00
}
// 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 ( ) ;
}
// Set the C type and account for null characters at the end of the data.
2020-08-15 01:37:54 +02:00
if ( sqlsrv_php_type . typeinfo . encoding = = SQLSRV_ENCODING_BINARY ) {
2017-08-14 20:44:54 +02:00
c_type = SQL_C_BINARY ;
extra = 0 ;
2020-08-15 01:37:54 +02:00
} else {
2017-08-14 20:44:54 +02:00
c_type = SQL_C_CHAR ;
2020-08-15 01:37:54 +02:00
extra = sizeof ( SQLCHAR ) ;
// For numbers, no need to convert
if ( sqlsrv_php_type . typeinfo . encoding = = CP_UTF8 & & ! is_a_numeric_type ( sql_field_type ) ) {
c_type = SQL_C_WCHAR ;
extra = sizeof ( SQLWCHAR ) ;
2021-06-08 21:37:28 +02:00
sql_display_size = ( sql_display_size * sizeof ( SQLWCHAR ) ) ;
2020-08-15 01:37:54 +02:00
}
2017-08-14 20:44:54 +02:00
}
2017-03-24 23:46:53 +01:00
2021-06-08 21:37:28 +02:00
// If this is a large type, then read the first chunk to get the actual length from SQLGetData
2020-08-06 18:46:15 +02:00
// The user may use "SET TEXTSIZE" to specify the size of varchar(max), nvarchar(max),
// varbinary(max), text, ntext, and image data returned by a SELECT statement.
2021-06-08 21:37:28 +02:00
// For varbinary(max), varchar(max) and nvarchar(max), sql_display_size will be 0, regardless
if ( sql_display_size = = 0 | |
( sql_field_type = = SQL_WLONGVARCHAR | | sql_field_type = = SQL_LONGVARCHAR | | sql_field_type = = SQL_LONGVARBINARY ) ) {
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
field_len_temp = initial_field_len ;
field_value_temp = static_cast < char * > ( sqlsrv_malloc ( field_len_temp + extra + 1 ) ) ;
r = stmt - > current_results - > get_data ( field_index + 1 , c_type , field_value_temp , ( field_len_temp + extra ) , & field_len_temp , false /*handle_warning*/ ) ;
} else {
field_len_temp = sql_display_size ;
field_value_temp = static_cast < char * > ( sqlsrv_malloc ( sql_display_size + extra + 1 ) ) ;
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
// get the data
r = stmt - > current_results - > get_data ( field_index + 1 , c_type , field_value_temp , sql_display_size + extra , & field_len_temp , false /*handle_warning*/ ) ;
}
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02: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
2021-06-08 21:37:28 +02:00
if ( field_len_temp = = SQL_NULL_DATA ) {
field_value = NULL ;
sqlsrv_free ( field_value_temp ) ;
return ;
}
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +02:00
if ( r = = SQL_SUCCESS_WITH_INFO ) {
SQLCHAR state [ SQL_SQLSTATE_BUFSIZE ] = { L ' \0 ' } ;
SQLSMALLINT len = 0 ;
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +02:00
stmt - > current_results - > get_diag_field ( 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
if ( is_truncated_warning ( state ) ) {
SQLLEN chunk_field_len = 0 ;
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +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
2021-06-08 21:37:28 +02:00
// reset the field_len_temp
field_len_temp = initial_field_len ;
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +02:00
do {
SQLLEN buffer_len = field_len_temp ;
// Double the size.
field_len_temp * = 2 ;
2018-01-15 20:57:52 +01:00
2021-06-08 21:37:28 +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
2021-06-08 21:37:28 +02:00
field_len_temp - = buffer_len ;
2016-01-30 03:00:20 +01:00
2021-06-08 21:37:28 +02:00
// Get the rest of the data
r = stmt - > current_results - > get_data ( field_index + 1 , c_type , field_value_temp + buffer_len ,
field_len_temp + extra , & chunk_field_len , false /*handle_warning*/ ) ;
// 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 ( chunk_field_len ! = SQL_NO_TOTAL )
field_len_temp + = chunk_field_len ;
else
field_len_temp + = buffer_len ;
2017-08-14 20:44:54 +02:00
2021-06-08 21:37:28 +02:00
if ( r = = SQL_SUCCESS_WITH_INFO ) {
core : : SQLGetDiagField ( stmt , 1 , SQL_DIAG_SQLSTATE , state , SQL_SQLSTATE_BUFSIZE , & len ) ;
2017-08-14 20:44:54 +02:00
}
2021-06-08 21:37:28 +02:00
} while ( r = = SQL_SUCCESS_WITH_INFO & & is_truncated_warning ( state ) ) ;
} // if (field_len_temp == SQL_NO_TOTAL)
2017-08-14 20:44:54 +02:00
else {
2021-06-08 21:37:28 +02:00
// The field length (or its estimate) is returned, thus no need to double the allocation size.
// Allocate field_len_temp (which is the field length retrieved from the first SQLGetData) but with some padding
// because there is a chance that the estimated field_len_temp is not accurate enough
SQLLEN buffer_len = 50 ;
field_value_temp = static_cast < char * > ( sqlsrv_realloc ( field_value_temp , field_len_temp + buffer_len + 1 ) ) ;
field_len_temp - = initial_field_len ;
// Get the rest of the data
r = stmt - > current_results - > get_data ( field_index + 1 , c_type , field_value_temp + initial_field_len ,
field_len_temp + buffer_len , & chunk_field_len , false /*handle_warning*/ ) ;
field_len_temp = initial_field_len + chunk_field_len ;
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
2017-08-14 20:44:54 +02:00
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
// Reallocate field_value_temp next
field_value_temp = static_cast < char * > ( sqlsrv_realloc ( field_value_temp , field_len_temp + extra + 1 ) ) ;
2017-08-14 20:44:54 +02:00
}
2021-06-08 21:37:28 +02:00
} // if (is_truncated_warning(state))
} // if (r == SQL_SUCCESS_WITH_INFO)
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
if ( c_type = = SQL_C_WCHAR ) {
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
2021-06-08 21:37:28 +02:00
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE , get_last_error_message ( ) ) {
2017-08-14 20:44:54 +02:00
throw core : : CoreException ( ) ;
}
2021-06-08 21:37:28 +02:00
}
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02: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 ) ;
2017-08-14 20:44:54 +02:00
}
2016-06-13 23:44:53 +02:00
2021-06-08 21:37:28 +02:00
// finalized the returned values and set field_len to 0 if field_len_temp is negative (which may happen with unixODBC connection pooling)
field_value = field_value_temp ;
* field_len = ( field_len_temp > 0 ) ? field_len_temp : 0 ;
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.
2021-06-08 21:37:28 +02:00
// SQL_C_BINARY fields don't return a NULL terminator, so we allocate an extra byte on each field and add 1 to fill the null terminator
if ( field_len_temp > 0 ) {
2017-02-01 03:10:37 +01:00
field_value_temp [ field_len_temp ] = ' \0 ' ;
}
2017-08-14 20:44:54 +02:00
}
2021-06-08 21:37:28 +02:00
catch ( core : : CoreException & ) {
2017-08-14 20:44:54 +02:00
field_value = NULL ;
* field_len = 0 ;
2021-06-08 21:37:28 +02:00
sqlsrv_free ( field_value_temp ) ;
2017-08-14 20:44:54 +02:00
throw ;
2021-06-08 21:37:28 +02:00
} catch ( . . . ) {
2017-08-14 20:44:54 +02:00
field_value = NULL ;
* field_len = 0 ;
2021-06-08 21:37:28 +02:00
sqlsrv_free ( field_value_temp ) ;
2017-08-14 20:44:54 +02:00
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.
2020-04-21 00:17:21 +02:00
stmt_option const * get_stmt_option ( sqlsrv_conn const * conn , _In_ zend_ulong key , _In_ const stmt_option stmt_opts [ ] )
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 :
2021-05-26 00:36:01 +02:00
case SQLSRV_PHPTYPE_TABLE :
2016-01-30 03:00:20 +01:00
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 ;
}
2021-05-11 01:33:14 +02:00
void adjustDecimalPrecision ( _Inout_ zval * param_z , _In_ SQLSMALLINT decimal_digits )
2016-01-30 03:00:20 +01:00
{
2021-05-11 01:33:14 +02:00
char * value = Z_STRVAL_P ( param_z ) ;
int value_len = Z_STRLEN_P ( param_z ) ;
// If the length is greater than maxDecimalStrLen, do not convert the string
// 6 is derived from: 1 for the decimal point; 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
const int MAX_DECIMAL_STRLEN = SQL_SERVER_DECIMAL_MAXIMUM_PRECISION + 6 ;
if ( value_len > MAX_DECIMAL_STRLEN ) {
return ;
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
// If std::stold() succeeds, 'index' is the position of the first character after the numerical value
long double d = 0 ;
size_t index ;
try {
d = std : : stold ( std : : string ( value ) , & index ) ;
2017-09-06 01:51:40 +02:00
}
2021-05-11 01:33:14 +02:00
catch ( const std : : logic_error & ) {
return ; // invalid input caused the conversion to throw an exception
}
if ( index < value_len ) {
return ; // the input contains something else apart from the numerical value
2017-02-21 00:24:41 +01:00
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
// Navigate to the first digit or the decimal point
short is_negative = ( d < 0 ) ? 1 : 0 ;
char * src = value + is_negative ;
while ( * src ! = DECIMAL_POINT & & ! isdigit ( static_cast < unsigned int > ( * src ) ) ) {
src + + ;
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
// Check if the value is in scientific notation
char * exp = strchr ( src , ' E ' ) ;
if ( exp = = NULL ) {
exp = strchr ( src , ' e ' ) ;
}
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
// Find the decimal point
char * pt = strchr ( src , DECIMAL_POINT ) ;
2016-01-30 03:00:20 +01:00
2021-05-11 01:33:14 +02:00
char buffer [ 50 ] = " " ; // A buffer with 2 blank spaces, as leeway
int offset = 1 + is_negative ; // The position to start copying the original numerical value
2020-06-19 23:45:13 +02:00
if ( exp = = NULL ) {
if ( pt = = NULL ) {
return ; // decimal point not found
}
2020-12-24 04:23:06 +01:00
int src_length = strnlen_s ( src ) ;
2020-10-17 00:17:58 +02:00
int num_decimals = src_length - ( pt - src ) - 1 ;
2020-06-19 23:45:13 +02:00
if ( num_decimals < = decimal_digits ) {
return ; // no need to adjust number of decimals
}
memcpy_s ( buffer + offset , src_length , src , src_length ) ;
round_up_decimal_numbers ( buffer , ( pt - src ) + offset , decimal_digits , offset , src_length + offset ) ;
}
else {
int power = atoi ( exp + 1 ) ;
if ( abs ( power ) > SQL_SERVER_DECIMAL_MAXIMUM_PRECISION ) {
return ; // Out of range, so let the server handle this
}
2020-10-17 00:17:58 +02:00
int num_decimals = 0 ;
2020-06-19 23:45:13 +02:00
if ( power = = 0 ) {
// Simply chop off the exp part
2020-10-17 00:17:58 +02:00
int length = ( exp - src ) ;
2020-06-19 23:45:13 +02:00
memcpy_s ( buffer + offset , length , src , length ) ;
if ( pt ! = NULL ) {
// Adjust decimal places only if decimal point is found and number of decimals more than decimal_digits
num_decimals = exp - pt - 1 ;
if ( num_decimals > decimal_digits ) {
round_up_decimal_numbers ( buffer , ( pt - src ) + offset , decimal_digits , offset , length + offset ) ;
}
}
} else {
2020-10-17 00:17:58 +02:00
int oldpos = 0 ;
2020-06-19 23:45:13 +02:00
if ( pt = = NULL ) {
2020-07-15 02:22:46 +02:00
oldpos = exp - src ; // Decimal point not found, use the exp sign
2020-06-19 23:45:13 +02:00
}
else {
oldpos = pt - src ;
num_decimals = exp - pt - 1 ;
if ( power > 0 & & num_decimals < = power ) {
2020-07-15 02:22:46 +02:00
return ; // The result will be a whole number, do nothing and return
2020-06-19 23:45:13 +02:00
}
}
// Derive the new position for the decimal point in the buffer
2020-10-17 00:17:58 +02:00
int newpos = oldpos + power ;
2020-06-19 23:45:13 +02:00
if ( power > 0 ) {
newpos = newpos + offset ;
if ( num_decimals = = 0 ) {
memset ( buffer + offset + oldpos , ' 0 ' , power ) ; // Fill parts of the buffer with zeroes first
}
else {
buffer [ newpos ] = DECIMAL_POINT ;
}
}
else {
// The negative "power" part shows exactly how many places to move the decimal point.
// Whether to pad zeroes depending on the original position of the decimal point pos.
if ( newpos < = 0 ) {
// If newpos is negative or zero, pad zeroes (size of '0.' + places to move) in the buffer
short numzeroes = 2 + abs ( newpos ) ;
memset ( buffer + offset , ' 0 ' , numzeroes ) ;
newpos = offset + 1 ; // The new decimal position should be offset + '0'
buffer [ newpos ] = DECIMAL_POINT ; // Replace that '0' with the decimal point
offset = numzeroes + offset ; // Short offset now in the buffer
}
else {
newpos = newpos + offset ;
buffer [ newpos ] = DECIMAL_POINT ;
}
}
// Start copying the content to the buffer until the exp sign or one more digit after decimal_digits
char * p = src ;
2020-10-17 00:17:58 +02:00
int idx = offset ;
int lastpos = newpos + decimal_digits + 1 ;
2020-06-19 23:45:13 +02:00
while ( p ! = exp & & idx < = lastpos ) {
if ( * p = = DECIMAL_POINT ) {
p + + ;
continue ;
}
if ( buffer [ idx ] = = DECIMAL_POINT ) {
idx + + ;
}
buffer [ idx + + ] = * p ;
p + + ;
}
// Round up is required only when number of decimals is more than decimal_digits
num_decimals = idx - newpos - 1 ;
if ( num_decimals > decimal_digits ) {
round_up_decimal_numbers ( buffer , newpos , decimal_digits , offset , idx ) ;
}
}
}
// Set the minus sign if negative
if ( is_negative ) {
buffer [ 0 ] = ' - ' ;
}
2020-12-24 04:23:06 +01:00
zend_string * zstr = zend_string_init ( buffer , strnlen_s ( buffer ) , 0 ) ;
2020-06-19 23:45:13 +02:00
zend_string_release ( Z_STR_P ( param_z ) ) ;
ZVAL_NEW_STR ( param_z , zstr ) ;
}
2020-10-17 00:17:58 +02:00
int round_up_decimal_numbers ( _Inout_ char * buffer , _In_ int decimal_pos , _In_ int num_decimals , _In_ int offset , _In_ int lastpos )
2020-06-19 23:45:13 +02:00
{
// This helper method assumes the 'buffer' has some extra blank spaces at the beginning without the minus '-' sign.
// 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.
2020-10-17 00:17:58 +02:00
int pos = decimal_pos + num_decimals + 1 ;
2020-06-19 23:45:13 +02:00
if ( pos < lastpos ) {
short n = buffer [ pos ] - ' 0 ' ;
if ( n > = 5 ) {
// Start rounding up - starting from the digit left of pos all the way to the first digit
bool carry_over = true ;
for ( short p = pos - 1 ; p > = offset & & carry_over ; p - - ) {
if ( buffer [ p ] = = DECIMAL_POINT ) {
continue ;
}
n = buffer [ p ] - ' 0 ' ;
carry_over = ( + + n = = 10 ) ;
if ( n = = 10 ) {
n = 0 ;
}
buffer [ p ] = ' 0 ' + n ;
}
if ( carry_over ) {
buffer [ offset - 1 ] = ' 1 ' ;
}
}
if ( num_decimals = = 0 ) {
buffer [ decimal_pos ] = ' \0 ' ;
return decimal_pos ;
}
else {
buffer [ pos ] = ' \0 ' ;
return pos ;
}
}
// Do nothing and just return
return lastpos ;
}
2021-05-11 01:33:14 +02:00
} // end of anonymous namespace
////////////////////////////////////////////////////////////////////////////////////////////////
//
// *** implementations of structures used for SQLBindParameter ***
//
void sqlsrv_param : : release_data ( )
{
if ( Z_TYPE ( placeholder_z ) = = IS_STRING ) {
zend_string_release ( Z_STR ( placeholder_z ) ) ;
}
2021-07-20 00:54:59 +02:00
ZVAL_UNDEF ( & placeholder_z ) ;
buffer = NULL ;
2021-05-11 01:33:14 +02:00
param_stream = NULL ;
num_bytes_read = 0 ;
param_ptr_z = NULL ;
}
void sqlsrv_param : : copy_param_meta_ae ( _Inout_ zval * param_z , _In_ param_meta_data & meta )
{
// Always Encrypted (AE) enabled - copy the meta data from SQLDescribeParam()
sql_data_type = meta . sql_type ;
column_size = meta . column_size ;
decimal_digits = meta . decimal_digits ;
// Due to strict rules of AE, convert long to double if the sql type is decimal (numeric)
if ( Z_TYPE_P ( param_z ) = = IS_LONG & & ( sql_data_type = = SQL_DECIMAL | | sql_data_type = = SQL_NUMERIC ) ) {
convert_to_double ( param_z ) ;
}
}
bool sqlsrv_param : : prepare_param ( _In_ zval * param_ref , _Inout_ zval * param_z )
{
// For input parameters, check if the original parameter was null
was_null = ( Z_TYPE_P ( param_z ) = = IS_NULL ) ;
return true ;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param : : process_param ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
// Get param php type
param_php_type = Z_TYPE_P ( param_z ) ;
switch ( param_php_type ) {
case IS_NULL :
process_null_param ( param_z ) ;
break ;
case IS_TRUE :
case IS_FALSE :
process_bool_param ( param_z ) ;
break ;
case IS_LONG :
process_long_param ( param_z ) ;
break ;
case IS_DOUBLE :
process_double_param ( param_z ) ;
break ;
case IS_STRING :
process_string_param ( stmt , param_z ) ;
break ;
case IS_RESOURCE :
process_resource_param ( param_z ) ;
break ;
case IS_OBJECT :
process_object_param ( stmt , param_z ) ;
break ;
case IS_ARRAY :
default :
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_pos + 1 ) ;
break ;
}
}
void sqlsrv_param : : process_null_param ( _Inout_ zval * param_z )
{
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
// Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases,
2021-10-20 18:27:13 +02:00
// if the server type is a binary type, then the server expects the sql_type to be binary type
2021-05-11 01:33:14 +02:00
// as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the
2021-10-20 18:27:13 +02:00
// server. For all other server types, setting the sql_type to sql_varchar works fine.
// It must be varchar with column size 0 for ISNULL to work properly.
sql_data_type = ( encoding = = SQLSRV_ENCODING_BINARY ) ? SQL_BINARY : SQL_VARCHAR ;
2021-05-11 01:33:14 +02:00
}
c_data_type = ( encoding = = SQLSRV_ENCODING_BINARY ) ? SQL_C_BINARY : SQL_C_CHAR ;
if ( column_size = = SQLSRV_UNKNOWN_SIZE ) {
2021-10-20 18:27:13 +02:00
column_size = ( encoding = = SQLSRV_ENCODING_BINARY ) ? 1 : 0 ;
2021-05-11 01:33:14 +02:00
decimal_digits = 0 ;
}
buffer = NULL ;
buffer_length = 0 ;
strlen_or_indptr = SQL_NULL_DATA ;
}
void sqlsrv_param : : process_bool_param ( _Inout_ zval * param_z )
{
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
sql_data_type = SQL_INTEGER ;
}
c_data_type = SQL_C_SLONG ;
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
// Convert the lval to 0 or 1
convert_to_long ( param_z ) ;
buffer = & param_z - > value ;
buffer_length = sizeof ( Z_LVAL_P ( param_z ) ) ;
strlen_or_indptr = buffer_length ;
}
void sqlsrv_param : : process_long_param ( _Inout_ zval * param_z )
{
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
//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_data_type = SQL_BIGINT ;
} else {
sql_data_type = SQL_INTEGER ;
}
}
// When binding any integer, the zend_long value and its length are used as the buffer
// and buffer length. When the buffer is 8 bytes use the corresponding C type for
// 8-byte integers
# ifdef ZEND_ENABLE_ZVAL_LONG64
c_data_type = SQL_C_SBIGINT ;
# else
c_data_type = SQL_C_SLONG ;
# endif
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
buffer = & param_z - > value ;
buffer_length = sizeof ( Z_LVAL_P ( param_z ) ) ;
strlen_or_indptr = buffer_length ;
}
void sqlsrv_param : : process_double_param ( _Inout_ zval * param_z )
{
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
sql_data_type = SQL_FLOAT ;
}
// The column size and decimal digits are by default 0
// Ignore column_size and decimal_digits because they will be inferred by ODBC
c_data_type = SQL_C_DOUBLE ;
buffer = & param_z - > value ;
buffer_length = sizeof ( Z_DVAL_P ( param_z ) ) ;
strlen_or_indptr = buffer_length ;
}
bool sqlsrv_param : : derive_string_types_sizes ( _In_ zval * param_z )
{
SQLSRV_ASSERT ( encoding = = SQLSRV_ENCODING_CHAR | | encoding = = SQLSRV_ENCODING_UTF8 | | encoding = = SQLSRV_ENCODING_BINARY , " Invalid encoding in sqlsrv_param::derive_string_types_sizes " ) ;
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
switch ( encoding ) {
case SQLSRV_ENCODING_CHAR :
sql_data_type = SQL_VARCHAR ;
break ;
case SQLSRV_ENCODING_BINARY :
sql_data_type = SQL_VARBINARY ;
break ;
case SQLSRV_ENCODING_UTF8 :
sql_data_type = SQL_WVARCHAR ;
break ;
default :
break ;
}
}
bool is_numeric = is_a_numeric_type ( sql_data_type ) ;
// Derive the C Data type next
switch ( encoding ) {
case SQLSRV_ENCODING_CHAR :
c_data_type = SQL_C_CHAR ;
break ;
case SQLSRV_ENCODING_BINARY :
c_data_type = SQL_C_BINARY ;
break ;
case SQLSRV_ENCODING_UTF8 :
c_data_type = is_numeric ? SQL_C_CHAR : SQL_C_WCHAR ;
break ;
default :
break ;
}
// Derive the column size also only if it is unknown
if ( column_size = = SQLSRV_UNKNOWN_SIZE ) {
size_t char_size = ( encoding = = SQLSRV_ENCODING_UTF8 ) ? sizeof ( SQLWCHAR ) : sizeof ( char ) ;
SQLULEN byte_len = Z_STRLEN_P ( param_z ) * char_size ;
if ( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) {
column_size = SQL_SERVER_MAX_TYPE_SIZE ;
} else {
column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size ;
}
}
return is_numeric ;
}
2021-05-26 00:36:01 +02:00
bool sqlsrv_param : : convert_input_str_to_utf16 ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * param_z )
2021-05-11 01:33:14 +02:00
{
// This converts the string in param_z and stores the wide string in the member placeholder_z
char * str = Z_STRVAL_P ( param_z ) ;
SQLLEN str_length = Z_STRLEN_P ( param_z ) ;
if ( str_length > 0 ) {
sqlsrv_malloc_auto_ptr < SQLWCHAR > wide_buffer ;
unsigned int wchar_size = 0 ;
wide_buffer = utf16_string_from_mbcs_string ( encoding , reinterpret_cast < const char * > ( str ) , static_cast < int > ( str_length ) , & wchar_size , true ) ;
2021-05-26 00:36:01 +02:00
if ( wide_buffer = = 0 ) {
return false ;
2021-05-11 01:33:14 +02:00
}
wide_buffer [ wchar_size ] = L ' \0 ' ;
core : : sqlsrv_zval_stringl ( & placeholder_z , reinterpret_cast < char * > ( wide_buffer . get ( ) ) , wchar_size * sizeof ( SQLWCHAR ) ) ;
} else {
// If the string is empty, then nothing needs to be done
core : : sqlsrv_zval_stringl ( & placeholder_z , " " , 0 ) ;
}
2021-05-26 00:36:01 +02:00
return true ;
2021-05-11 01:33:14 +02:00
}
void sqlsrv_param : : process_string_param ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
bool is_numeric = derive_string_types_sizes ( param_z ) ;
// With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns.
// Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL
// or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC.
// In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits).
if ( sql_data_type = = SQL_DECIMAL | | sql_data_type = = SQL_NUMERIC ) {
adjustDecimalPrecision ( param_z , decimal_digits ) ;
}
if ( ! is_numeric & & encoding = = CP_UTF8 ) {
// Convert the input param value to wide string and save it for later
if ( Z_STRLEN_P ( param_z ) > INT_MAX ) {
LOG ( SEV_ERROR , " Convert input parameter to utf16: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
// This changes the member placeholder_z to hold the wide string
2021-05-26 00:36:01 +02:00
bool converted = convert_input_str_to_utf16 ( stmt , param_z ) ;
CHECK_CUSTOM_ERROR ( ! converted , stmt , SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE , param_pos + 1 , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
2021-05-11 01:33:14 +02:00
// Bind the wide string in placeholder_z
buffer = Z_STRVAL ( placeholder_z ) ;
buffer_length = Z_STRLEN ( placeholder_z ) ;
} else {
buffer = Z_STRVAL_P ( param_z ) ;
buffer_length = Z_STRLEN_P ( param_z ) ;
}
strlen_or_indptr = buffer_length ;
}
void sqlsrv_param : : process_resource_param ( _Inout_ zval * param_z )
{
SQLSRV_ASSERT ( encoding = = SQLSRV_ENCODING_CHAR | | encoding = = SQLSRV_ENCODING_UTF8 | | encoding = = SQLSRV_ENCODING_BINARY , " Invalid encoding in sqlsrv_param::get_resource_param_info " ) ;
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
switch ( encoding ) {
case SQLSRV_ENCODING_CHAR :
sql_data_type = SQL_VARCHAR ;
break ;
case SQLSRV_ENCODING_BINARY :
sql_data_type = SQL_VARBINARY ;
break ;
case SQLSRV_ENCODING_UTF8 :
sql_data_type = SQL_WVARCHAR ;
break ;
default :
break ;
}
}
// The column_size will be inferred by ODBC unless it is SQLSRV_UNKNOWN_SIZE
if ( column_size = = SQLSRV_UNKNOWN_SIZE ) {
column_size = 0 ;
}
switch ( encoding ) {
case SQLSRV_ENCODING_CHAR :
c_data_type = SQL_C_CHAR ;
break ;
case SQLSRV_ENCODING_BINARY :
c_data_type = SQL_C_BINARY ;
break ;
case SQLSRV_ENCODING_UTF8 :
c_data_type = SQL_C_WCHAR ;
break ;
default :
break ;
}
param_ptr_z = param_z ;
buffer = reinterpret_cast < SQLPOINTER > ( this ) ;
buffer_length = 0 ;
strlen_or_indptr = SQL_DATA_AT_EXEC ;
}
2021-05-26 00:36:01 +02:00
bool sqlsrv_param : : convert_datetime_to_string ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * param_z )
2021-05-11 01:33:14 +02:00
{
// This changes the member placeholder_z to hold the converted string of the datetime object
zval function_z ;
zval format_z ;
zval params [ 1 ] ;
ZVAL_UNDEF ( & function_z ) ;
ZVAL_UNDEF ( & format_z ) ;
ZVAL_UNDEF ( params ) ;
// If the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
// sql type, it lacks the timezone.
if ( sql_data_type = = SQL_SS_TIMESTAMPOFFSET ) {
2021-05-26 00:36:01 +02:00
ZVAL_STRINGL ( & format_z , DateTime : : DATETIMEOFFSET_FORMAT , DateTime : : DATETIMEOFFSET_FORMAT_LEN ) ;
2021-05-11 01:33:14 +02:00
} else if ( sql_data_type = = SQL_TYPE_DATE ) {
2021-05-26 00:36:01 +02:00
ZVAL_STRINGL ( & format_z , DateTime : : DATE_FORMAT , DateTime : : DATE_FORMAT_LEN ) ;
2021-05-11 01:33:14 +02:00
} else {
2021-05-26 00:36:01 +02:00
ZVAL_STRINGL ( & format_z , DateTime : : DATETIME_FORMAT , DateTime : : DATETIME_FORMAT_LEN ) ;
2021-05-11 01:33:14 +02:00
}
// call the DateTime::format member function to convert the object to a string that SQL Server understands
2021-05-26 00:36:01 +02:00
ZVAL_STRINGL ( & function_z , " format " , sizeof ( " format " ) - 1 ) ;
//core::sqlsrv_zval_stringl(&function_z, "format", sizeof("format") - 1);
2021-05-11 01:33:14 +02:00
params [ 0 ] = format_z ;
// If placeholder_z is a string, release it first before assigning a new string value
if ( Z_TYPE ( placeholder_z ) = = IS_STRING & & Z_STR ( placeholder_z ) ! = NULL ) {
zend_string_release ( Z_STR ( placeholder_z ) ) ;
}
2021-05-26 00:36:01 +02:00
2021-05-11 01:33:14 +02:00
// 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 , & placeholder_z , 1 , params ) ;
zend_string_release ( Z_STR ( format_z ) ) ;
zend_string_release ( Z_STR ( function_z ) ) ;
2021-05-26 00:36:01 +02:00
return ( zr ! = FAILURE ) ;
2021-05-11 01:33:14 +02:00
}
2021-05-26 00:36:01 +02:00
bool sqlsrv_param : : preprocess_datetime_object ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * param_z )
2021-05-11 01:33:14 +02:00
{
bool valid_class_name_found = false ;
zend_class_entry * class_entry = Z_OBJCE_P ( param_z ) ;
while ( class_entry ! = NULL ) {
SQLSRV_ASSERT ( class_entry - > name ! = NULL , " sqlsrv_param::get_object_param_info -- class_entry->name is NULL. " ) ;
if ( class_entry - > name - > len = = DateTime : : DATETIME_CLASS_NAME_LEN & & class_entry - > name ! = NULL & &
stricmp ( class_entry - > name - > val , DateTime : : DATETIME_CLASS_NAME ) = = 0 ) {
valid_class_name_found = true ;
break ;
} else {
// Check the parent
class_entry = class_entry - > parent ;
}
}
2021-05-26 00:36:01 +02:00
if ( ! valid_class_name_found ) {
return false ;
2021-05-11 01:33:14 +02:00
}
// Derive the param SQL type only if it is unknown
if ( sql_data_type = = SQL_UNKNOWN_TYPE ) {
// For SQL Server 2005 or earlier, make it a SQLSRV_SQLTYPE_DATETIME.
// Otherwise it should be SQLSRV_SQLTYPE_TIMESTAMPOFFSET because these
// are the date types of the highest precision for the server
if ( stmt - > conn - > server_version < = SERVER_VERSION_2005 ) {
sql_data_type = SQL_TYPE_TIMESTAMP ;
} else {
sql_data_type = SQL_SS_TIMESTAMPOFFSET ;
}
}
c_data_type = SQL_C_CHAR ;
// Derive the column size also only if it is unknown
if ( column_size = = SQLSRV_UNKNOWN_SIZE ) {
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 ;
}
}
2021-05-26 00:36:01 +02:00
return true ;
2021-05-11 01:33:14 +02:00
}
void sqlsrv_param : : process_object_param ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
// Assume the param refers to a DateTime object since it's the only type the drivers support.
// Verification occurs in the calling function as the drivers convert the DateTime object
// to a string before sending it to the server.
2021-05-26 00:36:01 +02:00
bool succeeded = preprocess_datetime_object ( stmt , param_z ) ;
if ( succeeded ) {
succeeded = convert_datetime_to_string ( stmt , param_z ) ;
}
CHECK_CUSTOM_ERROR ( ! succeeded , stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
2021-05-11 01:33:14 +02:00
buffer = Z_STRVAL ( placeholder_z ) ;
buffer_length = Z_STRLEN ( placeholder_z ) - 1 ;
strlen_or_indptr = buffer_length ;
}
void sqlsrv_param : : bind_param ( _Inout_ sqlsrv_stmt * stmt )
{
if ( was_null ) {
strlen_or_indptr = SQL_NULL_DATA ;
}
core : : SQLBindParameter ( stmt , param_pos + 1 , direction , c_data_type , sql_data_type , column_size , decimal_digits , buffer , buffer_length , & strlen_or_indptr ) ;
}
void sqlsrv_param : : init_data_from_zval ( _Inout_ sqlsrv_stmt * stmt )
{
// Get the stream from the param zval value
num_bytes_read = 0 ;
param_stream = NULL ;
core : : sqlsrv_php_stream_from_zval_no_verify ( * stmt , param_stream , param_ptr_z ) ;
}
bool sqlsrv_param : : send_data_packet ( _Inout_ sqlsrv_stmt * stmt )
{
// Check EOF first
if ( php_stream_eof ( param_stream ) ) {
// But 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 ( ) ;
}
// Reset num_bytes_read
num_bytes_read = 0 ;
return false ;
} else {
// Read the data from the stream, send it via SQLPutData and track how much is already sent.
char buffer [ PHP_STREAM_BUFFER_SIZE + 1 ] = { ' \0 ' } ;
std : : size_t buffer_size = sizeof ( buffer ) - 3 ; // -3 to preserve enough space for a cut off UTF-8 character
std : : size_t read = php_stream_read ( param_stream , buffer , buffer_size ) ;
if ( read > UINT_MAX ) {
LOG ( SEV_ERROR , " PHP stream: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
num_bytes_read + = read ;
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 ) ;
} else if ( read > 0 ) {
// If this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
// twice.
// If we support other encondings in the future, we'll simply need to read a single byte and then retry the conversion
// since all other MBCS supported by SQL Server are 2 byte maximum size.
if ( encoding = = CP_UTF8 ) {
// The size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is an
// expansion of 2x the UTF-8 size.
SQLWCHAR wbuffer [ PHP_STREAM_BUFFER_SIZE + 1 ] = { L ' \0 ' } ;
int wbuffer_size = static_cast < int > ( sizeof ( wbuffer ) / sizeof ( SQLWCHAR ) ) ;
DWORD last_error_code = ERROR_SUCCESS ;
// The buffer_size is the # of wchars. Set to buffer_size / 2
# ifndef _WIN32
int wsize = SystemLocale : : ToUtf16Strict ( encoding , buffer , static_cast < int > ( read ) , wbuffer , wbuffer_size , & last_error_code ) ;
# else
int wsize = MultiByteToWideChar ( encoding , MB_ERR_INVALID_CHARS , buffer , static_cast < int > ( read ) , wbuffer , wbuffer_size ) ;
last_error_code = GetLastError ( ) ;
# endif // !_WIN32
if ( wsize = = 0 & & last_error_code = = ERROR_NO_UNICODE_TRANSLATION ) {
// This will calculate how many bytes were cut off from the last UTF-8 character and read that many more
// in, then reattempt the conversion. If it fails the second time, then an error is returned.
size_t need_to_read = calc_utf8_missing ( stmt , buffer , read ) ;
// read the missing bytes
size_t new_read = php_stream_read ( param_stream , static_cast < char * > ( buffer ) + read , need_to_read ) ;
// if the bytes couldn't be read, then we return an error
CHECK_CUSTOM_ERROR ( new_read ! = need_to_read , stmt , SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE , get_last_error_message ( ERROR_NO_UNICODE_TRANSLATION ) ) {
throw core : : CoreException ( ) ;
}
// Try the conversion again with the complete character
# ifndef _WIN32
wsize = SystemLocale : : ToUtf16Strict ( encoding , buffer , static_cast < int > ( read + new_read ) , wbuffer , static_cast < int > ( sizeof ( wbuffer ) / sizeof ( SQLWCHAR ) ) ) ;
# else
wsize = MultiByteToWideChar ( 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 ) ) {
throw core : : CoreException ( ) ;
}
}
core : : SQLPutData ( stmt , wbuffer , wsize * sizeof ( SQLWCHAR ) ) ;
}
else {
core : : SQLPutData ( stmt , buffer , read ) ;
} // NOT UTF8
} // read > 0
return true ;
} // NOT EOF
}
bool sqlsrv_param_inout : : prepare_param ( _In_ zval * param_ref , _Inout_ zval * param_z )
{
// Save the output param reference now
param_ptr_z = param_ref ;
int type = Z_TYPE_P ( param_z ) ;
was_null = ( type = = IS_NULL ) ;
was_bool = ( type = = IS_TRUE | | type = = IS_FALSE ) ;
if ( direction = = SQL_PARAM_INPUT_OUTPUT ) {
// 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.
bool matched = false ;
switch ( php_out_type ) {
case SQLSRV_PHPTYPE_INT :
if ( was_null | | was_bool ) {
convert_to_long ( param_z ) ;
}
matched = ( Z_TYPE_P ( param_z ) = = IS_LONG ) ;
break ;
case SQLSRV_PHPTYPE_FLOAT :
if ( was_null ) {
convert_to_double ( param_z ) ;
}
matched = ( Z_TYPE_P ( param_z ) = = IS_DOUBLE ) ;
break ;
case SQLSRV_PHPTYPE_STRING :
// anything can be converted to a string
convert_to_string ( param_z ) ;
matched = true ;
break ;
case SQLSRV_PHPTYPE_NULL :
case SQLSRV_PHPTYPE_DATETIME :
case SQLSRV_PHPTYPE_STREAM :
default :
SQLSRV_ASSERT ( false , " sqlsrv_param_inout::prepare_param -- invalid type for an output parameter. " ) ;
break ;
}
return matched ;
} else if ( direction = = SQL_PARAM_OUTPUT ) {
// If the user specifies a certain type for an output parameter, we have to convert the zval
// to that type so that when the buffer is filled, the type is correct. But first,
// should check if a LOB type is specified.
switch ( php_out_type ) {
case SQLSRV_PHPTYPE_INT :
convert_to_long ( param_z ) ;
break ;
case SQLSRV_PHPTYPE_FLOAT :
convert_to_double ( param_z ) ;
break ;
case SQLSRV_PHPTYPE_STRING :
convert_to_string ( param_z ) ;
break ;
case SQLSRV_PHPTYPE_NULL :
case SQLSRV_PHPTYPE_DATETIME :
case SQLSRV_PHPTYPE_STREAM :
default :
SQLSRV_ASSERT ( false , " sqlsrv_param_inout::prepare_param -- invalid type for an output parameter " ) ;
break ;
}
return true ;
} else {
SQLSRV_ASSERT ( false , " sqlsrv_param_inout::prepare_param -- wrong param direction. " ) ;
}
return false ;
}
// Derives the ODBC C type constant that matches the PHP type and/or the encoding given
// If SQL type or column size is unknown, derives the appropriate values as well using the provided param zval and encoding
void sqlsrv_param_inout : : process_param ( _Inout_ sqlsrv_stmt * stmt , zval * param_z )
{
// Get param php type NOW because the original parameter might have been converted beforehand
param_php_type = Z_TYPE_P ( param_z ) ;
switch ( param_php_type ) {
case IS_LONG :
process_long_param ( param_z ) ;
break ;
case IS_DOUBLE :
process_double_param ( param_z ) ;
break ;
case IS_STRING :
process_string_param ( stmt , param_z ) ;
break ;
default :
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE , param_pos + 1 ) ;
break ;
}
// Save the pointer to the statement object
this - > stmt = stmt ;
}
void sqlsrv_param_inout : : process_string_param ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
bool is_numeric_type = derive_string_types_sizes ( param_z ) ;
buffer = Z_STRVAL_P ( param_z ) ;
buffer_length = Z_STRLEN_P ( param_z ) ;
if ( ZSTR_IS_INTERNED ( Z_STR_P ( param_z ) ) ) {
// PHP 5.4 added interned strings, and since we obviously want to change that string here in some fashion,
// we reallocate the string if it's interned
core : : sqlsrv_zval_stringl ( param_z , static_cast < const char * > ( buffer ) , buffer_length ) ;
// reset buffer and its length
buffer = Z_STRVAL_P ( param_z ) ;
buffer_length = Z_STRLEN_P ( param_z ) ;
}
// If it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
2021-05-26 00:36:01 +02:00
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXT SQL type,
2021-05-11 01:33:14 +02:00
// convert it to wchar first
if ( direction = = SQL_PARAM_INPUT_OUTPUT & &
( c_data_type = = SQL_C_WCHAR | |
( c_data_type = = SQL_C_BINARY & &
( sql_data_type = = SQL_WCHAR | | sql_data_type = = SQL_WVARCHAR | | sql_data_type = = SQL_WLONGVARCHAR ) ) ) ) {
if ( buffer_length > 0 ) {
sqlsrv_malloc_auto_ptr < SQLWCHAR > wide_buffer ;
unsigned int wchar_size = 0 ;
wide_buffer = utf16_string_from_mbcs_string ( SQLSRV_ENCODING_UTF8 , reinterpret_cast < const char * > ( buffer ) , static_cast < int > ( buffer_length ) , & wchar_size ) ;
CHECK_CUSTOM_ERROR ( wide_buffer = = 0 , stmt , SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE , param_pos + 1 , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
wide_buffer [ wchar_size ] = L ' \0 ' ;
core : : sqlsrv_zval_stringl ( param_z , reinterpret_cast < char * > ( wide_buffer . get ( ) ) , wchar_size * sizeof ( SQLWCHAR ) ) ;
buffer = Z_STRVAL_P ( param_z ) ;
buffer_length = Z_STRLEN_P ( param_z ) ;
}
}
strlen_or_indptr = buffer_length ;
// Since this is an output string, assure there is enough space to hold the requested size and
// update all the variables accordingly (param_z, buffer, buffer_length, and strlen_or_indptr)
resize_output_string_buffer ( param_z , is_numeric_type ) ;
if ( is_numeric_type ) {
encoding = SQLSRV_ENCODING_CHAR ;
}
// For output parameters, if we set the column_size to be same as the buffer_len,
// then if there is a truncation due to the data coming from the server being
// greater than the column_size, we don't get any truncation error. In order to
// avoid this silent truncation, we set the column_size to be "MAX" size for
// string types. This will guarantee that there is no silent truncation for
// output parameters.
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
if ( direction = = SQL_PARAM_OUTPUT & & ! stmt - > conn - > ce_option . enabled ) {
switch ( sql_data_type ) {
case SQL_VARBINARY :
case SQL_VARCHAR :
case SQL_WVARCHAR :
column_size = SQL_SS_LENGTH_UNLIMITED ;
break ;
default :
break ;
}
}
}
// Called when the output parameter is ready to be finalized, using the value stored in param_ptr_z
void sqlsrv_param_inout : : finalize_output_value ( )
{
if ( param_ptr_z = = NULL ) {
return ;
}
zval * value_z = Z_REFVAL_P ( param_ptr_z ) ;
switch ( Z_TYPE_P ( value_z ) ) {
case IS_STRING :
finalize_output_string ( ) ;
break ;
case IS_LONG :
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if ( strlen_or_indptr = = SQL_NULL_DATA ) {
ZVAL_NULL ( value_z ) ;
} else if ( was_bool ) {
convert_to_boolean ( value_z ) ;
} else {
ZVAL_LONG ( value_z , static_cast < int > ( Z_LVAL_P ( value_z ) ) ) ;
}
break ;
case IS_DOUBLE :
// For a long or a float, simply check if NULL was returned and if so, set the parameter to a PHP null
if ( strlen_or_indptr = = SQL_NULL_DATA ) {
ZVAL_NULL ( value_z ) ;
} else if ( php_out_type = = SQLSRV_PHPTYPE_INT ) {
// First check if its value is out of range
double dval = Z_DVAL_P ( value_z ) ;
if ( dval > INT_MAX | | dval < INT_MIN ) {
CHECK_CUSTOM_ERROR ( true , stmt , SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED ) {
throw core : : CoreException ( ) ;
}
}
// Even if the output param is a boolean, still convert to a long
// integer first to take care of rounding
convert_to_long ( value_z ) ;
if ( was_bool ) {
convert_to_boolean ( value_z ) ;
}
}
break ;
default :
SQLSRV_ASSERT ( false , " Should not have reached here - invalid output parameter type in sqlsrv_param_inout::finalize_output_value. " ) ;
break ;
}
value_z = NULL ;
param_ptr_z = NULL ; // Do not keep the reference now that the output param has been processed
}
// A helper method called by finalize_output_value() to finalize output string parameters
void sqlsrv_param_inout : : finalize_output_string ( )
{
zval * value_z = Z_REFVAL_P ( param_ptr_z ) ;
// Adjust the length of the string to the value returned by SQLBindParameter in the strlen_or_indptr argument
if ( strlen_or_indptr = = 0 ) {
core : : sqlsrv_zval_stringl ( value_z , " " , 0 ) ;
return ;
}
if ( strlen_or_indptr = = SQL_NULL_DATA ) {
zend_string_release ( Z_STR_P ( value_z ) ) ;
ZVAL_NULL ( value_z ) ;
return ;
}
// If there was more to output than buffer size to hold it, then throw a truncation error
SQLLEN str_len = strlen_or_indptr ;
char * str = Z_STRVAL_P ( value_z ) ;
int null_size = 0 ;
switch ( encoding ) {
case SQLSRV_ENCODING_UTF8 :
null_size = sizeof ( SQLWCHAR ) ; // The string isn't yet converted to UTF-8, still UTF-16
break ;
case SQLSRV_ENCODING_SYSTEM :
null_size = sizeof ( SQLCHAR ) ;
break ;
case SQLSRV_ENCODING_BINARY :
null_size = 0 ;
break ;
default :
SQLSRV_ASSERT ( false , " Should not have reached here - invalid encoding in sqlsrv_param_inout::process_output_string. " ) ;
break ;
}
CHECK_CUSTOM_ERROR ( str_len > ( buffer_length - null_size ) , stmt , SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
2021-05-26 00:36:01 +02:00
// For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
2021-05-11 01:33:14 +02:00
// A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains data up to the
// original buffer_length and is NULL terminated.
// The IF statement can be true when using connection pooling with unixODBC 2.3.4.
if ( str_len = = SQL_NO_TOTAL ) {
str_len = buffer_length - null_size ;
}
if ( encoding = = SQLSRV_ENCODING_BINARY ) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The
// original allocation null terminates the buffer already.
if ( str_len < buffer_length ) {
str [ str_len ] = ' \0 ' ;
}
core : : sqlsrv_zval_stringl ( value_z , str , str_len ) ;
}
else {
if ( encoding ! = SQLSRV_ENCODING_CHAR ) {
char * outString = NULL ;
SQLLEN outLen = 0 ;
bool result = convert_string_from_utf16 ( 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 ( ) ;
}
if ( stmt - > format_decimals & & ( sql_data_type = = SQL_DECIMAL | | sql_data_type = = SQL_NUMERIC ) ) {
format_decimal_numbers ( NO_CHANGE_DECIMAL_PLACES , decimal_digits , outString , & outLen ) ;
}
core : : sqlsrv_zval_stringl ( value_z , outString , outLen ) ;
sqlsrv_free ( outString ) ;
}
else {
if ( stmt - > format_decimals & & ( sql_data_type = = SQL_DECIMAL | | sql_data_type = = SQL_NUMERIC ) ) {
format_decimal_numbers ( NO_CHANGE_DECIMAL_PLACES , decimal_digits , str , & str_len ) ;
}
core : : sqlsrv_zval_stringl ( value_z , str , str_len ) ;
}
}
value_z = NULL ;
}
void sqlsrv_param_inout : : resize_output_string_buffer ( _Inout_ zval * param_z , _In_ bool is_numeric_type )
{
// Prerequisites: buffer, buffer_length, column_size, and strlen_or_indptr have been set to a known value
// Purpose:
// Verify there is enough space to hold the output string parameter, and allocate if necessary. The param_z
// is updated to contain the new buffer with the correct size and its reference is incremented, and all required
// values for SQLBindParameter will also be updated.
SQLLEN original_len = buffer_length ;
SQLLEN expected_len ;
SQLLEN buffer_null_extra ;
SQLLEN elem_size ;
// Calculate the size of each 'element' represented by column_size. WCHAR is the size of a wide char (2), and so is
// a N(VAR)CHAR/NTEXT field being returned as a binary field.
elem_size = ( c_data_type = = SQL_C_WCHAR | |
( c_data_type = = SQL_C_BINARY & &
( sql_data_type = = SQL_WCHAR | | sql_data_type = = SQL_WVARCHAR | | sql_data_type = = SQL_WLONGVARCHAR ) ) ) ? sizeof ( SQLWCHAR ) : sizeof ( SQLCHAR ) ;
// account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning
SQLULEN field_size = column_size ;
// With AE enabled, column_size is already retrieved from SQLDescribeParam, but column_size
// does not include the negative sign or decimal place for numeric values
2021-05-26 00:36:01 +02:00
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
// and numerics with precision/scale specified
2021-05-11 01:33:14 +02:00
if ( is_numeric_type ) {
// Include the possible negative sign
field_size + = elem_size ;
// Include the decimal dot for output params by adding elem_size
if ( decimal_digits > 0 ) {
field_size + = elem_size ;
}
}
if ( column_size = = SQL_SS_LENGTH_UNLIMITED ) {
field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size ;
}
expected_len = field_size * elem_size + elem_size ;
// Binary fields aren't null terminated, so we need to account for that in our buffer length calcuations
buffer_null_extra = ( c_data_type = = SQL_C_BINARY ) ? elem_size : 0 ;
2020-06-19 23:45:13 +02:00
2021-05-11 01:33:14 +02:00
// Increment to include the null terminator since the Zend length doesn't include the null terminator
buffer_length + = elem_size ;
2020-06-19 23:45:13 +02:00
2021-05-11 01:33:14 +02:00
// if the current buffer size is smaller than the necessary size, resize the buffer and set the zval to the new
// length.
if ( buffer_length < expected_len ) {
SQLSRV_ASSERT ( expected_len > = expected_len - buffer_null_extra , " Integer overflow/underflow caused a corrupt field length. " ) ;
// allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since
// we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about
// not having a NULL terminator on a string.
zend_string * param_z_string = zend_string_realloc ( Z_STR_P ( param_z ) , expected_len , 0 ) ;
// A zval string len doesn't include the null. This calculates the length it should be
// regardless of whether the ODBC type contains the NULL or not.
// initialize the newly allocated space
char * p = ZSTR_VAL ( param_z_string ) ;
p = p + original_len ;
memset ( p , ' \0 ' , expected_len - original_len ) ;
ZVAL_NEW_STR ( param_z , param_z_string ) ;
// buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the
// buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY
buffer_length = Z_STRLEN_P ( param_z ) - buffer_null_extra ;
// Zend string length doesn't include the null terminator
ZSTR_LEN ( Z_STR_P ( param_z ) ) - = elem_size ;
}
buffer = Z_STRVAL_P ( param_z ) ;
// The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which
// may be less than the size of the buffer since the output may be more than the input. If it is greater,
// then the error 22001 is returned by ODBC.
if ( strlen_or_indptr > buffer_length - ( elem_size - buffer_null_extra ) ) {
strlen_or_indptr = buffer_length - ( elem_size - buffer_null_extra ) ;
}
}
2021-05-26 00:36:01 +02:00
// Change the column encoding based on the sql data type
/*static*/ void sqlsrv_param_tvp : : sql_type_to_encoding ( _In_ SQLSMALLINT sql_type , _Inout_ SQLSRV_ENCODING * encoding )
{
switch ( sql_type ) {
case SQL_BIGINT :
case SQL_DECIMAL :
case SQL_NUMERIC :
case SQL_BIT :
case SQL_INTEGER :
case SQL_SMALLINT :
case SQL_TINYINT :
case SQL_FLOAT :
case SQL_REAL :
* encoding = SQLSRV_ENCODING_CHAR ;
break ;
case SQL_BINARY :
case SQL_LONGVARBINARY :
case SQL_VARBINARY :
case SQL_SS_UDT :
* encoding = SQLSRV_ENCODING_BINARY ;
break ;
default :
// Do nothing
break ;
}
}
2021-06-02 21:16:51 +02:00
void sqlsrv_param_tvp : : get_tvp_metadata ( _In_ sqlsrv_stmt * stmt , _In_ zend_string * table_type_name , _In_ zend_string * schema_name )
2021-05-26 00:36:01 +02:00
{
SQLHANDLE chstmt = SQL_NULL_HANDLE ;
SQLRETURN rc ;
SQLSMALLINT data_type , dec_digits ;
SQLINTEGER col_size ;
SQLLEN cb_data_type , cb_col_size , cb_dec_digits ;
2021-06-02 21:16:51 +02:00
char * table_type = ZSTR_VAL ( table_type_name ) ;
2021-05-26 00:36:01 +02:00
core : : SQLAllocHandle ( SQL_HANDLE_STMT , * ( stmt - > conn ) , & chstmt ) ;
rc = SQLSetStmtAttr ( chstmt , SQL_SOPT_SS_NAME_SCOPE , ( SQLPOINTER ) SQL_SS_NAME_SCOPE_TABLE_TYPE , SQL_IS_UINTEGER ) ;
CHECK_CUSTOM_ERROR ( ! SQL_SUCCEEDED ( rc ) , stmt , SQLSRV_ERROR_TVP_FETCH_METADATA , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
// Check table type name and see if the schema is specified. Otherwise, assume DBO
2021-06-02 21:16:51 +02:00
if ( schema_name ! = NULL ) {
char * schema = ZSTR_VAL ( schema_name ) ;
rc = SQLColumns ( chstmt , NULL , 0 , reinterpret_cast < SQLCHAR * > ( schema ) , SQL_NTS , reinterpret_cast < SQLCHAR * > ( table_type ) , SQL_NTS , NULL , 0 ) ;
2021-05-26 00:36:01 +02:00
} else {
2021-06-02 21:16:51 +02:00
rc = SQLColumns ( chstmt , NULL , 0 , NULL , SQL_NTS , reinterpret_cast < SQLCHAR * > ( table_type ) , SQL_NTS , NULL , 0 ) ;
2021-05-26 00:36:01 +02:00
}
CHECK_CUSTOM_ERROR ( ! SQL_SUCCEEDED ( rc ) , stmt , SQLSRV_ERROR_TVP_FETCH_METADATA , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
SQLSRV_ENCODING stmt_encoding = ( stmt - > encoding ( ) = = SQLSRV_ENCODING_DEFAULT ) ? stmt - > conn - > encoding ( ) : stmt - > encoding ( ) ;
if ( rc = = SQL_SUCCESS | | rc = = SQL_SUCCESS_WITH_INFO ) {
SQLBindCol ( chstmt , 5 , SQL_C_SSHORT , & data_type , 0 , & cb_data_type ) ;
SQLBindCol ( chstmt , 7 , SQL_C_SLONG , & col_size , 0 , & cb_col_size ) ;
SQLBindCol ( chstmt , 9 , SQL_C_SSHORT , & dec_digits , 0 , & cb_dec_digits ) ;
SQLUSMALLINT pos = 0 ;
while ( SQL_SUCCESS = = rc ) {
rc = SQLFetch ( chstmt ) ;
if ( rc = = SQL_NO_DATA ) {
CHECK_CUSTOM_ERROR ( tvp_columns . size ( ) = = 0 , stmt , SQLSRV_ERROR_TVP_FETCH_METADATA , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
break ;
}
sqlsrv_malloc_auto_ptr < sqlsrv_param_tvp > param_ptr ;
// The SQL data type is used to derive the column encoding
SQLSRV_ENCODING column_encoding = stmt_encoding ;
sql_type_to_encoding ( data_type , & column_encoding ) ;
param_ptr = new ( sqlsrv_malloc ( sizeof ( sqlsrv_param_tvp ) ) ) sqlsrv_param_tvp ( pos , column_encoding , data_type , col_size , dec_digits , this ) ;
param_ptr - > num_rows = this - > num_rows ; // Each column inherits the number of rows from the TVP
2021-07-20 00:54:59 +02:00
tvp_columns [ pos ] = param_ptr . get ( ) ;
2021-05-26 00:36:01 +02:00
param_ptr . transferred ( ) ;
pos + + ;
}
} else {
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_TVP_FETCH_METADATA , param_pos + 1 ) ;
}
SQLCloseCursor ( chstmt ) ;
SQLFreeHandle ( SQL_HANDLE_STMT , chstmt ) ;
}
void sqlsrv_param_tvp : : release_data ( )
{
2021-07-20 00:54:59 +02:00
// Clean up tvp_columns
std : : map < SQLUSMALLINT , sqlsrv_param_tvp * > : : iterator it ;
for ( it = tvp_columns . begin ( ) ; it ! = tvp_columns . end ( ) ; + + it ) {
sqlsrv_param_tvp * ptr = it - > second ;
if ( ptr ) {
ptr - > release_data ( ) ;
sqlsrv_free ( ptr ) ;
}
2021-05-26 00:36:01 +02:00
}
2021-07-20 00:54:59 +02:00
tvp_columns . clear ( ) ;
2021-05-26 00:36:01 +02:00
sqlsrv_param : : release_data ( ) ;
}
void sqlsrv_param_tvp : : process_param ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
if ( sql_data_type = = SQL_SS_TABLE ) {
// This is a table-valued parameter
param_php_type = IS_ARRAY ;
c_data_type = SQL_C_DEFAULT ;
// The decimal_digits must be 0 for TVP
decimal_digits = 0 ;
// The column_size for a TVP is the row array size
// The following method will verify the input array and also derive num_rows
this - > num_rows = 0 ;
int num_columns = parse_tv_param_arrays ( stmt , param_z ) ;
column_size = num_rows ;
strlen_or_indptr = ( num_columns = = 0 ) ? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC ;
} else {
// This is one of the constituent columns of the table-valued parameter
// The column value of the first row is already saved in member variable param_ptr_z
process_param_column_value ( stmt ) ;
}
}
int sqlsrv_param_tvp : : parse_tv_param_arrays ( _Inout_ sqlsrv_stmt * stmt , _Inout_ zval * param_z )
{
// If this is not a table-valued parameter, simply return
if ( sql_data_type ! = SQL_SS_TABLE ) {
return 0 ;
}
// This method verifies if the table-valued parameter (i.e. param_z) provided by the user is valid.
// The number of columns in the given table-valued parameter is returned, which may be zero.
HashTable * inputs_ht = Z_ARRVAL_P ( param_z ) ;
zend_string * tvp_name = NULL ;
2021-06-02 21:16:51 +02:00
zend_string * schema_name = NULL ;
2021-05-26 00:36:01 +02:00
zval * tvp_data_z = NULL ;
HashPosition pos ;
zend_hash_internal_pointer_reset_ex ( inputs_ht , & pos ) ;
if ( zend_hash_has_more_elements_ex ( inputs_ht , & pos ) = = SUCCESS ) {
zend_ulong num_index = - 1 ;
size_t key_len = 0 ;
int key_type = zend_hash_get_current_key ( inputs_ht , & tvp_name , & num_index ) ;
if ( key_type = = HASH_KEY_IS_STRING ) {
key_len = ZSTR_LEN ( tvp_name ) ;
tvp_data_z = zend_hash_get_current_data_ex ( inputs_ht , & pos ) ;
}
CHECK_CUSTOM_ERROR ( ( key_type = = HASH_KEY_IS_LONG | | key_len = = 0 ) , stmt , SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
}
2021-06-02 21:16:51 +02:00
2021-05-26 00:36:01 +02:00
// TODO: Find the docs page somewhere that says a TVP can not be null but it may have null columns??
CHECK_CUSTOM_ERROR ( tvp_data_z = = NULL | | Z_TYPE_P ( tvp_data_z ) = = IS_NULL | | Z_TYPE_P ( tvp_data_z ) ! = IS_ARRAY , stmt , SQLSRV_ERROR_TVP_INVALID_INPUTS , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
2021-09-29 21:27:52 +02:00
// Save the TVP type name for SQLSetDescField later
buffer = ZSTR_VAL ( tvp_name ) ;
buffer_length = SQL_NTS ;
2021-06-02 21:16:51 +02:00
// Check if schema is provided by the user
if ( zend_hash_move_forward_ex ( inputs_ht , & pos ) = = SUCCESS ) {
zval * schema_z = zend_hash_get_current_data_ex ( inputs_ht , & pos ) ;
if ( schema_z ! = NULL & & Z_TYPE_P ( schema_z ) = = IS_STRING ) {
schema_name = Z_STR_P ( schema_z ) ;
2021-09-29 21:27:52 +02:00
ZVAL_NEW_STR ( & placeholder_z , schema_name ) ;
2021-06-02 21:16:51 +02:00
}
}
2021-05-26 00:36:01 +02:00
// Save the TVP multi-dim array data, which should be something like this
// [
// [r1c1, r1c2, r1c3],
// [r2c1, r2c2, r2c3],
// [r3c1, r3c2, r3c3]
// ]
param_ptr_z = tvp_data_z ;
HashTable * rows_ht = Z_ARRVAL_P ( tvp_data_z ) ;
this - > num_rows = zend_hash_num_elements ( rows_ht ) ;
if ( this - > num_rows = = 0 ) {
// TVP has no data
return 0 ;
}
// Given the table type name, get its column meta data next
size_t total_num_columns = 0 ;
2021-06-02 21:16:51 +02:00
get_tvp_metadata ( stmt , tvp_name , schema_name ) ;
2021-05-26 00:36:01 +02:00
total_num_columns = tvp_columns . size ( ) ;
// (1) Is the array empty?
// (2) Check individual rows and see if their sizes are consistent?
zend_ulong id = - 1 ;
zend_string * key = NULL ;
zval * row_z = NULL ;
int num_columns = 0 ;
int type = HASH_KEY_NON_EXISTENT ;
// Loop through the rows to check the number of columns
ZEND_HASH_FOREACH_KEY_VAL ( rows_ht , id , key , row_z ) {
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG ;
CHECK_CUSTOM_ERROR ( type = = HASH_KEY_IS_STRING , stmt , SQLSRV_ERROR_TVP_STRING_KEYS , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
if ( Z_ISREF_P ( row_z ) ) {
ZVAL_DEREF ( row_z ) ;
}
// Individual row must be an array
CHECK_CUSTOM_ERROR ( Z_TYPE_P ( row_z ) ! = IS_ARRAY , stmt , SQLSRV_ERROR_TVP_ROW_NOT_ARRAY , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
// Are all the TVP's rows the same size
num_columns = zend_hash_num_elements ( Z_ARRVAL_P ( row_z ) ) ;
CHECK_CUSTOM_ERROR ( num_columns ! = total_num_columns , stmt , SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE , param_pos + 1 , total_num_columns ) {
throw core : : CoreException ( ) ;
}
} ZEND_HASH_FOREACH_END ( ) ;
// Return the number of columns
return num_columns ;
}
void sqlsrv_param_tvp : : process_param_column_value ( _Inout_ sqlsrv_stmt * stmt )
{
// This is one of the constituent columns of the table-valued parameter
// The corresponding column value of the TVP's first row is already saved in
// the member variable param_ptr_z, which may be a NULL value
zval * data_z = param_ptr_z ;
param_php_type = is_a_string_type ( sql_data_type ) ? IS_STRING : Z_TYPE_P ( data_z ) ;
switch ( param_php_type ) {
case IS_TRUE :
case IS_FALSE :
case IS_LONG :
case IS_DOUBLE :
sqlsrv_param : : process_param ( stmt , data_z ) ;
buffer = & placeholder_z . value ; // use placeholder zval for binding later
break ;
case IS_RESOURCE :
sqlsrv_param : : process_resource_param ( data_z ) ;
break ;
case IS_STRING :
case IS_OBJECT :
if ( param_php_type = = IS_STRING ) {
derive_string_types_sizes ( data_z ) ;
} else {
// If preprocessing a datetime object fails, throw an error of invalid php type
bool succeeded = preprocess_datetime_object ( stmt , data_z ) ;
CHECK_CUSTOM_ERROR ( ! succeeded , stmt , SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE , parent_tvp - > param_pos + 1 , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
}
buffer = reinterpret_cast < SQLPOINTER > ( this ) ;
buffer_length = 0 ;
strlen_or_indptr = SQL_DATA_AT_EXEC ;
break ;
case IS_NULL :
process_null_param_value ( stmt ) ;
break ;
default :
THROW_CORE_ERROR ( stmt , SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE , parent_tvp - > param_pos + 1 , param_pos + 1 ) ;
break ;
}
// Release the reference
param_ptr_z = NULL ;
}
void sqlsrv_param_tvp : : process_null_param_value ( _Inout_ sqlsrv_stmt * stmt )
{
// This is one of the constituent columns of the table-valued parameter
// This method is called when the corresponding column value of the TVP's first row is NULL
// So keep looking in the subsequent rows and find the first non-NULL value in the same column
HashTable * rows_ht = Z_ARRVAL_P ( parent_tvp - > param_ptr_z ) ;
zval * row_z = NULL ;
zval * value_z = NULL ;
int php_type = IS_NULL ;
int row_id = 1 ; // Start from the second row
while ( ( row_z = zend_hash_index_find ( rows_ht , row_id + + ) ) ! = NULL ) {
if ( Z_ISREF_P ( row_z ) ) {
ZVAL_DEREF ( row_z ) ;
}
value_z = zend_hash_index_find ( Z_ARRVAL_P ( row_z ) , param_pos ) ;
php_type = Z_TYPE_P ( value_z ) ;
if ( php_type ! = IS_NULL ) {
// Save this non-NULL value before calling process_param_column_value()
param_ptr_z = value_z ;
process_param_column_value ( stmt ) ;
break ;
}
}
if ( php_type = = IS_NULL ) {
// This means that the entire column contains nothing but NULLs
sqlsrv_param : : process_null_param ( param_ptr_z ) ;
2021-06-08 21:37:28 +02:00
}
2021-05-26 00:36:01 +02:00
}
void sqlsrv_param_tvp : : bind_param ( _Inout_ sqlsrv_stmt * stmt )
2021-06-08 21:37:28 +02:00
{
2021-05-26 00:36:01 +02:00
core : : SQLBindParameter ( stmt , param_pos + 1 , direction , c_data_type , sql_data_type , column_size , decimal_digits , buffer , buffer_length , & strlen_or_indptr ) ;
2021-06-08 21:37:28 +02:00
// No need to continue if this is one of the constituent columns of the table-valued parameter
if ( sql_data_type ! = SQL_SS_TABLE ) {
return ;
}
2021-05-26 00:36:01 +02:00
if ( num_rows = = 0 ) {
// TVP has no data
return ;
}
2021-09-29 21:27:52 +02:00
// Set Table-Valued parameter type name (and the schema where it is defined)
SQLHDESC hIpd = NULL ;
core : : SQLGetStmtAttr ( stmt , SQL_ATTR_IMP_PARAM_DESC , & hIpd , 0 , 0 ) ;
if ( buffer ! = NULL ) {
// SQL_CA_SS_TYPE_NAME is optional for stored procedure calls, but it must be
// specified for SQL statements that are not procedure calls to enable the
// server to determine the type of the table-valued parameter.
char * tvp_name = reinterpret_cast < char * > ( buffer ) ;
SQLRETURN r = : : SQLSetDescField ( hIpd , param_pos + 1 , SQL_CA_SS_TYPE_NAME , reinterpret_cast < SQLCHAR * > ( tvp_name ) , SQL_NTS ) ;
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
}
if ( Z_TYPE ( placeholder_z ) = = IS_STRING ) {
// If the table type for the table-valued parameter is defined in a different
// schema than the default, SQL_CA_SS_SCHEMA_NAME must be specified. If not,
// the server will not be able to determine the type of the table-valued parameter.
char * schema_name = Z_STRVAL ( placeholder_z ) ;
SQLRETURN r = : : SQLSetDescField ( hIpd , param_pos + 1 , SQL_CA_SS_SCHEMA_NAME , reinterpret_cast < SQLCHAR * > ( schema_name ) , SQL_NTS ) ;
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
// Free and reset the placeholder_z
zend_string_release ( Z_STR ( placeholder_z ) ) ;
ZVAL_UNDEF ( & placeholder_z ) ;
}
2021-05-26 00:36:01 +02:00
// Bind the TVP columns one by one
// Register this object first using SQLSetDescField() for sending TVP data post execution
2021-06-08 21:37:28 +02:00
SQLHDESC desc ;
core : : SQLGetStmtAttr ( stmt , SQL_ATTR_APP_PARAM_DESC , & desc , 0 , 0 ) ;
SQLRETURN r = : : SQLSetDescField ( desc , param_pos + 1 , SQL_DESC_DATA_PTR , reinterpret_cast < SQLPOINTER > ( this ) , 0 ) ;
2021-05-26 00:36:01 +02:00
CHECK_SQL_ERROR_OR_WARNING ( r , stmt ) {
throw core : : CoreException ( ) ;
}
2021-06-08 21:37:28 +02:00
// First set focus on this parameter
size_t ordinal = param_pos + 1 ;
core : : SQLSetStmtAttr ( stmt , SQL_SOPT_SS_PARAM_FOCUS , reinterpret_cast < SQLPOINTER > ( ordinal ) , SQL_IS_INTEGER ) ;
// Bind the TVP columns
2021-05-26 00:36:01 +02:00
HashTable * rows_ht = Z_ARRVAL_P ( param_ptr_z ) ;
zval * row_z = zend_hash_index_find ( rows_ht , 0 ) ;
if ( Z_ISREF_P ( row_z ) ) {
ZVAL_DEREF ( row_z ) ;
}
HashTable * cols_ht = Z_ARRVAL_P ( row_z ) ;
zend_ulong id = - 1 ;
zend_string * key = NULL ;
zval * data_z = NULL ;
int num_columns = 0 ;
// In case there are null values in the first row, have to loop
// through the entire first row of column values using the Zend macros.
ZEND_HASH_FOREACH_KEY_VAL ( cols_ht , id , key , data_z ) {
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG ;
CHECK_CUSTOM_ERROR ( type = = HASH_KEY_IS_STRING , stmt , SQLSRV_ERROR_TVP_STRING_KEYS , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
// Assume the user has supplied data for all columns in the right order
SQLUSMALLINT pos = static_cast < SQLUSMALLINT > ( id ) ;
sqlsrv_param * column_param = tvp_columns [ pos ] ;
SQLSRV_ASSERT ( column_param ! = NULL , " sqlsrv_param_tvp::bind_param -- column param should not be null " ) ;
// If data_z is NULL, will need to keep looking in the subsequent rows of
// the same column until a non-null value is found. Since Zend macros must be
// used to traverse the array items, nesting Zend macros in different directions
// does not work.
// Therefore, save data_z for later processing and binding.
column_param - > param_ptr_z = data_z ;
num_columns + + ;
} ZEND_HASH_FOREACH_END ( ) ;
2021-06-08 21:37:28 +02:00
// Process the columns and bind each of them using the saved data
for ( int i = 0 ; i < num_columns ; i + + ) {
sqlsrv_param * column_param = tvp_columns [ i ] ;
2021-05-26 00:36:01 +02:00
column_param - > process_param ( stmt , NULL ) ;
column_param - > bind_param ( stmt ) ;
2021-06-08 21:37:28 +02:00
}
// Reset focus
core : : SQLSetStmtAttr ( stmt , SQL_SOPT_SS_PARAM_FOCUS , reinterpret_cast < SQLPOINTER > ( 0 ) , SQL_IS_INTEGER ) ;
2021-05-26 00:36:01 +02:00
}
// For each of the constituent columns of the table-valued parameter, check its PHP type
// For pure scalar types, map the cell value (based on current_row and ordinal) to the
// member placeholder_z
void sqlsrv_param_tvp : : populate_cell_placeholder ( _Inout_ sqlsrv_stmt * stmt , _In_ int ordinal )
{
2021-06-08 21:37:28 +02:00
if ( sql_data_type = = SQL_SS_TABLE | | ordinal > = num_rows ) {
return ;
}
2021-05-26 00:36:01 +02:00
zval * row_z = NULL ;
2021-06-08 21:37:28 +02:00
HashTable * values_ht = NULL ;
zval * value_z = NULL ;
2021-05-26 00:36:01 +02:00
int type = IS_NULL ;
2021-06-08 21:37:28 +02:00
2021-05-26 00:36:01 +02:00
switch ( param_php_type ) {
case IS_TRUE :
case IS_FALSE :
case IS_LONG :
case IS_DOUBLE :
2021-06-08 21:37:28 +02:00
// Find the row from the TVP data based on ordinal
2021-05-26 00:36:01 +02:00
row_z = zend_hash_index_find ( Z_ARRVAL_P ( parent_tvp - > param_ptr_z ) , ordinal ) ;
if ( Z_ISREF_P ( row_z ) ) {
ZVAL_DEREF ( row_z ) ;
}
// Now find the column value based on param_pos
value_z = zend_hash_index_find ( Z_ARRVAL_P ( row_z ) , param_pos ) ;
type = Z_TYPE_P ( value_z ) ;
// First check if value_z is NULL
if ( type = = IS_NULL ) {
ZVAL_NULL ( & placeholder_z ) ;
strlen_or_indptr = SQL_NULL_DATA ;
} else {
// Once the placeholder is bound with the correct value from the array, update current_row
if ( param_php_type = = IS_DOUBLE ) {
if ( type ! = IS_DOUBLE ) {
// If value_z type is different from param_php_type convert first
convert_to_double ( value_z ) ;
}
strlen_or_indptr = sizeof ( Z_DVAL_P ( value_z ) ) ;
ZVAL_DOUBLE ( & placeholder_z , Z_DVAL_P ( value_z ) ) ;
} else {
if ( type ! = IS_LONG ) {
// If value_z type is different from param_php_type convert first
// Even for boolean values
convert_to_long ( value_z ) ;
}
strlen_or_indptr = sizeof ( Z_LVAL_P ( value_z ) ) ;
ZVAL_LONG ( & placeholder_z , Z_LVAL_P ( value_z ) ) ;
}
}
current_row + + ;
break ;
default :
// Do nothing for non-scalar types
break ;
}
}
// If this is the table-valued parameter, loop through each parameter column
// and populate the cell's placeholder_z.
// If this is one of the constituent columns of the table-valued parameter,
// call SQLPutData() to send the cell value to the server (based on current_row
// and param_pos)
bool sqlsrv_param_tvp : : send_data_packet ( _Inout_ sqlsrv_stmt * stmt )
{
2021-06-08 21:37:28 +02:00
if ( sql_data_type ! = SQL_SS_TABLE ) {
2021-05-26 00:36:01 +02:00
// This is one of the constituent columns of the table-valued parameter
2021-06-08 21:37:28 +02:00
// Check current_row first
2021-05-26 00:36:01 +02:00
if ( current_row > = num_rows ) {
return false ;
}
// Find the row from the TVP data based on current_row
zval * row_z = zend_hash_index_find ( Z_ARRVAL_P ( parent_tvp - > param_ptr_z ) , current_row ) ;
if ( Z_ISREF_P ( row_z ) ) {
ZVAL_DEREF ( row_z ) ;
}
// Now find the column value based on param_pos
zval * value_z = zend_hash_index_find ( Z_ARRVAL_P ( row_z ) , param_pos ) ;
// First check if value_z is NULL
if ( Z_TYPE_P ( value_z ) = = IS_NULL ) {
core : : SQLPutData ( stmt , NULL , SQL_NULL_DATA ) ;
current_row + + ;
} else {
switch ( param_php_type ) {
case IS_RESOURCE :
{
num_bytes_read = 0 ;
param_stream = NULL ;
// Get the stream from the zval value
core : : sqlsrv_php_stream_from_zval_no_verify ( * stmt , param_stream , value_z ) ;
// Keep sending the packets until EOF is reached
while ( sqlsrv_param : : send_data_packet ( stmt ) ) {
}
current_row + + ;
}
break ;
case IS_OBJECT :
{
// This method updates placeholder_z as a string
bool succeeded = convert_datetime_to_string ( stmt , value_z ) ;
// Conversion failed so assume the input was an invalid PHP type
CHECK_CUSTOM_ERROR ( ! succeeded , stmt , SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE , parent_tvp - > param_pos + 1 , param_pos + 1 ) {
throw core : : CoreException ( ) ;
}
core : : SQLPutData ( stmt , Z_STRVAL ( placeholder_z ) , SQL_NTS ) ;
current_row + + ;
}
break ;
case IS_STRING :
{
int type = Z_TYPE_P ( value_z ) ;
if ( type ! = IS_STRING ) {
convert_to_string ( value_z ) ;
}
SQLLEN value_len = Z_STRLEN_P ( value_z ) ;
if ( value_len = = 0 ) {
// If it's an empty string
core : : SQLPutData ( stmt , Z_STRVAL_P ( value_z ) , 0 ) ;
} else {
if ( encoding = = CP_UTF8 & & ! is_a_numeric_type ( sql_data_type ) ) {
if ( value_len > INT_MAX ) {
LOG ( SEV_ERROR , " Convert input parameter to utf16: buffer length exceeded. " ) ;
throw core : : CoreException ( ) ;
}
// This method would change the member placeholder_z
bool succeeded = convert_input_str_to_utf16 ( stmt , value_z ) ;
CHECK_CUSTOM_ERROR ( ! succeeded , stmt , SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE , parent_tvp - > param_pos + 1 , param_pos + 1 , get_last_error_message ( ) ) {
throw core : : CoreException ( ) ;
}
send_string_data_in_batches ( stmt , & placeholder_z ) ;
} else {
send_string_data_in_batches ( stmt , value_z ) ;
}
}
current_row + + ;
}
break ;
default :
// Do nothing for basic types as they should be processed elsewhere
break ;
}
} // else not IS_NULL
2021-06-08 21:37:28 +02:00
} else {
2021-05-26 00:36:01 +02:00
// This is the table-valued parameter
2021-06-08 21:37:28 +02:00
if ( current_row < num_rows ) {
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
for ( size_t i = 0 ; i < tvp_columns . size ( ) ; i + + ) {
tvp_columns [ i ] - > populate_cell_placeholder ( stmt , current_row ) ;
}
// This indicates a TVP row is available
core : : SQLPutData ( stmt , reinterpret_cast < SQLPOINTER > ( 1 ) , 1 ) ;
current_row + + ;
} else {
// This indicates there is no more TVP row
core : : SQLPutData ( stmt , reinterpret_cast < SQLPOINTER > ( 0 ) , 0 ) ;
}
}
// Return false to indicate that the current row has been sent
return false ;
2021-05-26 00:36:01 +02:00
}
// A helper method for sending large string data in batches
void sqlsrv_param_tvp : : send_string_data_in_batches ( _Inout_ sqlsrv_stmt * stmt , _In_ zval * value_z )
{
SQLLEN len = Z_STRLEN_P ( value_z ) ;
SQLLEN batch = ( encoding = = CP_UTF8 ) ? PHP_STREAM_BUFFER_SIZE / sizeof ( SQLWCHAR ) : PHP_STREAM_BUFFER_SIZE ;
char * p = Z_STRVAL_P ( value_z ) ;
2021-06-08 21:37:28 +02:00
while ( len > batch ) {
core : : SQLPutData ( stmt , p , batch ) ;
len - = batch ;
p + = batch ;
}
// Put final batch
2021-05-26 00:36:01 +02:00
core : : SQLPutData ( stmt , p , len ) ;
}
2021-05-11 01:33:14 +02:00
void sqlsrv_params_container : : clean_up_param_data ( _In_opt_ bool only_input /* = false*/ )
{
current_param = NULL ;
remove_params ( input_params ) ;
if ( ! only_input ) {
remove_params ( output_params ) ;
}
}
// 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 strlen_or_indptr
// argument passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary.
// If a NULL was returned by SQL Server to any output parameter, set the parameter to NULL as well
void sqlsrv_params_container : : finalize_output_parameters ( )
{
std : : map < SQLUSMALLINT , sqlsrv_param * > : : iterator it ;
for ( it = output_params . begin ( ) ; it ! = output_params . end ( ) ; + + it ) {
sqlsrv_param_inout * ptr = dynamic_cast < sqlsrv_param_inout * > ( it - > second ) ;
if ( ptr ) {
ptr - > finalize_output_value ( ) ;
}
}
}
sqlsrv_param * sqlsrv_params_container : : find_param ( _In_ SQLUSMALLINT param_num , _In_ bool is_input )
{
try {
if ( is_input ) {
return input_params . at ( param_num ) ;
} else {
return output_params . at ( param_num ) ;
}
2021-05-26 00:36:01 +02:00
} catch ( std : : out_of_range & ) {
2021-05-11 01:33:14 +02:00
// not found
return NULL ;
}
}
bool sqlsrv_params_container : : get_next_parameter ( _Inout_ sqlsrv_stmt * stmt )
{
// Get the param ptr when binding the resource parameter
SQLPOINTER param = NULL ;
SQLRETURN r = core : : SQLParamData ( stmt , & param ) ;
// If no more data, all the bound parameters have been exhausted, so return false (done)
if ( SQL_SUCCEEDED ( r ) | | r = = SQL_NO_DATA ) {
// Done now, reset current_param
current_param = NULL ;
return false ;
2021-09-29 21:27:52 +02:00
} else if ( r = = SQL_NEED_DATA ) {
if ( param ! = NULL ) {
current_param = reinterpret_cast < sqlsrv_param * > ( param ) ;
SQLSRV_ASSERT ( current_param ! = NULL , " sqlsrv_params_container::get_next_parameter - The parameter requested is missing! " ) ;
current_param - > init_data_from_zval ( stmt ) ;
} else {
// Do not reset current_param when param is NULL, because
// it means that data is expected from the existing current_param
}
2021-05-11 01:33:14 +02:00
}
return true ;
}
// The following helper method sends one stream packet at a time, if available
bool sqlsrv_params_container : : send_next_packet ( _Inout_ sqlsrv_stmt * stmt )
{
if ( current_param = = NULL ) {
// If current_stream is NULL, either this is the first time checking or the previous parameter
// is done. In either case, MUST call get_next_parameter() to see if there is any more
// parameter requested by ODBC. Otherwise, "Function sequence error" will result, meaning the
// ODBC functions are called out of the order required by the ODBC Specification
if ( get_next_parameter ( stmt ) = = false ) {
return false ;
}
}
// The helper method send_stream_packet() returns false when EOF is reached
2022-01-04 21:22:14 +01:00
if ( current_param & & current_param - > send_data_packet ( stmt ) = = false ) {
2021-05-11 01:33:14 +02:00
// Now that EOF has been reached, reset current_param for next round
// Bear in mind that SQLParamData might request the same stream resource again
current_param = NULL ;
}
// Returns true regardless such that either get_next_parameter() will be called or next packet will be sent
return true ;
2016-01-30 03:00:20 +01:00
}