Feature request - add ReturnDatesAsStrings option to statement level for sqlsrv (#844)
* Added ReturnDatesAsStrings option to the statement level * Added new tests for ReturnDatesAsStrings at statement level * Added more datetime types as per review
This commit is contained in:
parent
7521f095ee
commit
902a03263e
|
@ -1098,6 +1098,7 @@ enum SQLSRV_STMT_OPTIONS {
|
||||||
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
|
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
|
||||||
SQLSRV_STMT_OPTION_SCROLLABLE,
|
SQLSRV_STMT_OPTION_SCROLLABLE,
|
||||||
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
||||||
|
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
||||||
|
|
||||||
// Driver specific connection options
|
// Driver specific connection options
|
||||||
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
|
SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000,
|
||||||
|
@ -1282,6 +1283,11 @@ struct stmt_option_buffered_query_limit : public stmt_option_functor {
|
||||||
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct stmt_option_date_as_string : public stmt_option_functor {
|
||||||
|
|
||||||
|
virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC );
|
||||||
|
};
|
||||||
|
|
||||||
// used to hold the table for statment options
|
// used to hold the table for statment options
|
||||||
struct stmt_option {
|
struct stmt_option {
|
||||||
|
|
||||||
|
@ -1393,6 +1399,7 @@ struct sqlsrv_stmt : public sqlsrv_context {
|
||||||
// last results
|
// last results
|
||||||
unsigned long query_timeout; // maximum allowed statement execution time
|
unsigned long query_timeout; // maximum allowed statement execution time
|
||||||
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
|
zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB)
|
||||||
|
bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings
|
||||||
|
|
||||||
// holds output pointers for SQLBindParameter
|
// holds output pointers for SQLBindParameter
|
||||||
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
|
// We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving
|
||||||
|
|
|
@ -141,6 +141,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error
|
||||||
last_field_index( -1 ),
|
last_field_index( -1 ),
|
||||||
past_next_result_end( false ),
|
past_next_result_end( false ),
|
||||||
query_timeout( QUERY_TIMEOUT_INVALID ),
|
query_timeout( QUERY_TIMEOUT_INVALID ),
|
||||||
|
date_as_string(false),
|
||||||
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
|
buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ),
|
||||||
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
|
param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte
|
||||||
send_streams_at_exec( true ),
|
send_streams_at_exec( true ),
|
||||||
|
@ -1404,6 +1405,15 @@ void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, s
|
||||||
core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC );
|
core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
|
||||||
|
{
|
||||||
|
if (zend_is_true(value_z)) {
|
||||||
|
stmt->date_as_string = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stmt->date_as_string = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// internal function to release the active stream. Called by each main API function
|
// internal function to release the active stream. Called by each main API function
|
||||||
// that will alter the statement and cancel any retrieval of data from a stream.
|
// that will alter the statement and cancel any retrieval of data from a stream.
|
||||||
|
|
|
@ -173,6 +173,7 @@ namespace SSStmtOptionNames {
|
||||||
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
|
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
|
||||||
const char SCROLLABLE[] = "Scrollable";
|
const char SCROLLABLE[] = "Scrollable";
|
||||||
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
|
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
|
||||||
|
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace SSConnOptionNames {
|
namespace SSConnOptionNames {
|
||||||
|
@ -243,6 +244,12 @@ const stmt_option SS_STMT_OPTS[] = {
|
||||||
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
||||||
std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit )
|
std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit )
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
SSStmtOptionNames::DATE_AS_STRING,
|
||||||
|
sizeof( SSStmtOptionNames::DATE_AS_STRING ),
|
||||||
|
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
||||||
|
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
|
||||||
|
},
|
||||||
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -988,7 +995,7 @@ PHP_FUNCTION( sqlsrv_prepare )
|
||||||
|
|
||||||
// Initialize the options array to be passed to the core layer
|
// Initialize the options array to be passed to the core layer
|
||||||
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
||||||
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */,
|
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */,
|
||||||
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
|
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
|
||||||
|
|
||||||
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
||||||
|
@ -1111,7 +1118,7 @@ PHP_FUNCTION( sqlsrv_query )
|
||||||
|
|
||||||
// Initialize the options array to be passed to the core layer
|
// Initialize the options array to be passed to the core layer
|
||||||
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
||||||
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR,
|
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR,
|
||||||
0 /*persistent*/ TSRMLS_CC );
|
0 /*persistent*/ TSRMLS_CC );
|
||||||
|
|
||||||
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
||||||
|
|
|
@ -129,6 +129,10 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_
|
||||||
fetch_fields_count ( 0 )
|
fetch_fields_count ( 0 )
|
||||||
{
|
{
|
||||||
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC );
|
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC );
|
||||||
|
|
||||||
|
// initialize date_as_string based on the corresponding connection option
|
||||||
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
||||||
|
date_as_string = ss_conn->date_as_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
|
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
|
||||||
|
@ -230,7 +234,7 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _
|
||||||
case SQL_SS_TIMESTAMPOFFSET:
|
case SQL_SS_TIMESTAMPOFFSET:
|
||||||
case SQL_SS_TIME2:
|
case SQL_SS_TIME2:
|
||||||
case SQL_TYPE_TIMESTAMP:
|
case SQL_TYPE_TIMESTAMP:
|
||||||
if( reinterpret_cast<ss_sqlsrv_conn*>( this->conn )->date_as_string ) {
|
if (this->date_as_string) {
|
||||||
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
||||||
}
|
}
|
||||||
|
@ -1678,8 +1682,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
|
||||||
case SQL_SS_TIME2:
|
case SQL_SS_TIME2:
|
||||||
case SQL_TYPE_TIMESTAMP:
|
case SQL_TYPE_TIMESTAMP:
|
||||||
{
|
{
|
||||||
ss_sqlsrv_conn* c = static_cast<ss_sqlsrv_conn*>( stmt->conn );
|
if (stmt->date_as_string) {
|
||||||
if( c->date_as_string ) {
|
|
||||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
||||||
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
||||||
}
|
}
|
||||||
|
|
120
test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt
Normal file
120
test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
--TEST--
|
||||||
|
Test retrieving null datetime values with statement option ReturnDatesAsStrings as true
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test retrieving null datetime values with statement option ReturnDatesAsStrings as true,
|
||||||
|
which is false by default. Whether retrieved as strings or date time objects should return
|
||||||
|
NULLs.
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
function testFetch($conn, $query, $columns, $withBuffer = false)
|
||||||
|
{
|
||||||
|
// The statement option ReturnDatesAsStrings set to true
|
||||||
|
// Test different fetching
|
||||||
|
if ($withBuffer){
|
||||||
|
$options = array('Scrollable' => 'buffered', 'ReturnDatesAsStrings' => true);
|
||||||
|
} else {
|
||||||
|
$options = array('ReturnDatesAsStrings' => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = count($columns);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $query, array(), $options);
|
||||||
|
// Fetch by getting one field at a time
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
if( sqlsrv_fetch( $stmt ) === false) {
|
||||||
|
fatalError("Failed in retrieving data\n");
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$field = sqlsrv_get_field($stmt, $i); // expect string
|
||||||
|
if (!is_null($field)) {
|
||||||
|
echo "Expected null for column $columns[$i] but got: ";
|
||||||
|
var_dump($field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch row as an object
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
$object = sqlsrv_fetch_object($stmt);
|
||||||
|
|
||||||
|
$objArray = (array)$object; // turn the object into an associated array
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$col = $columns[$i];
|
||||||
|
$val = $objArray[$col];
|
||||||
|
|
||||||
|
if (!is_null($val)) {
|
||||||
|
echo "Expected null for column $columns[$i] but got: ";
|
||||||
|
var_dump($val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTestTable($conn, $tableName, $columns)
|
||||||
|
{
|
||||||
|
// Create the test table of date and time columns
|
||||||
|
$dataTypes = array('date', 'smalldatetime', 'datetime', 'datetime2', 'datetimeoffset', 'time');
|
||||||
|
|
||||||
|
$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]),
|
||||||
|
new AE\ColumnMeta($dataTypes[1], $columns[1]),
|
||||||
|
new AE\ColumnMeta($dataTypes[2], $columns[2]),
|
||||||
|
new AE\ColumnMeta($dataTypes[3], $columns[3]),
|
||||||
|
new AE\ColumnMeta($dataTypes[4], $columns[4]),
|
||||||
|
new AE\ColumnMeta($dataTypes[5], $columns[5]));
|
||||||
|
AE\createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
// Insert null values
|
||||||
|
$inputData = array($colMeta[0]->colName => null,
|
||||||
|
$colMeta[1]->colName => null,
|
||||||
|
$colMeta[2]->colName => null,
|
||||||
|
$colMeta[3]->colName => null,
|
||||||
|
$colMeta[4]->colName => null,
|
||||||
|
$colMeta[5]->colName => null);
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, $inputData);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed to insert data.\n");
|
||||||
|
}
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTest($tableName, $columns, $dateAsString)
|
||||||
|
{
|
||||||
|
// Connect
|
||||||
|
$conn = connect(array('ReturnDatesAsStrings' => $dateAsString));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
testFetch($conn, $query, $columns);
|
||||||
|
testFetch($conn, $query, $columns, true);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
|
||||||
|
$tableName = "TestNullDateTime";
|
||||||
|
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
$conn = connect();
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
createTestTable($conn, $tableName, $columns);
|
||||||
|
|
||||||
|
runTest($tableName, $columns, true);
|
||||||
|
runTest($tableName, $columns, false);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
|
@ -0,0 +1,197 @@
|
||||||
|
--TEST--
|
||||||
|
Test retrieving datetime values with statement option ReturnDatesAsStrings set to true
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test retrieving datetime values with statement option ReturnDatesAsStrings set to true,
|
||||||
|
which is false by default. The statement option should override the corresponding
|
||||||
|
connection option ReturnDatesAsStrings.
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
function compareDateTime($expectedStr, $actualObj)
|
||||||
|
{
|
||||||
|
$dtime = date_create($expectedStr);
|
||||||
|
$dtExpected = $dtime->format('Y-m-d H:i:s.u');
|
||||||
|
|
||||||
|
// actual datetime value from date time object to string
|
||||||
|
$dtActual = date_format($actualObj, 'Y-m-d H:i:s.u');
|
||||||
|
|
||||||
|
return ($dtActual === $dtExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testNoOption($conn, $tableName, $inputs, $exec)
|
||||||
|
{
|
||||||
|
// Without the statement option, should return datetime values as strings
|
||||||
|
// because the connection option ReturnDatesAsStrings is set to true
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
if ($exec) {
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
} else {
|
||||||
|
$stmt = sqlsrv_prepare($conn, $query);
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
|
||||||
|
// Compare values only
|
||||||
|
$diffs = array_diff($inputs, $results);
|
||||||
|
if (!empty($diffs)) {
|
||||||
|
echo 'The results are different from the input values: ';
|
||||||
|
print_r($diffs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testStmtOption($conn, $tableName, $inputs, $stmtDateAsStr)
|
||||||
|
{
|
||||||
|
// The statement option should always override the connection option
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
$options = array('ReturnDatesAsStrings' => $stmtDateAsStr);
|
||||||
|
$stmt = sqlsrv_query($conn, $query, array(), $options);
|
||||||
|
|
||||||
|
if ($stmtDateAsStr) {
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Compare values only
|
||||||
|
$diffs = array_diff($inputs, $results);
|
||||||
|
if (!empty($diffs)) {
|
||||||
|
echo 'The results are different from the input values: ';
|
||||||
|
print_r($diffs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
|
||||||
|
// Expect DateTime Objects in $results
|
||||||
|
for ($i = 0; $i < count($inputs); $i++) {
|
||||||
|
if (is_object($results[$i])) {
|
||||||
|
$matched = compareDateTime($inputs[$i], $results[$i]);
|
||||||
|
if (!$matched) {
|
||||||
|
echo "Expected a DateTime object of $inputs[$i] but got: \n";
|
||||||
|
var_dump($results[$i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Expect a DateTime object but got $results[$i]\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFetching($conn, $tableName, $inputs, $columns, $withBuffer)
|
||||||
|
{
|
||||||
|
// The statement option ReturnDatesAsStrings set to true
|
||||||
|
// Test different fetching
|
||||||
|
$query = "SELECT * FROM $tableName";
|
||||||
|
if ($withBuffer){
|
||||||
|
$options = array('Scrollable' => 'buffered', 'ReturnDatesAsStrings' => true);
|
||||||
|
} else {
|
||||||
|
$options = array('ReturnDatesAsStrings' => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = count($inputs);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $query, array(), $options);
|
||||||
|
|
||||||
|
// Fetch by getting one field at a time
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
|
||||||
|
if( sqlsrv_fetch( $stmt ) === false) {
|
||||||
|
fatalError("Failed in retrieving data\n");
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$field = sqlsrv_get_field($stmt, $i); // expect string
|
||||||
|
if ($field != $inputs[$i]) {
|
||||||
|
echo "Expected $inputs[$i] for column $columns[$i] but got: ";
|
||||||
|
var_dump($field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch row as an object
|
||||||
|
sqlsrv_execute($stmt);
|
||||||
|
$object = sqlsrv_fetch_object($stmt);
|
||||||
|
|
||||||
|
$objArray = (array)$object; // turn the object into an associated array
|
||||||
|
for ($i = 0; $i < $size; $i++) {
|
||||||
|
$col = $columns[$i];
|
||||||
|
$val = $objArray[$col];
|
||||||
|
|
||||||
|
if ($val != $inputs[$i]) {
|
||||||
|
echo "Expected $inputs[$i] for column $columns[$i] but got: ";
|
||||||
|
var_dump($val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
date_default_timezone_set('America/Los_Angeles');
|
||||||
|
|
||||||
|
// Connect with ReturnDatesAsStrings option set to true
|
||||||
|
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate input values for the test table
|
||||||
|
$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(),
|
||||||
|
CONVERT(smalldatetime, SYSDATETIME()),
|
||||||
|
CONVERT(datetime, SYSDATETIME()),
|
||||||
|
SYSDATETIMEOFFSET(),
|
||||||
|
CONVERT(time, SYSDATETIME())';
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
|
||||||
|
// Create the test table of date and time columns
|
||||||
|
$tableName = 'StmtDateAsString';
|
||||||
|
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
|
||||||
|
$dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time');
|
||||||
|
|
||||||
|
$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]),
|
||||||
|
new AE\ColumnMeta($dataTypes[1], $columns[1]),
|
||||||
|
new AE\ColumnMeta($dataTypes[2], $columns[2]),
|
||||||
|
new AE\ColumnMeta($dataTypes[3], $columns[3]),
|
||||||
|
new AE\ColumnMeta($dataTypes[4], $columns[4]),
|
||||||
|
new AE\ColumnMeta($dataTypes[5], $columns[5]));
|
||||||
|
AE\createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
// Insert data values
|
||||||
|
$inputData = array($colMeta[0]->colName => $values[0],
|
||||||
|
$colMeta[1]->colName => $values[1],
|
||||||
|
$colMeta[2]->colName => $values[2],
|
||||||
|
$colMeta[3]->colName => $values[3],
|
||||||
|
$colMeta[4]->colName => $values[4],
|
||||||
|
$colMeta[5]->colName => $values[5]);
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, $inputData);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed to insert data.\n");
|
||||||
|
}
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
|
// Do not set ReturnDatesAsStrings at statement level
|
||||||
|
testNoOption($conn, $tableName, $values, true);
|
||||||
|
testNoOption($conn, $tableName, $values, false);
|
||||||
|
|
||||||
|
// Set ReturnDatesAsStrings to false at statement level
|
||||||
|
testStmtOption($conn, $tableName, $values, false);
|
||||||
|
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
// Now connect but with ReturnDatesAsStrings option set to false
|
||||||
|
$conn = connect(array('ReturnDatesAsStrings' => false));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ReturnDatesAsStrings to true at statement level
|
||||||
|
testStmtOption($conn, $tableName, $values, true);
|
||||||
|
|
||||||
|
// Test fetching by setting ReturnDatesAsStrings to true at statement level
|
||||||
|
testFetching($conn, $tableName, $values, $columns, true);
|
||||||
|
testFetching($conn, $tableName, $values, $columns, false);
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
|
@ -0,0 +1,104 @@
|
||||||
|
--TEST--
|
||||||
|
Test retrieving datetime values as output params with statement option ReturnDatesAsStrings
|
||||||
|
--DESCRIPTION--
|
||||||
|
Test retrieving datetime values as output params with statement option ReturnDatesAsStrings
|
||||||
|
with sqlsrv_prepare. When ReturnDatesAsStrings option is false, expect an error to return.
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_versions_old.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once('MsCommon.inc');
|
||||||
|
|
||||||
|
function runTest($conn, $storedProcName, $inputValue, $sqlType, $dateAsString)
|
||||||
|
{
|
||||||
|
$outDateStr = '';
|
||||||
|
$outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1);
|
||||||
|
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||||
|
array(array(&$outDateStr, SQLSRV_PARAM_OUT, null, $sqlType)), array('ReturnDatesAsStrings' => $dateAsString));
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed when preparing to call $storedProcName");
|
||||||
|
}
|
||||||
|
$result = sqlsrv_execute($stmt);
|
||||||
|
if ($dateAsString) {
|
||||||
|
// Expect to succeed when returning a DateTime value as a string
|
||||||
|
// The output param value should be the same as the input value
|
||||||
|
if (!$result) {
|
||||||
|
fatalError("Failed when invoking $storedProcName");
|
||||||
|
}
|
||||||
|
if ($outDateStr != $inputValue) {
|
||||||
|
echo "Expected $inputValue but got $outDateStr\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Expect to fail with an error message because setting a DateTime object as the
|
||||||
|
// output parameter is not allowed
|
||||||
|
if ($result) {
|
||||||
|
fatalError("Returning DateTime as output param is expected to fail!");
|
||||||
|
}
|
||||||
|
// Check if the error message is the expected one
|
||||||
|
$error = sqlsrv_errors()[0]['message'];
|
||||||
|
$message = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters';
|
||||||
|
if (strpos($error, $message) === false) {
|
||||||
|
print_r(sqlsrv_errors());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
|
date_default_timezone_set('America/Los_Angeles');
|
||||||
|
|
||||||
|
// Connect with ReturnDatesAsStrings option set to true
|
||||||
|
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||||
|
if (!$conn) {
|
||||||
|
fatalError("Could not connect.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate input values for the test table
|
||||||
|
$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)';
|
||||||
|
$stmt = sqlsrv_query($conn, $query);
|
||||||
|
$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC);
|
||||||
|
|
||||||
|
// Create the test table of date and time columns
|
||||||
|
$tableName = 'OuputParamDateAsString';
|
||||||
|
$columns = array('c1', 'c2', 'c3', 'c4');
|
||||||
|
$dataTypes = array('date', 'datetime2', 'datetimeoffset', 'time');
|
||||||
|
$sqlTypes = array(SQLSRV_SQLTYPE_DATE,
|
||||||
|
SQLSRV_SQLTYPE_DATETIME2,
|
||||||
|
SQLSRV_SQLTYPE_DATETIMEOFFSET,
|
||||||
|
SQLSRV_SQLTYPE_TIME);
|
||||||
|
$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]),
|
||||||
|
new AE\ColumnMeta($dataTypes[1], $columns[1]),
|
||||||
|
new AE\ColumnMeta($dataTypes[2], $columns[2]),
|
||||||
|
new AE\ColumnMeta($dataTypes[3], $columns[3]));
|
||||||
|
AE\createTable($conn, $tableName, $colMeta);
|
||||||
|
|
||||||
|
// Insert data values
|
||||||
|
$inputData = array($colMeta[0]->colName => $values[0],
|
||||||
|
$colMeta[1]->colName => $values[1],
|
||||||
|
$colMeta[2]->colName => $values[2],
|
||||||
|
$colMeta[3]->colName => $values[3]);
|
||||||
|
$stmt = AE\insertRow($conn, $tableName, $inputData);
|
||||||
|
if (!$stmt) {
|
||||||
|
fatalError("Failed to insert data.\n");
|
||||||
|
}
|
||||||
|
sqlsrv_free_stmt($stmt);
|
||||||
|
|
||||||
|
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
|
||||||
|
runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], true);
|
||||||
|
runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
sqlsrv_close($conn);
|
||||||
|
|
||||||
|
echo "Done\n";
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
Loading…
Reference in a new issue