Saved php types with metadata when fetching (#1049)

This commit is contained in:
Jenny Tam 2019-11-06 13:14:28 -08:00 committed by GitHub
parent e30752fc6c
commit b77bfa82f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 231 additions and 26 deletions

View file

@ -781,8 +781,12 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
"Invalid column number in pdo_sqlsrv_stmt_get_col_data" );
// set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding
sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast<SQLUINTEGER>( driver_stmt->current_meta_data[colno]->field_type ),
static_cast<SQLUINTEGER>( driver_stmt->current_meta_data[colno]->field_size ), true );
// save the php type for next use
sqlsrv_php_type = driver_stmt->sql_type_to_php_type(
static_cast<SQLINTEGER>(driver_stmt->current_meta_data[colno]->field_type),
static_cast<SQLUINTEGER>(driver_stmt->current_meta_data[colno]->field_size),
true);
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
// if a column is bound to a type different than the column type, figure out a way to convert it to the
// type they want
@ -825,6 +829,9 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno,
break;
}
}
// save the php type for the bound column
driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type;
}
SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID;

View file

@ -1576,15 +1576,23 @@ struct field_meta_data {
SQLSMALLINT field_scale;
SQLSMALLINT field_is_nullable;
bool field_is_money_type;
sqlsrv_phptype sqlsrv_php_type;
field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0),
field_scale (0), field_is_nullable(0), field_is_money_type(false)
{
reset_php_type();
}
~field_meta_data()
{
}
void reset_php_type()
{
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
}
};
// *** statement constants ***

View file

@ -244,6 +244,12 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D )
// delete sensivity data
clean_up_sensitivity_metadata();
// reset sqlsrv php type in meta data
size_t num_fields = this->current_meta_data.size();
for (size_t f = 0; f < num_fields; f++) {
this->current_meta_data[f]->reset_php_type();
}
// create a new result set
if( cursor_type == SQLSRV_CURSOR_BUFFERED ) {
sqlsrv_malloc_auto_ptr<sqlsrv_buffered_result_set> result;
@ -1121,9 +1127,6 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in;
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
// Make sure that the statement was executed and not just prepared.
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
throw core::CoreException();
@ -1132,37 +1135,47 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i
// if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they
// may also be retrieved.
if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) {
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( reinterpret_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( field_value ) {
efree( field_value );
field_value = NULL;
*field_len = 0;
}
}
sqlsrv_phptype invalid;
invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
for( int i = stmt->last_field_index + 1; i < field_index; ++i ) {
SQLSRV_ASSERT( reinterpret_cast<field_cache*>( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." );
core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC );
// delete the value returned since we only want it cached, not the actual value
if( field_value ) {
efree( field_value );
field_value = NULL;
*field_len = 0;
}
}
}
// If the php type was not specified set the php type to be the default type.
if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" );
sql_field_type = stmt->current_meta_data[field_index]->field_type;
if (stmt->current_meta_data[field_index]->field_precision > 0) {
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
// Get the corresponding php type from the sql type and then save the result for later
if (stmt->current_meta_data[field_index]->sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) {
SQLLEN sql_field_type = 0;
SQLLEN sql_field_len = 0;
sql_field_type = stmt->current_meta_data[field_index]->field_type;
if (stmt->current_meta_data[field_index]->field_precision > 0) {
sql_field_len = stmt->current_meta_data[field_index]->field_precision;
}
else {
sql_field_len = stmt->current_meta_data[field_index]->field_size;
}
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
stmt->current_meta_data[field_index]->sqlsrv_php_type = sqlsrv_php_type;
}
else {
sql_field_len = stmt->current_meta_data[field_index]->field_size;
// use the previously saved php type
sqlsrv_php_type = stmt->current_meta_data[field_index]->sqlsrv_php_type;
}
// Get the corresponding php type from the sql type.
sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast<SQLINTEGER>(sql_field_type), static_cast<SQLUINTEGER>(sql_field_len), prefer_string);
}
}
// Verify that we have an acceptable type to convert.
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) {
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(sqlsrv_php_type), stmt, SQLSRV_ERROR_INVALID_TYPE) {
throw core::CoreException();
}

View file

