Feature request: support extended string types (#1043)

This commit is contained in:
Jenny Tam 2019-10-23 15:12:52 -07:00 committed by GitHub
parent 255752066d
commit 6a7136d977
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 436 additions and 19 deletions

View file

@ -20,10 +20,10 @@ env:
- TEST_PHP_SQL_PWD=Password123
before_install:
- docker pull mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu
- docker pull mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu
install:
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu
- docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu
- docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql .
before_script:

View file

@ -520,7 +520,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
fetch_numeric( false ),
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES )
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED)
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
@ -1104,6 +1105,27 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
}
break;
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
if (Z_TYPE_P(val) != IS_LONG) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
zend_long value = Z_LVAL_P(val);
if (value == PDO_PARAM_STR_NATL) {
driver_dbh->use_national_characters = 1;
}
else if (value == PDO_PARAM_STR_CHAR) {
driver_dbh->use_national_characters = 0;
}
else {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID);
}
}
break;
#endif
// Not supported
case PDO_ATTR_FETCH_TABLE_NAMES:
case PDO_ATTR_FETCH_CATALOG_NAMES:
@ -1275,6 +1297,14 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
break;
}
#if PHP_VERSION_ID >= 70200
case PDO_ATTR_DEFAULT_STR_PARAM:
{
ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL);
break;
}
#endif
default:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
@ -1425,14 +1455,18 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
// Return:
// 0 for failure, 1 for success.
int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len,
enum pdo_param_type /*paramtype*/ TSRMLS_DC )
enum pdo_param_type paramtype TSRMLS_DC )
{
PDO_RESET_DBH_ERROR;
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
bool use_national_char_set = false;
pdo_sqlsrv_dbh* driver_dbh = static_cast<pdo_sqlsrv_dbh*>(dbh->driver_data);
SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL.");
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
// 1. PDO::quote() - object name is PDO
// 2. PDOStatement::execute() - object name is PDOStatement
@ -1461,13 +1495,12 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null");
if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) {
encoding = driver_stmt->encoding();
}
else {
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( stmt->driver_data );
encoding = driver_dbh->encoding();
encoding = driver_stmt->encoding();
if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) {
pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>(stmt->driver_data);
encoding = stmt_driver_dbh->encoding();
}
// get the placeholder at the current position in driver_stmt->placeholders ht
// Normally it's not a good idea to alter the internal pointer in a hashed array
// (see pull request 634 on GitHub) but in this case this is for internal use only
@ -1489,6 +1522,16 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
}
}
use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8);
#if PHP_VERSION_ID >= 70200
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
use_national_char_set = true;
}
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
use_national_char_set = false;
}
#endif
if ( encoding == SQLSRV_ENCODING_BINARY ) {
// convert from char* to hex digits using os
std::basic_ostringstream<char> os;
@ -1533,7 +1576,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
// count the number of quotes needed
unsigned int quotes_needed = 2; // the initial start and end quotes of course
// include the N proceeding the initial quote if encoding is UTF8
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
if (use_national_char_set) {
quotes_needed = 3;
}
for ( size_t index = 0; index < unquoted_len; ++index ) {
@ -1547,7 +1590,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const
unsigned int out_current = 0;
// insert N if the encoding is UTF8
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
if (use_national_char_set) {
( *quoted )[out_current++] = 'N';
}
// insert initial quote

View file

@ -1276,18 +1276,35 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
throw pdo::PDOException();
}
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
// also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
int pdo_type = param->param_type;
if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
direction = SQL_PARAM_INPUT_OUTPUT;
pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
}
else {
direction = SQL_PARAM_OUTPUT;
}
}
// check if the user has specified the character set to use, take it off but ignore
#if PHP_VERSION_ID >= 70200
if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
}
if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
}
#endif
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
// and the SQLSRV_PHPTYPE_* constant
// vso 2829: derive the pdo_type for input/output parameter as well
int pdo_type = (direction == SQL_PARAM_OUTPUT) ? param->param_type : param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
switch (pdo_type) {
case PDO_PARAM_BOOL:
@ -1354,13 +1371,17 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
throw pdo::PDOException();
}
// the encoding by default is that set on the statement
SQLSRV_ENCODING encoding = driver_stmt->encoding();
// if the statement's encoding is the default, then use the one on the connection
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
encoding = driver_stmt->conn->encoding();
}
// if the user provided an encoding, use it instead
// Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
// But this extended type will be ignored in real prepared statements, so the encoding deliberately
// set in the statement or driver options will still take precedence
if( !Z_ISUNDEF(param->driver_params) ) {
CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt,
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
@ -1383,6 +1404,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
break;
}
}
// and bind the parameter
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
sql_type, column_size, decimal_digits TSRMLS_CC );

View file

@ -461,6 +461,10 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true}
},
{
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
},
{ UINT_MAX, {} }
};

View file

