implemented emulate prepare and added tests

This commit is contained in:
v-kaywon 2017-04-20 17:17:05 -07:00
parent 012b16a64f
commit 7aaa16d49e
6 changed files with 604 additions and 47 deletions

View file

@ -589,6 +589,8 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql,
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
size_t sql_rewrite_len = 0;
sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
hash_auto_ptr placeholders;
sqlsrv_malloc_auto_ptr<sql_string_parser> sql_parser;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null");
@ -660,8 +662,17 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql,
}
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
// set to the substituted query
if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
// parse placeholders in the sql query into the placeholders ht
ALLOC_HASHTABLE( placeholders );
core::sqlsrv_zend_hash_init( *driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */ TSRMLS_CC );
sql_parser = new ( sqlsrv_malloc( sizeof( sql_string_parser ))) sql_string_parser( *driver_dbh, stmt->query_string,
static_cast<int>(stmt->query_stringlen), placeholders );
sql_parser->parse_sql_string( TSRMLS_C );
driver_stmt->placeholders = placeholders;
placeholders.transferred();
}
stmt->driver_data = driver_stmt;
driver_stmt.transferred();
}
@ -1279,13 +1290,67 @@ int pdo_sqlsrv_dbh_quote( pdo_dbh_t* dbh, const char* unquoted, size_t unquoted_
PDO_VALIDATE_CONN;
PDO_LOG_DBH_ENTRY;
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding;
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
// get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from:
// 1. PDO::quote() - object name is PDO
// 2. PDO::execute() - object name is PDOStatement
zend_execute_data* execute_data = EG( current_execute_data );
zval *object = getThis();
// iterate through parents to find "PDOStatement"
bool is_statement = false;
zend_class_entry* curr_class = ( Z_OBJ_P( object ))->ce;
while ( curr_class != NULL ) {
if ( strcmp( reinterpret_cast<const char*>( curr_class->name->val ), "PDOStatement" ) == 0 ) {
is_statement = true;
break;
}
curr_class = curr_class->parent;
}
// only change the encoding if quote is called from the statement level (which should only be called when a statement
// is prepared with emulate prepared on)
if ( is_statement ) {
pdo_stmt_t *stmt = Z_PDO_STMT_P( object );
// set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
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();
}
// get the placeholder at the current position in driver_stmt->placeholders ht
zval* placeholder = NULL;
if (( placeholder = zend_hash_get_current_data( driver_stmt->placeholders )) != NULL && zend_hash_move_forward( driver_stmt->placeholders ) == SUCCESS ) {
pdo_bound_param_data* param = NULL;
if ( Z_TYPE_P( placeholder ) == IS_STRING ) {
param = reinterpret_cast<pdo_bound_param_data*>( zend_hash_find_ptr( stmt->bound_params, Z_STR_P( placeholder )));
}
else if ( Z_TYPE_P( placeholder ) == IS_LONG) {
param = reinterpret_cast<pdo_bound_param_data*>( zend_hash_index_find_ptr( stmt->bound_params, Z_LVAL_P( placeholder )));
}
if ( NULL != param ) {
SQLSRV_ENCODING param_encoding = static_cast<SQLSRV_ENCODING>( Z_LVAL( param->driver_params ));
if ( param_encoding != SQLSRV_ENCODING_INVALID ) {
encoding = param_encoding;
}
}
}
}
if ( encoding == SQLSRV_ENCODING_BINARY ) {
// convert from char* to hex digits using os
std::basic_ostringstream<char> os;
for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
// when an int is < 16 and is appended to os, its hex representation which starts
// with '0' does not get appended properly (the starting '0' does not get appended)
// thus append '0' first
if (( int )unquoted[index] < 16 ) {
os << '0';
}
os << std::hex << ( int )unquoted[ index ];
}
std::basic_string<char> str_hex = os.str();

View file

