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:
parent
e51380612d
commit
7521f095ee
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
163
test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt
Normal file
163
test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt
Normal 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
|
Loading…
Reference in a new issue