From e80a5d1b3f9c807bd2fa3572bf3e7cec74457a2a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 16 Oct 2019 11:52:27 -0700 Subject: [PATCH 1/6] Feature request: support extended string types --- source/pdo_sqlsrv/pdo_dbh.cpp | 79 +++++++++-- source/pdo_sqlsrv/pdo_stmt.cpp | 28 +++- source/pdo_sqlsrv/pdo_util.cpp | 4 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 5 +- source/shared/core_sqlsrv.h | 3 + .../pdo_1018_emulate_prepare_natl_char.phpt | 109 +++++++++++++++ .../pdo_1018_quote_param_str_natl_char.phpt | 90 ++++++++++++ .../pdo_1018_real_prepare_natl_char.phpt | 131 ++++++++++++++++++ test/functional/pdo_sqlsrv/skipif_old_php.inc | 10 ++ 9 files changed, 444 insertions(+), 15 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_old_php.inc diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 15aec1173..466e5db97 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -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(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,19 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast(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( 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(stmt->driver_data); + encoding = stmt_driver_dbh->encoding(); + } + //if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) { + // encoding = driver_stmt->encoding(); + //} + //else { + // pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->driver_data ); + // encoding = 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 +1529,21 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } } + // The default encoding is taken into account too. PDO_PARAM_STR_CHAR or SQLSRV_ENCODING_CHAR should override the default. + //bool use_national_char_set = false; + //if (driver_dbh->use_national_characters || encoding == SQLSRV_ENCODING_UTF8) { + // use_national_char_set = true; + //} + 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 os; @@ -1533,7 +1588,8 @@ 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 ( encoding == SQLSRV_ENCODING_UTF8 ) { + if (use_national_char_set) { quotes_needed = 3; } for ( size_t index = 0; index < unquoted_len; ++index ) { @@ -1547,7 +1603,8 @@ 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 ( encoding == SQLSRV_ENCODING_UTF8 ) { + if (use_national_char_set) { ( *quoted )[out_current++] = 'N'; } // insert initial quote diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 0cd562d9c..d91839192 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -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; + } + if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR; + } +#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; + //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 ) { @@ -1382,7 +1403,8 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, param->paramno + 1 ); break; } - } + } + // and bind the parameter core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, sql_type, column_size, decimal_digits TSRMLS_CC ); diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 6cfb43acf..b9c0b4ccc 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -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, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index e0f5f2201..8aa71a630 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -183,6 +183,8 @@ 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 +388,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[]; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index f468a7b6b..28f46f689 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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. diff --git a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt new file mode 100644 index 000000000..4157136f0 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt @@ -0,0 +1,109 @@ +--TEST-- +GitHub issue 1018 - Test PDO::prepare() 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-- + +--FILE-- + 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 + $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 + $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 diff --git a/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt new file mode 100644 index 000000000..bc2913f6b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt @@ -0,0 +1,90 @@ +--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-- + +--FILE-- +query('select 1'); + $error = '*An invalid attribute was designated on the PDOStatement object.'; + $pdoParam = ($isChar) ? PDO::PARAM_STR_CHAR : PDO::PARAM_STR_NATL; + + $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.'; + $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 diff --git a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt new file mode 100644 index 000000000..6c81780d3 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt @@ -0,0 +1,131 @@ +--TEST-- +GitHub issue 1018 - Test PDO::prepare() 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-- + +--FILE-- + 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 diff --git a/test/functional/pdo_sqlsrv/skipif_old_php.inc b/test/functional/pdo_sqlsrv/skipif_old_php.inc new file mode 100644 index 000000000..19f97bc56 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_old_php.inc @@ -0,0 +1,10 @@ + From 3a18f9d0d39e5045c8357ffe2363e50d825ed4bb Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 16 Oct 2019 12:23:12 -0700 Subject: [PATCH 2/6] Cosmetic changes --- source/pdo_sqlsrv/pdo_dbh.cpp | 98 +++++++++++--------------- source/pdo_sqlsrv/pdo_stmt.cpp | 41 ++++++----- source/pdo_sqlsrv/pdo_util.cpp | 8 +-- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 9 ++- 4 files changed, 70 insertions(+), 86 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 466e5db97..eedd07b11 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -521,7 +521,7 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo fetch_datetime( false ), format_decimals( false ), decimal_places( NO_CHANGE_DECIMAL_PLACES ), - use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED) + 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; @@ -1106,24 +1106,24 @@ 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; + 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 @@ -1298,11 +1298,11 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout } #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; - } + 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: @@ -1462,10 +1462,10 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const PDO_LOG_DBH_ENTRY; SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR; - bool use_national_char_set = false; - - pdo_sqlsrv_dbh* driver_dbh = static_cast(dbh->driver_data); - SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL."); + bool use_national_char_set = false; + + pdo_sqlsrv_dbh* driver_dbh = static_cast(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 @@ -1495,18 +1495,11 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast(stmt->driver_data); SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null"); - encoding = driver_stmt->encoding(); - if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) { - pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast(stmt->driver_data); - encoding = stmt_driver_dbh->encoding(); - } - //if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) { - // encoding = driver_stmt->encoding(); - //} - //else { - // pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( 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(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 @@ -1529,19 +1522,14 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } } - // The default encoding is taken into account too. PDO_PARAM_STR_CHAR or SQLSRV_ENCODING_CHAR should override the default. - //bool use_national_char_set = false; - //if (driver_dbh->use_national_characters || encoding == SQLSRV_ENCODING_UTF8) { - // use_national_char_set = true; - //} - use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8); + 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; - } + 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 ) { @@ -1588,8 +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) { + if (use_national_char_set) { quotes_needed = 3; } for ( size_t index = 0; index < unquoted_len; ++index ) { @@ -1603,8 +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) { + if (use_national_char_set) { ( *quoted )[out_current++] = 'N'; } // insert initial quote diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index d91839192..66d9d6880 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1276,35 +1276,34 @@ 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 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; + 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; - } - if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { - pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR; - } + // 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; + } + 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 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: @@ -1379,9 +1378,9 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, encoding = driver_stmt->conn->encoding(); } - // 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 + // 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 ) { @@ -1403,8 +1402,8 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, param->paramno + 1 ); break; } - } - + } + // and bind the parameter core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, sql_type, column_size, decimal_digits TSRMLS_CC ); diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index b9c0b4ccc..cff7add5c 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -461,10 +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} - }, + { + 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, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 8aa71a630..2b7269c0c 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -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,8 +183,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { bool fetch_datetime; bool format_decimals; short decimal_places; - short use_national_characters; - + short use_national_characters; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); }; @@ -389,7 +388,7 @@ enum PDO_ERROR_CODES { PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED, - PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID, + PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID }; extern pdo_error PDO_ERRORS[]; From d6d1bbca89de62cb0ca1572f9d5f05c7e7fd8860 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 16 Oct 2019 14:34:47 -0700 Subject: [PATCH 3/6] Used sql 2019 CTP3.2 for travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0618bebfd..d908eff64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: From 2163f99f11e214afaf1a75c5db547cc1758b6b05 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 18 Oct 2019 14:07:39 -0700 Subject: [PATCH 4/6] Corrected the logging of extended string types --- source/pdo_sqlsrv/pdo_stmt.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 66d9d6880..c28aa1bcf 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1293,13 +1293,14 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, // 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; - } - if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { - pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR; + 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."); } - LOG(SEV_NOTICE, "PHP Extended String type set but is ignored."); #endif // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant From 03fed92884cd579ead4b929f4e126b32d9c13c7b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Oct 2019 13:47:01 -0700 Subject: [PATCH 5/6] Added or modified comments as per review --- .../pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt | 6 +++--- .../pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt | 3 +++ .../pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt index 4157136f0..49cbbdd2d 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt @@ -1,5 +1,5 @@ --TEST-- -GitHub issue 1018 - Test PDO::prepare() with the extended string types +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, @@ -75,7 +75,7 @@ try { $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 + // Test case 8: uses the default PDO::PARAM_STR_CHAR without specifying but specifies 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); @@ -95,7 +95,7 @@ try { $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 + // Test case 12: uses the default PDO::PARAM_STR_NATL without specifying but specifies 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); diff --git a/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt index bc2913f6b..5b39f9f15 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt @@ -19,6 +19,7 @@ function testErrorCase2($conn, $isChar) $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())) { @@ -33,6 +34,8 @@ 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())) { diff --git a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt index 6c81780d3..e058994e5 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt @@ -1,5 +1,5 @@ --TEST-- -GitHub issue 1018 - Test PDO::prepare() with the extended string types +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, From b5d9dead1f62d03ecc72de8d7035e1c2a4ce2679 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Oct 2019 14:39:12 -0700 Subject: [PATCH 6/6] Minor fix --- .../pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt index 49cbbdd2d..5c389bb9f 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt @@ -75,7 +75,7 @@ try { $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 specifies UTF 8 encoding + // 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); @@ -95,7 +95,7 @@ try { $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 specifies UTF 8 encoding + // 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);