Feature request - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects (#842)

* Feature request - issue 648

* Fixed constructor for field_cache and added another test

* Added tests for FETCH_BOUND

* Added a new test for output param

* Modified output param test to set attributes differently

* Removed a useless helped function in a test

* Combined two new tests into one as per review

* Uncommented dropTable
This commit is contained in:
Jenny Tam 2018-09-17 16:24:52 -07:00 committed by GitHub
parent e51380612d
commit 7521f095ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 602 additions and 56 deletions

View file

@ -80,6 +80,7 @@ enum PDO_STMT_OPTIONS {
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
PDO_STMT_OPTION_EMULATE_PREPARES,
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE
};
// List of all the statement options supported by this driver.
@ -93,6 +94,7 @@ const stmt_option PDO_STMT_OPTS[] = {
{ NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit ) },
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
};
@ -495,7 +497,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
direct_query( false ),
query_timeout( QUERY_TIMEOUT_INVALID ),
client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )),
fetch_numeric( false )
fetch_numeric( false ),
fetch_datetime( false )
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
@ -1061,6 +1064,10 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false;
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false;
break;
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
@ -1212,6 +1219,12 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
break;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_dbh->fetch_datetime );
break;
}
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
@ -1569,6 +1582,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_
option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE;
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
break;
default:
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
throw core::CoreException();

View file

@ -285,6 +285,7 @@ namespace {
{ "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE },
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
// PDO::PARAM_STR uses the size of the string in the variable

View file

@ -51,6 +51,9 @@ inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orienta
// for list of supported pdo types.
SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type TSRMLS_DC )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>(driver_stmt);
SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null");
switch( pdo_type ) {
case PDO_PARAM_BOOL:
@ -64,9 +67,12 @@ SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _I
return SQLSRV_PHPTYPE_NULL;
case PDO_PARAM_LOB:
// TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented.
return SQLSRV_PHPTYPE_STRING;
if (pdo_stmt->fetch_datetime) {
return SQLSRV_PHPTYPE_DATETIME;
} else {
// TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented.
return SQLSRV_PHPTYPE_STRING;
}
case PDO_PARAM_STMT:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
break;
@ -213,61 +219,63 @@ void meta_data_free( _Inout_ field_meta_data* meta )
zval convert_to_zval( _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len )
{
zval out_zval;
ZVAL_UNDEF( &out_zval );
ZVAL_UNDEF(&out_zval);
switch( sqlsrv_php_type ) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
if( *in_val == NULL ) {
ZVAL_NULL( &out_zval );
switch (sqlsrv_php_type) {
case SQLSRV_PHPTYPE_INT:
case SQLSRV_PHPTYPE_FLOAT:
{
if (*in_val == NULL) {
ZVAL_NULL(&out_zval);
}
else {
if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
ZVAL_LONG(&out_zval, **(reinterpret_cast<int**>(in_val)));
}
else {
if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) {
ZVAL_LONG( &out_zval, **( reinterpret_cast<int**>( in_val )));
}
else {
ZVAL_DOUBLE( &out_zval, **( reinterpret_cast<double**>( in_val )));
}
ZVAL_DOUBLE(&out_zval, **(reinterpret_cast<double**>(in_val)));
}
if( *in_val ) {
sqlsrv_free( *in_val );
}
break;
}
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented
{
if( *in_val == NULL ) {
ZVAL_NULL( &out_zval );
}
else {
ZVAL_STRINGL( &out_zval, reinterpret_cast<char*>( *in_val ), field_len );
sqlsrv_free( *in_val );
}
break;
if (*in_val) {
sqlsrv_free(*in_val);
}
case SQLSRV_PHPTYPE_DATETIME:
DIE( "Unsupported php type" );
out_zval = *( reinterpret_cast<zval*>( *in_val ));
break;
case SQLSRV_PHPTYPE_NULL:
ZVAL_NULL( &out_zval );
break;
break;
}
case SQLSRV_PHPTYPE_STRING:
case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented
{
if (*in_val == NULL) {
default:
DIE( "Unknown php type" );
break;
ZVAL_NULL(&out_zval);
}
else {
ZVAL_STRINGL(&out_zval, reinterpret_cast<char*>(*in_val), field_len);
sqlsrv_free(*in_val);
}
break;
}
case SQLSRV_PHPTYPE_DATETIME:
if (*in_val == NULL) {
ZVAL_NULL(&out_zval);
}
else {
out_zval = *(reinterpret_cast<zval*>(*in_val));
sqlsrv_free(*in_val);
}
break;
case SQLSRV_PHPTYPE_NULL:
ZVAL_NULL(&out_zval);
break;
default:
DIE("Unknown php type");
break;
}
return out_zval;
@ -339,6 +347,11 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt
pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false;
}
void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC )
{
pdo_sqlsrv_stmt *pdo_stmt = static_cast<pdo_sqlsrv_stmt*>( stmt );
pdo_stmt->fetch_datetime = ( zend_is_true( value_z )) ? true : false;
}
// log a function entry point
#ifndef _WIN32
@ -865,6 +878,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false;
break;
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
break;
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
@ -946,6 +963,12 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
break;
}
case SQLSRV_ATTR_FETCHES_DATETIME_TYPE:
{
ZVAL_BOOL( return_value, driver_stmt->fetch_datetime );
break;
}
default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
@ -1365,6 +1388,17 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
}
break;
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
if ( this->fetch_datetime ) {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
}
else {
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
}
break;
case SQL_BIGINT:
case SQL_CHAR:
case SQL_DECIMAL:
@ -1373,10 +1407,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type,
case SQL_WCHAR:
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_TYPE_DATE:
case SQL_SS_TIMESTAMPOFFSET:
case SQL_SS_TIME2:
case SQL_TYPE_TIMESTAMP:
case SQL_LONGVARCHAR:
case SQL_WLONGVARCHAR:
case SQL_SS_XML:

