From 57f21e89c9c1bc3da78ca9a08ebf1cd9fee9c394 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Sun, 17 Jul 2016 23:15:25 -0700 Subject: [PATCH 1/2] updated binaries --- .../php_pdo_sqlsrv_7_nts_x64.dll | Bin .../php_pdo_sqlsrv_7_nts_x86.dll | Bin .../php_pdo_sqlsrv_7_ts_x64.dll | Bin .../php_pdo_sqlsrv_7_ts_x86.dll | Bin .../php_sqlsrv_7_nts_x64.dll | Bin .../php_sqlsrv_7_nts_x86.dll | Bin .../php_sqlsrv_7_ts_x64.dll | Bin .../php_sqlsrv_7_ts_x86.dll | Bin 8 files changed, 0 insertions(+), 0 deletions(-) rename {binaries(4.0.8629) => binaries}/php_pdo_sqlsrv_7_nts_x64.dll (100%) rename {binaries(4.0.8629) => binaries}/php_pdo_sqlsrv_7_nts_x86.dll (100%) rename {binaries(4.0.8629) => binaries}/php_pdo_sqlsrv_7_ts_x64.dll (100%) rename {binaries(4.0.8629) => binaries}/php_pdo_sqlsrv_7_ts_x86.dll (100%) rename {binaries(4.0.8629) => binaries}/php_sqlsrv_7_nts_x64.dll (100%) rename {binaries(4.0.8629) => binaries}/php_sqlsrv_7_nts_x86.dll (100%) rename {binaries(4.0.8629) => binaries}/php_sqlsrv_7_ts_x64.dll (100%) rename {binaries(4.0.8629) => binaries}/php_sqlsrv_7_ts_x86.dll (100%) diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_nts_x64.dll b/binaries/php_pdo_sqlsrv_7_nts_x64.dll similarity index 100% rename from binaries(4.0.8629)/php_pdo_sqlsrv_7_nts_x64.dll rename to binaries/php_pdo_sqlsrv_7_nts_x64.dll diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_nts_x86.dll b/binaries/php_pdo_sqlsrv_7_nts_x86.dll similarity index 100% rename from binaries(4.0.8629)/php_pdo_sqlsrv_7_nts_x86.dll rename to binaries/php_pdo_sqlsrv_7_nts_x86.dll diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_ts_x64.dll b/binaries/php_pdo_sqlsrv_7_ts_x64.dll similarity index 100% rename from binaries(4.0.8629)/php_pdo_sqlsrv_7_ts_x64.dll rename to binaries/php_pdo_sqlsrv_7_ts_x64.dll diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_ts_x86.dll b/binaries/php_pdo_sqlsrv_7_ts_x86.dll similarity index 100% rename from binaries(4.0.8629)/php_pdo_sqlsrv_7_ts_x86.dll rename to binaries/php_pdo_sqlsrv_7_ts_x86.dll diff --git a/binaries(4.0.8629)/php_sqlsrv_7_nts_x64.dll b/binaries/php_sqlsrv_7_nts_x64.dll similarity index 100% rename from binaries(4.0.8629)/php_sqlsrv_7_nts_x64.dll rename to binaries/php_sqlsrv_7_nts_x64.dll diff --git a/binaries(4.0.8629)/php_sqlsrv_7_nts_x86.dll b/binaries/php_sqlsrv_7_nts_x86.dll similarity index 100% rename from binaries(4.0.8629)/php_sqlsrv_7_nts_x86.dll rename to binaries/php_sqlsrv_7_nts_x86.dll diff --git a/binaries(4.0.8629)/php_sqlsrv_7_ts_x64.dll b/binaries/php_sqlsrv_7_ts_x64.dll similarity index 100% rename from binaries(4.0.8629)/php_sqlsrv_7_ts_x64.dll rename to binaries/php_sqlsrv_7_ts_x64.dll diff --git a/binaries(4.0.8629)/php_sqlsrv_7_ts_x86.dll b/binaries/php_sqlsrv_7_ts_x86.dll similarity index 100% rename from binaries(4.0.8629)/php_sqlsrv_7_ts_x86.dll rename to binaries/php_sqlsrv_7_ts_x86.dll From 458b7857577b5eda9d9b9abbfdb292bcf28cc314 Mon Sep 17 00:00:00 2001 From: Meet Bhagdev Date: Sat, 30 Jul 2016 01:20:43 -0700 Subject: [PATCH 2/2] 4.1.0 Latest Snap of Code --- README.md | 55 ++++------------ pdo_sqlsrv/CREDITS | 2 +- pdo_sqlsrv/core_results.cpp | 22 ++++++- pdo_sqlsrv/core_sqlsrv.h | 3 +- pdo_sqlsrv/core_stmt.cpp | 9 +-- pdo_sqlsrv/pdo_dbh.cpp | 121 ++++++++++++++++++++++++++---------- pdo_sqlsrv/pdo_init.cpp | 15 ++--- pdo_sqlsrv/pdo_sqlsrv.h | 14 ++++- pdo_sqlsrv/pdo_stmt.cpp | 62 +++++++++++++----- pdo_sqlsrv/version.h | 6 +- sqlsrv/CREDITS | 2 +- sqlsrv/core_results.cpp | 22 ++++++- sqlsrv/core_sqlsrv.h | 3 +- sqlsrv/core_stmt.cpp | 9 +-- sqlsrv/php_sqlsrv.h | 2 +- sqlsrv/stmt.cpp | 2 +- sqlsrv/version.h | 6 +- 17 files changed, 226 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 66e9c565..84928250 100644 --- a/README.md +++ b/README.md @@ -10,50 +10,18 @@ The Microsoft Drivers for PHP for SQL Server Team ##Announcements -June 30, 2016 (4.0.6): The quality of SQLSRV and PDO_SQLSRV is improved and includes some memory leak fixes: -- Fixed a heap corruption when binding parameters in a prepare statement with error -- Fixed leaks in SQLSRV streams and output parameters handling -- Fixed leaks in SQLSRV fetch object -- Fixed leaks in SQLSRV binding object parameters -- Fixed leaks in SQLSRV buffered result set -- Fixed leaks in SQLSRV getting datetime and stream fields -- Fixed leaks in PDO_SQLSRV field cache -- Fixed leaks in PDO_SQLSRV construct when connecting with error -- Fixed leaks in PDO_SQLSRV exception handling - -June 13, 2016 (4.0.5): The quality of SQLSRV and PDO_SQLSRV is improved and includes some bug fixes: -- Added ability to connect to Microsoft ODBC Driver 13. -- Fixed some memory leaks in data retrieval. -- Fixed issue with error handling in bound stream parameters when send_stream_at_exec option is set to false. -- Fixed issue with when connecting twice when Sqlsrv_errors configured to only WARNINGS. -- Fixed issue with double and string as input-output parameters in PDO_SQLSRV driver. -- Fixed a heap corruption in PDO_SQLSRV connection. - -May 3, 2016 (4.0.4): The quality of SQLSRV and PDO_SQLSRV is improved and includes some bug fixes: -- Fixed retrieving stream data and metadata. -- Fixed issue with bind stream parameters. -- Fixed issue with retrieval in error case when trying to retrieve a non-streamble data type with SQLSRV_SQLTYPE_STREAM option -- Fixed issue with querying after another query with empty array of parameters. -- Fixed issue with retrieving integers as output parameter in SQLSRV 64-bit. -- Fixed issue scrollable statement option in SQLSRV_PDO 64-bit. -- Improved handling closed connection and statement resources. -- Fixed issue with binding bit parameter. -- Fix for The $driver_options (for specifying encoding) is included in PDOStatement::bindParam is included in PHP 7.0.6 release. - -April 12, 2016 (4.0.3): The PDO_SQLSRV driver (32-bit and 64-bit) is now available. For the SQLSRV driver, we also have a few bug fixes to share: -- Fixed ability to fetch a user defined object into a class -- Fixed issue with re-preparing the same statement with referenced datetime parameters -- Fixed issue with binding output parameters with php type string with binary and char encodings and sql types SQLSRV_SQLTYPE_NCHAR and SQLSRV_SQLTYPE_NVARCHAR - -March 15, 2016 (4.0.2): 64-bit support is now available for the SQLSRV driver. We also have some additional minor improvements to share: -- Fixed the ability to retrieve strings as an output parameter -- Fixed a number of memory leaks in initialization - -Feb 23, 2016 (4.0.1): Thanks to the community’s input, we have mostly been focusing on making updates to support native 64-bit and the PDO driver. We will be sharing these updates in the coming weeks once we have something functional. In the meantime, we have a couple of minor updates to the SQLSRV driver to share: -- Fixed the ability to bind parameters with datetime types -- Fixed output and bidirectional (input/output) parameters. Note to users: we determined that output and bidirectional parameters now need to be passed in by reference (i.e. &$var) so that they can be updated with the output data and added an error check for these cases. -- Updated refcounting to avoid unnecessary reference counting for scalar values +July 28, 2016 (4.1.0): Thanks to the community's input, this release expands drivers functionalities and also includes some bug fixes: + - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + Note for exceptions: + - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. + - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. + - Fixed float truncation when using buffered query. + - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a Unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. + - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. + - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor`should be called to get the output value. ## Build @@ -107,7 +75,6 @@ SQLSRV: ## Future Plans -- Linux Version - Expand SQL 16 Feature Support (example: Always Encrypted) - Build Verification/Fundamental Tests - Bug Fixes diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS index 2122944a..3f8712e8 100644 --- a/pdo_sqlsrv/CREDITS +++ b/pdo_sqlsrv/CREDITS @@ -1 +1 @@ -Microsoft Drivers 4.0.0 for PHP for SQL Server (PDO driver) +Microsoft Drivers 4.1.0 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/core_results.cpp b/pdo_sqlsrv/core_results.cpp index 20c3d041..f449dbce 100644 --- a/pdo_sqlsrv/core_results.cpp +++ b/pdo_sqlsrv/core_results.cpp @@ -88,7 +88,25 @@ template SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { + // get to display size by removing the null terminator from buffer length + size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); + std::basic_ostringstream os; + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision as long will not be truncated + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + os.precision( real_precision ); + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + os.precision( float_precision ); + } std::locale loc; os.imbue( loc ); std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); @@ -100,13 +118,13 @@ SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buff return SQL_ERROR; } - if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) { + if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); return SQL_ERROR; } - *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); return SQL_SUCCESS; diff --git a/pdo_sqlsrv/core_sqlsrv.h b/pdo_sqlsrv/core_sqlsrv.h index b68986cf..543b56c1 100644 --- a/pdo_sqlsrv/core_sqlsrv.h +++ b/pdo_sqlsrv/core_sqlsrv.h @@ -1276,7 +1276,7 @@ struct sqlsrv_stmt : public sqlsrv_context { virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; }; @@ -1337,7 +1337,6 @@ void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); -void core_finalize_output_parameters(sqlsrv_stmt* stmt TSRMLS_DC); //********************************************************************************************************************************* diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp index 8375ef0b..0bfe6ce2 100644 --- a/pdo_sqlsrv/core_stmt.cpp +++ b/pdo_sqlsrv/core_stmt.cpp @@ -1305,10 +1305,6 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) return true; } -void core_finalize_output_parameters(sqlsrv_stmt* stmt TSRMLS_DC) { - finalize_output_parameters(stmt TSRMLS_CC); -} - void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) { TSRMLS_C; @@ -1908,12 +1904,13 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* break; case IS_STRING: { - SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char )); + size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { column_size = SQL_SERVER_MAX_TYPE_SIZE; } else { - column_size = Z_STRLEN_P( param_z ); + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; } break; } diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/pdo_sqlsrv/pdo_dbh.cpp index ee11851d..256d7ee9 100644 --- a/pdo_sqlsrv/pdo_dbh.cpp +++ b/pdo_sqlsrv/pdo_dbh.cpp @@ -73,6 +73,7 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, PDO_STMT_OPTION_EMULATE_PREPARES, + PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, }; // List of all the statement options supported by this driver. @@ -85,6 +86,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_CURSOR_SCROLL_TYPE, std::unique_ptr( new stmt_option_cursor_scroll_type ) }, { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, + { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -362,11 +364,13 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { // constructor for the internal object for connections pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ) : - sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), - stmts( NULL ), - direct_query( false ), - query_timeout( QUERY_TIMEOUT_INVALID ), - client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size ) ) + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), + stmts( NULL ), + direct_query( false ), + query_timeout( QUERY_TIMEOUT_INVALID ), + client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), + bind_param_encoding( SQLSRV_ENCODING_CHAR ), + fetch_numeric( false ) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -906,6 +910,10 @@ int pdo_sqlsrv_dbh_set_attr( pdo_dbh_t *dbh, zend_long attr, zval *val TSRMLS_DC driver_dbh->client_buffer_max_size = Z_LVAL_P( val ); break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; + break; + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -1047,6 +1055,12 @@ int pdo_sqlsrv_dbh_get_attr( pdo_dbh_t *dbh, zend_long attr, zval *return_value break; } + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_numeric ); + break; + } + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1190,41 +1204,81 @@ char * pdo_sqlsrv_dbh_last_id( pdo_dbh_t *dbh, const char *name, _Out_ size_t* l int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_len, char **quoted, size_t* quoted_len, enum pdo_param_type /*paramtype*/ TSRMLS_DC ) { - PDO_RESET_DBH_ERROR; - PDO_VALIDATE_CONN; + PDO_RESET_DBH_ERROR; + PDO_VALIDATE_CONN; PDO_LOG_DBH_ENTRY; - // count the number of quotes needed - unsigned int quotes_needed = 2; // the initial start and end quotes of course - for(size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { - if( unquoted[ index ] == '\'' ) { - ++quotes_needed; - } - } + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); + SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding; - *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. - unsigned int out_current = 0; + if ( encoding == SQLSRV_ENCODING_BINARY ) { + // convert from char* to hex digits using os + std::basic_ostringstream os; + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { + os << std::hex << ( int )unquoted[index]; + } + std::basic_string str_hex = os.str(); + // each character is represented by 2 digits of hex + size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator + char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator + strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str()); + // include length of '0x' in the binary string + *quoted_len = unquoted_str_len + 2; + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); + unsigned int out_current = 0; + // insert '0x' + ( *quoted )[out_current++] = '0'; + ( *quoted )[out_current++] = 'x'; + for ( size_t index = 0; index < unquoted_str_len && unquoted_str[index] != '\0'; ++index ) { + ( *quoted )[out_current++] = unquoted_str[index]; + } + // null terminator + ( *quoted )[out_current] = '\0'; + sqlsrv_free( unquoted_str ); + return 1; + } + else { + // count the number of quotes needed + unsigned int quotes_needed = 2; // the initial start and end quotes of course + // include the N proceeding the initial quote if encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + quotes_needed = 3; + } - // insert initial quote - (*quoted)[ out_current++ ] ='\''; + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { + if ( unquoted[index] == '\'' ) { + ++quotes_needed; + } + } - for(size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + *quoted_len = unquoted_len + quotes_needed; // length returned to the caller should not account for null terminator. + *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); // include space for null terminator. + unsigned int out_current = 0; - if( unquoted[ index ] == '\'' ) { - (*quoted)[ out_current++ ] = '\''; - (*quoted)[ out_current++ ] = '\''; - } - else { - (*quoted)[ out_current++ ] = unquoted[ index ]; - } - } + // insert N if the encoding is UTF8 + if ( encoding == SQLSRV_ENCODING_UTF8 ) { + ( *quoted )[out_current++] = 'N'; + } + // insert initial quote + ( *quoted )[out_current++] = '\''; - // trailing quote and null terminator - (*quoted)[ out_current++ ] ='\''; - (*quoted)[ out_current ] = '\0'; + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { - return 1; + if ( unquoted[index] == '\'' ) { + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current++] = '\''; + } + else { + ( *quoted )[out_current++] = unquoted[index]; + } + } + + // trailing quote and null terminator + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current] = '\0'; + + return 1; + } } // This method is not implemented by this driver. @@ -1283,6 +1337,9 @@ void add_stmt_option_key( sqlsrv_context& ctx, size_t key, HashTable* options_ht option_key = PDO_STMT_OPTION_EMULATE_PREPARES; break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; + default: CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); diff --git a/pdo_sqlsrv/pdo_init.cpp b/pdo_sqlsrv/pdo_init.cpp index 9e6717bc..46ef9bb8 100644 --- a/pdo_sqlsrv/pdo_init.cpp +++ b/pdo_sqlsrv/pdo_init.cpp @@ -372,14 +372,15 @@ namespace { } // array of pdo constants. - sqlsrv_attr_pdo_constant pdo_attr_constants[] = { + sqlsrv_attr_pdo_constant pdo_attr_constants[] = { - // driver specific attributes - { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, - { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, - { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, - { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, - { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + // driver specific attributes + { "SQLSRV_ATTR_ENCODING" , SQLSRV_ATTR_ENCODING }, + { "SQLSRV_ATTR_QUERY_TIMEOUT" , SQLSRV_ATTR_QUERY_TIMEOUT }, + { "SQLSRV_ATTR_DIRECT_QUERY" , SQLSRV_ATTR_DIRECT_QUERY }, + { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, + { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, + { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, // used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int, // PDO::PARAM_STR uses the size of the string in the variable diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/pdo_sqlsrv/pdo_sqlsrv.h index ff35643e..677c3b44 100644 --- a/pdo_sqlsrv/pdo_sqlsrv.h +++ b/pdo_sqlsrv/pdo_sqlsrv.h @@ -48,6 +48,7 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_DIRECT_QUERY, SQLSRV_ATTR_CURSOR_SCROLL_TYPE, SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, + SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, }; // valid set of values for TransactionIsolation connection option @@ -176,6 +177,8 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { bool direct_query; long query_timeout; zend_long client_buffer_max_size; + SQLSRV_ENCODING bind_param_encoding; + bool fetch_numeric; pdo_sqlsrv_dbh( SQLHANDLE h, error_callback e, void* driver TSRMLS_DC ); }; @@ -210,6 +213,10 @@ struct stmt_option_emulate_prepares : public stmt_option_functor { virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); }; +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ); +}; + extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; // a core layer pdo stmt object. This object inherits and overrides the callbacks necessary @@ -220,17 +227,19 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { direct_query( false ), direct_query_subst_string( NULL ), direct_query_subst_string_len( 0 ), - bound_column_param_types( NULL ) + bound_column_param_types( NULL ), + fetch_numeric( false ) { pdo_sqlsrv_dbh* db = static_cast( c ); direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; } virtual ~pdo_sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); bool direct_query; // flag set if the query should be executed directly or prepared const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters @@ -239,6 +248,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { // meta data for current result set std::vector > current_meta_data; pdo_param_type* bound_column_param_types; + bool fetch_numeric; }; diff --git a/pdo_sqlsrv/pdo_stmt.cpp b/pdo_sqlsrv/pdo_stmt.cpp index 3eb73775..893889be 100644 --- a/pdo_sqlsrv/pdo_stmt.cpp +++ b/pdo_sqlsrv/pdo_stmt.cpp @@ -330,6 +330,12 @@ void stmt_option_emulate_prepares:: operator()( sqlsrv_stmt* stmt, stmt_option c pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; } +void stmt_option_fetch_numeric:: operator()( sqlsrv_stmt* stmt, stmt_option const* /*opt*/, zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; +} + // log a function entry point #define PDO_LOG_STMT_ENTRY \ @@ -700,8 +706,9 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, sqlsrv_phptype sqlsrv_php_type; SQLSRV_ASSERT( colno >= 0 && colno < static_cast( driver_stmt->current_meta_data.size()), "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); - sqlsrv_php_type = driver_stmt->sql_type_to_php_type(static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), - static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), true ); + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[ colno ]->field_type ), + static_cast( driver_stmt->current_meta_data[ colno ]->field_size ), + true, driver_stmt->fetch_numeric ); // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding sqlsrv_php_type.typeinfo.encoding = driver_stmt->encoding(); @@ -748,11 +755,6 @@ int pdo_sqlsrv_stmt_get_col_data(pdo_stmt_t *stmt, int colno, SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); - - // if the current column is the last fetch column, finalize output params - if ( stmt->column_count == colno + 1 ) { - core_finalize_output_parameters( driver_stmt TSRMLS_CC ); - } zval* zval_ptr = ( zval* )( sqlsrv_malloc( sizeof( zval ))); *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); @@ -785,6 +787,7 @@ int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_ PDO_VALIDATE_STMT; PDO_LOG_STMT_ENTRY; + pdo_sqlsrv_stmt* pdo_stmt = static_cast( stmt->driver_data ); sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); try { @@ -815,6 +818,10 @@ int pdo_sqlsrv_stmt_set_attr(pdo_stmt_t *stmt, zend_long attr, zval *val TSRMLS_ core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); break; + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + pdo_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; + break; + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -890,6 +897,12 @@ int pdo_sqlsrv_stmt_get_attr( pdo_stmt_t *stmt, zend_long attr, zval *return_val break; } + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_numeric ); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -1064,6 +1077,11 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // since the param isn't reliable, we don't do anything here case PDO_PARAM_EVT_ALLOC: + // if emulate prepare is on, set the bind_param_encoding so it can be used in PDO::quote when binding parameters on the client side + if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->dbh->driver_data ); + driver_dbh->bind_param_encoding = static_cast( Z_LVAL( param->driver_params )); + } break; case PDO_PARAM_EVT_FREE: break; @@ -1252,7 +1270,7 @@ int pdo_sqlsrv_stmt_param_hook(pdo_stmt_t *stmt, // Returns a sqlsrv_phptype for a given SQL Server data type. -sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream ) +sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_over_stream, bool prefer_number_to_string ) { sqlsrv_phptype sqlsrv_phptype; int local_encoding = this->encoding(); @@ -1264,15 +1282,31 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUI } switch( sql_type ) { - case SQL_BIT: - case SQL_INTEGER: - case SQL_SMALLINT: - case SQL_TINYINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + if ( prefer_number_to_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; + case SQL_FLOAT: + case SQL_REAL: + if ( prefer_number_to_string ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = local_encoding; + } + break; case SQL_BIGINT: case SQL_CHAR: case SQL_DECIMAL: - case SQL_FLOAT: - case SQL_REAL: case SQL_GUID: case SQL_NUMERIC: case SQL_WCHAR: diff --git a/pdo_sqlsrv/version.h b/pdo_sqlsrv/version.h index c3d06906..ad3856fa 100644 --- a/pdo_sqlsrv/version.h +++ b/pdo_sqlsrv/version.h @@ -16,10 +16,10 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#define VER_FILEVERSION_STR "4.0.0.0" -#define _FILEVERSION 4,0,0,0 +#define VER_FILEVERSION_STR "4.1.0.0" +#define _FILEVERSION 4,1,0,0 #define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 0 +#define SQLVERSION_MINOR 1 #define SQLVERSION_MMDD 0 #define SQLVERSION_REVISION 0 diff --git a/sqlsrv/CREDITS b/sqlsrv/CREDITS index 2122944a..73961bd6 100644 --- a/sqlsrv/CREDITS +++ b/sqlsrv/CREDITS @@ -1 +1 @@ -Microsoft Drivers 4.0.0 for PHP for SQL Server (PDO driver) +Microsoft Drivers 4.1.0 for PHP for SQL Server diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp index 20c3d041..f449dbce 100644 --- a/sqlsrv/core_results.cpp +++ b/sqlsrv/core_results.cpp @@ -88,7 +88,25 @@ template SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error ) { + // get to display size by removing the null terminator from buffer length + size_t display_size = ( buffer_length - sizeof( Char )) / sizeof( Char ); + std::basic_ostringstream os; + // use the display size to determine the sql type. And if it is a double, set the precision accordingly + // the display sizes are set by the ODBC driver based on the precision of the sql type + // otherwise we can just use the default precision as long will not be truncated + size_t real_display_size = 14; + size_t float_display_size = 24; + size_t real_precision = 7; + size_t float_precision = 15; + // this is the case of sql type float(24) or real + if ( display_size == real_display_size ) { + os.precision( real_precision ); + } + // this is the case of sql type float(53) + else if ( display_size == float_display_size ) { + os.precision( float_precision ); + } std::locale loc; os.imbue( loc ); std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data ); @@ -100,13 +118,13 @@ SQLRETURN number_to_string( Number* number_data, _Out_ void* buffer, SQLLEN buff return SQL_ERROR; } - if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) { + if( str_num.size() * sizeof(Char) > (size_t) buffer_length ) { last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 ); return SQL_ERROR; } - *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator + *out_buffer_length = str_num.size() * sizeof( Char ); // str_num.size() already include the NULL terminator memcpy_s( buffer, buffer_length, str_num.c_str(), *out_buffer_length ); return SQL_SUCCESS; diff --git a/sqlsrv/core_sqlsrv.h b/sqlsrv/core_sqlsrv.h index b68986cf..543b56c1 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/sqlsrv/core_sqlsrv.h @@ -1276,7 +1276,7 @@ struct sqlsrv_stmt : public sqlsrv_context { virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) = 0; + virtual sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string = false ) = 0; }; @@ -1337,7 +1337,6 @@ void core_sqlsrv_set_send_at_exec( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, zval* value_z TSRMLS_DC ); void core_sqlsrv_set_buffered_query_limit( sqlsrv_stmt* stmt, SQLLEN limit TSRMLS_DC ); -void core_finalize_output_parameters(sqlsrv_stmt* stmt TSRMLS_DC); //********************************************************************************************************************************* diff --git a/sqlsrv/core_stmt.cpp b/sqlsrv/core_stmt.cpp index 8375ef0b..0bfe6ce2 100644 --- a/sqlsrv/core_stmt.cpp +++ b/sqlsrv/core_stmt.cpp @@ -1305,10 +1305,6 @@ bool core_sqlsrv_send_stream_packet( sqlsrv_stmt* stmt TSRMLS_DC ) return true; } -void core_finalize_output_parameters(sqlsrv_stmt* stmt TSRMLS_DC) { - finalize_output_parameters(stmt TSRMLS_CC); -} - void stmt_option_functor::operator()( sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, zval* /*value_z*/ TSRMLS_DC ) { TSRMLS_C; @@ -1908,12 +1904,13 @@ void default_sql_size_and_scale( sqlsrv_stmt* stmt, unsigned int paramno, zval* break; case IS_STRING: { - SQLULEN byte_len = Z_STRLEN_P( param_z ) * ((encoding == SQLSRV_ENCODING_UTF8) ? sizeof( wchar_t ) : sizeof( char )); + size_t char_size = ( encoding == SQLSRV_ENCODING_UTF8 ) ? sizeof( wchar_t ) : sizeof( char ); + SQLULEN byte_len = Z_STRLEN_P( param_z ) * char_size; if( byte_len > SQL_SERVER_MAX_FIELD_SIZE ) { column_size = SQL_SERVER_MAX_TYPE_SIZE; } else { - column_size = Z_STRLEN_P( param_z ); + column_size = SQL_SERVER_MAX_FIELD_SIZE / char_size; } break; } diff --git a/sqlsrv/php_sqlsrv.h b/sqlsrv/php_sqlsrv.h index 490b6dc0..809fbfbd 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/sqlsrv/php_sqlsrv.h @@ -175,7 +175,7 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { void new_result_set( TSRMLS_D ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ); + sqlsrv_phptype sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ); bool prepared; // whether the statement has been prepared yet (used for error messages) zend_ulong conn_index; // index into the connection hash that contains this statement structure diff --git a/sqlsrv/stmt.cpp b/sqlsrv/stmt.cpp index 5435b224..0795f73f 100644 --- a/sqlsrv/stmt.cpp +++ b/sqlsrv/stmt.cpp @@ -164,7 +164,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) } // Returns a php type for a given sql type. Also sets the encoding wherever applicable. -sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream ) +sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string_to_stream, bool prefer_number_to_string ) { sqlsrv_phptype ss_phptype; ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID; diff --git a/sqlsrv/version.h b/sqlsrv/version.h index c3d06906..ad3856fa 100644 --- a/sqlsrv/version.h +++ b/sqlsrv/version.h @@ -16,10 +16,10 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#define VER_FILEVERSION_STR "4.0.0.0" -#define _FILEVERSION 4,0,0,0 +#define VER_FILEVERSION_STR "4.1.0.0" +#define _FILEVERSION 4,1,0,0 #define SQLVERSION_MAJOR 4 -#define SQLVERSION_MINOR 0 +#define SQLVERSION_MINOR 1 #define SQLVERSION_MMDD 0 #define SQLVERSION_REVISION 0