diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 63e0598ce..24832f824 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -589,6 +589,8 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql, sqlsrv_malloc_auto_ptr sql_rewrite; size_t sql_rewrite_len = 0; sqlsrv_malloc_auto_ptr driver_stmt; + hash_auto_ptr placeholders; + sqlsrv_malloc_auto_ptr sql_parser; pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( 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(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( 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( 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( stmt->driver_data ); + 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 + 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( zend_hash_find_ptr( stmt->bound_params, Z_STR_P( placeholder ))); + } + else if ( Z_TYPE_P( placeholder ) == IS_LONG) { + param = reinterpret_cast( zend_hash_index_find_ptr( stmt->bound_params, Z_LVAL_P( placeholder ))); + } + if ( NULL != param ) { + SQLSRV_ENCODING param_encoding = static_cast( 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 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 str_hex = os.str(); diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 74c1a222b..0bc645387 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -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( 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( 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; + } +} \ No newline at end of file diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index b2dc45eaa..cd7413460 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -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( stmt->driver_data ); + //sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( 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( stmt->active_query_stringlen ); diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 973c3c3b2..3a1678956 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -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 > current_meta_data; diff --git a/test/pdo_sqlsrv/pdo_092_emulate_prepare_statement_utf8.phpt b/test/pdo_sqlsrv/pdo_092_emulate_prepare_statement_utf8.phpt new file mode 100644 index 000000000..de6ccbb50 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_092_emulate_prepare_statement_utf8.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test emulate prepare utf8 encoding set at the statement level +--SKIPIF-- +--FILE-- + +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 \ No newline at end of file diff --git a/test/pdo_sqlsrv/pdo_140_emulate_prepare_mix_binary.phpt b/test/pdo_sqlsrv/pdo_140_emulate_prepare_mix_binary.phpt new file mode 100644 index 000000000..430b54160 --- /dev/null +++ b/test/pdo_sqlsrv/pdo_140_emulate_prepare_mix_binary.phpt @@ -0,0 +1,259 @@ +--TEST-- +Test emulate prepare with mix bound param encodings including binary data +--SKIPIF-- +--FILE-- + &$field_value) { + $placeholder = $placeholder_prefix . $max_placeholder++; + $blob_key = $placeholder . $blob_suffix; + if (isset($columnInformation['blobs'][$field_name])) { + $blobs[$blob_key] = fopen('php://memory', 'a'); + fwrite($blobs[$blob_key], $field_value); + rewind($blobs[$blob_key]); + $this->bindParam($placeholder, $blobs[$blob_key], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + } + else { + // Even though not a blob, make sure we retain a copy of these values. + $blobs[$blob_key] = $field_value; + $this->bindParam($placeholder, $blobs[$blob_key], PDO::PARAM_STR); + } + } + } +} + +//******************************************************* +// TEST BEGIN +//******************************************************* + +$connection_options['pdo'] = array(); +$connection_options['pdo'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; + +$database = "tempdb"; +$cnn = new PDO("sqlsrv:Server=$serverName;Database=$database", $username, $password, $connection_options['pdo']); +$cnn->setAttribute(PDO::ATTR_STATEMENT_CLASS, [MyStatement::class]); + +// Drop +try { + $pdo_options = array(); + $pdo_options[PDO::ATTR_EMULATE_PREPARES] = TRUE; + $pdo_options[PDO::SQLSRV_ATTR_DIRECT_QUERY] = TRUE; + $pdo_options[PDO::ATTR_CURSOR] = PDO::CURSOR_SCROLL; + $pdo_options[PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE] = PDO::SQLSRV_CURSOR_BUFFERED; + $st = $cnn->prepare('DROP TABLE WATCHDOG', $pdo_options); + + $st->execute(); +} +catch(\Exception $e) {} + +$tablescript = <<prepare($tablescript, $pdo_options); +$st->execute(); + +$query = <<prepare($query, $pdo_options); + +$st->BindValues($values, $blobs, ':db_insert', $columnInformation); +$st->execute(); + +$st = $cnn->query("SELECT * FROM [watchdog]"); +var_dump($st->fetchAll()); + +$st = NULL; +$cnn = NULL; + +?> +--EXPECT-- +array(1) { + [0]=> + array(22) { + ["wid"]=> + string(1) "1" + [0]=> + string(1) "1" + ["uid"]=> + string(1) "0" + [1]=> + string(1) "0" + ["type"]=> + string(3) "php" + [2]=> + string(3) "php" + ["message"]=> + string(51) "%type: @message in %function (line %line of %file)." + [3]=> + string(51) "%type: @message in %function (line %line of %file)." + ["variables"]=> + string(2188) "a:5:{s:5:"%type";s:45:"Drupal\Core\Database\DatabaseExceptionWrapper";s:8:"@message";s:1913:"SQLSTATE[IMSSP]: An error occurred translating the query string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. +.: MERGE INTO [cache_data] _target +USING (SELECT T.* FROM (values(:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6)) as T([cid], [expire], [created], [tags], [checksum], [data], [serialized])) _source +ON _target.[cid] = _source.[cid] +WHEN MATCHED THEN UPDATE SET _target.[expire] = _source.[expire], _target.[created] = _source.[created], _target.[tags] = _source.[tags], _target.[checksum] = _source.[checksum], _target.[data] = _source.[data], _target.[serialized] = _source.[serialized] +WHEN NOT MATCHED THEN INSERT ([cid], [expire], [created], [tags], [checksum], [data], [serialized]) VALUES (_source.[cid], _source.[expire], _source.[created], _source.[tags], _source.[checksum], _source.[data], _source.[serialized]) +OUTPUT $action;; Array +( + [:db_insert_placeholder_0] => Array + ( + [value] => route:/:XDEBUG_SESSION_START=58E1C1C4 + [datatype] => 2 + ) + + [:db_insert_placeholder_1] => Array + ( + [value] => -1 + [datatype] => 2 + ) + + [:db_insert_placeholder_2] => Array + ( + [value] => 1470205773.7 + [datatype] => 2 + ) + + [:db_insert_placeholder_3] => Array + ( + [value] => route_match + [datatype] => 2 + ) + + [:db_insert_placeholder_4] => Array + ( + [value] => 4 + [datatype] => 2 + ) + + [:db_insert_placeholder_5] => Array + ( + [value] => Resource id #4 + [datatype] => 3 + ) + + [:db_insert_placeholder_6] => Array + ( + [value] => 1 + [datatype] => 2 + ) + +) +";s:9:"%function";s:65:"Drupal\Core\Routing\RouteProvider->getRouteCollectionForRequest()";s:5:"%file";s:52:"D:\d8\core\lib\Drupal\Core\Routing\RouteProvider.php";s:5:"%line";i:167;}" + [4]=> + string(2188) "a:5:{s:5:"%type";s:45:"Drupal\Core\Database\DatabaseExceptionWrapper";s:8:"@message";s:1913:"SQLSTATE[IMSSP]: An error occurred translating the query string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. +.: MERGE INTO [cache_data] _target +USING (SELECT T.* FROM (values(:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6)) as T([cid], [expire], [created], [tags], [checksum], [data], [serialized])) _source +ON _target.[cid] = _source.[cid] +WHEN MATCHED THEN UPDATE SET _target.[expire] = _source.[expire], _target.[created] = _source.[created], _target.[tags] = _source.[tags], _target.[checksum] = _source.[checksum], _target.[data] = _source.[data], _target.[serialized] = _source.[serialized] +WHEN NOT MATCHED THEN INSERT ([cid], [expire], [created], [tags], [checksum], [data], [serialized]) VALUES (_source.[cid], _source.[expire], _source.[created], _source.[tags], _source.[checksum], _source.[data], _source.[serialized]) +OUTPUT $action;; Array +( + [:db_insert_placeholder_0] => Array + ( + [value] => route:/:XDEBUG_SESSION_START=58E1C1C4 + [datatype] => 2 + ) + + [:db_insert_placeholder_1] => Array + ( + [value] => -1 + [datatype] => 2 + ) + + [:db_insert_placeholder_2] => Array + ( + [value] => 1470205773.7 + [datatype] => 2 + ) + + [:db_insert_placeholder_3] => Array + ( + [value] => route_match + [datatype] => 2 + ) + + [:db_insert_placeholder_4] => Array + ( + [value] => 4 + [datatype] => 2 + ) + + [:db_insert_placeholder_5] => Array + ( + [value] => Resource id #4 + [datatype] => 3 + ) + + [:db_insert_placeholder_6] => Array + ( + [value] => 1 + [datatype] => 2 + ) + +) +";s:9:"%function";s:65:"Drupal\Core\Routing\RouteProvider->getRouteCollectionForRequest()";s:5:"%file";s:52:"D:\d8\core\lib\Drupal\Core\Routing\RouteProvider.php";s:5:"%line";i:167;}" + ["severity"]=> + string(1) "3" + [5]=> + string(1) "3" + ["link"]=> + string(0) "" + [6]=> + string(0) "" + ["location"]=> + string(64) "http://local.d7test.com/index.php/?XDEBUG_SESSION_START=58E1C1C4" + [7]=> + string(64) "http://local.d7test.com/index.php/?XDEBUG_SESSION_START=58E1C1C4" + ["referer"]=> + string(0) "" + [8]=> + string(0) "" + ["hostname"]=> + string(9) "127.0.0.1" + [9]=> + string(9) "127.0.0.1" + ["timestamp"]=> + string(10) "1470205774" + [10]=> + string(10) "1470205774" + } +} \ No newline at end of file