diff --git a/README.md b/README.md index 66e9c565..639a03bc 100644 --- a/README.md +++ b/README.md @@ -10,50 +10,24 @@ 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 +August 22, 2016 (4.1.1): Updated Windows drivers built and compiled with PHP 7.0.9 are available and include a couple of bug fixes: -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. +- Fixed issue with storing integers in varchar field. +- Fixed issue with invalid connection handler if one connection fails. +- Fixed crash when emulate prepare is on. -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 @@ -99,15 +73,13 @@ For samples, please see the sample folder. For setup instructions, see [here] [ ## Limitations -This preview contains the PHP 7 port of the SQLSRV and PDO_SQLSRV drivers, and does not provide backwards compatibility with PHP 5. The following items have known issues: - -SQLSRV: -- Memory management. +- This release contains the PHP 7 port of the SQLSRV and PDO_SQLSRV drivers, and does not provide backwards compatibility with PHP 5. +- Binding output parameter using emulate prepare is not supported. +## Known Issues +- User defined data types and SQL_VARIANT. ## Future Plans - -- Linux Version - Expand SQL 16 Feature Support (example: Always Encrypted) - Build Verification/Fundamental Tests - Bug Fixes diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_nts.dll b/binaries(4.0.8629)/php_pdo_sqlsrv_7_nts.dll new file mode 100644 index 00000000..54aba82d Binary files /dev/null and b/binaries(4.0.8629)/php_pdo_sqlsrv_7_nts.dll differ diff --git a/binaries(4.0.8629)/php_pdo_sqlsrv_7_ts.dll b/binaries(4.0.8629)/php_pdo_sqlsrv_7_ts.dll new file mode 100644 index 00000000..20d3f1fa Binary files /dev/null and b/binaries(4.0.8629)/php_pdo_sqlsrv_7_ts.dll differ diff --git a/binaries(4.0.8629)/php_sqlsrv_7_nts.dll b/binaries(4.0.8629)/php_sqlsrv_7_nts.dll new file mode 100644 index 00000000..d75692d9 Binary files /dev/null and b/binaries(4.0.8629)/php_sqlsrv_7_nts.dll differ diff --git a/binaries(4.0.8629)/php_sqlsrv_7_ts.dll b/binaries(4.0.8629)/php_sqlsrv_7_ts.dll new file mode 100644 index 00000000..45f4edf3 Binary files /dev/null and b/binaries(4.0.8629)/php_sqlsrv_7_ts.dll differ diff --git a/pdo_sqlsrv/CREDITS b/pdo_sqlsrv/CREDITS index 2122944a..76237ad5 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 for PHP for SQL Server (PDO driver) diff --git a/pdo_sqlsrv/core_conn.cpp b/pdo_sqlsrv/core_conn.cpp index e188fbb5..8dde4b5c 100644 --- a/pdo_sqlsrv/core_conn.cpp +++ b/pdo_sqlsrv/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/pdo_sqlsrv/core_init.cpp b/pdo_sqlsrv/core_init.cpp index ee23c369..575d1f69 100644 --- a/pdo_sqlsrv/core_init.cpp +++ b/pdo_sqlsrv/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/pdo_sqlsrv/core_results.cpp b/pdo_sqlsrv/core_results.cpp index 20c3d041..a5471ac8 100644 --- a/pdo_sqlsrv/core_results.cpp +++ b/pdo_sqlsrv/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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..d91a050a 100644 --- a/pdo_sqlsrv/core_sqlsrv.h +++ b/pdo_sqlsrv/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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); //********************************************************************************************************************************* @@ -1613,7 +1612,7 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co const char* get_last_error_message( DWORD last_error = 0 ); // a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. diff --git a/pdo_sqlsrv/core_stmt.cpp b/pdo_sqlsrv/core_stmt.cpp index 8375ef0b..335186f7 100644 --- a/pdo_sqlsrv/core_stmt.cpp +++ b/pdo_sqlsrv/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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; @@ -1778,7 +1774,15 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* para case IS_TRUE: case IS_FALSE: case IS_LONG: - sql_c_type = SQL_C_LONG; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_c_type = SQL_C_SBIGINT; + } + else + { + sql_c_type = SQL_C_SLONG; + } break; case IS_DOUBLE: sql_c_type = SQL_C_DOUBLE; @@ -1841,7 +1845,16 @@ void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV case IS_TRUE: case IS_FALSE: case IS_LONG: - sql_type = SQL_INTEGER; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_type = SQL_BIGINT; + } + else + { + sql_type = SQL_INTEGER; + } + break; case IS_DOUBLE: sql_type = SQL_FLOAT; @@ -1908,12 +1921,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; } @@ -2096,8 +2110,8 @@ void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_ph calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == LONG_MAX || - sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) { + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { field_len_temp = INITIAL_FIELD_STRING_LEN; diff --git a/pdo_sqlsrv/core_stream.cpp b/pdo_sqlsrv/core_stream.cpp index 14f4ed1a..08b01d05 100644 --- a/pdo_sqlsrv/core_stream.cpp +++ b/pdo_sqlsrv/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/pdo_sqlsrv/core_util.cpp b/pdo_sqlsrv/core_util.cpp index 658a88d1..7e12d520 100644 --- a/pdo_sqlsrv/core_util.cpp +++ b/pdo_sqlsrv/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -306,12 +306,12 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); } -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) { va_list format_args; va_start( format_args, format ); - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); va_end( format_args ); diff --git a/pdo_sqlsrv/pdo_dbh.cpp b/pdo_sqlsrv/pdo_dbh.cpp index ee11851d..a0659797 100644 --- a/pdo_sqlsrv/pdo_dbh.cpp +++ b/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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; @@ -465,7 +469,7 @@ int pdo_sqlsrv_db_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) zend_string_release( Z_STR( server_z )); } dbh->error_mode = prev_err_mode; // reset the error mode - g_henv_cp->invalidate(); + g_henv_cp->last_error().reset(); // reset the last error; callee will check if last_error exist before freeing it and setting it to NULL return 0; } catch( ... ) { @@ -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 ); @@ -1081,8 +1095,8 @@ int pdo_sqlsrv_dbh_return_error( pdo_dbh_t *dbh, pdo_stmt_t *stmt, else { ctx_error = static_cast( dbh->driver_data )->last_error(); } - - pdo_sqlsrv_retrieve_context_error( ctx_error, info ); + + pdo_sqlsrv_retrieve_context_error( ctx_error, info ); return 1; } @@ -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..5c493669 100644 --- a/pdo_sqlsrv/pdo_init.cpp +++ b/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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_parser.cpp b/pdo_sqlsrv/pdo_parser.cpp index cd3ec874..68e1c8ef 100644 --- a/pdo_sqlsrv/pdo_parser.cpp +++ b/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/pdo_sqlsrv/pdo_sqlsrv.h b/pdo_sqlsrv/pdo_sqlsrv.h index ff35643e..3b801e14 100644 --- a/pdo_sqlsrv/pdo_sqlsrv.h +++ b/pdo_sqlsrv/pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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..e8ce257c 100644 --- a/pdo_sqlsrv/pdo_stmt.cpp +++ b/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -223,7 +223,7 @@ zval convert_to_zval( SQLSRV_PHPTYPE sqlsrv_php_type, void** in_val, SQLLEN fiel else { if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); + ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); } else { ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( in_val ))); @@ -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/pdo_util.cpp b/pdo_sqlsrv/pdo_util.cpp index 6598fd49..817d9eff 100644 --- a/pdo_sqlsrv/pdo_util.cpp +++ b/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -460,8 +460,7 @@ bool pdo_sqlsrv_handle_dbh_error( sqlsrv_context& ctx, unsigned int sqlsrv_error if( !warning ) { size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + MAX_DIGITS + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len )); + char* msg = static_cast( sqlsrv_malloc( msg_len )); core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, error->native_message ); php_error( E_WARNING, msg ); @@ -513,8 +512,7 @@ bool pdo_sqlsrv_handle_stmt_error( sqlsrv_context& ctx, unsigned int sqlsrv_erro if( !warning ) { size_t msg_len = strlen( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + MAX_DIGITS + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len )); + char* msg = static_cast( sqlsrv_malloc(SQL_MAX_MESSAGE_LENGTH+1)); core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, error->native_message ); php_error( E_WARNING, msg ); @@ -548,11 +546,6 @@ void pdo_sqlsrv_retrieve_context_error( sqlsrv_error const* last_error, zval* pd add_next_index_long( pdo_zval, last_error->native_code ); add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); } - else { - add_next_index_null( pdo_zval ); /* native code */ - add_next_index_null( pdo_zval ); /* native message */ - } - } // Formats the error message and writes to the php error log. diff --git a/pdo_sqlsrv/version.h b/pdo_sqlsrv/version.h index c3d06906..13c469ca 100644 --- a/pdo_sqlsrv/version.h +++ b/pdo_sqlsrv/version.h @@ -2,7 +2,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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..76237ad5 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 for PHP for SQL Server (PDO driver) diff --git a/sqlsrv/conn.cpp b/sqlsrv/conn.cpp index 963b101c..c2a9c70c 100644 --- a/sqlsrv/conn.cpp +++ b/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -1192,7 +1192,7 @@ void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, { int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); - CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, key ) { + CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { throw ss::SSException(); } @@ -1221,7 +1221,6 @@ void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) { try { - if( stmt_options ) { HashTable* options_ht = Z_ARRVAL_P( stmt_options ); @@ -1234,16 +1233,14 @@ void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, _Inout_ Has zval* conn_opt = NULL; int result = 0; - key_len = ZSTR_LEN( key ) + 1; type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - if( type != HASH_KEY_IS_STRING ) { - std::ostringstream itoa; - itoa << int_key; - CHECK_CUSTOM_ERROR( true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, itoa.str() ) { + if (type != HASH_KEY_IS_STRING) { + CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) { throw core::CoreException(); } } + key_len = ZSTR_LEN(key) + 1; add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); } ZEND_HASH_FOREACH_END(); } diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp index e188fbb5..8dde4b5c 100644 --- a/sqlsrv/core_conn.cpp +++ b/sqlsrv/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/sqlsrv/core_init.cpp b/sqlsrv/core_init.cpp index ee23c369..575d1f69 100644 --- a/sqlsrv/core_init.cpp +++ b/sqlsrv/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp index 20c3d041..a5471ac8 100644 --- a/sqlsrv/core_results.cpp +++ b/sqlsrv/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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..d91a050a 100644 --- a/sqlsrv/core_sqlsrv.h +++ b/sqlsrv/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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); //********************************************************************************************************************************* @@ -1613,7 +1612,7 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co const char* get_last_error_message( DWORD last_error = 0 ); // a wrapper around FormatMessage that can take variadic args rather than a a va_arg pointer -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ); +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ); // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. diff --git a/sqlsrv/core_stmt.cpp b/sqlsrv/core_stmt.cpp index 8375ef0b..335186f7 100644 --- a/sqlsrv/core_stmt.cpp +++ b/sqlsrv/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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; @@ -1778,7 +1774,15 @@ SQLSMALLINT default_c_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval const* para case IS_TRUE: case IS_FALSE: case IS_LONG: - sql_c_type = SQL_C_LONG; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_c_type = SQL_C_SBIGINT; + } + else + { + sql_c_type = SQL_C_SLONG; + } break; case IS_DOUBLE: sql_c_type = SQL_C_DOUBLE; @@ -1841,7 +1845,16 @@ void default_sql_type( sqlsrv_stmt* stmt, SQLULEN paramno, zval* param_z, SQLSRV case IS_TRUE: case IS_FALSE: case IS_LONG: - sql_type = SQL_INTEGER; + //ODBC 64-bit long and integer type are 4 byte values. + if ( ( Z_LVAL_P( param_z ) < INT_MIN ) || ( Z_LVAL_P( param_z ) > INT_MAX ) ) + { + sql_type = SQL_BIGINT; + } + else + { + sql_type = SQL_INTEGER; + } + break; case IS_DOUBLE: sql_type = SQL_FLOAT; @@ -1908,12 +1921,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; } @@ -2096,8 +2110,8 @@ void get_field_as_string( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_ph calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == LONG_MAX || - sql_display_size == LONG_MAX >> 1 || sql_display_size == ULONG_MAX - 1 ) { + if( sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { field_len_temp = INITIAL_FIELD_STRING_LEN; diff --git a/sqlsrv/core_stream.cpp b/sqlsrv/core_stream.cpp index 14f4ed1a..08b01d05 100644 --- a/sqlsrv/core_stream.cpp +++ b/sqlsrv/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/sqlsrv/core_util.cpp b/sqlsrv/core_util.cpp index 658a88d1..7e12d520 100644 --- a/sqlsrv/core_util.cpp +++ b/sqlsrv/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -306,12 +306,12 @@ void core_sqlsrv_format_driver_error( sqlsrv_context& ctx, sqlsrv_error_const co LOG( severity, "%1!s!: message = %2!s!", ctx.func(), formatted_error->native_message ); } -DWORD core_sqlsrv_format_message( char* output_buffer, unsigned output_len, const char* format, ... ) +DWORD core_sqlsrv_format_message( char*& output_buffer, unsigned output_len, const char* format, ... ) { va_list format_args; va_start( format_args, format ); - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, output_buffer, output_len, &format_args ); + DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, format, 0, 0, static_cast(output_buffer), SQL_MAX_MESSAGE_LENGTH, &format_args ); va_end( format_args ); diff --git a/sqlsrv/init.cpp b/sqlsrv/init.cpp index e05fa856..ae968652 100644 --- a/sqlsrv/init.cpp +++ b/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/sqlsrv/php_sqlsrv.h b/sqlsrv/php_sqlsrv.h index 490b6dc0..b7aa892d 100644 --- a/sqlsrv/php_sqlsrv.h +++ b/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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..1090de36 100644 --- a/sqlsrv/stmt.cpp +++ b/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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/util.cpp b/sqlsrv/util.cpp index 10eb8237..c2b2123b 100644 --- a/sqlsrv/util.cpp +++ b/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/sqlsrv/version.h b/sqlsrv/version.h index c3d06906..13c469ca 100644 --- a/sqlsrv/version.h +++ b/sqlsrv/version.h @@ -2,7 +2,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 4.0 for PHP for SQL Server +// Microsoft Drivers 4.1 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -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