/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "ha_ndbcluster_tables.h" #include "ndb_repl_tab.h" #ifdef HAVE_NDB_BINLOG #include "ha_ndbcluster_glue.h" #include "ha_ndbcluster_connection.h" /* do_retry_sleep() */ #include "ndb_table_guard.h" #include "ndb_share.h" Ndb_rep_tab_key::Ndb_rep_tab_key(const char* _db, const char* _table_name, uint _server_id) { uint db_len= (uint) strlen(_db); uint tabname_len = (uint) strlen(_table_name); assert(DB_MAXLEN < 256); /* Fits in Varchar */ assert(db_len <= DB_MAXLEN); assert(tabname_len <= TABNAME_MAXLEN); memcpy(&db[1], _db, db_len); db[ 0 ]= db_len; memcpy(&table_name[1], _table_name, tabname_len); table_name[ 0 ]= tabname_len; server_id= _server_id; null_terminate_strings(); } void Ndb_rep_tab_key::null_terminate_strings() { assert((uint) db[0] <= DB_MAXLEN); assert((uint) table_name[0] <= TABNAME_MAXLEN); db[ db[0] + 1] = '\0'; table_name[ table_name[0] + 1] = '\0'; } int Ndb_rep_tab_key::attempt_match(const char* keyptr, const uint keylen, const char* candidateptr, const uint candidatelen, const int exactmatchvalue) { if (my_strnncoll(system_charset_info, (const uchar*) keyptr, keylen, (const uchar*) candidateptr, candidatelen) == 0) { /* Exact match */ return exactmatchvalue; } else if (my_wildcmp(system_charset_info, keyptr, keyptr + keylen, candidateptr, candidateptr + candidatelen, '\\', wild_one, wild_many) == 0) { /* Wild match */ return 0; } /* No match */ return -1; }; int Ndb_rep_tab_key::get_match_quality(const Ndb_rep_tab_key* key, const Ndb_rep_tab_key* candidate_row) { /* 0= No match 1= Loosest match 8= Best match Actual mapping is : db table serverid Quality W W W 1 W W = 2 W = W 3 W = = 4 = W W 5 = W = 6 = = W 7 = = = 8 */ int quality = MIN_MATCH_VAL; int rc; if ((rc = attempt_match(&key->db[1], key->db[0], &candidate_row->db[1], candidate_row->db[0], EXACT_MATCH_DB)) == -1) { /* No match, drop out now */ return 0; } quality+= rc; if ((rc = attempt_match(&key->table_name[1], key->table_name[0], &candidate_row->table_name[1], candidate_row->table_name[0], EXACT_MATCH_TABLE_NAME)) == -1) { /* No match, drop out now */ return 0; } quality+= rc; if (candidate_row->server_id == key->server_id) { /* Exact match */ quality += EXACT_MATCH_SERVER_ID; } else if (candidate_row->server_id != 0) { /* No match */ return 0; } return quality; }; Ndb_rep_tab_row::Ndb_rep_tab_row() : binlog_type(0), cfs_is_null(true) { memset(conflict_fn_spec, 0, sizeof(conflict_fn_spec)); } const char* Ndb_rep_tab_reader::ndb_rep_db= NDB_REP_DB; const char* Ndb_rep_tab_reader::ndb_replication_table= NDB_REPLICATION_TABLE; const char* Ndb_rep_tab_reader::nrt_db= "db"; const char* Ndb_rep_tab_reader::nrt_table_name= "table_name"; const char* Ndb_rep_tab_reader::nrt_server_id= "server_id"; const char* Ndb_rep_tab_reader::nrt_binlog_type= "binlog_type"; const char* Ndb_rep_tab_reader::nrt_conflict_fn= "conflict_fn"; Ndb_rep_tab_reader::Ndb_rep_tab_reader() : binlog_flags(NBT_DEFAULT), conflict_fn_spec(NULL), warning_msg(NULL) { } int Ndb_rep_tab_reader::check_schema(const NdbDictionary::Table* reptab, NdbDictionary::Dictionary* dict, const char** error_str) { DBUG_ENTER("check_schema"); *error_str= NULL; const NdbDictionary::Column *col_db, *col_table_name, *col_server_id, *col_binlog_type, *col_conflict_fn; if (reptab->getNoOfPrimaryKeys() != 3) { *error_str= "Wrong number of primary key parts, expected 3"; DBUG_RETURN(-2); } col_db= reptab->getColumn(*error_str= nrt_db); if (col_db == NULL || !col_db->getPrimaryKey() || col_db->getType() != NdbDictionary::Column::Varbinary) DBUG_RETURN(-1); col_table_name= reptab->getColumn(*error_str= nrt_table_name); if (col_table_name == NULL || !col_table_name->getPrimaryKey() || col_table_name->getType() != NdbDictionary::Column::Varbinary) DBUG_RETURN(-1); col_server_id= reptab->getColumn(*error_str= nrt_server_id); if (col_server_id == NULL || !col_server_id->getPrimaryKey() || col_server_id->getType() != NdbDictionary::Column::Unsigned) DBUG_RETURN(-1); col_binlog_type= reptab->getColumn(*error_str= nrt_binlog_type); if (col_binlog_type == NULL || col_binlog_type->getPrimaryKey() || col_binlog_type->getType() != NdbDictionary::Column::Unsigned) DBUG_RETURN(-1); col_conflict_fn= reptab->getColumn(*error_str= nrt_conflict_fn); if (col_conflict_fn != NULL) { if ((col_conflict_fn->getPrimaryKey()) || (col_conflict_fn->getType() != NdbDictionary::Column::Varbinary)) DBUG_RETURN(-1); } DBUG_RETURN(0); } int Ndb_rep_tab_reader::scan_candidates(Ndb* ndb, const NdbDictionary::Table* reptab, const char* db, const char* table_name, uint server_id, Ndb_rep_tab_row& best_match) { uint retries= 100; int retry_sleep= 30; /* 30 milliseconds, transaction */ int best_match_quality= 0; NdbError ok; NdbError ndberror; /* Loop to enable temporary error retries */ while(true) { ndberror = ok; /* reset */ NdbTransaction *trans= ndb->startTransaction(); if (trans == NULL) { ndberror= ndb->getNdbError(); if (ndberror.status == NdbError::TemporaryError) { if (retries--) { do_retry_sleep(retry_sleep); continue; } } break; } NdbRecAttr* ra_binlog_type= NULL; NdbRecAttr* ra_conflict_fn_spec= NULL; Ndb_rep_tab_row row; bool have_conflict_fn_col = (reptab->getColumn(nrt_conflict_fn) != NULL); /* Define scan op on ndb_replication */ NdbScanOperation* scanOp = trans->getNdbScanOperation(reptab); if (scanOp == NULL) { ndberror= trans->getNdbError(); break; } if ((scanOp->readTuples(NdbScanOperation::LM_CommittedRead) != 0) || (scanOp->getValue(nrt_db, (char*) row.key.db) == NULL) || (scanOp->getValue(nrt_table_name, (char*) row.key.table_name) == NULL) || (scanOp->getValue(nrt_server_id, (char*) &row.key.server_id) == NULL) || ((ra_binlog_type = scanOp->getValue(nrt_binlog_type, (char*) &row.binlog_type)) == NULL) || (have_conflict_fn_col && ((ra_conflict_fn_spec= scanOp->getValue(nrt_conflict_fn, (char*) row.conflict_fn_spec)) == NULL))) { ndberror= scanOp->getNdbError(); break; } if (trans->execute(NdbTransaction::NoCommit, NdbOperation::AO_IgnoreError)) { ndberror= trans->getNdbError(); ndb->closeTransaction(trans); if (ndberror.status == NdbError::TemporaryError) { if (retries--) { do_retry_sleep(retry_sleep); continue; } } break; } /* Scroll through results, looking for best match */ DBUG_PRINT("info", ("Searching ndb_replication for %s.%s %u", db, table_name, server_id)); bool ambiguous_match = false; Ndb_rep_tab_key searchkey(db, table_name, server_id); int scan_rc; while ((scan_rc= scanOp->nextResult(true)) == 0) { if (ra_binlog_type->isNULL() == 1) { row.binlog_type= NBT_DEFAULT; } if (ra_conflict_fn_spec) { row.set_conflict_fn_spec_null(ra_conflict_fn_spec->isNULL() == 1); } /* Compare row to searchkey to get quality of match */ int match_quality= Ndb_rep_tab_key::get_match_quality(&searchkey, &row.key); #ifndef DBUG_OFF { row.null_terminate_strings(); DBUG_PRINT("info", ("Candidate : %s.%s %u : %u %s" " Match quality : %u.", row.key.get_db(), row.key.get_table_name(), row.key.server_id, row.binlog_type, row.get_conflict_fn_spec(), match_quality)); } #endif if (match_quality > 0) { if (match_quality == best_match_quality) { ambiguous_match = true; /* Ambiguous matches...*/ my_snprintf(warning_msg_buffer, sizeof(warning_msg_buffer), "Ambiguous matches in %s.%s for %s.%s (%u)." "Candidates : %s.%s (%u), %s.%s (%u).", ndb_rep_db, ndb_replication_table, db, table_name, server_id, &best_match.key.db[1], &best_match.key.table_name[1], best_match.key.server_id, &row.key.db[1], &row.key.table_name[1], row.key.server_id); DBUG_PRINT("info", ("%s", warning_msg_buffer)); } if (match_quality > best_match_quality) { /* New best match */ best_match= row; best_match_quality = match_quality; ambiguous_match = false; if (best_match_quality == Ndb_rep_tab_key::EXACT_MATCH_QUALITY) { /* We're done */ break; } } } /* if (match_quality > 0) */ } /* while ((scan_rc= scanOp->nextResult(true)) */ if (scan_rc < 0) { ndberror= scanOp->getNdbError(); if (ndberror.status == NdbError::TemporaryError) { if (retries--) { ndb->closeTransaction(trans); do_retry_sleep(retry_sleep); continue; } } } ndb->closeTransaction(trans); if (ambiguous_match) { warning_msg= warning_msg_buffer; best_match_quality = -1; } break; } /* while(true) */ if (ndberror.code != 0) { my_snprintf(warning_msg_buffer, sizeof(warning_msg_buffer), "Unable to retrieve %s.%s, logging and " "conflict resolution may not function " "as intended (ndberror %u)", ndb_rep_db, ndb_replication_table, ndberror.code); warning_msg= warning_msg_buffer; best_match_quality = -1; } return best_match_quality; } int Ndb_rep_tab_reader::lookup(Ndb* ndb, /* Keys */ const char* db, const char* table_name, uint server_id) { DBUG_ENTER("lookup"); int error= 0; NdbError ndberror; const char *error_str= ""; /* Set results to defaults */ binlog_flags= NBT_DEFAULT; conflict_fn_spec= NULL; warning_msg= NULL; ndb->setDatabaseName(ndb_rep_db); NdbDictionary::Dictionary *dict= ndb->getDictionary(); Ndb_table_guard ndbtab_g(dict, ndb_replication_table); const NdbDictionary::Table *reptab= ndbtab_g.get_table(); do { if (reptab == NULL) { if (dict->getNdbError().classification == NdbError::SchemaError || dict->getNdbError().code == 4009) { DBUG_PRINT("info", ("No %s.%s table", ndb_rep_db, ndb_replication_table)); DBUG_RETURN(0); } else { error= 0; ndberror= dict->getNdbError(); break; } } if ((error= check_schema(reptab, dict, &error_str)) != 0) { DBUG_PRINT("info", ("check_schema failed : %u, error_str : %s", error, error_str)); break; } Ndb_rep_tab_row best_match_row; int best_match_quality = scan_candidates(ndb, reptab, db, table_name, server_id, best_match_row); DBUG_PRINT("info", ("Best match at quality : %u", best_match_quality)); if (best_match_quality == -1) { /* Problem in matching, message already set */ assert(warning_msg != NULL); error= -3; break; } if (best_match_quality == 0) { /* No match : Use defaults */ } else { /* Have a matching row, copy out values */ /* Ensure VARCHARs are usable as strings */ best_match_row.null_terminate_strings(); binlog_flags= (enum Ndb_binlog_type) best_match_row.binlog_type; if (best_match_row.cfs_is_null) { DBUG_PRINT("info", ("Conflict FN SPEC is Null")); /* No conflict fn spec */ conflict_fn_spec= NULL; } else { const char* conflict_fn = best_match_row.get_conflict_fn_spec(); uint len= (uint) strlen(conflict_fn); if ((len + 1) > sizeof(conflict_fn_buffer)) { error= -2; error_str= "Conflict function specification too long."; break; } memcpy(conflict_fn_buffer, conflict_fn, len); conflict_fn_buffer[len] = '\0'; conflict_fn_spec = conflict_fn_buffer; } } } while(0); /* Error handling */ if (error == 0) { if (ndberror.code != 0) { my_snprintf(warning_msg_buffer, sizeof(warning_msg_buffer), "Unable to retrieve %s.%s, logging and " "conflict resolution may not function " "as intended (ndberror %u)", ndb_rep_db, ndb_replication_table, ndberror.code); warning_msg= warning_msg_buffer; error= -4; } } else { switch (error) { case -1: my_snprintf(warning_msg_buffer, sizeof(warning_msg_buffer), "Missing or wrong type for column '%s'", error_str); break; case -2: my_snprintf(warning_msg_buffer, sizeof(warning_msg_buffer), "%s", error_str); break; case -3: /* Message already set */ break; default: abort(); } warning_msg= warning_msg_buffer; error= 0; /* No real error, just use defaults */ } DBUG_PRINT("info", ("Rc : %d Retrieved Binlog flags : %u and function spec : %s", error, binlog_flags, (conflict_fn_spec != NULL ?conflict_fn_spec: "NULL"))); DBUG_RETURN(error); }; Uint32 Ndb_rep_tab_reader::get_binlog_flags() const { return binlog_flags; } const char* Ndb_rep_tab_reader::get_conflict_fn_spec() const { return conflict_fn_spec; } const char* Ndb_rep_tab_reader::get_warning_message() const { return warning_msg; } /* #ifdef HAVE_NDB_BINLOG */ #endif