Skip to content

Commit

Permalink
Removed the use of a conversion matrix (#1095)
Browse files Browse the repository at this point in the history
  • Loading branch information
yitam authored Feb 19, 2020
1 parent 9534f7b commit af3097d
Show file tree
Hide file tree
Showing 5 changed files with 570 additions and 51 deletions.
101 changes: 60 additions & 41 deletions source/shared/core_results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@

using namespace core;

// conversion matrix
// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
// this is initialized the first time the buffered result set is created.
sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix;

namespace {

// *** internal types ***
Expand Down Expand Up @@ -454,34 +449,6 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm
meta = static_cast<sqlsrv_buffered_result_set::meta_data*>( sqlsrv_malloc( col_count *
sizeof( sqlsrv_buffered_result_set::meta_data )));

// set up the conversion matrix if this is the first time we're called
if( conv_matrix.size() == 0 ) {

conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string;
conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double;
conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long;
conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string;
conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string;
conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double;
conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long;
conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string;
conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string;
conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string;
conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double;
conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long;
conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string;
conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string;
conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double;
conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string;
conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long;
conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string;
}

SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
stmt->encoding());

Expand Down Expand Up @@ -844,18 +811,70 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _
*out_buffer_length = SQL_NULL_DATA;
return SQL_SUCCESS;
}


// check to make sure the conversion type is valid
conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type );
if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) {
last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error )))
sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 );
return SQL_ERROR;
switch (meta[field_index].c_type) {
case SQL_C_CHAR:
switch (target_type) {
case SQL_C_CHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::system_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_WCHAR:
switch (target_type) {
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::wide_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::wstring_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::wstring_to_long(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_BINARY:
switch (target_type) {
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::binary_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::binary_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_LONG:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::long_to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::long_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
case SQL_C_DOUBLE:
switch (target_type) {
case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length);
case SQL_C_WCHAR: return sqlsrv_buffered_result_set::double_to_wide_string(field_index, buffer, buffer_length, out_buffer_length);
default:
break;
}
break;
default:
break;
}

return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length,
out_buffer_length );
// Should not have reached here, return an error
last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error)))
sqlsrv_error((SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0);
return SQL_ERROR;
}

SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier,
Expand Down
7 changes: 0 additions & 7 deletions source/shared/core_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1746,13 +1746,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
sqlsrv_malloc_auto_ptr<SQLCHAR> temp_string; // temp buffer to hold a converted field while in use
SQLLEN temp_length; // number of bytes in the temp conversion buffer

typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t;

// two dimentional sparse matrix that holds the [from][to] functions that do conversions
static conv_matrix_t conv_matrix;

// string conversion functions
SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length,
_Inout_ SQLLEN* out_buffer_length );
Expand Down
6 changes: 3 additions & 3 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1799,11 +1799,11 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i

case SQLSRV_PHPTYPE_INT:
{
sqlsrv_malloc_auto_ptr<long> field_value_temp;
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
sqlsrv_malloc_auto_ptr<SQLLEN> field_value_temp;
field_value_temp = static_cast<SQLLEN*>( sqlsrv_malloc( sizeof( SQLLEN )));
*field_value_temp = 0;

SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ),
field_len, true /*handle_warning*/ TSRMLS_CC );

CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
Expand Down
229 changes: 229 additions & 0 deletions test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
--TEST--
Prepare with cursor buffered and fetch a variety of types converted to different types
--DESCRIPTION--
Test various conversion functionalites for buffered queries with PDO_SQLSRV.
--SKIPIF--
<?php require_once('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");

$tableName = 'pdoFetchingClientBuffer';
$violation = 'Restricted data type attribute violation';
$outOfRange = 'Numeric value out of range';
$truncation = 'Fractional truncation';
$epsilon = 0.00001;

function fetchAsUTF8($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));

// Fetch all fields as UTF-8 strings
for ($i = 0; $i < count($inputs); $i++) {
$stmt->execute();
$f = $stmt->fetchColumn($i);

if ($f !== $inputs[$i]) {
var_dump($f);
}
}
} catch (PdoException $e) {
echo "Caught exception in fetchAsUTF8:\n";
echo $e->getMessage() . PHP_EOL;
}
}

function fetchArray($conn, $tableName, $inputs)
{
$query = "SELECT * FROM $tableName";
try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();

// By default, even numeric or datetime fields are fetched as strings
$result = $stmt->fetch(PDO::FETCH_NUM);
for ($i = 0; $i < count($inputs); $i++) {
if ($result[$i] !== $inputs[$i]) {
var_dump($f);
}
}
} catch (PdoException $e) {
echo "Caught exception in fetchArray:\n";
echo $e->getMessage() . PHP_EOL;
}
}