@ -0,0 +1,81 @@
--TEST--
GitHub issue #569 - direct query on varchar max fields results in function sequence error (Always Encrypted)
--DESCRIPTION--
This is similar to pdo_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tableName = 'pdoTestTable_569_ae';
createTable($conn, $tableName, array(new ColumnMeta("int", "id", "IDENTITY"), "c1" => "nvarchar(max)"));
$input = array();
$input[0] = 'some very large string';
$input[1] = '1234567890.1234';
$input[2] = 'über über';
$numRows = 3;
$tsql = "INSERT INTO $tableName (c1) VALUES (?)";
$stmt = $conn->prepare($tsql);
for ($i = 0; $i < $numRows; $i++) {
$stmt->bindParam(1, $input[$i]);
$stmt->execute();
}
$tsql = "SELECT id, c1 FROM $tableName ORDER BY id";
$stmt = $conn->prepare($tsql);
$stmt->execute();
// Fetch one row each time with different pdo type and/or encoding
$result = $stmt->fetch(PDO::FETCH_NUM);
if ($result[1] !== $input[0]) {
echo "Expected $input[0] but got: ";
var_dump($result[0]);
}
$stmt->bindColumn(2, $value, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_SYSTEM);
$result = $stmt->fetch(PDO::FETCH_BOUND);
if (!$result || $value !== $input[1]) {
echo "Expected $input[1] but got: ";
var_dump($result);
}
$stmt->bindColumn(2, $value, PDO::PARAM_STR);
$result = $stmt->fetch(PDO::FETCH_BOUND);
if (!$result || $value !== $input[2]) {
echo "Expected $input[2] but got: ";
var_dump($value);
}
// Fetch again but all at once
$stmt->execute();
$rows = $stmt->fetchall(PDO::FETCH_ASSOC);
for ($i = 0; $i < $numRows; $i++) {
$i = $rows[$i]['id'] - 1;
if ($rows[$i]['c1'] !== $input[$i]) {
echo "Expected $input[$i] but got: ";
var_dump($rows[$i]['c1']);
}
}
unset($stmt);
unset($conn);
} catch (PDOException $e) {
echo $e->getMessage();
}
echo "Done\n";
?>
--EXPECT--
Done

View file

@ -0,0 +1,96 @@
--TEST--
GitHub issue #569 - sqlsrv_query on varchar max fields results in function sequence error
--DESCRIPTION--
This is similar to srv_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
$conn = AE\connect(array('CharacterSet'=>'UTF-8'));
$tableName = 'srvTestTable_569_ae';
$columns = array(new AE\ColumnMeta('int', 'id', 'identity'),
new AE\ColumnMeta('nvarchar(max)', 'c1'));
AE\createTable($conn, $tableName, $columns);
$input = array();
$input[0] = 'some very large string';
$input[1] = '1234567890.1234';
$input[2] = 'über über';
$numRows = 3;
$isql = "INSERT INTO $tableName (c1) VALUES (?)";
for ($i = 0; $i < $numRows; $i++) {
$stmt = sqlsrv_prepare($conn, $isql, array($input[$i]));
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("Failed to insert row $i into $tableName");
}
}
// Select all from test table
$tsql = "SELECT id, c1 FROM $tableName ORDER BY id";
$stmt = sqlsrv_prepare($conn, $tsql);
if (!$stmt) {
fatalError("Failed to read from $tableName");
}
$result = sqlsrv_execute($stmt);
if (!$result) {
fatalError("Failed to select data from $tableName");
}
// Fetch each row as an array
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
$i = $row['id'] - 1;
if ($row['c1'] !== $input[$i]) {
echo "Expected $input[$i] but got: ";
var_dump($fieldVal);
}
}
// Fetch again, one field each time
sqlsrv_execute($stmt);
$i = 0;
while ($i < $numRows) {
sqlsrv_fetch($stmt);
switch ($i) {
case 0:
$fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
break;
case 1:
$stream = sqlsrv_get_field($stmt, 1);
while (!feof( $stream)) {
$fieldVal = fread($stream, 50);
}
break;
default:
$fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING('utf-8'));
break;
}
if ($fieldVal !== $input[$i]) {
echo 'Expected $input[$i] but got: ';
var_dump($fieldVal);
}
$i++;
}
dropTable($conn, $tableName);
echo "Done\n";
sqlsrv_free_stmt($stmt);
sqlsrv_close($conn);
?>
--EXPECT--
Done