diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 18f51e91c..54a49f147 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -66,6 +66,9 @@ struct col_cache { const int INITIAL_FIELD_STRING_LEN = 2048; // base allocation size when retrieving a string field +const char DECIMAL_POINT = '.'; +const int SQL_SERVER_DECIMAL_MAXIMUM_PRECISION = 38; // 38 is the maximum length of a stringified decimal number + // UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads const unsigned int UTF8_MIDBYTE_MASK = 0xc0; const unsigned int UTF8_MIDBYTE_TAG = 0x80; @@ -99,7 +102,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len); // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ); +SQLSMALLINT default_c_type(_Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding); void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ); // given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) @@ -107,6 +110,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); +int round_up_decimal_numbers(_Inout_ char* buffer, _In_ short decimal_pos, _In_ short decimals_places, _In_ short offset, _In_ short lastpos); void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, @@ -117,7 +121,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding, _In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits, _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len ); -void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits ); +void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits); void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param ); // send all the stream data void send_param_streams( _Inout_ sqlsrv_stmt* stmt ); @@ -125,7 +129,7 @@ void send_param_streams( _Inout_ sqlsrv_stmt* stmt ); void sqlsrv_output_param_dtor( _Inout_ zval* data ); // called when a bound stream parameter is to be destroyed. void sqlsrv_stream_dtor( _Inout_ zval* data ); - +bool is_a_numeric_type(_In_ SQLSMALLINT sql_type); } // constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. @@ -501,7 +505,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } } // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding ); + c_type = default_c_type(stmt, param_num, param_z, sql_type, encoding); // set the buffer based on the PHP parameter type switch( Z_TYPE_P( param_z )){ @@ -544,15 +548,21 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ break; case IS_STRING: { - if ( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC ) { - adjustInputPrecision( param_z, decimal_digits ); + // With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns. + // Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL + // or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC. + // In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits). + if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC) { + adjustDecimalPrecision(param_z, decimal_digits); } buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); + bool is_numeric = is_a_numeric_type(sql_type); + // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ){ + if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 && !is_numeric){ zval wbuffer_z; ZVAL_NULL( &wbuffer_z ); @@ -602,7 +612,9 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ buffer, buffer_len ); // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ) ); + // no need to use wide chars for numeric types + SQLSRV_ENCODING enc = (is_numeric) ? SQLSRV_ENCODING_CHAR : encoding; + sqlsrv_output_param output_param(param_ref, enc, param_num, static_cast(buffer_len)); output_param.saveMetaData(sql_type, column_size, decimal_digits); @@ -2041,7 +2053,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ) +SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding ) { SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P( param_z ); @@ -2079,6 +2091,22 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, sql_c_type = SQL_C_DOUBLE; break; case IS_STRING: + switch (encoding) { + case SQLSRV_ENCODING_CHAR: + sql_c_type = SQL_C_CHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + case CP_UTF8: + sql_c_type = (is_a_numeric_type(sql_type)) ? SQL_C_CHAR : SQL_C_WCHAR; + //sql_c_type = SQL_C_WCHAR; + break; + default: + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno); + break; + } + break; case IS_RESOURCE: switch( encoding ) { case SQLSRV_ENCODING_CHAR: @@ -2269,129 +2297,63 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f // Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the // number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. // - // Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0. - // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of + // Note: it's possible that the decimal data does not contain a decimal point because the field scale is 0. + // Thus, first check if the decimal point exists. If not, no formatting necessary, regardless of // format_decimals and decimals_places // - std::string str = field_value; - size_t pos = str.find_first_of('.'); - // The decimal dot is not found, simply return - if (pos == std::string::npos) { + // Check if it's a negative number and if necessary to add the leading zero + bool is_negative = (*field_value == '-'); + char *src = field_value + is_negative; + bool add_leading_zero = false; + + // If the decimal point is not found, simply return + char *pt = strchr(src, DECIMAL_POINT); + if (pt == NULL) { return; } - - SQLSMALLINT num_decimals = decimals_places; - if (num_decimals > field_scale) { - num_decimals = field_scale; + else if (pt == src) { + add_leading_zero = true; } - // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php - // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is - // followed by 5 or above. + SQLSMALLINT scale = decimals_places; + if (scale > field_scale) { + scale = field_scale; + } - bool isNegative = false; + char buffer[50] = " "; // A buffer with two blank spaces, as leeway + short offset = 1 + is_negative; + short src_length = strlen(src); - // If negative, remove the minus sign for now so as not to complicate the rounding process - if (str[0] == '-') { - isNegative = true; - std::ostringstream oss; - oss << str.substr(1); - str = oss.str(); - pos = str.find_first_of('.'); + if (add_leading_zero) { + buffer[offset++] = '0'; } + // Copy the original numerical value to the buffer + memcpy_s(buffer + offset, src_length, src, src_length); - // Adds the leading zero if not exists - if (pos == 0) { - std::ostringstream oss; - oss << '0' << str; - str = oss.str(); - pos++; - } + int last_pos = src_length + offset; - if (num_decimals == NO_CHANGE_DECIMAL_PLACES) { - // Add the minus sign back if negative - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0); - str = oss.str(); - } - } else { - // Start formatting - size_t last = 0; - if (num_decimals == 0) { - // Chop all decimal digits, including the decimal dot - size_t pos2 = pos + 1; - short n = str[pos2] - '0'; - if (n >= 5) { - // Start rounding up - starting from the digit left of the dot all the way to the first digit - bool carry_over = true; - for (short p = pos - 1; p >= 0 && carry_over; p--) { - n = str[p] - '0'; - if (n == 9) { - str[p] = '0' ; - carry_over = true; - } - else { - n++; - carry_over = false; - str[p] = '0' + n; - } - } - if (carry_over) { - std::ostringstream oss; - oss << '1' << str.substr(0, pos); - str = oss.str(); - pos++; - } - } - last = pos; - } - else { - size_t pos2 = pos + num_decimals + 1; - // No need to check if rounding is necessary when pos2 has passed the last digit in the input string - if (pos2 < str.length()) { - short n = str[pos2] - '0'; - if (n >= 5) { - // Start rounding up - starting from the digit left of pos2 all the way to the first digit - bool carry_over = true; - for (short p = pos2 - 1; p >= 0 && carry_over; p--) { - if (str[p] == '.') { // Skip the dot - continue; - } - n = str[p] - '0'; - if (n == 9) { - str[p] = '0' ; - carry_over = true; - } - else { - n++; - carry_over = false; - str[p] = '0' + n; - } - } - if (carry_over) { - std::ostringstream oss; - oss << '1' << str.substr(0, pos2); - str = oss.str(); - pos2++; - } - } - } - last = pos2; - } - // Add the minus sign back if negative - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0, last); - str = oss.str(); - } else { - str = str.substr(0, last); + // If no need to adjust decimal places, skip formatting + if (decimals_places != NO_CHANGE_DECIMAL_PLACES) { + short num_decimals = src_length - (pt - src) - 1; + + if (num_decimals > scale) { + last_pos = round_up_decimal_numbers(buffer, (pt - src) + offset, scale, offset, last_pos); } - } + } - size_t len = str.length(); - str.copy(field_value, len); + // Remove the extra white space if not used + char *p = buffer; + offset = 0; + while (isspace(*p++)) { + offset++; + } + if (is_negative) { + buffer[--offset] = '-'; + } + + short len = last_pos - offset; + memcpy_s(field_value, len, buffer + offset, len); field_value[len] = '\0'; *field_len = len; } @@ -2967,154 +2929,6 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* } } -void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits ) { - // 38 is the maximum length of a stringified decimal number - size_t maxDecimalPrecision = 38; - // 6 is derived from: 1 for '.'; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation); - // 1 for sign of scientific exponent; 2 for length of scientific exponent - // if the length is greater than maxDecimalStrLen, do not change the string - size_t maxDecimalStrLen = maxDecimalPrecision + 6; - if (Z_STRLEN_P(param_z) > maxDecimalStrLen) { - return; - } - std::vector digits; - unsigned char* ptr = reinterpret_cast(ZSTR_VAL( Z_STR_P( param_z ))); - bool isNeg = false; - bool isScientificNot = false; - char scientificChar = ' '; - short scientificExp = 0; - if( strchr( reinterpret_cast( ptr ), 'e' ) || strchr( reinterpret_cast( ptr ), 'E' )){ - isScientificNot = true; - } - // parse digits in param_z into the vector digits - if( *ptr == '+' || *ptr == '-' ){ - if( *ptr == '-' ){ - isNeg = true; - } - ptr++; - } - short numInt = 0; - short numDec = 0; - while( isdigit( *ptr )){ - digits.push_back( *ptr - '0' ); - ptr++; - numInt++; - } - if( *ptr == '.' ){ - ptr++; - if( !isScientificNot ){ - while( isdigit( *ptr ) && numDec < decimal_digits + 1 ){ - digits.push_back( *ptr - '0' ); - ptr++; - numDec++; - } - // make sure the rest of the number are digits - while( isdigit( *ptr )){ - ptr++; - } - } - else { - while( isdigit( *ptr )){ - digits.push_back( *ptr - '0' ); - ptr++; - numDec++; - } - } - } - if( isScientificNot ){ - if ( *ptr == 'e' || *ptr == 'E' ) { - scientificChar = *ptr; - } - ptr++; - bool isNegExp = false; - if( *ptr == '+' || *ptr == '-' ){ - if( *ptr == '-' ){ - isNegExp = true; - } - ptr++; - } - while( isdigit( *ptr )){ - scientificExp = scientificExp * 10 + ( *ptr - '0' ); - ptr++; - } - SQLSRV_ASSERT( scientificExp <= maxDecimalPrecision, "Input decimal overflow: sql decimal type only supports up to a precision of 38." ); - if( isNegExp ){ - scientificExp = scientificExp * -1; - } - } - // if ptr is not pointing to a null terminator at this point, that means the decimal string input is invalid - // do not change the string and let SQL Server handle the invalid decimal string - if ( *ptr != '\0' ) { - return; - } - // if number of decimal is less than the exponent, that means the number is a whole number, so no need to adjust the precision - if( numDec > scientificExp ){ - int decToRemove = numDec - scientificExp - decimal_digits; - if( decToRemove > 0 ){ - bool carryOver = false; - short backInd = 0; - // pop digits from the vector until there is only 1 more decimal place than required decimal_digits - while( decToRemove != 1 && !digits.empty() ){ - digits.pop_back(); - decToRemove--; - } - if( !digits.empty() ){ - // check if the last digit to be popped is greater than 5, if so, the digit before it needs to round up - carryOver = digits.back() >= 5; - digits.pop_back(); - backInd = static_cast(digits.size() - 1); - // round up from the end until no more carry over - while( carryOver && backInd >= 0 ){ - if( digits.at( backInd ) != 9 ){ - digits.at( backInd )++; - carryOver = false; - } - else{ - digits.at( backInd ) = 0; - } - backInd--; - } - } - std::ostringstream oss; - if( isNeg ){ - oss << '-'; - } - // insert 1 if carry over persist all the way to the beginning of the number - if( carryOver && backInd == -1 ){ - oss << 1; - } - if( digits.empty() && !carryOver ){ - oss << 0; - } - else{ - short i = 0; - for( i; i < numInt && i < digits.size(); i++ ){ - oss << digits[i]; - } - // fill string with 0 if the number of digits in digits is less then numInt - if( i < numInt ){ - for( i; i < numInt; i++ ){ - oss << 0; - } - } - if( numInt < digits.size() ){ - oss << '.'; - for( i; i < digits.size(); i++ ){ - oss << digits[i]; - } - } - if( scientificExp != 0 ){ - oss << scientificChar << std::to_string( scientificExp ); - } - } - std::string str = oss.str(); - zend_string* zstr = zend_string_init( str.c_str(), str.length(), 0 ); - zend_string_release( Z_STR_P( param_z )); - ZVAL_NEW_STR( param_z, zstr ); - } - } -} - // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) @@ -3152,4 +2966,200 @@ void sqlsrv_stream_dtor( _Inout_ zval* data ) sqlsrv_free( stream_encoding ); } +void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits) +{ + char* value = Z_STRVAL_P(param_z); + short value_len = Z_STRLEN_P(param_z); + + // If the length is greater than maxDecimalStrLen, do not convert the string + // 6 is derived from: 1 for the decimal point; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation); + // 1 for sign of scientific exponent; 2 for length of scientific exponent + const int MAX_DECIMAL_STRLEN = SQL_SERVER_DECIMAL_MAXIMUM_PRECISION + 6; + if (value_len > MAX_DECIMAL_STRLEN) { + return; + } + + // If std::stold() succeeds, 'idx' is the position of the first character after the numerical value + long double d = 0; + size_t idx; + try { + d = std::stold(std::string(value), &idx); + } + catch (const std::logic_error& err) { + return; // invalid input caused the conversion to throw an exception + } + if (idx < value_len) { + return; // the input contains something else apart from the numerical value + } + + // Navigate to the first digit or the decimal point + bool is_negative = (d < 0); + char *src = value + is_negative; + while (*src != DECIMAL_POINT && !isdigit(*src)) { + src++; + } + + // Check if the value is in scientific notation + char *exp = strchr(src, 'E'); + if (exp == NULL) { + exp = strchr(src, 'e'); + } + + // Find the decimal point + char *pt = strchr(src, DECIMAL_POINT); + + char buffer[50] = " "; // A buffer with 2 blank spaces, as leeway + short offset = 1 + is_negative; // The position to start copying the original numerical value + + if (exp == NULL) { + if (pt == NULL) { + return; // decimal point not found + } + + short src_length = strlen(src); + short num_decimals = src_length - (pt - src) - 1; + if (num_decimals <= decimal_digits) { + return; // no need to adjust number of decimals + } + + memcpy_s(buffer + offset, src_length, src, src_length); + round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, src_length + offset); + } + else { + int power = atoi(exp+1); + if (abs(power) > SQL_SERVER_DECIMAL_MAXIMUM_PRECISION) { + return; // Out of range, so let the server handle this + } + + short num_decimals = 0; + if (power == 0) { + // Simply chop off the exp part + short length = (exp - src); + memcpy_s(buffer + offset, length, src, length); + + if (pt != NULL) { + // Adjust decimal places only if decimal point is found and number of decimals more than decimal_digits + num_decimals = exp - pt - 1; + if (num_decimals > decimal_digits) { + round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, length + offset); + } + } + } else { + short oldpos = 0; + if (pt == NULL) { + pt = exp; // Decimal point not found, use the exp sign + oldpos = exp - src; + } + else { + oldpos = pt - src; + num_decimals = exp - pt - 1; + if (power > 0 && num_decimals <= power) { + return; // The result will be a whole number, do nothing and return + } + } + + // Derive the new position for the decimal point in the buffer + short newpos = oldpos + power; + if (power > 0) { + newpos = newpos + offset; + if (num_decimals == 0) { + memset(buffer + offset + oldpos, '0', power); // Fill parts of the buffer with zeroes first + } + else { + buffer[newpos] = DECIMAL_POINT; + } + } + else { + // The negative "power" part shows exactly how many places to move the decimal point. + // Whether to pad zeroes depending on the original position of the decimal point pos. + if (newpos <= 0) { + // If newpos is negative or zero, pad zeroes (size of '0.' + places to move) in the buffer + short numzeroes = 2 + abs(newpos); + memset(buffer + offset, '0', numzeroes); + newpos = offset + 1; // The new decimal position should be offset + '0' + buffer[newpos] = DECIMAL_POINT; // Replace that '0' with the decimal point + offset = numzeroes + offset; // Short offset now in the buffer + } + else { + newpos = newpos + offset; + buffer[newpos] = DECIMAL_POINT; + } + } + + // Start copying the content to the buffer until the exp sign or one more digit after decimal_digits + char *p = src; + short idx = offset; + short lastpos = newpos + decimal_digits + 1; + while (p != exp && idx <= lastpos) { + if (*p == DECIMAL_POINT) { + p++; + continue; + } + if (buffer[idx] == DECIMAL_POINT) { + idx++; + } + buffer[idx++] = *p; + p++; + } + // Round up is required only when number of decimals is more than decimal_digits + num_decimals = idx - newpos - 1; + if (num_decimals > decimal_digits) { + round_up_decimal_numbers(buffer, newpos, decimal_digits, offset, idx); + } + } + } + + // Set the minus sign if negative + if (is_negative) { + buffer[0] = '-'; + } + + zend_string* zstr = zend_string_init(buffer, strlen(buffer), 0); + zend_string_release(Z_STR_P(param_z)); + ZVAL_NEW_STR(param_z, zstr); +} + +int round_up_decimal_numbers(_Inout_ char* buffer, _In_ short decimal_pos, _In_ short num_decimals, _In_ short offset, _In_ short lastpos) +{ + // This helper method assumes the 'buffer' has some extra blank spaces at the beginning without the minus '-' sign. + // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php + // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is + // followed by 5 or above. + + short pos = decimal_pos + num_decimals + 1; + if (pos < lastpos) { + short n = buffer[pos] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of pos all the way to the first digit + bool carry_over = true; + for (short p = pos - 1; p >= offset && carry_over; p--) { + if (buffer[p] == DECIMAL_POINT) { + continue; + } + n = buffer[p] - '0'; + carry_over = (++n == 10); + if (n == 10) { + n = 0; + } + buffer[p] = '0' + n; + } + if (carry_over) { + buffer[offset - 1] = '1'; + } + } + if (num_decimals == 0) { + buffer[decimal_pos] = '\0'; + return decimal_pos; + } + else { + buffer[pos] = '\0'; + return pos; + } + } + + // Do nothing and just return + return lastpos; +} + + } diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt index 4a51b434c..25521e634 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt @@ -1,20 +1,24 @@ --TEST-- Test for inserting into and retrieving from decimal columns of different scale +--DESCRIPTION-- +This test is similar to sqlsrv_ae_insert_decimal.phpt but it requires enabling column encryption in order +to test the rounding of decimal numbers --SKIPIF-- --FILE-- $num, "Testing numbers between 1 and -1:" => $frac); $scalesToTest = array(0, 1, 2, 3, 4, 5, 7, 9, 19, 28, 38); try { - $conn = connect(); + $conn = connect("ColumnEncryption=Enabled;"); $tbname = "decimalTable"; + foreach ($numSets as $testName => $numSet) { echo "\n$testName\n"; foreach ($numSet as $input) { @@ -35,7 +39,9 @@ try { foreach ($decimalTypes as $key => $value) { $insertValues = array_merge($insertValues, array($key => $input)); } - insertRow($conn, $tbname, $insertValues); + + // Use prepare / execute to bind parameters + insertRow($conn, $tbname, $insertValues, "prepareBindParam"); $stmt = $conn->query("SELECT * FROM $tbname"); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -47,6 +53,7 @@ try { $conn->exec("TRUNCATE TABLE $tbname"); } } + dropTable($conn, $tbname); unset($conn); } catch (PDOException $e) { @@ -211,6 +218,16 @@ c4: 123456789012346261234567890123.4629 c5: 123456789012346261234567890123.46290 c7: 123456789012346261234567890123.4629000 c0: -13775323913775323913775323913775323913 +c0: 7654 +c1: 7654.0 +c2: 7654.00 +c3: 7654.000 +c4: 7654.0000 +c5: 7654.00000 +c7: 7654.0000000 +c9: 7654.000000000 +c19: 7654.0000000000000000000 +c28: 7654.0000000000000000000000000000 Testing numbers between 1 and -1: c1: .1 @@ -306,3 +323,13 @@ c38: .00000000000000000000123456789000000000 c28: -.0000000000000000000000012346 c38: -.00000000000000000000000123456789012346 c38: .00000000000000000000000000000001377532 +c0: 1 +c1: .9 +c2: .88 +c3: .880 +c4: .8800 +c5: .88000 +c7: .8800000 +c9: .880000000 +c19: .8800000000000000000 +c28: .8800000000000000000000000000 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt index 14205f954..b02959d9f 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt @@ -1,19 +1,80 @@ --TEST-- Test for inserting into and retrieving from decimal columns of different scale +--DESCRIPTION-- +This test is similar to sqlsrv_ae_insert_scientificNot.phpt but it requires enabling column encryption in order +to test the parsing of decimal numbers in scientific notation --SKIPIF-- --FILE-- $posExp, "Testing numbers between 1 and -1:" => $negExp); $scalesToTest = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19); +function testErrorCases($conn) +{ + // Create a dummy table + $tableName = "pdo_sci_not"; + createTable($conn, $tableName, array("Column1" => "decimal(38, 1)")); + + $expected = '*Invalid character value for cast specification'; + + $tsql = "INSERT INTO $tableName (Column1) VALUES (?)"; + $input = ".1e-0+1."; + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $input); + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + + $input = "10E+0.1."; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + + $input = "-9E0+2"; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + $input = "1234.1234.1234"; + $expected = "*String data, right truncation"; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + dropTable($conn, $tableName); +} + try { - $conn = connect(); + $conn = connect("ColumnEncryption=Enabled;"); + + testErrorCases($conn); + $tbname = "decimalTable"; foreach ($numSets as $testName => $numSet) { @@ -33,12 +94,9 @@ try { $insertValues = array(); foreach ($decimalTypes as $key => $value) { - if (isColEncrypted()) { - $insertValues = array_merge($insertValues, array($key => strval($input))); - } else { - $insertValues = array_merge($insertValues, array($key => $input)); - } + $insertValues = array_merge($insertValues, array($key => $input)); } + insertRow($conn, $tbname, $insertValues, "prepareBindParam"); $stmt = $conn->query("SELECT * FROM $tbname"); @@ -93,7 +151,7 @@ c6: 191.784647 c7: 191.7846470 c8: 191.78464696 c9: 191.784646962 -c19: 191.7846469620200000000 +c19: 191.7846469620226500000 c0: -833 c1: -833.3 c2: -833.33 @@ -115,7 +173,7 @@ c6: 850.000000 c7: 850.0000000 c8: 850.00000000 c9: 850.000000000 -c19: 850.0000000000000000000 +c19: 850.0000000000000600000 c0: -852 c1: -851.6 c2: -851.65 @@ -126,7 +184,7 @@ c6: -851.648352 c7: -851.6483516 c8: -851.64835165 c9: -851.648351648 -c19: -851.6483516483500000000 +c19: -851.6483516483516800000 c0: 316000 c1: 316000.0 c2: 316000.00 @@ -231,12 +289,12 @@ c1: -12345678.9 c2: -12345678.90 c3: -12345678.901 c4: -12345678.9012 -c5: -12345678.90124 +c5: -12345678.90123 c6: -12345678.901235 -c7: -12345678.9012350 -c8: -12345678.90123500 -c9: -12345678.901235000 -c19: -12345678.9012350000000000000 +c7: -12345678.9012346 +c8: -12345678.90123460 +c9: -12345678.901234600 +c19: -12345678.9012346000000000000 c0: 13775320000 c1: 13775320000.0 c2: 13775320000.00 @@ -248,6 +306,17 @@ c7: 13775320000.0000000 c8: 13775320000.00000000 c9: 13775320000.000000000 c19: 13775320000.0000000000000000000 +c0: 1000 +c1: 999.9 +c2: 999.90 +c3: 999.900 +c4: 999.9000 +c5: 999.90000 +c6: 999.900000 +c7: 999.9000000 +c8: 999.90000000 +c9: 999.900000000 +c19: 999.9000000000000000000 Testing numbers between 1 and -1: c0: -10 @@ -279,7 +348,7 @@ c6: -.019178 c7: -.0191785 c8: -.01917846 c9: -.019178465 -c19: -.0191784646962020000 +c19: -.0191784646962022650 c1: .1 c2: .08 c3: .083 @@ -299,7 +368,7 @@ c6: -.085000 c7: -.0850000 c8: -.08500000 c9: -.085000000 -c19: -.0850000000000000000 +c19: -.0850000000000000060 c1: .1 c2: .09 c3: .085 @@ -309,7 +378,7 @@ c6: .085165 c7: .0851648 c8: .08516484 c9: .085164835 -c19: .0851648351648350000 +c19: .0851648351648351680 c1: -.3 c2: -.32 c3: -.316 @@ -392,7 +461,7 @@ c6: .000123 c7: .0001235 c8: .00012346 c9: .000123457 -c19: .0001234567890123500 +c19: .0001234567890123460 c1: -.1 c2: -.14 c3: -.138 @@ -403,3 +472,11 @@ c7: -.1377532 c8: -.13775320 c9: -.137753200 c19: -.1377532000000000000 +c3: .001 +c4: .0010 +c5: .00100 +c6: .001000 +c7: .0009999 +c8: .00099990 +c9: .000999900 +c19: .0009999000000000000 diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt index 72991158a..50a5cf1a3 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt @@ -1,5 +1,7 @@ --TEST-- fetch columns using fetch mode and different ways of binding columns +--DESCRIPTION-- +This test should not use temporary table as it might occasionally cause deadlocked transactions. --SKIPIF-- --FILE-- @@ -27,19 +29,22 @@ function FetchInto_Query_Args() include("MsSetup.inc"); set_time_limit(0); - $tableName = GetTempTableName(); + $tableName = 'fetchinto_query_args'; $conn = new PDO( "sqlsrv:server=$server;database=$databaseName", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - $stmt = $conn->exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + dropTable($conn, $tableName); + $conn->exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); $numRows = 0; $query = GetQuery($tableName, ++$numRows); $stmt = $conn->query($query); + unset($stmt); $query = GetQuery($tableName, ++$numRows); $stmt = $conn->query($query); - $stmt = null; + unset($stmt); $sql = "SELECT * FROM $tableName ORDER BY c27_timestamp"; $obj1 = new PdoTestClass(); @@ -51,10 +56,11 @@ function FetchInto_Query_Args() $stmt2->setFetchMode(PDO::FETCH_INTO, $obj2); VerifyResults($stmt1, $stmt2, $tableName); + dropTable($conn, $tableName); - $stmt1 = null; - $stmt2 = null; - $conn = null; + unset($stmt1); + unset($stmt2); + unset($conn); } function VerifyResults($stmt1, $stmt2, $tableName) diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt index 37b2ddee3..0c06cd9d6 100644 --- a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt @@ -153,7 +153,7 @@ function runInOutProcWithErrors($conn, $utf8_2) function runIntDoubleProcWithErrors($conn) { - $sql = "{call pdoIntDoubleProc(?)}"; + $sql = "{call pdoUTF8InOutProc(?)}"; $val = pack('H*', 'ffffffff'); try { diff --git a/test/functional/sqlsrv/0065.phpt b/test/functional/sqlsrv/0065.phpt index 87745b34a..097d2c331 100644 --- a/test/functional/sqlsrv/0065.phpt +++ b/test/functional/sqlsrv/0065.phpt @@ -236,11 +236,12 @@ if ($t !== $u) { die("Round trip failed."); } +// $t is an invalid utf-8 string, expect the procedure to fail $t = pack('H*', 'ffffffff'); $sqlType = $params = array(array(&$t, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('utf-8'))); -$query = "{call IntDoubleProc(?)}"; +$query = "{call Utf8InOutProc(?)}"; $s = AE\executeQueryParams($c, $query, $params, true, "no error from an invalid utf-8 string"); dropTable($c, $tableName); diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt index 656c60259..afda09582 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt @@ -6,51 +6,54 @@ Test for inserting into and retrieving from decimal columns of different scale $num, "Testing numbers between 1 and -1:" => $frac); $scalesToTest = array(0, 1, 2, 3, 4, 5, 7, 9, 19, 28, 38); -try { - $conn = AE\connect(); - $tbname = "decimalTable"; - foreach ($numSets as $testName => $numSet) { - echo "\n$testName\n"; - foreach ($numSet as $input) { - $numInt = ceil(log10(abs($input) + 1)); - $decimalTypes = array(); - foreach ($scalesToTest as $scale) { - if ($scale < 39 - $numInt) { - array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); - } +$conn = AE\connect(); +$tbname = "decimalTable"; +foreach ($numSets as $testName => $numSet) { + echo "\n$testName\n"; + foreach ($numSet as $input) { + $numInt = ceil(log10(abs($input) + 1)); + $decimalTypes = array(); + foreach ($scalesToTest as $scale) { + if ($scale < 39 - $numInt) { + array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); } - if (empty($decimalTypes)) { - $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); - } - AE\createTable($conn, $tbname, $decimalTypes); + } + if (empty($decimalTypes)) { + $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); + } + AE\createTable($conn, $tbname, $decimalTypes); - $insertValues = array(); - foreach ($decimalTypes as $decimalType) { - $insertValues = array_merge($insertValues, array($decimalType->colName => $input)); - } - AE\insertRow($conn, $tbname, $insertValues); + $insertValues = array(); + foreach ($decimalTypes as $decimalType) { + $insertValues = array_merge($insertValues, array($decimalType->colName => $input)); + } + $stmt = AE\insertRow($conn, $tbname, $insertValues); + if (!$stmt) { + var_dump($insertValues); + fatalError("Failed to insert the above values\n"); + } - $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); - $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - foreach ($row as $key => $value) { - if ($value != 0) { - echo "$key: $value\n"; - } + $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + if (empty($row)) { + fatalError("Empty array is returned\n"); + } + foreach ($row as $key => $value) { + if ($value != 0) { + echo "$key: $value\n"; } - sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } + sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } - dropTable($conn, $tbname); - sqlsrv_close($conn); -} catch (PDOException $e) { - echo $e->getMessage(); } +dropTable($conn, $tbname); +sqlsrv_close($conn); ?> --EXPECT-- @@ -210,6 +213,16 @@ c4: 123456789012346261234567890123.4629 c5: 123456789012346261234567890123.46290 c7: 123456789012346261234567890123.4629000 c0: -13775323913775323913775323913775323913 +c0: 9876 +c1: 9876.0 +c2: 9876.00 +c3: 9876.000 +c4: 9876.0000 +c5: 9876.00000 +c7: 9876.0000000 +c9: 9876.000000000 +c19: 9876.0000000000000000000 +c28: 9876.0000000000000000000000000000 Testing numbers between 1 and -1: c1: .1 @@ -304,4 +317,14 @@ c28: .0000000000000000000012345679 c38: .00000000000000000000123456789000000000 c28: -.0000000000000000000000012346 c38: -.00000000000000000000000123456789012346 -c38: .00000000000000000000000000000001377532 \ No newline at end of file +c38: .00000000000000000000000000000001377532 +c0: -1 +c1: -1.0 +c2: -.99 +c3: -.990 +c4: -.9900 +c5: -.99000 +c7: -.9900000 +c9: -.990000000 +c19: -.9900000000000000000 +c28: -.9900000000000000000000000000 diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt index 16c78696d..2944832dd 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt @@ -6,54 +6,112 @@ Test for inserting into and retrieving from decimal columns of different scale $posExp, "Testing numbers between 1 and -1:" => $negExp); $scalesToTest = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19); -try { - $conn = AE\connect(); - $tbname = "decimalTable"; - foreach ($numSets as $testName => $numSet) { - echo "\n$testName\n"; - foreach ($numSet as $input) { - $numInt = ceil(log10(abs($input) + 1)); - $decimalTypes = array(); - foreach ($scalesToTest as $scale) { - if ($scale < 39 - $numInt) { - array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); - } - } - if (empty($decimalTypes)) { - $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); - } - AE\createTable($conn, $tbname, $decimalTypes); +function testErrorCases($conn) +{ + // Create a dummy table + $tableName = "srv_sci_not"; + $colMeta = array(new AE\ColumnMeta("decimal(38, 1)", "Column1")); + + AE\createTable($conn, $tableName, $colMeta); + + $expected = '*Invalid character value for cast specification'; + $tsql = "INSERT INTO $tableName (Column1) VALUES (?)"; + $input = "- 0E1.3"; + $param = array( + array(&$input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, 1)) + ); + + $stmt = sqlsrv_prepare($conn, $tsql, $param); + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + + $input = "8e0-2"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + + $input = "-19e032+"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + $input = "5678.5678.5678"; + $expected = "*String data, right truncation"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + dropTable($conn, $tableName); +} + +$conn = AE\connect(); - $insertValues = array(); - foreach ($decimalTypes as $decimalType) { - $scale = intval(ltrim($decimalType->colName, "c")); - array_push($insertValues, array($input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, $scale))); +testErrorCases($conn); + +$tbname = "decimalTable"; +foreach ($numSets as $testName => $numSet) { + echo "\n$testName\n"; + foreach ($numSet as $input) { + $numInt = ceil(log10(abs($input) + 1)); + $decimalTypes = array(); + foreach ($scalesToTest as $scale) { + if ($scale < 39 - $numInt) { + array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); } - $insertSql = "INSERT INTO $tbname VALUES(" . AE\getSeqPlaceholders(count($insertValues)) . ")"; - $stmt = sqlsrv_prepare($conn, $insertSql, $insertValues); - sqlsrv_execute($stmt); + } + if (empty($decimalTypes)) { + $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); + } + AE\createTable($conn, $tbname, $decimalTypes); + + $insertValues = array(); + foreach ($decimalTypes as $decimalType) { + $scale = intval(ltrim($decimalType->colName, "c")); + array_push($insertValues, array($input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, $scale))); + } - $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); - $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - foreach ($row as $key => $value) { - if ($value != 0) { - echo "$key: $value\n"; - } + $insertSql = "INSERT INTO $tbname VALUES(" . AE\getSeqPlaceholders(count($insertValues)) . ")"; + $stmt = sqlsrv_prepare($conn, $insertSql, $insertValues); + if (!sqlsrv_execute($stmt)) { + fatalError("Failed to execute $insertSql\n"); + } + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + if (empty($row)) { + fatalError("Empty array is returned\n"); + } + foreach ($row as $key => $value) { + if ($value != 0) { + echo "$key: $value\n"; } - sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } + sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } - dropTable($conn, $tbname); - sqlsrv_close($conn); -} catch (PDOException $e) { - echo $e->getMessage(); } +dropTable($conn, $tbname); +sqlsrv_close($conn); ?> --EXPECT-- @@ -246,6 +304,28 @@ c7: 13775320000.0000000 c8: 13775320000.00000000 c9: 13775320000.000000000 c19: 13775320000.0000000000000000000 +c0: -53687 +c1: -53687.1 +c2: -53687.09 +c3: -53687.092 +c4: -53687.0919 +c5: -53687.09185 +c6: -53687.091854 +c7: -53687.0918543 +c8: -53687.09185426 +c9: -53687.091854260 +c19: -53687.0918542600000000000 +c0: 1000 +c1: 999.9 +c2: 999.90 +c3: 999.900 +c4: 999.9000 +c5: 999.90000 +c6: 999.900000 +c7: 999.9000000 +c8: 999.90000000 +c9: 999.900000000 +c19: 999.9000000000000000000 Testing numbers between 1 and -1: c0: -10 @@ -401,3 +481,21 @@ c7: -.1377532 c8: -.13775320 c9: -.137753200 c19: -.1377532000000000000 +c1: .1 +c2: .05 +c3: .054 +c4: .0537 +c5: .05370 +c6: .053697 +c7: .0536971 +c8: .05369709 +c9: .053697092 +c19: .0536970918542600000 +c3: .001 +c4: .0010 +c5: .00100 +c6: .001000 +c7: .0009999 +c8: .00099990 +c9: .000999900 +c19: .0009999000000000000