@ -139,8 +139,8 @@ class conn_string_parser : private string_parser
int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len );
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC);
protected:
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
protected:
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
public:
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
@ -183,6 +183,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
bool fetch_datetime;
bool format_decimals;
short decimal_places;
short use_national_characters;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
};
@ -386,7 +387,8 @@ enum PDO_ERROR_CODES {
PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED,
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED
PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED,
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID
};
extern pdo_error PDO_ERRORS[];

View file

@ -240,6 +240,9 @@ const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1;
// default value of decimal places (no formatting required)
const short NO_CHANGE_DECIMAL_PLACES = -1;
// default value for national character set strings (user did not specify any preference)
const short CHARSET_PREFERENCE_NOT_SPECIFIED = -1;
// buffer size allocated to retrieve data from a PHP stream. This number
// was chosen since PHP doesn't return more than 8k at a time even if
// the amount requested was more.

View file

@ -0,0 +1,109 @@
--TEST--
GitHub issue 1018 - Test emulate prepared statements with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will affect "emulate prepared" statements. If the parameter encoding is specified,
it also matters. The N'' prefix will be used when either it is PDO::PARAM_STR_NATL or the
parameter encoding is UTF-8.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
$p = 'Ĉǽŋ';
$p1 = 'C??';
function toEmulatePrepare($conn, $pdoStrParam, $value, $testCase, $utf8 = false)
{
global $p;
$sql = 'SELECT :value';
$options = array(PDO::ATTR_EMULATE_PREPARES => true);
$stmt = $conn->prepare($sql, $options);
if ($utf8) {
$stmt->bindParam(':value', $p, $pdoStrParam, 0, PDO::SQLSRV_ENCODING_UTF8);
} else {
$stmt->bindParam(':value', $p, $pdoStrParam);
}
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_NUM);
trace("$testCase: expected $value and returned $result[0]\n");
if ($result[0] !== $value) {
echo("$testCase: expected $value but returned:\n");
var_dump($result);
}
}
try {
$conn = connect();
// Test case 1: PDO::PARAM_STR_NATL
$testCase = 'Test case 1: no default but specifies PDO::PARAM_STR_NATL';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 2: PDO::PARAM_STR_CHAR
$testCase = 'Test case 2: no default but specifies PDO::PARAM_STR_CHAR';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 3: no extended string types
$testCase = 'Test case 3: no default but no extended string types either';
toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase);
// Test case 4: no extended string types but specifies UTF 8 encoding
$testCase = 'Test case 4: no default but no extended string types but with UTF-8';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
////////////////////////////////////////////////////////////////////////
// NEXT tests: set the default string type: PDO::PARAM_STR_CHAR first
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR);
// Test case 5: overrides the default PDO::PARAM_STR_CHAR
$testCase = 'Test case 5: overrides the default PDO::PARAM_STR_CHAR';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 6: specifies PDO::PARAM_STR_CHAR directly
$testCase = 'Test case 6: specifies PDO::PARAM_STR_CHAR, same as the default';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 7: uses the default PDO::PARAM_STR_CHAR without specifying
$testCase = 'Test case 7: no extended string types (uses the default)';
toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase);
// Test case 8: uses the default PDO::PARAM_STR_CHAR without specifying but with UTF 8 encoding
$testCase = 'Test case 8: no extended string types (uses the default) but with UTF-8 ';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
////////////////////////////////////////////////////////////////////////
// NEXT tests: set the default string type: PDO::PARAM_STR_NATL
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
// Test case 9: overrides the default PDO::PARAM_STR_NATL
$testCase = 'Test case 9: overrides the default PDO::PARAM_STR_NATL';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase);
// Test case 10: specifies PDO::PARAM_STR_NATL directly
$testCase = 'Test case 10: specifies PDO::PARAM_STR_NATL, same as the default';
toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase);
// Test case 11: uses the default PDO::PARAM_STR_NATL without specifying
$testCase = 'Test case 11: no extended string types (uses the default)';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase);
// Test case 12: uses the default PDO::PARAM_STR_NATL without specifying but with UTF 8 encoding
$testCase = 'Test case 12: no extended string types (uses the default) but with UTF-8';
toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true);
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,93 @@
--TEST--
GitHub issue 1018 - Test PDO::quote() with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will affect how PDO::quote() works.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function testErrorCase2($conn, $isChar)
{
try {
$stmt = $conn->query('select 1');
$error = '*An invalid attribute was designated on the PDOStatement object.';
$pdoParam = ($isChar) ? PDO::PARAM_STR_CHAR : PDO::PARAM_STR_NATL;
// This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM is not a statement attribute
$stmt->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $pdoParam);
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM on statement\n";
var_dump($e->getMessage());
}
}
}
function testErrorCase($attr)
{
try {
$conn = connect();
$error = '*Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.';
// This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM expects either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL only
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $attr);
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM\n";
var_dump($e->getMessage());
}
}
}
try {
testErrorCase(true);
testErrorCase('abc');
testErrorCase(4);
$conn = connect();
testErrorCase2($conn, true);
testErrorCase2($conn, false);
// Start testing quote function
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR);
var_dump($conn->quote(null, PDO::PARAM_NULL));
var_dump($conn->quote('\'', PDO::PARAM_STR));
var_dump($conn->quote('foo', PDO::PARAM_STR));
var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR);
$conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
var_dump($conn->quote('über', PDO::PARAM_STR));
var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
unset($conn);
echo "Done\n";
} catch (PDOException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
string(2) "''"
string(4) "''''"
string(5) "'foo'"
string(5) "'foo'"
string(8) "N'über'"
bool(true)
bool(true)
string(5) "'foo'"
string(8) "N'über'"
string(8) "N'über'"
Done

View file

@ -0,0 +1,131 @@
--TEST--
GitHub issue 1018 - Test real prepared statements with the extended string types
--DESCRIPTION--
This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and
PDO::PARAM_STR_CHAR will NOT affect real prepared statements. Unlike emulate prepared statements,
real prepared statements will only be affected by the parameter encoding. If not set, it will use
the statement encoding or the connection one, which is by default UTF-8.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_old_php.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
$p = '銀河galaxy';
$p1 = '??galaxy';
$tableName = 'test1018';
function insertRead($conn, $pdoStrParam, $value, $testCase, $id, $encoding = false)
{
global $p, $tableName;
$sql = "INSERT INTO $tableName VALUES (:value)";
$options = array(PDO::ATTR_EMULATE_PREPARES => false); // it's false by default anyway
$stmt = $conn->prepare($sql, $options);
// Set param encoding only if $encoding is NOT FALSE
if ($encoding !== false) {
$stmt->bindParam(':value', $p, $pdoStrParam, 0, $encoding);
$encOptions = array(PDO::SQLSRV_ATTR_ENCODING => $encoding);
} else {
$stmt->bindParam(':value', $p, $pdoStrParam);
$encOptions = array();
}
$stmt->execute();
// Should also set statement encoding when $encoding is NOT FALSE
// such that data can be fetched with the right encoding
$sql = "SELECT Col1 FROM $tableName WHERE ID = $id";
$stmt = $conn->prepare($sql, $encOptions);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_NUM);
trace("$testCase: expected $value and returned $result[0]\n");
if ($result[0] !== $value) {
echo("$testCase: expected $value but returned:\n");
var_dump($result);
}
}
function testUTF8encoding($conn)
{
global $p, $tableName;
// Create a NVARCHAR column
$sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 NVARCHAR(100))";
$conn->query($sql);
// The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR
// will be ignored in the following test cases. Only the statement or
// the connection encoding matters.
// Test case 1: PDO::PARAM_STR_CHAR
$testCase = 'UTF-8 case 1: no default but specifies PDO::PARAM_STR_CHAR';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p, $testCase, 1);
// Test case 2: PDO::PARAM_STR_NATL
$testCase = 'UTF-8 case 2: no default but specifies PDO::PARAM_STR_NATL';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase, 2);
// Test case 3: no extended string types
$testCase = 'UTF-8 case 3: no default but no extended string types either';
insertRead($conn, PDO::PARAM_STR, $p, $testCase, 3);
// Test case 4: no extended string types but specifies UTF-8 encoding
$testCase = 'UTF-8 case 4: no default but no extended string types but with UTF-8 encoding';
insertRead($conn, PDO::PARAM_STR, $p, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8);
dropTable($conn, $tableName);
}
function testNonUTF8encoding($conn)
{
global $p, $p1, $tableName;
// Create a VARCHAR column
$sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 VARCHAR(100))";
$conn->query($sql);
// The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR
// will be ignored in the following test cases. Only the statement or
// the connection encoding matters.
// Test case 1: PDO::PARAM_STR_CHAR (expect $p1)
$testCase = 'System case 1: no default but specifies PDO::PARAM_STR_CHAR';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase, 1);
// Test case 2: PDO::PARAM_STR_NATL (expect $p1)
$testCase = 'System case 2: no default but specifies PDO::PARAM_STR_NATL';
insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p1, $testCase, 2);
// Test case 3: no extended string types (expect $p1)
$testCase = 'System case 3: no default but no extended string types either';
insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 3);
// Test case 4: no extended string types but specifies UTF-8 encoding (expect $p1)
$testCase = 'System case 4: no default but no extended string types but with UTF-8 encoding';
insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8);
dropTable($conn, $tableName);
}
try {
$conn = connect();
dropTable($conn, $tableName);
// The connection encoding is by default PDO::SQLSRV_ENCODING_UTF8. For this test
// no change is made to the connection encoding.
testUTF8encoding($conn);
testNonUTF8encoding($conn);
echo "Done\n";
} catch (PdoException $e) {
echo $e->getMessage() . PHP_EOL;
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,10 @@
<?php
if (!extension_loaded("pdo_sqlsrv")) {
die("skip Extension not loaded");
}
if (PHP_VERSION_ID < 70200) {
die("skip This test requires 7.2 or above");
}
?>