@ -24,17 +24,27 @@
// Constructor
conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht )
{
this->conn_str = dsn;
this->orig_str = dsn;
this->len = len;
this->conn_options_ht = conn_options_ht;
this->element_ht = conn_options_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
this->current_key_name = NULL;
}
sql_string_parser:: sql_string_parser( sqlsrv_context& ctx, const char* sql_str, int len, _Inout_ HashTable* placeholders_ht )
{
this->orig_str = sql_str;
this->len = len;
this->element_ht = placeholders_ht;
this->pos = -1;
this->ctx = &ctx;
this->current_key = 0;
}
// Move to the next character
inline bool conn_string_parser::next( void )
inline bool string_parser::next( void )
{
// if already at the end then return false
if( this->is_eos() ) {
@ -55,7 +65,7 @@ inline bool conn_string_parser::next( void )
}
// Check for end of string.
inline bool conn_string_parser::is_eos( void )
inline bool string_parser::is_eos( void )
{
if( this->pos == len )
{
@ -68,7 +78,7 @@ inline bool conn_string_parser::is_eos( void )
}
// Check for white space.
inline bool conn_string_parser::is_white_space( char c )
inline bool string_parser::is_white_space( char c )
{
if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) {
return true;
@ -98,7 +108,7 @@ bool conn_string_parser::discard_white_spaces()
return false;
}
while( this->is_white_space( this->conn_str[ pos ] )) {
while( this->is_white_space( this->orig_str[ pos ] )) {
if( !next() )
return false;
@ -107,8 +117,8 @@ bool conn_string_parser::discard_white_spaces()
return true;
}
// Add a key-value pair to the hashtable of connection options.
void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC )
// Add a key-value pair to the hashtable
void string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC )
{
zval value_z;
ZVAL_UNDEF( &value_z );
@ -122,7 +132,15 @@ void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_D
ZVAL_STRINGL( &value_z, const_cast<char*>( value ), len );
}
core::sqlsrv_zend_hash_index_update( *ctx, this->conn_options_ht, this->current_key, &value_z TSRMLS_CC );
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z TSRMLS_CC );
}
// Add a key-value pair to the hashtable with int value
void sql_string_parser::add_key_int_value_pair( unsigned int value TSRMLS_DC ) {
zval value_z;
ZVAL_LONG( &value_z, value );
core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z TSRMLS_CC );
}
// Validate a given DSN keyword.
@ -151,6 +169,76 @@ void conn_string_parser::validate_key(const char *key, int key_len TSRMLS_DC )
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast<char*>( key_name ) );
}
inline bool sql_string_parser::is_placeholder_char( char c )
{
switch ( c ) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case '_':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z': return true;
default: return false;
}
}
// Primary function which parses the connection string/DSN.
void conn_string_parser:: parse_conn_string( TSRMLS_D )
{
@ -180,7 +268,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
start_pos = this->pos;
// read the key name
while( this->conn_str[ pos ] != '=' ) {
while( this->orig_str[ pos ] != '=' ) {
if( !next() ) {
@ -188,7 +276,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
}
}
this->validate_key( &( this->conn_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC );
this->validate_key( &( this->orig_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC );
state = Value;
@ -197,13 +285,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
case Value:
{
SQLSRV_ASSERT(( this->conn_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: "
SQLSRV_ASSERT(( this->orig_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: "
"Equal was expected" );
next(); // skip "="
// if EOS encountered after 0 or more spaces OR semi-colon encountered.
if( !discard_white_spaces() || this->conn_str[ pos ] == ';' ) {
if( !discard_white_spaces() || this->orig_str[ pos ] == ';' ) {
add_key_value_pair( NULL, 0 TSRMLS_CC );
@ -213,13 +301,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
}
else {
// this->conn_str[ pos ] == ';'
// this->orig_str[ pos ] == ';'
state = NextKeyValuePair;
}
}
// if LCB
else if( this->conn_str[ pos ] == '{' ) {
else if( this->orig_str[ pos ] == '{' ) {
start_pos = this->pos; // starting character is LCB
state = ValueContent1;
@ -237,7 +325,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
case ValueContent1:
{
while ( this->conn_str[ pos ] != '}' ) {
while ( this->orig_str[ pos ] != '}' ) {
if ( ! next() ) {
@ -253,7 +341,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
case ValueContent2:
{
while( this->conn_str[ pos ] != ';' ) {
while( this->orig_str[ pos ] != ';' ) {
if( ! next() ) {
@ -261,13 +349,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
}
}
if( !this->is_eos() && this->conn_str[ pos ] == ';' ) {
if( !this->is_eos() && this->orig_str[ pos ] == ';' ) {
// semi-colon encountered, so go to next key-value pair
state = NextKeyValuePair;
}
add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )),
"conn_string_parser::parse_conn_string: Invalid state encountered " );
@ -282,14 +370,14 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
if( !next() ) {
// EOS
add_key_value_pair( &( this->conn_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC );
break;
}
SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" );
// if second RCB encountered than go back to ValueContent1
if( this->conn_str[ pos ] == '}' ) {
if( this->orig_str[ pos ] == '}' ) {
if( !next() ) {
@ -304,20 +392,20 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
int end_pos = this->pos;
// discard any trailing white-spaces.
if( this->is_white_space( this->conn_str[ pos ] )) {
if( this->is_white_space( this->orig_str[ pos ] )) {
if( ! this->discard_white_spaces() ) {
//EOS
add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
break;
}
}
// if semi-colon than go to next key-value pair
if ( this->conn_str[ pos ] == ';' ) {
if ( this->orig_str[ pos ] == ';' ) {
add_key_value_pair( &( this->conn_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC );
state = NextKeyValuePair;
break;
}
@ -328,7 +416,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
}
case NextKeyValuePair:
{
SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ),
SQLSRV_ASSERT(( this->orig_str[ pos ] == ';' ),
"conn_string_parser::parse_conn_string: semi-colon was expected." );
// Call next() to skip the semi-colon.
@ -338,7 +426,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
break;
}
if( this->conn_str[ pos ] == ';' ) {
if( this->orig_str[ pos ] == ';' ) {
// a second semi-colon is error case.
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos );
@ -360,3 +448,47 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
}
}
// Primary function which parses out the named placeholders from a sql string.
void sql_string_parser::parse_sql_string( TSRMLS_D ) {
try {
while ( !this->is_eos() ) {
int start_pos = -1;
// skip until a '"', '\'', ':' or '?'
char sym;
while ( this->orig_str[pos] != '"' && this->orig_str[pos] != '\'' && this->orig_str[pos] != ':' && this->orig_str[pos] != '?' && !this->is_eos() ) {
next();
}
sym = this->orig_str[pos];
// if '"' or '\'', skip until the next '"' or '\'' respectively
if ( sym == '"' || sym == '\'' ) {
next();
while ( this->orig_str[pos] != sym && !this->is_eos() ) {
next();
}
}
// if ':', store placeholder in the placeholders hashtable
else if ( sym == ':' ) {
start_pos = this->pos;
next();
// keep going until the next space or line break
// while (!is_white_space(this->orig_str[pos]) && !this->is_eos()) {
while ( is_placeholder_char( this->orig_str[pos] )) {
next();
}
add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC );
this->current_key++;
}
// if '?', don't need to parse anymore since the position of the bound_param is already stored in the bound_params ht
else if ( sym == '?' ) {
next();
// add dummy value to placeholders ht to keep count of the number of placeholders
add_key_int_value_pair( this->current_key );
this->current_key++;
}
}
}
catch ( pdo::PDOException& ) {
throw;
}
}

View file

@ -486,9 +486,10 @@ int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC)
// *stmt - pointer to current statement
// Return:
// 1 for success.
int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
int pdo_sqlsrv_stmt_dtor( pdo_stmt_t *stmt TSRMLS_DC )
{
sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
//sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>( stmt->driver_data );
LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" );
@ -498,7 +499,13 @@ int pdo_sqlsrv_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
return 1;
}
driver_stmt->~sqlsrv_stmt();
if ( driver_stmt->placeholders != NULL ) {
zend_hash_destroy( driver_stmt->placeholders );
FREE_HASHTABLE( driver_stmt->placeholders );
driver_stmt->placeholders = NULL;
}
(( sqlsrv_stmt* )driver_stmt )->~sqlsrv_stmt();
sqlsrv_free( driver_stmt );
@ -547,6 +554,8 @@ int pdo_sqlsrv_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
// if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the
// subtituted query provided by PDO
if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
// reset the placeholders hashtable internal in case the user reexecutes a statement
zend_hash_internal_pointer_reset(driver_stmt->placeholders);
query = stmt->active_query_string;
query_len = static_cast<unsigned int>( stmt->active_query_stringlen );

View file

@ -125,12 +125,28 @@ PHP_MINFO_FUNCTION(pdo_sqlsrv);
extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP
// Basic string parser
class string_parser
{
protected:
const char* orig_str;
sqlsrv_context* ctx;
int len;
int pos;
unsigned int current_key;
HashTable* element_ht;
inline bool next(void);
inline bool is_eos(void);
inline bool is_white_space(char c);
void add_key_value_pair(const char* value, int len TSRMLS_DC);
};
//*********************************************************************************************************************************
// PDO DSN Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN connection string.
class conn_string_parser
class conn_string_parser : private string_parser
{
enum States
{
@ -144,26 +160,31 @@ class conn_string_parser
};
private:
const char* conn_str;
sqlsrv_context* ctx;
int len;
int pos;
unsigned int current_key;
const char* current_key_name;
HashTable* conn_options_ht;
inline bool next( void );
inline bool is_eos( void );
inline bool is_white_space( char c );
bool discard_white_spaces( void );
int discard_trailing_white_spaces( const char* str, int len );
void validate_key( const char *key, int key_len TSRMLS_DC );
void add_key_value_pair( const char* value, int len TSRMLS_DC );
bool discard_white_spaces(void);
int discard_trailing_white_spaces(const char* str, int len);
void validate_key(const char *key, int key_len TSRMLS_DC);
public:
conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht );
void parse_conn_string( TSRMLS_D );
};
//*********************************************************************************************************************************
// PDO Query Parser
//*********************************************************************************************************************************
// Parser class used to parse DSN named placeholders.
class sql_string_parser : private string_parser
{
private:
bool is_placeholder_char(char);
public:
void add_key_int_value_pair(unsigned int value TSRMLS_DC);
sql_string_parser(sqlsrv_context& ctx, const char* sql_str, int len, _Inout_ HashTable* placeholder_ht);
void parse_sql_string(TSRMLS_D);
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
@ -228,6 +249,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
direct_query( false ),
direct_query_subst_string( NULL ),
direct_query_subst_string_len( 0 ),
placeholders(NULL),
bound_column_param_types( NULL ),
fetch_numeric( false )
{
@ -245,6 +267,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
bool direct_query; // flag set if the query should be executed directly or prepared
const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters
size_t direct_query_subst_string_len; // length of query string used for direct queries
HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare
// meta data for current result set
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;

View file

@ -0,0 +1,69 @@
--TEST--
Test emulate prepare utf8 encoding set at the statement level
--SKIPIF--
--FILE--
<?php
require_once("autonomous_setup.php");
$pdo_options = [];
$pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
$pdo_options[PDO::SQLSRV_ATTR_ENCODING] = PDO::SQLSRV_ENCODING_UTF8;
$database = "tempdb";
$connection = new \PDO("sqlsrv:server=$serverName;Database=$database", $username, $password, $pdo_options);
$pdo_options = array();
$pdo_options[PDO::ATTR_EMULATE_PREPARES] = TRUE;
$pdo_options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = TRUE;
$pdo_options[PDO::SQLSRV_ATTR_ENCODING] = PDO::SQLSRV_ENCODING_UTF8;
$pdo_options[PDO::ATTR_CURSOR] = PDO::CURSOR_SCROLL;
$pdo_options[PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE] = PDO::SQLSRV_CURSOR_BUFFERED;
// Drop
try {
$st = $connection->prepare("DROP TABLE TEST", $pdo_options);
$st->execute();
}
catch(\Exception $e) {}
// Recreate
$st = $connection->prepare("CREATE TABLE TEST([id] [int] IDENTITY(1,1) NOT NULL, [name] nvarchar(max))", $pdo_options);
$st->execute();
$prefix = '가각';
$name = '가각ácasa';
$name2 = '가각sample2';
$pdo_options[PDO::ATTR_EMULATE_PREPARES] = FALSE;
$st = $connection->prepare("INSERT INTO TEST(name) VALUES(:p0)", $pdo_options);
$st->execute(['p0' => $name]);
$pdo_options[PDO::ATTR_EMULATE_PREPARES] = TRUE;
$st = $connection->prepare("INSERT INTO TEST(name) VALUES(:p0)", $pdo_options);
$st->execute(['p0' => $name2]);
$statement = $connection->prepare("SELECT * FROM TEST WHERE NAME LIKE :p0", $pdo_options);
$statement->execute(['p0' => "$prefix%"]);
foreach ($statement as $row) {
echo "\n" . 'FOUND: ' . $row['name'];
}
$pdo_options = array();
$pdo_options[PDO::ATTR_EMULATE_PREPARES] = FALSE;
$pdo_options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = TRUE;
$pdo_options[PDO::SQLSRV_ATTR_ENCODING] = PDO::SQLSRV_ENCODING_UTF8;
$statement = $connection->prepare("SELECT * FROM TEST WHERE NAME LIKE :p0", $pdo_options);
$statement->execute(['p0' => "$prefix%"]);
foreach ($statement as $row) {
echo "\n" . 'FOUND: ' . $row['name'];
}
$stmt = NULL;
$connection = NULL;
?>
--EXPECT--
FOUND: 가각ácasa
FOUND: 가각sample2
FOUND: 가각ácasa
FOUND: 가각sample2

File diff suppressed because one or more lines are too long