Feature request: support extended string types (#1043)
This commit is contained in:
parent
255752066d
commit
6a7136d977
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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, {} }
|
||||
};
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
131
test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt
Normal file
131
test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt
Normal 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
|
10
test/functional/pdo_sqlsrv/skipif_old_php.inc
Normal file
10
test/functional/pdo_sqlsrv/skipif_old_php.inc
Normal 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");
|
||||
}
|
||||
?>
|
Loading…
Reference in a new issue