implemented emulate prepare and added tests
This commit is contained in:
parent
012b16a64f
commit
7aaa16d49e
|
@ -589,6 +589,8 @@ int pdo_sqlsrv_dbh_prepare( pdo_dbh_t *dbh, const char *sql,
|
||||||
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
|
sqlsrv_malloc_auto_ptr<char> sql_rewrite;
|
||||||
size_t sql_rewrite_len = 0;
|
size_t sql_rewrite_len = 0;
|
||||||
sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
|
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 );
|
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");
|
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
|
// else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
|
||||||
// set to the substituted query
|
// 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;
|
stmt->driver_data = driver_stmt;
|
||||||
driver_stmt.transferred();
|
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_VALIDATE_CONN;
|
||||||
PDO_LOG_DBH_ENTRY;
|
PDO_LOG_DBH_ENTRY;
|
||||||
|
|
||||||
pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
|
SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR;
|
||||||
SQLSRV_ENCODING encoding = driver_dbh->bind_param_encoding;
|
|
||||||
|
// 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 ) {
|
if ( encoding == SQLSRV_ENCODING_BINARY ) {
|
||||||
// convert from char* to hex digits using os
|
// convert from char* to hex digits using os
|
||||||
std::basic_ostringstream<char> os;
|
std::basic_ostringstream<char> os;
|
||||||
for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) {
|
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 ];
|
os << std::hex << ( int )unquoted[ index ];
|
||||||
}
|
}
|
||||||
std::basic_string<char> str_hex = os.str();
|
std::basic_string<char> str_hex = os.str();
|
||||||
|
|
|
@ -24,17 +24,27 @@
|
||||||
// Constructor
|
// Constructor
|
||||||
conn_string_parser:: conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht )
|
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->len = len;
|
||||||
this->conn_options_ht = conn_options_ht;
|
this->element_ht = conn_options_ht;
|
||||||
this->pos = -1;
|
this->pos = -1;
|
||||||
this->ctx = &ctx;
|
this->ctx = &ctx;
|
||||||
this->current_key = 0;
|
this->current_key = 0;
|
||||||
this->current_key_name = NULL;
|
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
|
// 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 already at the end then return false
|
||||||
if( this->is_eos() ) {
|
if( this->is_eos() ) {
|
||||||
|
@ -55,7 +65,7 @@ inline bool conn_string_parser::next( void )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for end of string.
|
// Check for end of string.
|
||||||
inline bool conn_string_parser::is_eos( void )
|
inline bool string_parser::is_eos( void )
|
||||||
{
|
{
|
||||||
if( this->pos == len )
|
if( this->pos == len )
|
||||||
{
|
{
|
||||||
|
@ -68,7 +78,7 @@ inline bool conn_string_parser::is_eos( void )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for white space.
|
// 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' ) {
|
if( c == ' ' || c == '\r' || c == '\n' || c == '\t' ) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -98,7 +108,7 @@ bool conn_string_parser::discard_white_spaces()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while( this->is_white_space( this->conn_str[ pos ] )) {
|
while( this->is_white_space( this->orig_str[ pos ] )) {
|
||||||
|
|
||||||
if( !next() )
|
if( !next() )
|
||||||
return false;
|
return false;
|
||||||
|
@ -107,8 +117,8 @@ bool conn_string_parser::discard_white_spaces()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a key-value pair to the hashtable of connection options.
|
// Add a key-value pair to the hashtable
|
||||||
void conn_string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC )
|
void string_parser::add_key_value_pair( const char* value, int len TSRMLS_DC )
|
||||||
{
|
{
|
||||||
zval value_z;
|
zval value_z;
|
||||||
ZVAL_UNDEF( &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 );
|
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.
|
// 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 ) );
|
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.
|
// Primary function which parses the connection string/DSN.
|
||||||
void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
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;
|
start_pos = this->pos;
|
||||||
|
|
||||||
// read the key name
|
// read the key name
|
||||||
while( this->conn_str[ pos ] != '=' ) {
|
while( this->orig_str[ pos ] != '=' ) {
|
||||||
|
|
||||||
if( !next() ) {
|
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;
|
state = Value;
|
||||||
|
|
||||||
|
@ -197,13 +285,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
|
|
||||||
case Value:
|
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" );
|
"Equal was expected" );
|
||||||
|
|
||||||
next(); // skip "="
|
next(); // skip "="
|
||||||
|
|
||||||
// if EOS encountered after 0 or more spaces OR semi-colon encountered.
|
// 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 );
|
add_key_value_pair( NULL, 0 TSRMLS_CC );
|
||||||
|
|
||||||
|
@ -213,13 +301,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// this->conn_str[ pos ] == ';'
|
// this->orig_str[ pos ] == ';'
|
||||||
state = NextKeyValuePair;
|
state = NextKeyValuePair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if LCB
|
// if LCB
|
||||||
else if( this->conn_str[ pos ] == '{' ) {
|
else if( this->orig_str[ pos ] == '{' ) {
|
||||||
|
|
||||||
start_pos = this->pos; // starting character is LCB
|
start_pos = this->pos; // starting character is LCB
|
||||||
state = ValueContent1;
|
state = ValueContent1;
|
||||||
|
@ -237,7 +325,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
|
|
||||||
case ValueContent1:
|
case ValueContent1:
|
||||||
{
|
{
|
||||||
while ( this->conn_str[ pos ] != '}' ) {
|
while ( this->orig_str[ pos ] != '}' ) {
|
||||||
|
|
||||||
if ( ! next() ) {
|
if ( ! next() ) {
|
||||||
|
|
||||||
|
@ -253,7 +341,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
|
|
||||||
case ValueContent2:
|
case ValueContent2:
|
||||||
{
|
{
|
||||||
while( this->conn_str[ pos ] != ';' ) {
|
while( this->orig_str[ pos ] != ';' ) {
|
||||||
|
|
||||||
if( ! next() ) {
|
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
|
// semi-colon encountered, so go to next key-value pair
|
||||||
state = NextKeyValuePair;
|
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() )),
|
SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )),
|
||||||
"conn_string_parser::parse_conn_string: Invalid state encountered " );
|
"conn_string_parser::parse_conn_string: Invalid state encountered " );
|
||||||
|
@ -282,14 +370,14 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
if( !next() ) {
|
if( !next() ) {
|
||||||
|
|
||||||
// EOS
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" );
|
SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" );
|
||||||
|
|
||||||
// if second RCB encountered than go back to ValueContent1
|
// if second RCB encountered than go back to ValueContent1
|
||||||
if( this->conn_str[ pos ] == '}' ) {
|
if( this->orig_str[ pos ] == '}' ) {
|
||||||
|
|
||||||
if( !next() ) {
|
if( !next() ) {
|
||||||
|
|
||||||
|
@ -304,20 +392,20 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
int end_pos = this->pos;
|
int end_pos = this->pos;
|
||||||
|
|
||||||
// discard any trailing white-spaces.
|
// 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() ) {
|
if( ! this->discard_white_spaces() ) {
|
||||||
|
|
||||||
//EOS
|
//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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if semi-colon than go to next key-value pair
|
// 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;
|
state = NextKeyValuePair;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -328,7 +416,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
}
|
}
|
||||||
case NextKeyValuePair:
|
case NextKeyValuePair:
|
||||||
{
|
{
|
||||||
SQLSRV_ASSERT(( this->conn_str[ pos ] == ';' ),
|
SQLSRV_ASSERT(( this->orig_str[ pos ] == ';' ),
|
||||||
"conn_string_parser::parse_conn_string: semi-colon was expected." );
|
"conn_string_parser::parse_conn_string: semi-colon was expected." );
|
||||||
|
|
||||||
// Call next() to skip the semi-colon.
|
// Call next() to skip the semi-colon.
|
||||||
|
@ -338,7 +426,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D )
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( this->conn_str[ pos ] == ';' ) {
|
if( this->orig_str[ pos ] == ';' ) {
|
||||||
|
|
||||||
// a second semi-colon is error case.
|
// a second semi-colon is error case.
|
||||||
THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos );
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -486,9 +486,10 @@ int pdo_sqlsrv_stmt_describe_col(pdo_stmt_t *stmt, int colno TSRMLS_DC)
|
||||||
// *stmt - pointer to current statement
|
// *stmt - pointer to current statement
|
||||||
// Return:
|
// Return:
|
||||||
// 1 for success.
|
// 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" );
|
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;
|
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 );
|
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
|
// if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the
|
||||||
// subtituted query provided by PDO
|
// subtituted query provided by PDO
|
||||||
if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
|
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 = stmt->active_query_string;
|
||||||
query_len = static_cast<unsigned int>( stmt->active_query_stringlen );
|
query_len = static_cast<unsigned int>( stmt->active_query_stringlen );
|
||||||
|
|
|
@ -125,12 +125,28 @@ PHP_MINFO_FUNCTION(pdo_sqlsrv);
|
||||||
|
|
||||||
extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP
|
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
|
// PDO DSN Parser
|
||||||
//*********************************************************************************************************************************
|
//*********************************************************************************************************************************
|
||||||
|
|
||||||
// Parser class used to parse DSN connection string.
|
// Parser class used to parse DSN connection string.
|
||||||
class conn_string_parser
|
class conn_string_parser : private string_parser
|
||||||
{
|
{
|
||||||
enum States
|
enum States
|
||||||
{
|
{
|
||||||
|
@ -144,26 +160,31 @@ class conn_string_parser
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char* conn_str;
|
|
||||||
sqlsrv_context* ctx;
|
|
||||||
int len;
|
|
||||||
int pos;
|
|
||||||
unsigned int current_key;
|
|
||||||
const char* current_key_name;
|
const char* current_key_name;
|
||||||
HashTable* conn_options_ht;
|
bool discard_white_spaces(void);
|
||||||
inline bool next( void );
|
int discard_trailing_white_spaces(const char* str, int len);
|
||||||
inline bool is_eos( void );
|
void validate_key(const char *key, int key_len TSRMLS_DC);
|
||||||
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 );
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht );
|
conn_string_parser( sqlsrv_context& ctx, const char* dsn, int len, _Inout_ HashTable* conn_options_ht );
|
||||||
void parse_conn_string( TSRMLS_D );
|
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
|
// Connection
|
||||||
//*********************************************************************************************************************************
|
//*********************************************************************************************************************************
|
||||||
|
@ -228,6 +249,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt {
|
||||||
direct_query( false ),
|
direct_query( false ),
|
||||||
direct_query_subst_string( NULL ),
|
direct_query_subst_string( NULL ),
|
||||||
direct_query_subst_string_len( 0 ),
|
direct_query_subst_string_len( 0 ),
|
||||||
|
placeholders(NULL),
|
||||||
bound_column_param_types( NULL ),
|
bound_column_param_types( NULL ),
|
||||||
fetch_numeric( false )
|
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
|
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
|
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
|
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
|
// meta data for current result set
|
||||||
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;
|
std::vector<field_meta_data*, sqlsrv_allocator< field_meta_data* > > current_meta_data;
|
||||||
|
|
69
test/pdo_sqlsrv/pdo_092_emulate_prepare_statement_utf8.phpt
Normal file
69
test/pdo_sqlsrv/pdo_092_emulate_prepare_statement_utf8.phpt
Normal 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
|
259
test/pdo_sqlsrv/pdo_140_emulate_prepare_mix_binary.phpt
Normal file
259
test/pdo_sqlsrv/pdo_140_emulate_prepare_mix_binary.phpt
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue