diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 87657efe..4ed1d4bf 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -34,7 +34,7 @@ namespace { const char LAST_INSERT_ID_QUERY[] = "SELECT @@IDENTITY;"; const size_t LAST_INSERT_ID_BUFF_LEN = 50; // size of the buffer to hold the string value of the last inserted id, which may be an int, bigint, decimal(p,0) or numeric(p,0) -const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT current_value FROM sys.sequences WHERE name=%s"; +const char SEQUENCE_CURRENT_VALUE_QUERY[] = "SELECT current_value FROM sys.sequences WHERE name=N'%s'"; const int LAST_INSERT_ID_QUERY_MAX_LEN = sizeof( SEQUENCE_CURRENT_VALUE_QUERY ) + SQL_MAX_SQLSERVERNAME + 2; // include the quotes // List of PDO supported connection options. @@ -1360,22 +1360,18 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, pdo_sqlsrv_dbh* driver_dbh = static_cast( dbh->driver_data ); SQLSRV_ASSERT( driver_dbh != NULL, "pdo_sqlsrv_dbh_last_id: driver_data object was NULL." ); - sqlsrv_malloc_auto_ptr id_str; - id_str = reinterpret_cast( sqlsrv_malloc( LAST_INSERT_ID_BUFF_LEN )); + char idSTR[LAST_INSERT_ID_BUFF_LEN] = { '\0' }; + char* str = NULL; + SQLLEN cbID = 0; try { char last_insert_id_query[LAST_INSERT_ID_QUERY_MAX_LEN] = {'\0'}; if( name == NULL ) { - strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); + strcpy_s(last_insert_id_query, sizeof(last_insert_id_query), LAST_INSERT_ID_QUERY); } else { - char* quoted_table = NULL; - size_t quoted_len = 0; - int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strnlen_s( name ), "ed_table, "ed_len, PDO_PARAM_NULL ); - SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name."); - snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, quoted_table ); - sqlsrv_free( quoted_table ); + snprintf(last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, name); } // temp PDO statement used for error handling if something happens @@ -1397,20 +1393,17 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, // execute the last insert id query core::SQLExecDirectW( driver_stmt, wsql_string ); - core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 ); - SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, - reinterpret_cast( len ), false ); - CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, - PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { + SQLRETURN r = core::SQLGetData(driver_stmt, 1, SQL_C_CHAR, idSTR, LAST_INSERT_ID_BUFF_LEN, &cbID, false); + CHECK_CUSTOM_ERROR((!SQL_SUCCEEDED(r) || cbID == SQL_NULL_DATA || cbID == SQL_NO_TOTAL), driver_stmt, + PDO_SQLSRV_ERROR_LAST_INSERT_ID) { throw core::CoreException(); } driver_stmt->~sqlsrv_stmt(); } catch( core::CoreException& ) { - // copy any errors on the statement to the connection so that the user sees them, since the statement is released // before this method returns strcpy_s( dbh->error_code, sizeof( dbh->error_code ), @@ -1420,18 +1413,20 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, if( driver_stmt ) { driver_stmt->~sqlsrv_stmt(); } - - strcpy_s( id_str.get(), 1, "" ); *len = 0; + str = reinterpret_cast(sqlsrv_malloc(0, sizeof(char), 1)); // return an empty string with a null terminator + str[0] = '\0'; + return str; } - char* ret_id_str = id_str.get(); - id_str.transferred(); - // restore error handling to its previous mode dbh->error_mode = prev_err_mode; - return ret_id_str; + // copy the last ID string and return it + *len = static_cast(cbID); + str = reinterpret_cast(sqlsrv_malloc(cbID, sizeof(char), 1)); // include space for null terminator + strcpy_s(str, cbID + 1, idSTR); + return str; } // pdo_sqlsrv_dbh_quote @@ -1523,44 +1518,27 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } #endif - 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 ) { - // if unquoted is < 0 or > 255, that means this is a non-ascii character. Translation from non-ascii to binary is not supported. - // return an empty terminated string for now - if (( int )unquoted[index] < 0 || ( int )unquoted[index] > 255) { - *quoted_len = 0; - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); - ( *quoted )[0] = '\0'; - return 1; + if ( encoding == SQLSRV_ENCODING_BINARY ) { + *quoted_len = (unquoted_len * 2) + 2; // each character will be converted to 2 hex digits and prepend '0x' to the result + *quoted = reinterpret_cast(sqlsrv_malloc(*quoted_len, sizeof(char), 1)); // include space for null terminator + memset(*quoted, '\0', *quoted_len + 1); + + unsigned int pos = 0; + (*quoted)[pos++] = '0'; + (*quoted)[pos++] = 'x'; + + for (size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index) { + // On success, snprintf returns the total number of characters written + // On failure, a negative number is returned + // The generated string has a length of at most len - 1, so + // len is 3 (2 hex digits + 1) + int n = snprintf((char*)(*quoted + pos), 3, "%02X", unquoted[index]); + if (n < 0) { + // Something went wrong, simply return 0 (failure) + return 0; } - // 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]; + pos += 2; } - std::basic_string str_hex = os.str(); - // each character is represented by 2 digits of hex - size_t unquoted_str_len = unquoted_len * 2; // length returned should not account for null terminator - char* unquoted_str = reinterpret_cast( sqlsrv_malloc( unquoted_str_len, sizeof( char ), 1 )); // include space for null terminator - strcpy_s( unquoted_str, unquoted_str_len + 1 /* include null terminator*/, str_hex.c_str() ); - // include length of '0x' in the binary string - *quoted_len = unquoted_str_len + 2; - *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); - unsigned int out_current = 0; - // insert '0x' - ( *quoted )[out_current++] = '0'; - ( *quoted )[out_current++] = 'x'; - for ( size_t index = 0; index < unquoted_str_len && unquoted_str[index] != '\0'; ++index ) { - ( *quoted )[out_current++] = unquoted_str[index]; - } - // null terminator - ( *quoted )[out_current] = '\0'; - sqlsrv_free( unquoted_str ); return 1; } else { diff --git a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt index c089e694..74e57c28 100644 --- a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt +++ b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt @@ -21,17 +21,25 @@ try { $tbname = "watchdog"; createTable( $cnn, $tbname, array( "system_encoding" => "nvarchar(128)", "utf8_encoding" => "nvarchar(128)", "binary_encoding" => "varbinary(max)")); + $query = <<prepare($query, $pdo_options); + $system_param = 'system encoded string'; $utf8_param = '가각ácasa'; $binary_param = fopen('php://memory', 'a'); fwrite($binary_param, 'asdgasdgasdgsadg'); rewind($binary_param); - $inputs = array("system_encoding" => $system_param, - "utf8_encoding" => new BindParamOp( 2, $utf8_param, "PDO::PARAM_STR", 0, "PDO::SQLSRV_ENCODING_UTF8" ), - "binary_encoding" => new BindParamOp( 3, $binary_param, "PDO::PARAM_LOB", 0, "PDO::SQLSRV_ENCODING_BINARY" )); + $st->bindParam(1, $system_param, PDO::PARAM_STR); + $st->bindParam(2, $utf8_param, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); + $st->bindParam(3, $binary_param, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); - insertRow($cnn, $tbname, $inputs, "prepareBindParam"); + $st->execute(); $data = selectAll($cnn, $tbname); var_dump($data);