function fetchBinaryAsNumber($conn, $tableName, $inputs)
{
global $violation;

$query = "SELECT c1 FROM $tableName";

try {
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED, PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE=>true));
$stmt->execute();

$stmt->bindColumn('c1', $binaryValue, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);
echo "in fetchBinaryAsNumber: exception should have been thrown!\n";
} catch (PdoException $e) {
// The varbinary field - expect the violation error
if (strpos($e->getMessage(), $violation) === false) {
echo "in fetchBinaryAsNumber: expected '$violation' but caught this:\n";
echo $e->getMessage() . PHP_EOL;
}
}
}

function fetchBinaryAsBinary($conn, $tableName, $inputs)
{
try {
$query = "SELECT c1 FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();

$stmt->bindColumn('c1', $binaryValue, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $stmt->fetch(PDO::FETCH_BOUND);

if ($binaryValue !== $inputs[0]) {
echo "Fetched binary value unexpected: $binaryValue\n";
}
} catch (PdoException $e) {
echo "Caught exception in fetchBinaryAsBinary:\n";
echo $e->getMessage() . PHP_EOL;
}
}

function fetchFloatAsInt($conn, $tableName)
{
global $truncation;

try {
$query = "SELECT c3 FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();

$stmt->bindColumn('c3', $floatValue, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);

// This should return SQL_SUCCESS_WITH_INFO with the truncation error
$info = $stmt->errorInfo();
if ($info[0] != '01S07' || $info[2] !== $truncation) {
print_r($stmt->errorInfo());
}
} catch (PdoException $e) {
echo "Caught exception in fetchFloatAsInt:\n";
echo $e->getMessage() . PHP_EOL;
}
}

function fetchCharAsInt($conn, $tableName, $column)
{
global $outOfRange;

try {
$query = "SELECT $column FROM $tableName";
$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();

$stmt->bindColumn($column, $value, PDO::PARAM_INT);
$row = $stmt->fetch(PDO::FETCH_BOUND);

// TODO 11297: fix this part outside Windows later
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
echo "in fetchCharAsInt: exception should have been thrown!\n";
} else {
if ($value != 0) {
var_dump($value);
}
}
} catch (PdoException $e) {
// The (n)varchar field - expect the outOfRange error
if (strpos($e->getMessage(), $outOfRange) === false) {
echo "in fetchCharAsInt ($column): expected '$outOfRange' but caught this:\n";
echo $e->getMessage() . PHP_EOL;
}
}
}

function fetchAsNumerics($conn, $tableName, $inputs)
{
// The following calls expect different errors
fetchFloatAsInt($conn, $tableName);
fetchCharAsInt($conn, $tableName, 'c6');
fetchCharAsInt($conn, $tableName, 'c7');

// The following should work
try {
$query = "SELECT c2, c4 FROM $tableName";
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

$stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED));
$stmt->execute();

$stmt->bindColumn('c2', $intValue, PDO::PARAM_INT);
$stmt->bindColumn('c4', $decValue, PDO::PARAM_INT);

$row = $stmt->fetch(PDO::FETCH_BOUND);

if ($intValue !== intval($inputs[1])) {
var_dump($intValue);
}
if ($decValue !== intval($inputs[3])) {
var_dump($decValue);
}
} catch (PdoException $e) {
echo "Caught exception in fetchAsNumerics:\n";
echo $e->getMessage() . PHP_EOL;
}
}

try {
$conn = connect();
$conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);

$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7');
$types = array('varbinary(10)', 'int', 'float(53)', 'decimal(16, 6)', 'datetime2', 'varchar(50)', 'nvarchar(50)');
$inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäოрŀề');

// Create table
$colMeta = array(new ColumnMeta($types[0], $columns[0]),
new ColumnMeta($types[1], $columns[1]),
new ColumnMeta($types[2], $columns[2]),
new ColumnMeta($types[3], $columns[3]),
new ColumnMeta($types[4], $columns[4]),
new ColumnMeta($types[5], $columns[5]),
new ColumnMeta($types[6], $columns[6]));
createTable($conn, $tableName, $colMeta);

// Prepare the input values and insert one row
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($query);
for ($i = 0; $i < count($columns); $i++) {
if ($i == 0) {
$stmt->bindParam($i+1, $inputs[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
} else {
$stmt->bindParam($i+1, $inputs[$i]);
}
}
$stmt->execute();
unset($stmt);

// Starting fetching using client buffers
fetchAsUTF8($conn, $tableName, $inputs);
fetchArray($conn, $tableName, $inputs);
fetchBinaryAsNumber($conn, $tableName, $inputs);
fetchBinaryAsBinary($conn, $tableName, $inputs);
fetchAsNumerics($conn, $tableName, $inputs);

// dropTable($conn, $tableName);
echo "Done\n";
unset($conn);
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done
Loading

0 comments on commit af3097d

Please sign in to comment.