diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 9e7aee27..909e350e 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1129,11 +1129,11 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } } catch( core::CoreException& ) { - + zval_ptr_dtor(return_value); return FAILURE; } catch(...) { - + zval_ptr_dtor(return_value); DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); } diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index faa909d4..f1902bb7 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -988,7 +988,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) return; } - if (stmt->current_sensitivity_metadata != NULL) { + if (stmt->current_sensitivity_metadata) { // Already cached, so return return; } @@ -1873,7 +1873,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // be returned as a zval. case SQLSRV_PHPTYPE_DATETIME: { - char* field_value_temp = NULL; + sqlsrv_malloc_auto_ptr field_value_temp; SQLLEN field_len_temp = 0; field_value_temp = static_cast(sqlsrv_malloc(MAX_DATETIME_STRING_LEN)); @@ -1882,8 +1882,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true TSRMLS_CC); if (r == SQL_NO_DATA || field_len_temp == SQL_NULL_DATA) { - sqlsrv_free(field_value_temp); - field_value_temp = NULL; + field_value_temp.reset(); field_len_temp = 0; } @@ -1892,6 +1891,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i } field_value = field_value_temp; + field_value_temp.transferred(); *field_len = field_len_temp; break; @@ -2420,142 +2420,151 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { - if( Z_ISUNDEF(stmt->output_params) ) + if (Z_ISUNDEF(stmt->output_params)) return; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + HashTable* params_ht = Z_ARRVAL(stmt->output_params); zend_ulong index = -1; zend_string* key = NULL; void* output_param_temp = NULL; - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: + try { + ZEND_HASH_FOREACH_KEY_PTR(params_ht, index, key, output_param_temp) { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; - if( str_len == 0 ) { - core::sqlsrv_zval_stringl( value_z, "", 0 ); - continue; - } - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( SQLWCHAR ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx - // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to - // output_param->original_buffer_len data 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 ) + sqlsrv_output_param* output_param = static_cast(output_param_temp); + zval* value_z = Z_REFVAL_P(output_param->param_z); + switch (Z_TYPE_P(value_z)) { + case IS_STRING: { - str_len = output_param->original_buffer_len - null_size; - } - - if (output_param->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 < output_param->original_buffer_len) { - str[str_len] = '\0'; + // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter + char* str = Z_STRVAL_P(value_z); + SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; + if (str_len == 0) { + core::sqlsrv_zval_stringl(value_z, "", 0); + continue; } - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - param_meta_data metaData = output_param->getMetaData(); - - if (output_param->encoding != SQLSRV_ENCODING_CHAR) { - char* outString = NULL; - SQLLEN outLen = 0; - bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast(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 && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { - format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, outString, &outLen); - } - - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); + if (str_len == SQL_NULL_DATA) { + zend_string_release(Z_STR_P(value_z)); + ZVAL_NULL(value_z); + continue; } - else { - if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { - format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len); - } + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch (output_param->encoding) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof(SQLWCHAR); // string isn't yet converted to UTF-8, still UTF-16 + break; + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; + break; + case SQLSRV_ENCODING_BINARY: + null_size = 0; + break; + default: + SQLSRV_ASSERT(false, "Invalid encoding in output_param structure."); + break; + } + CHECK_CUSTOM_ERROR(str_len > (output_param->original_buffer_len - null_size), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1) + { + throw core::CoreException(); + } + + // For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx + // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to + // output_param->original_buffer_len data 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 = output_param->original_buffer_len - null_size; + } + + if (output_param->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 < output_param->original_buffer_len) { + str[str_len] = '\0'; + } core::sqlsrv_zval_stringl(value_z, str, str_len); } - } - } - break; - case IS_LONG: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } - break; - case IS_DOUBLE: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { - ZVAL_NULL(value_z); - } - else if (output_param->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(); + else { + param_meta_data metaData = output_param->getMetaData(); + + if (output_param->encoding != SQLSRV_ENCODING_CHAR) { + char* outString = NULL; + SQLLEN outLen = 0; + bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast(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 && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, outString, &outLen); + } + + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); + } + else { + if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len); + } + + core::sqlsrv_zval_stringl(value_z, str, str_len); } } - // 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 (output_param->is_bool) { - convert_to_boolean(value_z); - } } break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - + case IS_LONG: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { + ZVAL_NULL(value_z); + } + else if (output_param->is_bool) { + convert_to_boolean(value_z); + } + else { + ZVAL_LONG(value_z, static_cast(Z_LVAL_P(value_z))); + } + break; + case IS_DOUBLE: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { + ZVAL_NULL(value_z); + } + else if (output_param->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(); + } + } + // 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 (output_param->is_bool) { + convert_to_boolean(value_z); + } + } + break; + default: + DIE("Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter."); + break; + } + value_z = NULL; + } ZEND_HASH_FOREACH_END(); + } + catch (core::CoreException&) { + // empty the hash table due to exception caught + zend_hash_clean(Z_ARRVAL(stmt->output_params)); + throw; + } // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean(Z_ARRVAL(stmt->output_params)); return; } diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 7d0e1beb..b44d514c 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -44,7 +44,11 @@ int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS // read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are // set when sqlsrv_get_field is called by the user specifying which field type they want. -size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC ) +#if PHP_VERSION_ID >= 70400 +ssize_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +#else +size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +#endif { SQLLEN read = 0; SQLSMALLINT c_type = SQL_C_CHAR; @@ -184,15 +188,20 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) return static_cast( read ); } - - catch( core::CoreException& ) { - + catch (core::CoreException&) { +#if PHP_VERSION_ID >= 70400 + return -1; +#else return 0; +#endif } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); + catch (...) { + LOG(SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught."); +#if PHP_VERSION_ID >= 70400 + return -1; +#else return 0; +#endif } } diff --git a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt index a5c31e09..0c4f88f8 100644 --- a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt +++ b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt @@ -24,6 +24,6 @@ if ($c !== false) { Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |TCP Provider: Error code (0x2726|0x2AF9)|Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+ Stack trace: -#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\) +#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct(\(\)|\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\)) #1 {main} thrown in .+(\/|\\)pdo_utf8_conn\.php on line [0-9]+ diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 62bc542d..5f1a4f43 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -431,6 +431,6 @@ Test_9 : FETCH_INVALID : Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x Stack trace: -#0 %s: fetchAllInvalid(Object(PDO), 'PDO_MainTypes') +#0 %s: fetchAllInvalid(%S) #1 {main} thrown in %s on line %x diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt index b8fd577a..a14d6dde 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt @@ -35,7 +35,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_PRIOR ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -53,7 +53,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, -1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -63,7 +63,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -73,7 +73,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, 1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -99,7 +99,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, -1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -125,7 +125,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -135,7 +135,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_PRIOR); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -145,7 +145,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, -1); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt index 000931b6..5170bd97 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt @@ -257,6 +257,6 @@ Test_9 : FETCH_INVALID : Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x Stack trace: -#0 %s: fetchWithStyle(Object(PDO), 'PDO_MainTypes', 'PDO::FETCH_INVA...') +#0 %s: fetchWithStyle(%S) #1 {main} thrown in %s on line %x diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt index 7eebb400..67c601b8 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt @@ -59,7 +59,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { echo "$sqlType should be compatible with $dataType\n"; $success = false; } diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt index 25d7d15f..ea5285d8 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt @@ -69,7 +69,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { echo "$sqlType should be compatible with $dataType\n"; $success = false; } diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt index 77d53147..8bbe9606 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt @@ -57,7 +57,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { echo "$sqlType should be compatible with $dataType\n"; $success = false; } diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt index 459169d9..c60e23de 100644 --- a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt @@ -44,7 +44,8 @@ function fetchData($conn, $table, $size) echo "Expect this to fail\n"; } else { $error = 'Memory limit of 1 KB exceeded for buffered query'; - if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + $errors = sqlsrv_errors(); + if (!empty($errors) && strpos($errors[0]['message'], $error) === false) { print_r(sqlsrv_errors()); } }