From: Luis Soares Date: November 22 2010 2:11am Subject: bzr commit into mysql-next-mr branch (luis.soares:3209) WL#5597 List-Archive: http://lists.mysql.com/commits/124576 Message-Id: <201011220212.oALNRCBk015080@rcsinet15.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============3277495290838078915==" --===============3277495290838078915== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///home/lsoares/Workspace/bzr/work/features/wl5597/mysql-next-mr/ based on revid:luis.soares@stripped 3209 Luis Soares 2010-11-22 WL#5597 Work-in-progress. Improvements in this cset: - fixing a couple of test cases that failed. - improved and documented (doxygen) hash interface. - better handling for hashing collisions. modified: sql/log_event.cc sql/rpl_utility.cc sql/rpl_utility.h === modified file 'sql/log_event.cc' --- a/sql/log_event.cc 2010-11-19 22:44:52 +0000 +++ b/sql/log_event.cc 2010-11-22 02:11:25 +0000 @@ -8234,6 +8234,7 @@ int Rows_log_event::do_hash_scan_and_upd const uchar *bi_ends= NULL; const uchar *ai_start= NULL; const uchar *ai_ends= NULL; + HASH_ROW_POS_ENTRY* entry; DBUG_ENTER("Rows_log_event::do_hash_scan_and_update"); @@ -8263,11 +8264,13 @@ int Rows_log_event::do_hash_scan_and_upd /* move BI to index 0 */ memcpy(m_table->record[0], m_table->record[1], m_table->s->reclength); - - m_hash.add_row(m_table, &m_cols, - bi_start, bi_ends, - ai_start, ai_ends); - + + /* create an entry to add to the hash table */ + entry= m_hash.make_entry(bi_start, bi_ends, ai_start, ai_ends); + + /* add it to the hash table */ + m_hash.put(m_table, &m_cols, entry); + /** Last row hashed. We are handling the last (pair of) row(s). So now we do the table scan and match against the entries in the hash @@ -8288,7 +8291,10 @@ int Rows_log_event::do_hash_scan_and_upd goto err; } - /* Continue until we find the right record or reached the end of the table */ + /* + Scan the table only once and compare against entries in hash. + When a match is found, apply the changes. + */ do { /* get the first record from the table */ @@ -8298,12 +8304,40 @@ int Rows_log_event::do_hash_scan_and_upd switch (error) { case 0: { - m_hash.search_and_remove_row(table, &m_cols, &bi_start, &bi_ends, &ai_start, &ai_ends); + entry= NULL; + m_hash.get(table, &m_cols, &entry); store_record(table, record[1]); - if (bi_start != NULL) + /** + If there are collisions we need to be sure that this is + indeed the record we want. Loop through all records for + the given key and explicitly compare them against the + record we got from the storage engine. + */ + while(entry) { - /* At this point, both table->record[0] and + m_curr_row= entry->bi_start; + m_curr_row_end= entry->bi_ends; + + if ((error= unpack_current_row(rli, &m_cols))) + goto close_table; + + if (record_compare(m_table, &m_cols)) + m_hash.next(&entry); + else + break; // we found a match + } + + /** + We found the entry we needed, just apply the changes. + */ + if (entry) + { + // just to be safe, copy the record from the SE to table->record[0] + memcpy(table->record[0], table->record[1], table->s->reclength); + + /** + At this point, both table->record[0] and table->record[1] have the SE row that matched the one in the hash table. @@ -8313,10 +8347,12 @@ int Rows_log_event::do_hash_scan_and_upd unpacked correctly to table->record[0] in UPDATE implementation of do_exec_row(). */ + m_curr_row= entry->bi_start; + m_curr_row_end= entry->bi_ends; - m_curr_row= bi_start; - m_curr_row_end= bi_ends; - + /* we don't need this entry anymore, just delete it */ + m_hash.del(entry); + if ((error= do_apply_row(rli))) { if (handle_idempotent_errors(rli, &error) || error) @@ -8347,7 +8383,8 @@ int Rows_log_event::do_hash_scan_and_upd (!error || (error == HA_ERR_RECORD_DELETED))); close_table: - m_table->file->print_error(error, MYF(0)); + if (error) + m_table->file->print_error(error, MYF(0)); m_table->file->ha_rnd_end(); } === modified file 'sql/rpl_utility.cc' --- a/sql/rpl_utility.cc 2010-11-19 23:00:43 +0000 +++ b/sql/rpl_utility.cc 2010-11-22 02:11:25 +0000 @@ -1062,27 +1062,90 @@ table_def::~table_def() Utility methods for handling row based operations. */ -static uchar * hash_slave_rows_get_key(const uchar *record, - size_t *length, - my_bool not_used __attribute__((unused))) +/** + + Internal structure that acts as a preamble for HASH_ROW_POS_ENTRY + in memory structure. Allocation in make_entry is done as follows: + + preamble_ptr= malloc (sizeof(preamble)+sizeof(entry)); + entry_ptr= preamble_ptr+1; + + preamble_ptr -----> |-------------------------| + | hash_slave_row_preamble | + | - key | + | - length | + | - state | + entry_ptr -----> |-------------------------| + | HASH_ROW_POS_ENTRY | + | - bi_start | + | - bi_ends | + | - ai_start | + | - ai_ends | + |-------------------------| + + + return entry_ptr; + + When iterating over an entry with multiple records, we can just use + pointer arithmetic to retrieve the preamble pointer. This way we + hide from the hash table user the gory details of key bookeeping + for the hash (including collision handling). + +*/ +typedef struct hash_row_pos_preamble +{ + + /** + The pointer to the hash table key. + */ + uchar* key; + + /** + Length of the key. + */ + uint length; + + /** + The actual key. + */ + my_hash_value_type hash_value; + + /** + The search state used to iterate over multiple entries for a + given key. + */ + HASH_SEARCH_STATE search_state; + + /** + Wether this search_state is usable or not. + */ + bool is_search_state_inited; + +} HASH_ROW_POS_PREAMBLE; + + +static uchar* +hash_slave_rows_get_key(const uchar *record, + size_t *length, + my_bool not_used __attribute__((unused))) { DBUG_ENTER("get_key"); - hash_slave_rows_entry *entry=(hash_slave_rows_entry *) record; - *length= entry->length; + HASH_ROW_POS_PREAMBLE *preamble=(HASH_ROW_POS_PREAMBLE *) record; + *length= preamble->length; - DBUG_RETURN((uchar*) entry->key); + DBUG_RETURN((uchar*) preamble->key); } static void -hash_slave_rows_free_entry(hash_slave_rows_entry *entry) +hash_slave_rows_free_entry(HASH_ROW_POS_PREAMBLE *preamble) { DBUG_ENTER("free_entry"); - my_free(entry); + if (preamble) + my_free(preamble); DBUG_VOID_RETURN; } - bool Hash_slave_rows::is_empty(void) { return (m_hash.records == 0); @@ -1096,7 +1159,7 @@ bool Hash_slave_rows::init(void) { my_hash_init(&m_hash, &my_charset_bin, /* the charater set information */ - 16 /* FIXME */, /* growth size */ + 16 /* TODO */, /* growth size */ 0, /* key offset */ 0, /* key length */ hash_slave_rows_get_key, /* get function pointer */ @@ -1104,7 +1167,6 @@ bool Hash_slave_rows::init(void) MYF(0)); /* flags */ return 0; - } bool Hash_slave_rows::deinit(void) @@ -1120,71 +1182,160 @@ int Hash_slave_rows::size() return m_hash.records; } +HASH_ROW_POS_ENTRY* Hash_slave_rows::make_entry(const uchar* bi_start, const uchar* bi_ends, + const uchar* ai_start, const uchar* ai_ends) +{ + DBUG_ENTER("Hash_slave_rows::make_entry"); + + size_t size= sizeof(struct hash_row_pos_preamble) + + sizeof(struct hash_row_pos_entry); + + HASH_ROW_POS_PREAMBLE *preamble= + (HASH_ROW_POS_PREAMBLE*) my_malloc(size, MYF(0)); -bool -Hash_slave_rows::search_and_remove_row(TABLE *table, - MY_BITMAP *cols, - const uchar **bi_start, const uchar **bi_ends, - const uchar **ai_start, const uchar **ai_ends) + if (!preamble) + DBUG_RETURN(NULL); + + HASH_ROW_POS_ENTRY* entry= (HASH_ROW_POS_ENTRY*) (preamble+1); + + /** + Filling in the preamble. + */ + preamble->key= (uchar*)&preamble->hash_value; + preamble->length= sizeof(my_hash_value_type); + preamble->search_state= -1; + preamble->hash_value= -1; + preamble->is_search_state_inited= false; + + /** + Filling in the values. + */ + entry->bi_start= (const uchar *) bi_start; + entry->bi_ends= (const uchar *) bi_ends; + entry->ai_start= (const uchar *) ai_start; + entry->ai_ends= (const uchar *) ai_ends; + + DBUG_PRINT("debug", ("Added record to hash with key=%u", preamble->hash_value)); + + /** + Return the pointer to the entry. The caller should not + be exposed to the internal preamble. + */ + DBUG_RETURN(entry); +} + +bool +Hash_slave_rows::put(TABLE *table, + MY_BITMAP *cols, + HASH_ROW_POS_ENTRY* entry) { - DBUG_ENTER("Hash_slave_rows::search_row"); + DBUG_ENTER("Hash_slave_rows::put"); + + HASH_ROW_POS_PREAMBLE* preamble= ((HASH_ROW_POS_PREAMBLE*)entry)-1; + + /** + Skip blobs from key calculation. + Handle X bits. + Handle nulled fields. + Handled fields not signaled. + */ + make_hash_key(table, cols, &preamble->hash_value); + my_hash_insert(&m_hash, (uchar *) preamble); + DBUG_PRINT("debug", ("Added record to hash with key=%u", preamble->hash_value)); + DBUG_RETURN(false); +} +bool +Hash_slave_rows::get(TABLE *table, + MY_BITMAP *cols, + HASH_ROW_POS_ENTRY** entry) +{ + DBUG_ENTER("Hash_slave_rows::get"); HASH_SEARCH_STATE state; - my_hash_value_type se_key; /* storage engine record based row key */ - *bi_start= *bi_ends= *ai_start= *ai_ends= NULL; + HASH_ROW_POS_PREAMBLE* preamble; + my_hash_value_type key; - make_hash_key(table, cols, &se_key); + make_hash_key(table, cols, &key); + + DBUG_PRINT("debug", ("Looking for record with key=%u in the hash.", key)); + + preamble= (HASH_ROW_POS_PREAMBLE*) my_hash_first(&m_hash, + (const uchar*) &key, + sizeof(my_hash_value_type), + &state); + if (preamble) + { + DBUG_PRINT("debug", ("Found record with key=%u in the hash.", key)); - DBUG_PRINT("debug", ("Looking for record with key=%u in the hash.", se_key)); - - /** - This is only needed because records are hashed without blobs, so - we may have false positives. - */ - hash_slave_rows_entry *entry= (hash_slave_rows_entry *) my_hash_first(&m_hash, - (const uchar*) &se_key, - sizeof(my_hash_value_type), - &state); - while (entry) - { - const uchar* key= (const uchar*) &entry->key; - /** - unpack again the full BI record to table->record[0]. Now, - both table->record[0] and table->record[1] should have - approximately the same contents (except maybe for blobs - - thence we need to do full comparison below). + Save the search state in case we need to go through entries for + the given key. */ -/* event->m_curr_row= entry->m_curr_row; - if (!(error= event->unpack_current_row(rli, &cols))) - {*/ - /* - compare the row_entry with row taking into account - blobs, if there is any. If there is a match do the - operation and remove the entry from the hash table. - */ -/* if (!record_compare(table, cols)) - {*/ - *bi_start= entry->bi_start; - *bi_ends= entry->bi_ends; - *ai_start= entry->ai_start; - *ai_ends= entry->ai_ends; - - my_hash_delete(&m_hash, (uchar *)entry); - DBUG_RETURN(0); -/* } - } - else - DBUG_RETURN(error);*/ + preamble->search_state= state; + preamble->is_search_state_inited= true; + + *entry= (HASH_ROW_POS_ENTRY*) (preamble+1); + } + else + *entry= NULL; - /* Next row for the loop... */ - entry= (hash_slave_rows_entry *)my_hash_next(&m_hash, - key, - sizeof(my_hash_value_type), - &state); + DBUG_RETURN(false); +} + +bool Hash_slave_rows::next(HASH_ROW_POS_ENTRY** entry) +{ + DBUG_ENTER("Hash_slave_rows::next"); + + if (*entry) + { + HASH_ROW_POS_PREAMBLE* preamble= + ((HASH_ROW_POS_PREAMBLE*) *entry) - 1; + + if (preamble->is_search_state_inited) + { + my_hash_value_type key= preamble->hash_value; + HASH_SEARCH_STATE state= preamble->search_state; + preamble->search_state= -1; + preamble->is_search_state_inited= false; + + DBUG_PRINT("debug", ("Looking for record with key=%u in the hash (next).", key)); + + preamble= (HASH_ROW_POS_PREAMBLE*) my_hash_next(&m_hash, + (const uchar*) &key, + sizeof(my_hash_value_type), + &state); + if (preamble) + { + DBUG_PRINT("debug", ("Found record with key=%u in the hash (next).", key)); + preamble->search_state= state; + preamble->is_search_state_inited= true; + *entry= (HASH_ROW_POS_ENTRY*) (preamble+1); + } + else + *entry= NULL; + } + else + DBUG_RETURN(true); } + else + DBUG_RETURN(true); - DBUG_RETURN(0); + DBUG_RETURN(false); +} + +bool +Hash_slave_rows::del(HASH_ROW_POS_ENTRY* entry) +{ + DBUG_ENTER("Hash_slave_rows::del"); + if (entry) + { + HASH_ROW_POS_PREAMBLE* preamble= + ((HASH_ROW_POS_PREAMBLE*)entry)-1; + my_hash_delete(&m_hash, (uchar *) preamble); + } + else + DBUG_RETURN(true); + DBUG_RETURN(false); } bool @@ -1232,28 +1383,11 @@ Hash_slave_rows::make_hash_key(TABLE *ta ptr++) { Field *f= (*ptr); - /* field is set in the read_set and is not a blob */ - if (bitmap_is_set(cols, f->field_index)) - { - if (f->type() == MYSQL_TYPE_BLOB) - { - Field_blob *fb= (Field_blob*) f; - uchar* ptr; - uint32 length= fb->get_length(); - if (length) - { - fb->get_ptr(&ptr); - crc= my_checksum(crc, ptr, length); - } - } - else - crc= my_checksum(crc, f->ptr, f->data_length()); - - // TODO: what if the table only has blobs ? - // Maybe don't use hash_scan approach or include - // blobs in this calculation. - } + /* field is set in the read_set and is not a blob */ + if (bitmap_is_set(cols, f->field_index) && + (f->type() != MYSQL_TYPE_BLOB)) + crc= my_checksum(crc, f->ptr, f->data_length()); } /* @@ -1271,8 +1405,6 @@ Hash_slave_rows::make_hash_key(TABLE *ta record[table->s->null_bytes - 1]= saved_filler; } - - DBUG_PRINT("debug", ("Created key=%u", crc)); DBUG_ASSERT(crc > 0); @@ -1280,42 +1412,5 @@ Hash_slave_rows::make_hash_key(TABLE *ta DBUG_RETURN(false); } -bool -Hash_slave_rows::add_row(TABLE *table, - MY_BITMAP *cols, - const uchar *bi_start, const uchar *bi_ends, - const uchar *ai_start, const uchar *ai_ends) -{ - DBUG_ENTER("Hash_slave_rows::add_row"); - - /** - Insert the actual entry. - */ - hash_slave_rows_entry *entry= (hash_slave_rows_entry*) malloc(sizeof(hash_slave_rows_entry)); - if (!entry) - DBUG_RETURN(1); - - entry->key= (uchar*)&entry->hash_value; - entry->length= sizeof(my_hash_value_type); - - /** - Skip blobs from key calculation. - Handle X bits. - Handle nulled fields. - Handled fields not signaled. - */ - make_hash_key(table, cols, &entry->hash_value); - - entry->bi_start= (const uchar *) bi_start; - entry->bi_ends= (const uchar *) bi_ends; - entry->ai_start= (const uchar *) ai_start; - entry->ai_ends= (const uchar *) ai_ends; - - DBUG_PRINT("debug", ("Added record to hash with key=%u", entry->hash_value)); - - my_hash_insert(&m_hash, (uchar *) entry); - - DBUG_RETURN(0); -} #endif === modified file 'sql/rpl_utility.h' --- a/sql/rpl_utility.h 2010-11-19 23:00:43 +0000 +++ b/sql/rpl_utility.h 2010-11-22 02:11:25 +0000 @@ -38,17 +38,8 @@ class Relay_log_info; no index on the slave's table. */ -struct hash_slave_rows_entry +typedef struct hash_row_pos_entry { - uchar* key; - - /** - Length of the key. - */ - uint length; - - my_hash_value_type hash_value; - /** Points at the position where the row starts in the event buffer (ie, area in memory before unpacking takes @@ -59,48 +50,132 @@ struct hash_slave_rows_entry const uchar *ai_start; const uchar *ai_ends; -}; + +} HASH_ROW_POS_ENTRY; + class Hash_slave_rows { public: + + /** + This member function allocates an entry to be added to the hash + table. It should be called before calling member function add. + + @param bi_start the position to where in the rows buffer the + before image begins. + @param bi_ends the position to where in the rows buffer the + before image ends. + @param ai_start the position to where in the rows buffer the + after image starts (if any). + @param ai_ends the position to where in the rows buffer the + after image ends (if any). + @returns NULL if a problem occured, a valid pointer otherwise. + */ + HASH_ROW_POS_ENTRY* make_entry(const uchar *bi_start, const uchar *bi_ends, + const uchar *ai_start, const uchar *ai_ends); + + /** + Member function that puts data into the hash table. + + @param table The table holding the buffer used to calculate the + key, ie, table->record[0]. + @param cols The read_set bitmap signaling which columns are used. + @param entry The entry with the values to store. + + @returns true if something went wrong, false otherwise. + */ + bool put(TABLE* table, MY_BITMAP *cols, HASH_ROW_POS_ENTRY* entry); + /** - @param table The table that is being scanned. - @param cols The read_set bitmap signaling which columns are used. - @param unpacked_record The position in memory where the unpacked record is. + This member function gets the entry, from the hash table, that + matches the data in table->record[0] and signaled using cols. + + @param table The table holding the buffer containing data used to + make the entry lookup. + @param cols Bitmap signaling which columns, from + table->record[0], should be used. + @param entry Pointer that will hold a reference to the entry + fetched. If the entry is not found, then NULL + shall be returned. + @returns true if something went wrong, false otherwise. */ + bool get(TABLE *table, MY_BITMAP *cols, HASH_ROW_POS_ENTRY** entry); - bool add_row(TABLE* table, - MY_BITMAP *cols, - const uchar *bi_start, const uchar *bi_end, - const uchar *ai_start, const uchar *ai_end); + /** + This member function gets the entry that stands next to the one + pointed to by *entry. Before calling this member function, the + entry that one uses as parameter must have: 1. been obtained + through get() or next() invocations; and 2. must have not been + used before in a next() operation. + + @param entry[IN/OUT] contains a pointer to an entry that we can + use to search for another adjacent entry + (ie, that shares the same key). + + @returns true if something went wrong, false otherwise. In the + case that this entry was already used in a next() + operation this member function returns true and does not + update the pointer. + */ + bool next(HASH_ROW_POS_ENTRY** entry); /** - @param table - @param cols - @param unpacked_record + Deletes the entry pointed by entry. This is the only + safe way to free memory allocated for the structure + pointed to by entry. + + @param entry Pointer to the entry to be deleted. + @returns true if something went wrong, false otherwise. */ - bool search_and_remove_row(TABLE *table, - MY_BITMAP *cols, - const uchar **bi_start, const uchar **bi_ends, - const uchar **ai_start, const uchar **ai_ends); + bool del(HASH_ROW_POS_ENTRY* entry); + /** + Initializes the hash table. + + @returns true if something went wrong, false otherwise. + */ bool init(void); + + /** + De-initializes the hash table. + + @returns true if something went wrong, false otherwise. + */ bool deinit(void); + + /** + Checks if the hash table is empty or not. + + @returns true if the hash table has zero entries, false otherwise. + */ bool is_empty(void); + + /** + Returns the number of entries in the hash table. + + @returns the number of entries in the hash table. + */ int size(); private: + /** + The hashtable itself. + */ HASH m_hash; /** + Auxiliar and internal method used to create an hash key, based on + the data in table->record[0] buffer and signaled as used in cols. + @param table The table that is being scanned @param cols The read_set bitmap signaling which columns are used. @param key Output parameter where the key will be stored. + + @retuns true if something went wrong, false otherwise. */ bool make_hash_key(TABLE *table, MY_BITMAP* cols, my_hash_value_type *key); - }; #endif --===============3277495290838078915== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/luis.soares@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: luis.soares@stripped # target_branch: file:///home/lsoares/Workspace/bzr/work/features\ # /wl5597/mysql-next-mr/ # testament_sha1: 9c4666df3c5508be1516367eeb06079f4309d6c1 # timestamp: 2010-11-22 02:11:43 +0000 # source_branch: /home/lsoares/Workspace/bzr/clones/mysql-next-mr-\ # wl5597 # base_revision_id: luis.soares@stripped\ # ce924ic5s0kyl12h # # Begin bundle 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 --===============3277495290838078915==--