diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 43435eb3f..ab289479f 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -44,8 +44,10 @@ RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools ENV PATH="/opt/mssql-tools/bin:${PATH}" -#install coveralls -RUN python -m pip install --upgrade pip && pip install cpp-coveralls +#install coveralls (upgrade both pip and requests first) +RUN python -m pip install --upgrade pip +RUN python -m pip install --upgrade requests +RUN python -m pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 35e49cbe3..59b5afcd3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1939,9 +1939,10 @@ namespace core { inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC ) { + // Skip this if not SQL_ERROR - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { + if (r == SQL_ERROR) { SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ae86d4884..58ec45208 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1298,15 +1298,15 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) php_stream* param_stream = NULL; core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); + // if we're at the end, then reset both current_stream and current_stream_read + if (php_stream_eof(param_stream)) { + // yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again + int ret = php_stream_seek(param_stream, 0, SEEK_SET); + if (ret != 0) { + LOG(SEV_ERROR, "PHP stream: stream seek failed."); + throw core::CoreException(); } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream = sqlsrv_stream(NULL, SQLSRV_ENCODING_CHAR); stmt->current_stream_read = 0; } // read the data from the stream, send it via SQLPutData and track how much we've sent. @@ -1322,7 +1322,12 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { + if (read == 0) { + // send an empty string, which is what a 0 length does. + char buff[1]; // temp storage to hand to SQLPutData + core::SQLPutData(stmt, buff, 0 TSRMLS_CC); + } + else if (read > 0) { // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it // twice. diff --git a/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc b/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc index 14f89a1a3..2fe6d98ab 100644 --- a/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc +++ b/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc @@ -12,7 +12,7 @@ $int_col = array(1, 2); $bin = fopen('php://memory', 'a'); -fwrite($bin, '00'); +fwrite($bin, hex2bin('6162636465')); // 'abcde' rewind($bin); $binary_col = array($bin, $bin); diff --git a/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt b/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt index 99c9b6c5d..bdd87ffe0 100644 Binary files a/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt and b/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt differ diff --git a/test/functional/pdo_sqlsrv/pdostatement_bindParam_empty_binary.phpt b/test/functional/pdo_sqlsrv/pdostatement_bindParam_empty_binary.phpt new file mode 100644 index 000000000..e3db103fd --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdostatement_bindParam_empty_binary.phpt @@ -0,0 +1,92 @@ +--TEST-- +PDOStatement::BindParam for binary types with empty strings and non-empty ones +--DESCRIPTION-- +PDOStatement::BindParam for binary types with empty strings and non-empty ones +Related to GitHub PR 865 - verify that the same binary data can be reused rather +than flushed after the first use +--SKIPIF-- + +--FILE-- +prepare($query); + $stmt->bindParam(1, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(3, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + + $stmt->execute(); + fclose($bin); + + $bin2 = fopen('php://memory', 'a'); + fwrite($bin2, $inputs[1]); // 'ABC' will be 0x414243 in hex + rewind($bin2); + + $stmt->bindParam(1, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(3, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + + $stmt->execute(); + fclose($bin2); + + // Verify the data by fetching and comparing against the inputs + $query = "SELECT * FROM $tableName"; + $stmt = $conn->query($query); + $rowset = $stmt->fetchAll(); + + for ($i = 0; $i < 2; $i++) { + for ($j = 0; $j < 3; $j++) { + $str = $rowset[$i][$j]; + $len = strlen($str); + $failed = false; + + if ($j == 0) { + // binary fields have fixed size, unlike varbinary ones + if ($len !== $size || trim($str) !== $inputs[$i]) { + $failed = true; + } + } else { + if ($len !== strlen($inputs[$i]) || $str !== $inputs[$i]) { + $failed = true; + } + } + + if ($failed) { + $row = $i + 1; + $col = $j + 1; + echo "Unexpected value returned from row $row and column $col: \n"; + var_dump($str); + } + } + } + + dropTable($conn, $tableName); + unset($stmt); + unset($conn); + + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e); + exit; +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index dc7ccae64..62bc542d8 100644 Binary files a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt and b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt differ diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt index 3a2802dcd..01475a6a0 100644 Binary files a/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt and b/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt differ diff --git a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt index 339880bc8..f235ed94b 100644 Binary files a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt and b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt differ diff --git a/test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt b/test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt new file mode 100644 index 000000000..8ddff7fba --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt @@ -0,0 +1,105 @@ +--TEST-- +Test for inserting empty strings and non-empty ones into binary types +--DESCRIPTION-- +Test for inserting empty strings and non-empty ones into binary types +Related to GitHub PR 865 - verify that the same binary data can be reused rather +than flushed after the first use +--SKIPIF-- + +--FILE-- + $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS); + + +$inputs = array(new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_BINARY($size)"), + new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_VARBINARY($size)"), + new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_VARBINARY('max')")); +$r; +$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS); + +// Verify the data by fetching and comparing against the inputs +$query = "SELECT * FROM $tableName"; +$stmt = sqlsrv_query($conn, $query); +if (!$stmt) { + fatalError("Failed to retrieve data from $tableName"); +} + +for ($i = 0; $i < 2; $i++) { + $rowNum = $i + 1; + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + if (!$row) { + fatalError("Failed in sqlsrv_fetch_array for row $rowNum"); + } + + for ($j = 0; $j < 3; $j++) { + $str = $row[$j]; + $len = strlen($str); + $failed = false; + + if ($j == 0) { + // binary fields have fixed size, unlike varbinary ones + if ($len !== $size || trim($str) !== $inputValues[$i]) { + $failed = true; + } + } else { + $inputLen = strlen($inputValues[$i]); + if ($len !== $inputLen || $str !== $inputValues[$i]) { + $failed = true; + } + } + + if ($failed) { + $colNum = $j + 1; + echo "Unexpected value returned from row $rowNum and column $colNum: \n"; + var_dump($str); + } + } +} + +dropTable($conn, $tableName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +echo "Done\n"; + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_largeData.phpt b/test/functional/sqlsrv/test_largeData.phpt index 5f1969ae6..e282df2a7 100644 --- a/test/functional/sqlsrv/test_largeData.phpt +++ b/test/functional/sqlsrv/test_largeData.phpt @@ -42,6 +42,12 @@ class my_stream { function stream_seek($offset, $whence) { + // For the purpose of this test only support SEEK_SET to $offset 0 + if ($whence == SEEK_SET && $offset == 0) { + $this->total_read = $offset; + return true; + } + return false; } }