Drizzled Public API Documentation

queue_consumer.cc
00001 /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2011 David Shrewsbury
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 #include <config.h>
00022 #include <plugin/slave/queue_consumer.h>
00023 #include <drizzled/errmsg_print.h>
00024 #include <drizzled/execute.h>
00025 #include <drizzled/message/transaction.pb.h>
00026 #include <drizzled/message/statement_transform.h>
00027 #include <drizzled/sql/result_set.h>
00028 #include <string>
00029 #include <vector>
00030 #include <boost/thread.hpp>
00031 #include <boost/lexical_cast.hpp>
00032 #include <google/protobuf/text_format.h>
00033 
00034 using namespace std;
00035 using namespace drizzled;
00036 
00037 namespace slave
00038 {
00039 
00040 bool QueueConsumer::init()
00041 {
00042   setApplierState("", true);
00043   return true;
00044 }
00045 
00046 
00047 void QueueConsumer::shutdown()
00048 {
00049   setApplierState(getErrorMessage(), false);
00050 }
00051 
00052 
00053 bool QueueConsumer::process()
00054 {
00055   for (size_t index= 0; index < _master_ids.size(); index++)
00056   {
00057     /* We go ahead and get the string version of the master ID
00058      * so we don't have to keep converting it from int to string.
00059      */
00060     const string master_id= boost::lexical_cast<string>(_master_ids[index]);
00061 
00062     if (not processSingleMaster(master_id))
00063       return false;
00064   }
00065 
00066   return true;
00067 }
00068 
00069 bool QueueConsumer::processSingleMaster(const string &master_id)
00070 {
00071   TrxIdList completedTransactionIds;
00072 
00073   getListOfCompletedTransactions(master_id, completedTransactionIds);
00074 
00075   for (size_t x= 0; x < completedTransactionIds.size(); x++)
00076   {
00077     string commit_id;
00078     string originating_server_uuid;
00079     uint64_t originating_commit_id= 0;
00080     uint64_t trx_id= completedTransactionIds[x];
00081 
00082     vector<string> aggregate_sql;  /* final SQL to execute */
00083     vector<string> segmented_sql;  /* carryover from segmented statements */
00084 
00085     message::Transaction transaction;
00086     uint32_t segment_id= 1;
00087 
00088     while (getMessage(transaction, commit_id, master_id, trx_id, originating_server_uuid,
00089                       originating_commit_id, segment_id++))
00090     {
00091       convertToSQL(transaction, aggregate_sql, segmented_sql);
00092       transaction.Clear();
00093     }
00094 
00095     /*
00096      * The last message in a transaction should always have a commit_id
00097      * value larger than 0, though other messages of the same transaction
00098      * will have commit_id = 0.
00099      */
00100     assert((not commit_id.empty()) && (commit_id != "0"));
00101     assert(segmented_sql.empty());
00102 
00103     if (not aggregate_sql.empty())
00104     {
00105       /*
00106        * Execution using drizzled::Execute requires some special escaping.
00107        */
00108       vector<string>::iterator agg_iter;
00109       for (agg_iter= aggregate_sql.begin(); agg_iter != aggregate_sql.end(); ++agg_iter)
00110       {
00111         string &sql= *agg_iter;
00112         string::iterator si= sql.begin();
00113         for (; si != sql.end(); ++si)
00114         {
00115           if (*si == '\"')
00116           {
00117             si= sql.insert(si, '\\');
00118             ++si;
00119           }
00120           else if (*si == '\\')
00121           {
00122             si= sql.insert(si, '\\');
00123             ++si;
00124             si= sql.insert(si, '\\');
00125             ++si;
00126             si= sql.insert(si, '\\');
00127             ++si;
00128           }
00129           else if (*si == ';')
00130           {
00131             si= sql.insert(si, '\\');
00132             ++si;  /* advance back to the semicolon */
00133           }
00134         }
00135       }
00136     }
00137 
00138     if (not executeSQLWithCommitId(aggregate_sql, commit_id, 
00139                                    originating_server_uuid, 
00140                                    originating_commit_id,
00141                                    master_id))
00142     {
00143       if (_ignore_errors)
00144       {
00145         clearErrorState();
00146 
00147         /* Still need to record that we handled this trx */
00148         vector<string> sql;
00149         string tmp("UPDATE `sys_replication`.`applier_state`"
00150                    " SET `last_applied_commit_id` = ");
00151         tmp.append(commit_id);
00152         tmp.append(" WHERE `master_id` = ");
00153         tmp.append(master_id);
00154         sql.push_back(tmp);
00155         executeSQL(sql);
00156       }
00157       else
00158       {
00159         return false;
00160       }
00161     }
00162 
00163     if (not deleteFromQueue(master_id, trx_id))
00164     {
00165       return false;
00166     }
00167   }
00168 
00169   return true;
00170 }
00171 
00172 
00173 bool QueueConsumer::getMessage(message::Transaction &transaction,
00174                               string &commit_id,
00175                               const string &master_id,
00176                               uint64_t trx_id,
00177                               string &originating_server_uuid,
00178                               uint64_t &originating_commit_id,
00179                               uint32_t segment_id)
00180 {
00181   string sql("SELECT `msg`, `commit_order`, `originating_server_uuid`, "
00182              "`originating_commit_id` FROM `sys_replication`.`queue`"
00183              " WHERE `trx_id` = ");
00184   sql.append(boost::lexical_cast<string>(trx_id));
00185   sql.append(" AND `seg_id` = ", 16);
00186   sql.append(boost::lexical_cast<string>(segment_id));
00187   sql.append(" AND `master_id` = ", 19),
00188   sql.append(master_id);
00189 
00190   sql::ResultSet result_set(4);
00191   Execute execute(*(_session.get()), true);
00192   
00193   execute.run(sql, result_set);
00194   
00195   assert(result_set.getMetaData().getColumnCount() == 4);
00196 
00197   /* Really should only be 1 returned row */
00198   uint32_t found_rows= 0;
00199   while (result_set.next())
00200   {
00201     string msg= result_set.getString(0);
00202     string com_id= result_set.getString(1);
00203     string orig_server_uuid= result_set.getString(2);
00204     string orig_commit_id= result_set.getString(3);
00205 
00206     if ((msg == "") || (found_rows == 1))
00207       break;
00208 
00209     /* No columns should be NULL */
00210     assert(result_set.isNull(0) == false);
00211     assert(result_set.isNull(1) == false);
00212     assert(result_set.isNull(2) == false);
00213     assert(result_set.isNull(3) == false);
00214 
00215 
00216     google::protobuf::TextFormat::ParseFromString(msg, &transaction);
00217 
00218     commit_id= com_id;
00219     originating_server_uuid= orig_server_uuid;
00220     originating_commit_id= boost::lexical_cast<uint64_t>(orig_commit_id);
00221     found_rows++;
00222   }
00223 
00224   if (found_rows == 0)
00225     return false;
00226   
00227   return true;
00228 }
00229 
00230 bool QueueConsumer::getListOfCompletedTransactions(const string &master_id,
00231                                                    TrxIdList &list)
00232 {
00233   Execute execute(*(_session.get()), true);
00234   
00235   string sql("SELECT `trx_id` FROM `sys_replication`.`queue`"
00236              " WHERE `commit_order` IS NOT NULL AND `commit_order` > 0"
00237              " AND `master_id` = "
00238              + master_id
00239              + " ORDER BY `commit_order` ASC");
00240   
00241   /* ResultSet size must match column count */
00242   sql::ResultSet result_set(1);
00243 
00244   execute.run(sql, result_set);
00245 
00246   assert(result_set.getMetaData().getColumnCount() == 1);
00247 
00248   while (result_set.next())
00249   {
00250     assert(result_set.isNull(0) == false);
00251     string value= result_set.getString(0);
00252     
00253     /* empty string returned when no more results */
00254     if (value != "")
00255     {
00256       list.push_back(boost::lexical_cast<uint64_t>(result_set.getString(0)));
00257     }
00258   }
00259   
00260   return true;
00261 }
00262 
00263 
00264 bool QueueConsumer::convertToSQL(const message::Transaction &transaction,
00265                                 vector<string> &aggregate_sql,
00266                                 vector<string> &segmented_sql)
00267 {
00268   if (transaction.has_event())
00269     return true;
00270 
00271   size_t num_statements= transaction.statement_size();
00272 
00273   /*
00274    * Loop through all Statement messages within this Transaction and
00275    * convert each to equivalent SQL statements. Complete Statements will
00276    * be appended to aggregate_sql, while segmented Statements will remain
00277    * in segmented_sql to be appended to until completed, or rolled back.
00278    */
00279 
00280   for (size_t idx= 0; idx < num_statements; idx++)
00281   {
00282     const message::Statement &statement= transaction.statement(idx);
00283     
00284     /* We won't bother with executing a rolled back transaction */
00285     if (statement.type() == message::Statement::ROLLBACK)
00286     {
00287       assert(idx == (num_statements - 1));  /* should be the final Statement */
00288       aggregate_sql.clear();
00289       segmented_sql.clear();
00290       break;
00291     }
00292 
00293     switch (statement.type())
00294     {
00295       /* DDL cannot be in a transaction, so precede with a COMMIT */
00296       case message::Statement::TRUNCATE_TABLE:
00297       case message::Statement::CREATE_SCHEMA:
00298       case message::Statement::ALTER_SCHEMA:
00299       case message::Statement::DROP_SCHEMA:
00300       case message::Statement::CREATE_TABLE:
00301       case message::Statement::ALTER_TABLE:
00302       case message::Statement::DROP_TABLE:
00303       case message::Statement::RAW_SQL:  /* currently ALTER TABLE or RENAME */
00304       {
00305         segmented_sql.push_back("COMMIT");
00306         break;
00307       }
00308 
00309       /* Cancel any ongoing statement */
00310       case message::Statement::ROLLBACK_STATEMENT:
00311       {
00312         segmented_sql.clear();
00313         continue;
00314       }
00315       
00316       default:
00317       {
00318         break;
00319       }
00320     }
00321 
00322     if (message::transformStatementToSql(statement, segmented_sql,
00323                                          message::DRIZZLE, true))
00324     {
00325       return false;
00326     }
00327 
00328     if (isEndStatement(statement))
00329     {
00330       aggregate_sql.insert(aggregate_sql.end(),
00331                            segmented_sql.begin(),
00332                            segmented_sql.end());
00333       segmented_sql.clear();
00334     }
00335   }
00336 
00337   return true;
00338 }
00339 
00340 
00341 bool QueueConsumer::isEndStatement(const message::Statement &statement)
00342 {
00343   switch (statement.type())
00344   {
00345     case (message::Statement::INSERT):
00346     {
00347       const message::InsertData &data= statement.insert_data();
00348       if (not data.end_segment())
00349         return false;
00350       break;
00351     }
00352     case (message::Statement::UPDATE):
00353     {
00354       const message::UpdateData &data= statement.update_data();
00355       if (not data.end_segment())
00356         return false;
00357       break;
00358     }
00359     case (message::Statement::DELETE):
00360     {
00361       const message::DeleteData &data= statement.delete_data();
00362       if (not data.end_segment())
00363         return false;
00364       break;
00365     }
00366     default:
00367       return true;
00368   }
00369   return true;
00370 }
00371 
00372 
00373 /*
00374  * TODO: This currently updates every row in the applier_state table.
00375  * This use to be a single row. With multi-master support, we now need
00376  * a row for every master so we can track the last applied commit ID
00377  * value for each. Eventually, we may want multiple consumer threads,
00378  * so then we'd need to update each row independently.
00379  */
00380 void QueueConsumer::setApplierState(const string &err_msg, bool status)
00381 {
00382   vector<string> statements;
00383   string sql;
00384   string msg(err_msg);
00385 
00386   if (not status)
00387   {
00388     sql= "UPDATE `sys_replication`.`applier_state` SET `status` = 'STOPPED'";
00389   }
00390   else
00391   {
00392     sql= "UPDATE `sys_replication`.`applier_state` SET `status` = 'RUNNING'";
00393   }
00394   
00395   sql.append(", `error_msg` = '", 17);
00396 
00397   /* Escape embedded quotes and statement terminators */
00398   string::iterator it;
00399   for (it= msg.begin(); it != msg.end(); ++it)
00400   {
00401     if (*it == '\'')
00402     {
00403       it= msg.insert(it, '\'');
00404       ++it;  /* advance back to the quote */
00405     }
00406     else if (*it == ';')
00407     {
00408       it= msg.insert(it, '\\');
00409       ++it;  /* advance back to the semicolon */
00410     }
00411   }
00412   
00413   sql.append(msg);
00414   sql.append("'", 1);
00415 
00416   statements.push_back(sql);
00417   executeSQL(statements);
00418 }
00419 
00420 
00421 bool QueueConsumer::executeSQLWithCommitId(vector<string> &sql,
00422                                            const string &commit_id, 
00423                                            const string &originating_server_uuid, 
00424                                            uint64_t originating_commit_id,
00425                                            const string &master_id)
00426 {
00427   string tmp("UPDATE `sys_replication`.`applier_state`"
00428              " SET `last_applied_commit_id` = ");
00429   tmp.append(commit_id);
00430   tmp.append(", `originating_server_uuid` = '");
00431   tmp.append(originating_server_uuid);
00432   tmp.append("' , `originating_commit_id` = ");
00433   tmp.append(boost::lexical_cast<string>(originating_commit_id));
00434 
00435   tmp.append(" WHERE `master_id` = ");
00436   tmp.append(master_id);
00437 
00438   sql.push_back(tmp);
00439  
00440   _session->setOriginatingServerUUID(originating_server_uuid);
00441   _session->setOriginatingCommitID(originating_commit_id);
00442  
00443   return executeSQL(sql);
00444 }
00445 
00446 
00447 bool QueueConsumer::deleteFromQueue(const string &master_id, uint64_t trx_id)
00448 {
00449   string sql("DELETE FROM `sys_replication`.`queue` WHERE `trx_id` = ");
00450   sql.append(boost::lexical_cast<std::string>(trx_id));
00451 
00452   sql.append(" AND `master_id` = ");
00453   sql.append(master_id);
00454 
00455   vector<string> sql_vect;
00456   sql_vect.push_back(sql);
00457 
00458   return executeSQL(sql_vect);
00459 }
00460 
00461 } /* namespace slave */