View file

@ -48,6 +48,7 @@ enum PDO_SQLSRV_ATTR {
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
SQLSRV_ATTR_FETCHES_DATETIME_TYPE
};
// valid set of values for TransactionIsolation connection option
@ -203,6 +204,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
long query_timeout;
zend_long client_buffer_max_size;
bool fetch_numeric;
bool fetch_datetime;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
};
@ -241,6 +243,10 @@ struct stmt_option_fetch_numeric : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC );
};
struct stmt_option_fetch_datetime : public stmt_option_functor {
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC );
};
extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods;
// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary
@ -253,11 +259,13 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
direct_query_subst_string_len( 0 ),
placeholders(NULL),
bound_column_param_types( NULL ),
fetch_numeric( false )
fetch_numeric( false ),
fetch_datetime( false )
{
pdo_sqlsrv_dbh* db = static_cast<pdo_sqlsrv_dbh*>( c );
direct_query = db->direct_query;
fetch_numeric = db->fetch_numeric;
fetch_datetime = db->fetch_datetime;
}
virtual ~pdo_sqlsrv_stmt( void );
@ -275,6 +283,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;
pdo_param_type* bound_column_param_types;
bool fetch_numeric;
bool fetch_datetime;
};

View file

@ -36,7 +36,8 @@ struct field_cache {
: type( t )
{
// if the value is NULL, then just record a NULL pointer
if( field_value != NULL ) {
// field_len may be equal to SQL_NULL_DATA even when field_value is not null
if( field_value != NULL && field_len != SQL_NULL_DATA) {
value = sqlsrv_malloc( field_len );
memcpy_s( value, field_len, field_value, field_len );
len = field_len;

View file

@ -0,0 +1,87 @@
--TEST--
Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE and datetimes as output params
--DESCRIPTION--
Do not support returning DateTime objects as output parameters. Setting attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE to true should have no effect.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
date_default_timezone_set('America/Los_Angeles');
$attr = array(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE => false);
$conn = connect("", $attr);
// Generate input values for the test table
$query = 'SELECT SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)';
$stmt = $conn->query($query);
$values = $stmt->fetch(PDO::FETCH_NUM);
// create a test table with the above input date time values
$tableName = "TestDateTimeOutParam";
$columns = array('c1', 'c2', 'c3');
$dataTypes = array("datetime2", "datetimeoffset", "time");
$colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]),
new ColumnMeta($dataTypes[1], $columns[1]),
new ColumnMeta($dataTypes[2], $columns[2]));
createTable($conn, $tableName, $colMeta);
$query = "INSERT INTO $tableName VALUES(?, ?, ?)";
$stmt = $conn->prepare($query);
for ($i = 0; $i < count($columns); $i++) {
$stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB);
}
$stmt->execute();
$lobException = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.';
for ($i = 0; $i < count($columns); $i++) {
// create the stored procedure first
$storedProcName = "spDateTimeOutParam" . $i;
$procArgs = "@col $dataTypes[$i] OUTPUT";
$procCode = "SELECT @col = $columns[$i] FROM $tableName";
createProc($conn, $storedProcName, $procArgs, $procCode);
// call stored procedure to retrieve output param type PDO::PARAM_STR
$dateStr = '';
$outSql = getCallProcSqlPlaceholders($storedProcName, 1);
$options = array(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE => true);
$stmt = $conn->prepare($outSql, $options);
$stmt->bindParam(1, $dateStr, PDO::PARAM_STR, 1024);
$stmt->execute();
if ($dateStr != $values[$i]) {
echo "Expected $values[$i] for column ' . ($i+1) .' but got: ";
var_dump($dateStr);
}
// for output param type PDO::PARAM_LOB it should fail with the correct exception
try {
$stmt->bindParam(1, $dateStr, PDO::PARAM_LOB, 1024);
$stmt->execute();
echo "Expected this to fail\n";
} catch (PDOException $e) {
$message = $e->getMessage();
$matched = strpos($message, $lobException);
if (!$matched) {
var_dump($e->errorInfo);
}
}
dropProc($conn, $storedProcName);
}
dropTable($conn, $tableName);
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,238 @@
--TEST--
Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for date, time and datetime columns
--DESCRIPTION--
Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime, datetime2,
smalldatetime, datetimeoffset and time columns. The input values are based on current
timestamp and they are retrieved either as strings or date time objects. Note that the
existing attributes ATTR_STRINGIFY_FETCHES and SQLSRV_ATTR_FETCHES_NUMERIC_TYPE
should have no effect on data retrieval.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function checkStringValues($obj, $columns, $values)
{
$size = count($values);
$objArray = (array)$obj; // turn the object into an associated array
for ($i = 0; $i < $size; $i++) {
$col = $columns[$i];
$val = $objArray[$col];
if ($val != $values[$i]) {
echo "Expected $values[$i] for column $col but got: ";
var_dump($val);
}
}
}
function checkDTObjectValues($row, $columns, $values, $fetchStyle)
{
$size = count($values);
for ($i = 0; $i < $size; $i++) {
$col = $columns[$i];
if ($fetchStyle == PDO::FETCH_ASSOC) {
$dtObj = $row[$col];
} else {
// assume PDO::FETCH_BOTH
$dtObj = $row[$i];
}
checkColumnDTValue($i, $col, $values, $dtObj);
}
}
function checkColumnDTValue($index, $column, $values, $dtObj)
{
// expected datetime value as a string
$dtime = date_create($values[$index]);
$dtExpected = $dtime->format('Y-m-d H:i:s.u');
// actual datetime value from date time object to string
$dtActual = date_format($dtObj, 'Y-m-d H:i:s.u');
if ($dtActual != $dtExpected) {
echo "Expected $dtExpected for column $column but the actual value was $dtActual\n";
}
}
function runTest($conn, $query, $columns, $values, $useBuffer = false)
{
// fetch the date time values as strings or date time objects
// prepare with or without buffered cursor
$options = array();
if ($useBuffer) {
$options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED);
}
// fetch_numeric off, fetch_datetime off
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$obj = $stmt->fetch(PDO::FETCH_OBJ);
checkStringValues($obj, $columns, $values);
// fetch_numeric off, fetch_datetime on
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC);
// fetch_numeric on, fetch_datetime on
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_BOTH);
checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH);
// ATTR_STRINGIFY_FETCHES should have no effect when fetching date time objects
// Setting it to true only converts numeric values to strings when fetching
// See http://www.php.net/manual/en/pdo.setattribute.php for details
// stringify on, fetch_numeric off, fetch_datetime on
$conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$i = 0;
do {
$stmt->execute();
$dtObj = $stmt->fetchColumn($i);
checkColumnDTValue($i, $columns[$i], $values, $dtObj);
} while (++$i < count($columns));
// reset stringify to off
// fetch_numeric off, fetch_datetime off
$conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_OBJ);
checkStringValues($obj, $columns, $values);
// conn attribute fetch_datetime on, but statement attribute fetch_datetime off --
// expected strings to be returned because statement attribute overrides the
// connection attribute
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt->execute();
$obj = $stmt->fetch(PDO::FETCH_OBJ);
checkStringValues($obj, $columns, $values);
// conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on --
// expected datetime objects to be returned (this time no need to prepare the statement)
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC);
// likewise, conn attribute fetch_datetime off, but statement attribute
// fetch_datetime on -- expected datetime objects to be returned
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt = $conn->prepare($query, $options);
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_BOTH);
checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH);
// conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off --
// expected strings to be returned (again no need to prepare the statement)
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt->execute();
$obj = $stmt->fetch(PDO::FETCH_LAZY);
checkStringValues($obj, $columns, $values);
// last test: set statement attribute fetch_datetime on with no change to
// prepared statement -- expected datetime objects to be returned
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$i = 0;
do {
$stmt->execute();
$dtObj = $stmt->fetchColumn($i);
checkColumnDTValue($i, $columns[$i], $values, $dtObj);
} while (++$i < count($columns));
// keep the same settings but test with FETCH_BOUND
for ($i = 0; $i < count($columns); $i++) {
$dateObj = null;
$stmt->execute();
$stmt->bindColumn($i + 1, $dateObj, PDO::PARAM_LOB);
$row = $stmt->fetch(PDO::FETCH_BOUND);
checkColumnDTValue($i, $columns[$i], $values, $dateObj);
}
// redo the test but with fetch_datetime off
// expected strings to be returned
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
for ($i = 0; $i < count($columns); $i++) {
$dateStr = null;
$stmt->execute();
$stmt->bindColumn($i + 1, $dateStr);
$row = $stmt->fetch(PDO::FETCH_BOUND);
if ($dateStr != $values[$i]) {
$col = $columns[$i];
echo "Expected $values[$i] for column $col but the bound value was: ";
var_dump($dateStr);
}
}
}
try {
date_default_timezone_set('America/Los_Angeles');
$conn = connect();
// Generate input values for the test table
$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(),
CONVERT(smalldatetime, SYSDATETIME()),
CONVERT(datetime, SYSDATETIME()),
SYSDATETIMEOFFSET(),
CONVERT(time, SYSDATETIME())';
$stmt = $conn->query($query);
$values = $stmt->fetch(PDO::FETCH_NUM);
// create a test table with the above input date time values
$tableName = "TestDateTimeOffset";
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
$dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time');
$colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]),
new ColumnMeta($dataTypes[1], $columns[1]),
new ColumnMeta($dataTypes[2], $columns[2]),
new ColumnMeta($dataTypes[3], $columns[3]),
new ColumnMeta($dataTypes[4], $columns[4]),
new ColumnMeta($dataTypes[5], $columns[5]));
createTable($conn, $tableName, $colMeta);
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($query);
for ($i = 0; $i < count($columns); $i++) {
$stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB);
}
$stmt->execute();
$query = "SELECT * FROM $tableName";
runTest($conn, $query, $columns, $values);
runTest($conn, $query, $columns, $values, true);
dropTable($conn, $tableName);
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,163 @@
--TEST--
Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime types with null values
--DESCRIPTION--
Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for different datetime types with
null values. Whether retrieved as strings or date time objects should return NULLs.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function checkNullStrings($row, $columns)
{
$size = count($columns);
for ($i = 0; $i < $size; $i++) {
$col = $columns[$i];
$val = $row[$i];
if (!is_null($val)) {
echo "Expected NULL for column $col but got: ";
var_dump($val);
}
}
}
function checkNullDTObjects($row, $columns, $fetchStyle)
{
$size = count($columns);
for ($i = 0; $i < $size; $i++) {
$col = $columns[$i];
if ($fetchStyle == PDO::FETCH_ASSOC) {
$dtObj = $row[$col];
} else {
// assume PDO::FETCH_BOTH
$dtObj = $row[$i];
}
if (!is_null($dtObj)) {
echo "Expected NULL for column $col but got: ";
var_dump($dtObj);
}
}
}
function runTest($conn, $query, $columns, $useBuffer = false)
{
// fetch the date time values as strings or date time objects
// prepare with or without buffered cursor
$options = array();
if ($useBuffer) {
$options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED);
}
// fetch_numeric off, fetch_datetime off
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_NUM);
checkNullStrings($row, $columns);
// fetch_numeric off, fetch_datetime on
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC);
// fetch_numeric on, fetch_datetime on
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_BOTH);
checkNullDTObjects($row, $columns, PDO::FETCH_BOTH);
// conn attribute fetch_datetime on, but statement attribute fetch_datetime off --
// expected strings to be returned because statement attribute overrides the
// connection attribute
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt = $conn->prepare($query, $options);
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_NUM);
checkNullStrings($row, $columns);
// conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on --
// expected datetime objects to be returned (this time no need to prepare the statement)
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC);
// likewise, conn attribute fetch_datetime off, but statement attribute
// fetch_datetime on -- expected datetime objects to be returned
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt = $conn->prepare($query, $options);
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_BOTH);
checkNullDTObjects($row, $columns, PDO::FETCH_BOTH);
// conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off --
// expected strings to be returned (again no need to prepare the statement)
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_NUM);
checkNullStrings($row, $columns);
// last test: set statement attribute fetch_datetime on with no change to
// prepared statement -- expected datetime objects to be returned
$stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
$stmt->execute();
$i = 0;
do {
$stmt->execute();
$dtObj = $stmt->fetchColumn($i);
if (!is_null($dtObj)) {
echo "Expected NULL for column " . ($i + 1) . " but got: ";
var_dump($dtObj);
}
} while (++$i < count($columns));
}
try {
$conn = connect();
// create a test table
$tableName = "TestNullDateTime";
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
$colMeta = array(new ColumnMeta('date', $columns[0]),
new ColumnMeta('datetime', $columns[1]),
new ColumnMeta('smalldatetime', $columns[2]),
new ColumnMeta('datetime2', $columns[3]),
new ColumnMeta('datetimeoffset', $columns[4]),
new ColumnMeta('time', $columns[5]));
createTable($conn, $tableName, $colMeta);
$value = null;
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($query);
for ($i = 0; $i < count($columns); $i++) {
$stmt->bindParam($i+1, $value, PDO::PARAM_NULL);
}
$stmt->execute();
$query = "SELECT * FROM $tableName";
runTest($conn, $query, $columns);
runTest($conn, $query, $columns, true);
dropTable($conn, $tableName);
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done