From 0705d9b1a88e2d83267cf13cdaa8d4cf0bf24a98 Mon Sep 17 00:00:00 2001 From: yitam Date: Mon, 20 Feb 2017 15:24:41 -0800 Subject: [PATCH] calculate field size instead of using column size and fixed the parameter encoding --- source/shared/core_stmt.cpp | 10 +- source/sqlsrv/stmt.cpp | 7 +- ...srv_231_string_truncation_varchar_max.phpt | 189 ++++++++++++++++++ 3 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 test/sqlsrv/srv_231_string_truncation_varchar_max.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 09c8d9bff..a59c6571b 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2420,16 +2420,20 @@ void resize_output_buffer_if_necessary( sqlsrv_stmt* stmt, zval* param_z, SQLULE // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, // as is a n(var)char/ntext field being returned as a binary field. - elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR))) ? 2 : 1; + elem_size = (c_type == SQL_C_WCHAR || (c_type == SQL_C_BINARY && (sql_type == SQL_WCHAR || sql_type == SQL_WVARCHAR || sql_type == SQL_WLONGVARCHAR ))) ? 2 : 1; // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning - expected_len = column_size * elem_size + elem_size; + SQLULEN field_size = column_size; + if (column_size == SQL_SS_LENGTH_UNLIMITED) { + field_size = SQL_SERVER_MAX_FIELD_SIZE / elem_size; + } + expected_len = field_size * elem_size + elem_size; // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = column_size * elem_size; + without_null_len = field_size * elem_size; // increment to include the null terminator since the Zend length doesn't include the null terminator buffer_len += elem_size; diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 7558e1ab5..9451f0bcd 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -2028,11 +2028,10 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ul decimal_digits = 0; } - // if the user for some reason provides an output parameter with a null phptype and a specified + // if the user for some reason provides an inout / output parameter with a null phptype and a specified // sql server type, infer the php type from the sql server type. - if( direction == SQL_PARAM_OUTPUT && php_type_param_was_null && !sql_type_param_was_null ) { + if( direction != SQL_PARAM_INPUT && php_type_param_was_null && !sql_type_param_was_null ) { - int encoding; sqlsrv_phptype sqlsrv_phptype; sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); @@ -2043,7 +2042,7 @@ void parse_param_array( ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ul "validated sql type and column_size" ); php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); - encoding = sqlsrv_phptype.typeinfo.encoding; + encoding = ( SQLSRV_ENCODING ) sqlsrv_phptype.typeinfo.encoding; } // verify that the parameter is a valid output param type diff --git a/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt b/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt new file mode 100644 index 000000000..96d2a9b81 --- /dev/null +++ b/test/sqlsrv/srv_231_string_truncation_varchar_max.phpt @@ -0,0 +1,189 @@ +--TEST-- +GitHub issue #231 - String truncation when binding varchar(max) +--SKIPIF-- +--FILE-- +$username, "PWD"=>$password); +$conn = sqlsrv_connect($serverName, $connectionInfo); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$tableName = "#testDataTypes_GH231"; +$columnNames = array( "c1","c2" ); + +for ($k = 1; $k <= 8; $k++) +{ + $sqlType = GetSqlType($k); + $dataType = "[$columnNames[0]] int, [$columnNames[1]] $sqlType"; + + $sql = "CREATE TABLE [$tableName] ($dataType)"; + $stmt1 = sqlsrv_query($conn, $sql); + sqlsrv_free_stmt($stmt1); + + $sql = "INSERT INTO [$tableName] ($columnNames[0], $columnNames[1]) VALUES (?, ?)"; + $data = GetData($k); + $phpType = GetPhpType($k); + $driverType = GetDriverType($k, strlen($data)); + + $params = array($k, array($data, SQLSRV_PARAM_IN, $phpType, $driverType)); + $stmt2 = sqlsrv_prepare($conn, $sql, $params); + sqlsrv_execute($stmt2); + sqlsrv_free_stmt($stmt2); + + ExecProc($conn, $tableName, $columnNames, $k, $data, $sqlType); + + $stmt3 = sqlsrv_query($conn, "DROP TABLE [$tableName]"); + sqlsrv_free_stmt($stmt3); +} + +sqlsrv_close($conn); + + +function ExecProc($conn, $tableName, $columnNames, $k, $data, $sqlType) +{ + $spArgs = "@p1 int, @p2 $sqlType OUTPUT"; + $spCode = "SET @p2 = ( SELECT c2 FROM $tableName WHERE c1 = @p1 )"; + $procName = "testBindOutSp"; + + $stmt1 = sqlsrv_query($conn, "CREATE PROC [$procName] ($spArgs) AS BEGIN $spCode END"); + sqlsrv_free_stmt($stmt1); + + echo "\nData Type: ".$sqlType." binding as \n"; + + $direction = SQLSRV_PARAM_OUT; + echo "Output parameter: \t"; + CallProc($conn, $procName, $k, $direction, $data); + + $direction = SQLSRV_PARAM_INOUT; + echo "InOut parameter: \t"; + CallProc($conn, $procName, $k, $direction, $data); + + $stmt2 = sqlsrv_query($conn, "DROP PROC [$procName]"); + sqlsrv_free_stmt($stmt2); +} + +function CallProc($conn, $procName, $k, $direction, $data) +{ + $driverType = GetDriverType($k, strlen($data)); + $callArgs = "?, ?"; + + // Data to initialize $callResult variable. This variable should be shorter than inserted data in the table + $initData = "ShortString"; + $callResult = $initData; + + // Make sure not to specify the PHP type + $params = array( array( $k, SQLSRV_PARAM_IN ), + array( &$callResult, $direction, null, $driverType )); + $stmt = sqlsrv_query($conn, "{ CALL [$procName] ($callArgs)}", $params); + if($stmt === false) { + die( print_r( sqlsrv_errors(), true )); + } + + // $callResult should be updated to the value in the table + $matched = ($callResult === $data); + if ($matched) + echo "data matched!\n"; + else + echo "failed!\n"; + + sqlsrv_free_stmt($stmt); +} + +function GetData($k) +{ + $data = "LongStringForTesting"; + if ($k == 8) { + $data = "The quick brown fox jumps over the lazy dog0123456789"; + } + + return $data; +} + +function GetPhpType($k) +{ + $phpType = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR); + if ($k == 7) { + $phpType = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY); + } + + return $phpType; +} + +function GetSqlType($k) +{ + switch ($k) + { + case 1: return ("char(512)"); + case 2: return ("varchar(512)"); + case 3: return ("varchar(max)"); + case 4: return ("nchar(512)"); + case 5: return ("nvarchar(512)"); + case 6: return ("nvarchar(max)"); + case 7: return ("varbinary(max)"); + case 8: return ("xml"); + default: break; + } + return ("udt"); +} + +function GetDriverType($k, $dataSize) +{ + switch ($k) + { + case 1: return (SQLSRV_SQLTYPE_CHAR($dataSize)); + case 2: return (SQLSRV_SQLTYPE_VARCHAR($dataSize)); + case 3: return (SQLSRV_SQLTYPE_VARCHAR('max')); + case 4: return (SQLSRV_SQLTYPE_NCHAR($dataSize)); + case 5: return (SQLSRV_SQLTYPE_NVARCHAR($dataSize)); + case 6: return (SQLSRV_SQLTYPE_NVARCHAR('max')); + case 7: return (SQLSRV_SQLTYPE_VARBINARY('max')); + case 8: return (SQLSRV_SQLTYPE_XML); + + default: break; + } + return (SQLSRV_SQLTYPE_UDT); +} + +?> + +--EXPECT-- + +Data Type: char(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varchar(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nvarchar(512) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: nvarchar(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: varbinary(max) binding as +Output parameter: data matched! +InOut parameter: data matched! + +Data Type: xml binding as +Output parameter: data matched! +InOut parameter: data matched! +