Skip to content

Commit b3072a9

Browse files
authored
Modified how to send stream data using SQLPutData and SQLParamData (#865)
1 parent 18094a6 commit b3072a9

11 files changed

+224
-13
lines changed

Dockerfile-msphpsql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt
4444
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools
4545
ENV PATH="/opt/mssql-tools/bin:${PATH}"
4646

47-
#install coveralls
48-
RUN python -m pip install --upgrade pip && pip install cpp-coveralls
47+
#install coveralls (upgrade both pip and requests first)
48+
RUN python -m pip install --upgrade pip
49+
RUN python -m pip install --upgrade requests
50+
RUN python -m pip install cpp-coveralls
4951

5052
#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html )
5153
#One option is to get source from zip file of repository.

source/shared/core_sqlsrv.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1939,9 +1939,10 @@ namespace core {
19391939

19401940
inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC )
19411941
{
1942+
// Skip this if not SQL_ERROR -
19421943
// We check for the 'connection busy' error caused by having MultipleActiveResultSets off
19431944
// and return a more helpful message prepended to the ODBC errors if that error occurs
1944-
if( !SQL_SUCCEEDED( r )) {
1945+
if (r == SQL_ERROR) {
19451946

19461947
SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'};
19471948
SQLSMALLINT len = 0;

source/shared/core_stmt.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,15 +1298,15 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
12981298
php_stream* param_stream = NULL;
12991299
core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC );
13001300

1301-
// if we're at the end, then release our current parameter
1302-
if( php_stream_eof( param_stream )) {
1303-
// if no data was actually sent prior, then send a NULL
1304-
if( stmt->current_stream_read == 0 ) {
1305-
// send an empty string, which is what a 0 length does.
1306-
char buff[1]; // temp storage to hand to SQLPutData
1307-
core::SQLPutData( stmt, buff, 0 TSRMLS_CC );
1301+
// if we're at the end, then reset both current_stream and current_stream_read
1302+
if (php_stream_eof(param_stream)) {
1303+
// yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again
1304+
int ret = php_stream_seek(param_stream, 0, SEEK_SET);
1305+
if (ret != 0) {
1306+
LOG(SEV_ERROR, "PHP stream: stream seek failed.");
1307+
throw core::CoreException();
13081308
}
1309-
stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR );
1309+
stmt->current_stream = sqlsrv_stream(NULL, SQLSRV_ENCODING_CHAR);
13101310
stmt->current_stream_read = 0;
13111311
}
13121312
// 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 )
13221322
}
13231323

13241324
stmt->current_stream_read += static_cast<unsigned int>( read );
1325-
if( read > 0 ) {
1325+
if (read == 0) {
1326+
// send an empty string, which is what a 0 length does.
1327+
char buff[1]; // temp storage to hand to SQLPutData
1328+
core::SQLPutData(stmt, buff, 0 TSRMLS_CC);
1329+
}
1330+
else if (read > 0) {
13261331
// 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
13271332
// then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it
13281333
// twice.

test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
$int_col = array(1, 2);
1313

1414
$bin = fopen('php://memory', 'a');
15-
fwrite($bin, '00');
15+
fwrite($bin, hex2bin('6162636465')); // 'abcde'
1616
rewind($bin);
1717
$binary_col = array($bin, $bin);
1818

Binary file not shown.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
--TEST--
2+
PDOStatement::BindParam for binary types with empty strings and non-empty ones
3+
--DESCRIPTION--
4+
PDOStatement::BindParam for binary types with empty strings and non-empty ones
5+
Related to GitHub PR 865 - verify that the same binary data can be reused rather
6+
than flushed after the first use
7+
--SKIPIF--
8+
<?php require('skipif_mid-refactor.inc'); ?>
9+
--FILE--
10+
<?php
11+
require_once("MsCommon_mid-refactor.inc");
12+
13+
try {
14+
$conn = connect();
15+
$tableName = "pdoEmptyBinary";
16+
$size = 6;
17+
18+
$colMetaArr = array(new ColumnMeta("binary($size)", "BinaryCol"),
19+
new ColumnMeta("varbinary($size)", "VarBinaryCol"),
20+
new ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
21+
createTable($conn, $tableName, $colMetaArr);
22+
23+
// Insert two rows, first empty strings and the second not empty
24+
$inputs = array('', 'ABC');
25+
26+
$bin = fopen('php://memory', 'a');
27+
fwrite($bin, $inputs[0]); // an empty string will be 0x in hex
28+
rewind($bin);
29+
30+
$query = "INSERT INTO $tableName VALUES(?, ?, ?)";
31+
$stmt = $conn->prepare($query);
32+
$stmt->bindParam(1, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
33+
$stmt->bindParam(2, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
34+
$stmt->bindParam(3, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
35+
36+
$stmt->execute();
37+
fclose($bin);
38+
39+
$bin2 = fopen('php://memory', 'a');
40+
fwrite($bin2, $inputs[1]); // 'ABC' will be 0x414243 in hex
41+
rewind($bin2);
42+
43+
$stmt->bindParam(1, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
44+
$stmt->bindParam(2, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
45+
$stmt->bindParam(3, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
46+
47+
$stmt->execute();
48+
fclose($bin2);
49+
50+
// Verify the data by fetching and comparing against the inputs
51+
$query = "SELECT * FROM $tableName";
52+
$stmt = $conn->query($query);
53+
$rowset = $stmt->fetchAll();
54+
55+
for ($i = 0; $i < 2; $i++) {
56+
for ($j = 0; $j < 3; $j++) {
57+
$str = $rowset[$i][$j];
58+
$len = strlen($str);
59+
$failed = false;
60+
61+
if ($j == 0) {
62+
// binary fields have fixed size, unlike varbinary ones
63+
if ($len !== $size || trim($str) !== $inputs[$i]) {
64+
$failed = true;
65+
}
66+
} else {
67+
if ($len !== strlen($inputs[$i]) || $str !== $inputs[$i]) {
68+
$failed = true;
69+
}
70+
}
71+
72+
if ($failed) {
73+
$row = $i + 1;
74+
$col = $j + 1;
75+
echo "Unexpected value returned from row $row and column $col: \n";
76+
var_dump($str);
77+
}
78+
}
79+
}
80+
81+
dropTable($conn, $tableName);
82+
unset($stmt);
83+
unset($conn);
84+
85+
echo "Done\n";
86+
} catch (PDOException $e) {
87+
var_dump($e);
88+
exit;
89+
}
90+
?>
91+
--EXPECT--
92+
Done
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
--TEST--
2+
Test for inserting empty strings and non-empty ones into binary types
3+
--DESCRIPTION--
4+
Test for inserting empty strings and non-empty ones into binary types
5+
Related to GitHub PR 865 - verify that the same binary data can be reused rather
6+
than flushed after the first use
7+
--SKIPIF--
8+
<?php require('skipif_versions_old.inc'); ?>
9+
--FILE--
10+
<?php
11+
require_once('MsCommon.inc');
12+
13+
$conn = AE\connect();
14+
15+
$tableName = "sqlsrvEmptyBinary";
16+
$size = 6;
17+
18+
$colMetaArr = array(new AE\ColumnMeta("binary($size)", "BinaryCol"),
19+
new AE\ColumnMeta("varbinary($size)", "VarBinaryCol"),
20+
new AE\ColumnMeta("varbinary(max)", "VarBinaryMaxCol"));
21+
AE\createTable($conn, $tableName, $colMetaArr);
22+
23+
// Insert two rows, first empty strings and the second not empty
24+
$inputValues = array('', 'ABC');
25+
26+
$inputs = array(new AE\BindParamOption($inputValues[0],
27+
null,
28+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
29+
"SQLSRV_SQLTYPE_BINARY($size)"),
30+
new AE\BindParamOption($inputValues[0],
31+
null,
32+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
33+
"SQLSRV_SQLTYPE_VARBINARY($size)"),
34+
new AE\BindParamOption($inputValues[0],
35+
null,
36+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
37+
"SQLSRV_SQLTYPE_VARBINARY('max')"));
38+
$r;
39+
$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS);
40+
41+
42+
$inputs = array(new AE\BindParamOption($inputValues[1],
43+
null,
44+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
45+
"SQLSRV_SQLTYPE_BINARY($size)"),
46+
new AE\BindParamOption($inputValues[1],
47+
null,
48+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
49+
"SQLSRV_SQLTYPE_VARBINARY($size)"),
50+
new AE\BindParamOption($inputValues[1],
51+
null,
52+
"SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)",
53+
"SQLSRV_SQLTYPE_VARBINARY('max')"));
54+
$r;
55+
$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS);
56+
57+
// Verify the data by fetching and comparing against the inputs
58+
$query = "SELECT * FROM $tableName";
59+
$stmt = sqlsrv_query($conn, $query);
60+
if (!$stmt) {
61+
fatalError("Failed to retrieve data from $tableName");
62+
}
63+
64+
for ($i = 0; $i < 2; $i++) {
65+
$rowNum = $i + 1;
66+
$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
67+
if (!$row) {
68+
fatalError("Failed in sqlsrv_fetch_array for row $rowNum");
69+
}
70+
71+
for ($j = 0; $j < 3; $j++) {
72+
$str = $row[$j];
73+
$len = strlen($str);
74+
$failed = false;
75+
76+
if ($j == 0) {
77+
// binary fields have fixed size, unlike varbinary ones
78+
if ($len !== $size || trim($str) !== $inputValues[$i]) {
79+
$failed = true;
80+
}
81+
} else {
82+
$inputLen = strlen($inputValues[$i]);
83+
if ($len !== $inputLen || $str !== $inputValues[$i]) {
84+
$failed = true;
85+
}
86+
}
87+
88+
if ($failed) {
89+
$colNum = $j + 1;
90+
echo "Unexpected value returned from row $rowNum and column $colNum: \n";
91+
var_dump($str);
92+
}
93+
}
94+
}
95+
96+
dropTable($conn, $tableName);
97+
98+
sqlsrv_free_stmt($stmt);
99+
sqlsrv_close($conn);
100+
101+
echo "Done\n";
102+
103+
?>
104+
--EXPECT--
105+
Done

0 commit comments

Comments
 (0)