Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified how to send stream data using SQLPutData and SQLParamData #865

Merged
merged 14 commits into from
Oct 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Dockerfile-msphpsql
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion source/shared/core_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
david-puglielli marked this conversation as resolved.
Show resolved Hide resolved

SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'};
SQLSMALLINT len = 0;
Expand Down
23 changes: 14 additions & 9 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -1322,7 +1322,12 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
}

stmt->current_stream_read += static_cast<unsigned int>( 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.
Expand Down
2 changes: 1 addition & 1 deletion test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Binary file modified test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");

try {
$conn = connect();
$tableName = "pdoEmptyBinary";
$size = 6;

$colMetaArr = array(new ColumnMeta("binary($size)", "BinaryCol"),
new ColumnMeta("varbinary($size)", "VarBinaryCol"),
new ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
createTable($conn, $tableName, $colMetaArr);

// Insert two rows, first empty strings and the second not empty
$inputs = array('', 'ABC');

$bin = fopen('php://memory', 'a');
fwrite($bin, $inputs[0]); // an empty string will be 0x in hex
rewind($bin);

$query = "INSERT INTO $tableName VALUES(?, ?, ?)";
$stmt = $conn->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
Binary file modified test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt
Binary file not shown.
Binary file modified test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt
Binary file not shown.
Binary file modified test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt
Binary file not shown.
105 changes: 105 additions & 0 deletions test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');

$conn = AE\connect();

$tableName = "sqlsrvEmptyBinary";
$size = 6;

$colMetaArr = array(new AE\ColumnMeta("binary($size)", "BinaryCol"),
new AE\ColumnMeta("varbinary($size)", "VarBinaryCol"),
new AE\ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
AE\createTable($conn, $tableName, $colMetaArr);

// Insert two rows, first empty strings and the second not empty
$inputValues = array('', 'ABC');

$inputs = array(new AE\BindParamOption($inputValues[0],
null,
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
"SQLSRV_SQLTYPE_BINARY($size)"),
new AE\BindParamOption($inputValues[0],
null,
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
"SQLSRV_SQLTYPE_VARBINARY($size)"),
new AE\BindParamOption($inputValues[0],
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);


$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
6 changes: 6 additions & 0 deletions test/functional/sqlsrv/test_largeData.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down