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
|
- TEST_PHP_SQL_PWD=Password123
|
||||||
|
|
||||||
before_install:
|
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:
|
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 .
|
- docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql .
|
||||||
|
|
||||||
before_script:
|
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_numeric( false ),
|
||||||
fetch_datetime( false ),
|
fetch_datetime( false ),
|
||||||
format_decimals( 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 ) {
|
if( client_buffer_max_size < 0 ) {
|
||||||
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
|
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;
|
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
|
// Not supported
|
||||||
case PDO_ATTR_FETCH_TABLE_NAMES:
|
case PDO_ATTR_FETCH_TABLE_NAMES:
|
||||||
case PDO_ATTR_FETCH_CATALOG_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;
|
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:
|
default:
|
||||||
{
|
{
|
||||||
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
|
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR );
|
||||||
|
@ -1425,13 +1455,17 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name,
|
||||||
// Return:
|
// Return:
|
||||||
// 0 for failure, 1 for success.
|
// 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,
|
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_RESET_DBH_ERROR;
|
||||||
PDO_VALIDATE_CONN;
|
PDO_VALIDATE_CONN;
|
||||||
PDO_LOG_DBH_ENTRY;
|
PDO_LOG_DBH_ENTRY;
|
||||||
|
|
||||||
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
|
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:
|
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
|
||||||
// 1. PDO::quote() - object name is PDO
|
// 1. PDO::quote() - object name is PDO
|
||||||
|
@ -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);
|
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");
|
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();
|
||||||
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);
|
||||||
else {
|
encoding = stmt_driver_dbh->encoding();
|
||||||
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( stmt->driver_data );
|
|
||||||
encoding = driver_dbh->encoding();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the placeholder at the current position in driver_stmt->placeholders ht
|
// 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
|
// 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
|
// (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 ) {
|
if ( encoding == SQLSRV_ENCODING_BINARY ) {
|
||||||
// convert from char* to hex digits using os
|
// convert from char* to hex digits using os
|
||||||
std::basic_ostringstream<char> 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
|
// count the number of quotes needed
|
||||||
unsigned int quotes_needed = 2; // the initial start and end quotes of course
|
unsigned int quotes_needed = 2; // the initial start and end quotes of course
|
||||||
// include the N proceeding the initial quote if encoding is UTF8
|
// include the N proceeding the initial quote if encoding is UTF8
|
||||||
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
|
if (use_national_char_set) {
|
||||||
quotes_needed = 3;
|
quotes_needed = 3;
|
||||||
}
|
}
|
||||||
for ( size_t index = 0; index < unquoted_len; ++index ) {
|
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;
|
unsigned int out_current = 0;
|
||||||
|
|
||||||
// insert N if the encoding is UTF8
|
// insert N if the encoding is UTF8
|
||||||
if ( encoding == SQLSRV_ENCODING_UTF8 ) {
|
if (use_national_char_set) {
|
||||||
( *quoted )[out_current++] = 'N';
|
( *quoted )[out_current++] = 'N';
|
||||||
}
|
}
|
||||||
// insert initial quote
|
// 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 ) {
|
driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
|
||||||
throw pdo::PDOException();
|
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->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
|
||||||
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
|
if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
|
||||||
direction = SQL_PARAM_INPUT_OUTPUT;
|
direction = SQL_PARAM_INPUT_OUTPUT;
|
||||||
|
pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
direction = SQL_PARAM_OUTPUT;
|
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
|
// if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
|
||||||
// and the SQLSRV_PHPTYPE_* 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;
|
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
|
||||||
switch (pdo_type) {
|
switch (pdo_type) {
|
||||||
case PDO_PARAM_BOOL:
|
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 ) {
|
driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
|
||||||
throw pdo::PDOException();
|
throw pdo::PDOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// the encoding by default is that set on the statement
|
// the encoding by default is that set on the statement
|
||||||
SQLSRV_ENCODING encoding = driver_stmt->encoding();
|
SQLSRV_ENCODING encoding = driver_stmt->encoding();
|
||||||
// if the statement's encoding is the default, then use the one on the connection
|
// if the statement's encoding is the default, then use the one on the connection
|
||||||
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
|
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
|
||||||
encoding = driver_stmt->conn->encoding();
|
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) ) {
|
if( !Z_ISUNDEF(param->driver_params) ) {
|
||||||
CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt,
|
CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt,
|
||||||
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
|
PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
|
||||||
|
@ -1383,6 +1404,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// and bind the parameter
|
// and bind the parameter
|
||||||
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
|
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 );
|
sql_type, column_size, decimal_digits TSRMLS_CC );
|
||||||
|
|
|
@ -461,6 +461,10 @@ pdo_error PDO_ERRORS[] = {
|
||||||
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
|
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
|
||||||
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true}
|
{ 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, {} }
|
{ 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 );
|
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);
|
void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
|
void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht );
|
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 fetch_datetime;
|
||||||
bool format_decimals;
|
bool format_decimals;
|
||||||
short decimal_places;
|
short decimal_places;
|
||||||
|
short use_national_characters;
|
||||||
|
|
||||||
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC );
|
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_EMULATE_INOUT_UNSUPPORTED,
|
||||||
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
|
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
|
||||||
PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED,
|
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[];
|
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)
|
// default value of decimal places (no formatting required)
|
||||||
const short NO_CHANGE_DECIMAL_PLACES = -1;
|
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
|
// 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
|
// was chosen since PHP doesn't return more than 8k at a time even if
|
||||||
// the amount requested was more.
|
// 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