DatabaseCampaign: abstract campain for interaction with MySQL Database
The DatabaseCampaign interacts with the MySQL tables that are created
by the import-trace and prune-trace tools. It does offer all
unfinished experiment pilots from the database to the
fail-clients. Those clients send back a (by the experiment) defined
protobuf message as a result. The custom protobuf message does have to
need the form:
import "DatabaseCampaignMessage.proto";
message ExperimentMsg {
required DatabaseCampaignMessage fsppilot = 1;
repeated group Result = 2 {
// custom fields
required int32 bitoffset = 1;
optional int32 result = 2;
}
}
The DatabaseCampaignMessage is the pilot identifier from the
database. For each of the repeated result entries a row in a table is
allocated. The structure of this table is constructed (by protobuf
reflection) from the description of the message. Each field in the
Result group becomes a column in the result table. For the given
example it would be:
CREATE TABLE result_ExperimentMessage(
pilot_id INT,
bitoffset INT NOT NULL,
result INT,
PRIMARY_KEY(pilot_id)
)
Change-Id: I28fb5488e739d4098b823b42426c5760331027f8
This commit is contained in:
@ -5,6 +5,8 @@ set(SRCS
|
||||
ElfReader.hpp
|
||||
Database.hpp
|
||||
Database.cc
|
||||
DatabaseProtobufAdapter.hpp
|
||||
DatabaseProtobufAdapter.cc
|
||||
Demangler.hpp
|
||||
Demangler.cc
|
||||
Disassembler.hpp
|
||||
@ -57,4 +59,5 @@ endif()
|
||||
mark_as_advanced(FAIL_OBJDUMP)
|
||||
|
||||
add_library(fail-util ${SRCS})
|
||||
add_dependencies(fail-util fail-comm)
|
||||
target_link_libraries(fail-util ${PROTOBUF_LIBRARY} ${Boost_LIBRARIES} ${LIB_IBERTY} )
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
#include "Database.hpp"
|
||||
#include "util/CommandLine.hpp"
|
||||
#include "util/Logger.hpp"
|
||||
static fail::Logger log("Database", true);
|
||||
|
||||
static fail::Logger LOG("Database", true);
|
||||
|
||||
using namespace fail;
|
||||
|
||||
@ -14,14 +15,17 @@ Database::Database(const std::string &username, const std::string &host, const s
|
||||
if (!mysql_real_connect(handle, host.c_str(),
|
||||
username.c_str(),
|
||||
0, database.c_str(), 0, 0, 0)) {
|
||||
log << "cannot connect to MySQL server: " << mysql_error(handle) << std::endl;
|
||||
LOG << "cannot connect to MySQL server: " << mysql_error(handle) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
log << "opened MYSQL connection" << std::endl;
|
||||
LOG << "opened MYSQL connection" << std::endl;
|
||||
}
|
||||
|
||||
MYSQL_RES* Database::query(char const *query, bool get_result)
|
||||
{
|
||||
MYSQL_RES* Database::query(char const *query, bool get_result) {
|
||||
#ifndef __puma
|
||||
boost::lock_guard<boost::mutex> guard(m_handle_lock);
|
||||
#endif
|
||||
|
||||
if (mysql_query(handle, query)) {
|
||||
std::cerr << "query '" << query << "' failed: " << mysql_error(handle) << std::endl;
|
||||
return 0;
|
||||
@ -43,6 +47,26 @@ MYSQL_RES* Database::query(char const *query, bool get_result)
|
||||
return (MYSQL_RES *) 1; // Invalid PTR!!!
|
||||
}
|
||||
|
||||
MYSQL_RES* Database::query_stream(char const *query)
|
||||
{
|
||||
#ifndef __puma
|
||||
boost::lock_guard<boost::mutex> guard(m_handle_lock);
|
||||
#endif
|
||||
|
||||
if (mysql_query(handle, query)) {
|
||||
std::cerr << "query '" << query << "' failed: " << mysql_error(handle) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MYSQL_RES *res = mysql_use_result(handle);
|
||||
if (!res && mysql_errno(handle)) {
|
||||
std::cerr << "mysql_use_result for query '" << query << "' failed: " << mysql_error(handle) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
my_ulonglong Database::affected_rows()
|
||||
{
|
||||
@ -53,10 +77,10 @@ my_ulonglong Database::affected_rows()
|
||||
int Database::get_variant_id(const std::string &variant, const std::string &benchmark)
|
||||
{
|
||||
if (!query("CREATE TABLE IF NOT EXISTS variant ("
|
||||
" id int(11) NOT NULL AUTO_INCREMENT,"
|
||||
" variant varchar(255) NOT NULL,"
|
||||
" benchmark varchar(255) NOT NULL,"
|
||||
" PRIMARY KEY (id),"
|
||||
" id int(11) NOT NULL AUTO_INCREMENT,"
|
||||
" variant varchar(255) NOT NULL,"
|
||||
" benchmark varchar(255) NOT NULL,"
|
||||
" PRIMARY KEY (id),"
|
||||
"UNIQUE KEY variant (variant,benchmark))")) {
|
||||
return 0;
|
||||
}
|
||||
@ -85,9 +109,9 @@ int Database::get_variant_id(const std::string &variant, const std::string &benc
|
||||
int Database::get_fspmethod_id(const std::string &method)
|
||||
{
|
||||
if (!query("CREATE TABLE IF NOT EXISTS fspmethod ("
|
||||
" id int(11) NOT NULL AUTO_INCREMENT,"
|
||||
" method varchar(255) NOT NULL,"
|
||||
" PRIMARY KEY (id), UNIQUE KEY method (method))")) {
|
||||
" id int(11) NOT NULL AUTO_INCREMENT,"
|
||||
" method varchar(255) NOT NULL,"
|
||||
" PRIMARY KEY (id), UNIQUE KEY method (method))")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -120,11 +144,11 @@ void Database::cmdline_setup() {
|
||||
CommandLine &cmd = CommandLine::Inst();
|
||||
|
||||
DATABASE = cmd.addOption("d", "database", Arg::Required,
|
||||
"-d/--database\t MYSQL Database (default: taken from ~/.my.cnf)");
|
||||
"-d/--database\t MYSQL Database (default: taken from ~/.my.cnf)");
|
||||
HOSTNAME = cmd.addOption("H", "hostname", Arg::Required,
|
||||
"-h/--hostname\t MYSQL Hostname (default: taken from ~/.my.cnf)");
|
||||
"-h/--hostname\t MYSQL Hostname (default: taken from ~/.my.cnf)");
|
||||
USERNAME = cmd.addOption("u", "username", Arg::Required,
|
||||
"-u/--username\t MYSQL Username (default: taken from ~/.my.cnf, or your current user)");
|
||||
"-u/--username\t MYSQL Username (default: taken from ~/.my.cnf, or your current user)");
|
||||
}
|
||||
|
||||
Database * Database::cmdline_connect() {
|
||||
|
||||
@ -1,31 +1,86 @@
|
||||
#ifndef __UTIL_DATABASE_H__
|
||||
#define __UTIL_DATABASE_H__
|
||||
|
||||
#ifndef __puma
|
||||
#include <boost/thread.hpp>
|
||||
#endif
|
||||
|
||||
#include <mysql/mysql.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace fail {
|
||||
class Database {
|
||||
MYSQL *handle;
|
||||
MYSQL_RES *last_result;
|
||||
|
||||
public:
|
||||
Database(const std::string &username, const std::string &host, const std::string &database);
|
||||
~Database() { mysql_close(handle); }
|
||||
/** \class Database
|
||||
*
|
||||
* Database abstraction layer that handles the database connection
|
||||
* parameters, the database connection and provides different
|
||||
* database access methods
|
||||
*/
|
||||
class Database {
|
||||
MYSQL *handle; // !< The MySQL Database handle
|
||||
MYSQL_RES *last_result; // !< Used for mysql_result_free
|
||||
#ifndef __puma
|
||||
boost::mutex m_handle_lock;
|
||||
#endif
|
||||
|
||||
int get_variant_id(const std::string &variant, const std::string &benchmark);
|
||||
int get_fspmethod_id(const std::string &method);
|
||||
public:
|
||||
/**
|
||||
* Constructor that connects instantly to the database
|
||||
*/
|
||||
Database(const std::string &username, const std::string &host, const std::string &database);
|
||||
~Database() { mysql_close(handle); }
|
||||
|
||||
/**
|
||||
* Get the variant id for a specific variant/benchmark pair,
|
||||
* if it isn't defined in the database (variant table), it is
|
||||
* inserted
|
||||
*/
|
||||
int get_variant_id(const std::string &variant, const std::string &benchmark);
|
||||
|
||||
/**
|
||||
* Get the fault space pruning method id for a specific
|
||||
* pruning method, if it isn't defined in the database
|
||||
* (fspmethod table), it is inserted.
|
||||
*/
|
||||
int get_fspmethod_id(const std::string &method);
|
||||
|
||||
|
||||
MYSQL * getHandle() const { return handle; }
|
||||
MYSQL_RES *query(char const *query, bool get_result = false);
|
||||
my_ulonglong affected_rows();
|
||||
/**
|
||||
* Get the raw mysql database handle
|
||||
*/
|
||||
MYSQL * getHandle() const { return handle; }
|
||||
/**
|
||||
* Do a small database query. If get_result is set to false
|
||||
* (MYSQL_RES *)0 or (MYSQL_RES *)1 is given back to indicate
|
||||
* the result of the query. If the result should be fetched
|
||||
* a pointer is returned. This pointer is valid until the next
|
||||
* call to this->query(stmt, true)
|
||||
*/
|
||||
MYSQL_RES *query(char const *query, bool get_result = false);
|
||||
/**
|
||||
* Similar to Database::query, but this should be used for big
|
||||
* queries. The result is not copied instantly from the
|
||||
* database server, but a partial result is returned. Which
|
||||
* behaves as a normal MYSQL_RES pointer.
|
||||
*/
|
||||
MYSQL_RES *query_stream(char const *query);
|
||||
|
||||
// Interface to the command line parser
|
||||
static void cmdline_setup();
|
||||
static Database * cmdline_connect();
|
||||
};
|
||||
/**
|
||||
* How many rows were affected by the last query
|
||||
*/
|
||||
my_ulonglong affected_rows();
|
||||
|
||||
/**
|
||||
* Interface to the util/CommandLine.hpp interface. In you
|
||||
* application you first call cmdline_setup(), which adds
|
||||
* different command line options. The cmdline_connect()
|
||||
* function then parses the commandline and connects to the
|
||||
* database.
|
||||
*/
|
||||
static void cmdline_setup();
|
||||
static Database * cmdline_connect();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
465
src/core/util/DatabaseProtobufAdapter.cc
Normal file
465
src/core/util/DatabaseProtobufAdapter.cc
Normal file
@ -0,0 +1,465 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <assert.h>
|
||||
#include "DatabaseProtobufAdapter.hpp"
|
||||
#include "util/Logger.hpp"
|
||||
#include "util/StringJoiner.hpp"
|
||||
//FIXME
|
||||
#include "../experiments/dciao-kernelstructs/dciao_kernel.pb.h"
|
||||
|
||||
|
||||
static fail::Logger LOG("DatabaseProtobufAdapter", true);
|
||||
|
||||
|
||||
using namespace fail;
|
||||
using namespace google::protobuf;
|
||||
|
||||
DatabaseProtobufAdapter::TypeBridge::TypeBridge(const FieldDescriptor *desc)
|
||||
: desc(desc) {
|
||||
/* We get the value of the field option extension in_primary_key
|
||||
to determine which fields should be added additionally to the
|
||||
primary key, besides pilot_id */
|
||||
if (desc) { // top-level type bridge has no desc pointer
|
||||
const FieldOptions& field_options = desc->options();
|
||||
this->primary_key = field_options.GetExtension(sql_primary_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string DatabaseProtobufAdapter::TypeBridge_enum::sql_type() {
|
||||
/* enum types are mapped onto SQL enum types, therefore we have to
|
||||
gather all the stringified names for the enum, values */
|
||||
const google::protobuf::EnumDescriptor *e = desc->enum_type();
|
||||
std::stringstream ss;
|
||||
ss << "ENUM (";
|
||||
for (int i = 0; i < e->value_count(); i++) {
|
||||
ss << "'" << e->value(i)->name() << "'";
|
||||
if (i != e->value_count() - 1)
|
||||
ss << ", ";
|
||||
}
|
||||
ss << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string DatabaseProtobufAdapter::TypeBridge_message::sql_create_stmt() {
|
||||
/* The create statement for a message TypeBridge just propagates
|
||||
the call onto the enclosed types (including inner
|
||||
TypeBridge_message objects */
|
||||
std::stringstream ss;
|
||||
for (std::vector<TypeBridge *>::iterator it = types.begin();
|
||||
it != types.end(); ++it) {
|
||||
TypeBridge *bridge = *it;
|
||||
ss << bridge->sql_create_stmt();
|
||||
if (it+1 != types.end())
|
||||
ss << ", ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_int64::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_LONGLONG;
|
||||
bind->is_unsigned = 0;
|
||||
buffer = ref->GetInt64(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_uint64::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_LONGLONG;
|
||||
bind->is_unsigned = 1;
|
||||
buffer = ref->GetUInt64(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_int32::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_LONG;
|
||||
bind->is_unsigned = 0;
|
||||
buffer = ref->GetInt32(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_uint32::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_LONG;
|
||||
bind->is_unsigned = 1;
|
||||
buffer = ref->GetUInt32(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_float::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_FLOAT;
|
||||
buffer = ref->GetFloat(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_double::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_DOUBLE;
|
||||
buffer = ref->GetDouble(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_bool::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_TINY;
|
||||
bind->is_unsigned = 1;
|
||||
buffer = ref->GetBool(*msg, desc);
|
||||
bind->buffer = &buffer;
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_string::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_STRING;
|
||||
bind->buffer = (void *) ref->GetString(*msg, desc).c_str();
|
||||
bind->buffer_length = ref->GetString(*msg, desc).length();
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_enum::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
/* Handle the NULL case */
|
||||
if (insert_null(bind, msg)) return;
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_STRING;
|
||||
bind->buffer = (void *) ref->GetEnum(*msg, desc)->name().c_str();
|
||||
bind->buffer_length = ref->GetEnum(*msg, desc)->name().length();
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_message::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
std::stringstream ss;
|
||||
for (std::vector<TypeBridge *>::iterator it = types.begin();
|
||||
it != types.end(); ++it) {
|
||||
TypeBridge *bridge = *it;
|
||||
TypeBridge_message *msg_bridge = dynamic_cast<TypeBridge_message *> (bridge);
|
||||
if (msg_bridge != 0) {
|
||||
const Message *inner_msg = 0;
|
||||
if (msg_bridge->desc->is_repeated()) {
|
||||
std::vector<int> &selector = *top_level_msg()->selector;
|
||||
inner_msg = &ref->GetRepeatedMessage(*msg, msg_bridge->desc, selector[nesting_level+1]);
|
||||
} else {
|
||||
inner_msg = &ref->GetMessage(*msg, msg_bridge->desc);
|
||||
}
|
||||
msg_bridge->bind(bind, inner_msg);
|
||||
/* Increment bind pointer */
|
||||
bind = &bind[msg_bridge->field_count];
|
||||
} else {
|
||||
/* Bind the plain field and increment the binding pointer */
|
||||
bridge->bind(bind, msg);
|
||||
bind ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_repeated::bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
size_t count = ref->FieldSize(*msg,desc);
|
||||
if (count == 0) {
|
||||
bind->buffer_type = MYSQL_TYPE_NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer) delete[] buffer;
|
||||
buffer = new char[count * inner->element_size()];
|
||||
assert (inner->element_size() > 0);
|
||||
|
||||
char *p = buffer;
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
inner->copy_to(msg, i, p);
|
||||
p += inner->element_size();
|
||||
}
|
||||
|
||||
bind->buffer_type = MYSQL_TYPE_BLOB;
|
||||
bind->buffer = buffer;
|
||||
bind->buffer_length = count * inner->element_size();
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::TypeBridge_int32::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(int32_t *) p = ref->GetRepeatedInt32(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_uint32::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(uint32_t *) p = ref->GetRepeatedUInt32(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_int64::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(int64_t *) p = ref->GetRepeatedInt64(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_uint64::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(uint64_t *) p = ref->GetRepeatedUInt64(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_double::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(double *) p = ref->GetRepeatedDouble(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_float::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(float *) p = ref->GetRepeatedFloat(*msg, desc, i);
|
||||
}
|
||||
void DatabaseProtobufAdapter::TypeBridge_bool::copy_to(const google::protobuf::Message *msg, int i, void *p) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
*(char *) p = ref->GetRepeatedBool(*msg, desc, i) ? '1' : '0';
|
||||
}
|
||||
|
||||
void DatabaseProtobufAdapter::error_create_table() {
|
||||
std::cerr << "ERROR: Cannot create the result table from message description" << std::endl;
|
||||
std::cerr << " The form only from of messages is: [required DatabaseCampaignMessage, repeated group X {}]" << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int DatabaseProtobufAdapter::TypeBridge_message::gatherTypes(StringJoiner &insert_stmt, StringJoiner &primary_key) {
|
||||
/* Clear old state */
|
||||
repeated_message_stack.clear();
|
||||
repeated_message_stack.push_back(this);
|
||||
for (std::vector<TypeBridge *>::iterator it = types.begin(); it != types.end(); ++it)
|
||||
delete *it;
|
||||
types.clear();
|
||||
|
||||
size_t count = msg_type->field_count();
|
||||
field_count = 0;
|
||||
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
const FieldDescriptor *field = msg_type->field(i);
|
||||
assert(field != 0);
|
||||
|
||||
TypeBridge *bridge = 0;
|
||||
TypeBridge_message *msg_bridge;
|
||||
bool can_be_repeated = true; // default value
|
||||
|
||||
// For repeated messages
|
||||
TypeBridge_message *top_level_msg;
|
||||
|
||||
const FieldOptions& field_options = field->options();
|
||||
if (field_options.GetExtension(sql_ignore)) {
|
||||
// Field should be ignored
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (field->cpp_type()) {
|
||||
case FieldDescriptor::CPPTYPE_INT32:
|
||||
bridge = new TypeBridge_int32(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_UINT32:
|
||||
bridge = new TypeBridge_uint32(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_INT64:
|
||||
bridge = new TypeBridge_int64(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_UINT64:
|
||||
bridge = new TypeBridge_uint64(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_DOUBLE:
|
||||
bridge = new TypeBridge_double(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_FLOAT:
|
||||
bridge = new TypeBridge_float(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_BOOL:
|
||||
bridge = new TypeBridge_bool(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_ENUM:
|
||||
can_be_repeated = false;
|
||||
bridge = new TypeBridge_enum(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_STRING:
|
||||
can_be_repeated = false;
|
||||
bridge = new TypeBridge_string(field);
|
||||
break;
|
||||
case FieldDescriptor::CPPTYPE_MESSAGE:
|
||||
if (field->is_repeated()) {
|
||||
top_level_msg = this->top_level_msg();
|
||||
/* Here we check wether we are on the repeated path
|
||||
from the root, when we are a repeated entry is ok.
|
||||
*/
|
||||
if (!(this == top_level_msg->repeated_message_stack.back())) {
|
||||
LOG << "Cannot handle repeated inner message in two different paths: " << field->name() << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (field->is_optional()) {
|
||||
LOG << "Cannot handle optional inner message: " << field->name() << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
msg_bridge = new TypeBridge_message(field, field->message_type(), this);
|
||||
bridge = msg_bridge;
|
||||
types.push_back(bridge);
|
||||
if (field->is_repeated()) {
|
||||
while (1) {
|
||||
TypeBridge_message *back = top_level_msg->repeated_message_stack.back();
|
||||
if (back == msg_bridge)
|
||||
break;
|
||||
TypeBridge_message *p = msg_bridge;
|
||||
while (back != 0 && p->parent != back && p->parent != 0) {
|
||||
p = p->parent;
|
||||
}
|
||||
top_level_msg->repeated_message_stack.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
field_count += msg_bridge->gatherTypes(insert_stmt, primary_key);
|
||||
|
||||
/* Do not the normal field adding process */
|
||||
continue;
|
||||
|
||||
default:
|
||||
std::cerr << "unsupported field: " << field->name() << std::endl;
|
||||
exit(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (field->is_repeated() && ! can_be_repeated) {
|
||||
LOG << "Cannot handle repeated field: " << field->name() << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
if (field->is_repeated()) {
|
||||
bridge = new TypeBridge_repeated(field, bridge);
|
||||
}
|
||||
|
||||
types.push_back(bridge);
|
||||
field_count ++;
|
||||
insert_stmt.push_back(field->name());
|
||||
if (bridge->primary_key)
|
||||
primary_key.push_back(field->name());
|
||||
}
|
||||
|
||||
return field_count;
|
||||
}
|
||||
|
||||
|
||||
void DatabaseProtobufAdapter::create_table(const Descriptor *toplevel_desc) {
|
||||
assert (toplevel_desc != 0);
|
||||
|
||||
std::stringstream create_table_stmt, insert_stmt;
|
||||
StringJoiner insert_join, primary_join, question_marks;
|
||||
|
||||
result_table_name = "result_" + toplevel_desc->name();
|
||||
|
||||
/* Fill our top level dummy type bridge with the fields from the
|
||||
example message */
|
||||
top_level_msg.msg_type = toplevel_desc;
|
||||
int fields = top_level_msg.gatherTypes(insert_join, primary_join);
|
||||
for (int i = 0; i < fields; i++)
|
||||
question_marks.push_back("?");
|
||||
|
||||
create_table_stmt << "CREATE TABLE IF NOT EXISTS " << result_table_name << "(";
|
||||
create_table_stmt << top_level_msg.sql_create_stmt() << ", PRIMARY KEY(" << primary_join.join(", ") << "))";
|
||||
|
||||
insert_stmt << "INSERT INTO " << result_table_name << "(" << insert_join.join(",");
|
||||
insert_stmt << ") VALUES (" << question_marks.join(",") << ")";
|
||||
|
||||
// Create the Table
|
||||
db->query(create_table_stmt.str().c_str());
|
||||
|
||||
|
||||
// Prepare the insert statement
|
||||
stmt = mysql_stmt_init(db->getHandle());
|
||||
if (mysql_stmt_prepare(stmt, insert_stmt.str().c_str(), insert_stmt.str().length())) {
|
||||
LOG << "query '" << insert_stmt.str() << "' failed: " << mysql_error(db->getHandle()) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
int DatabaseProtobufAdapter::field_size_at_pos(const Message *msg, std::vector<int> selector, int pos) {
|
||||
std::vector< TypeBridge_message *>::iterator it;
|
||||
if (top_level_msg.repeated_message_stack.size() <= 1)
|
||||
return 1;
|
||||
|
||||
int i = 1;
|
||||
for (it = top_level_msg.repeated_message_stack.begin() + 1;
|
||||
i != pos && it != top_level_msg.repeated_message_stack.end(); ++it) {
|
||||
|
||||
TypeBridge_message *bridge = *it;
|
||||
const Reflection *ref = msg->GetReflection();
|
||||
if (bridge->desc && bridge->desc->is_repeated()) {
|
||||
msg = &ref->GetRepeatedMessage(*msg, bridge->desc, selector[i]);
|
||||
} else {
|
||||
assert(selector[i] == 0);
|
||||
msg = &ref->GetMessage(*msg, bridge->desc);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
const Reflection *ref = msg->GetReflection();
|
||||
return ref->FieldSize(*msg, top_level_msg.repeated_message_stack[i]->desc);
|
||||
}
|
||||
|
||||
bool DatabaseProtobufAdapter::insert_row(const google::protobuf::Message *msg) {
|
||||
const Reflection *ref = msg->GetReflection();
|
||||
const Descriptor *d = msg->GetDescriptor();
|
||||
assert (d != 0 && ref != 0);
|
||||
|
||||
MYSQL_BIND *bind = new MYSQL_BIND[top_level_msg.field_count];
|
||||
|
||||
/* We determine how many columns should be produced */
|
||||
std::vector<int> selector (top_level_msg.repeated_message_stack.size());
|
||||
|
||||
while (true) {
|
||||
// INSERT WITH SELECTOR
|
||||
top_level_msg.selector = &selector;
|
||||
|
||||
// Use the top_level_msg TypeBridge to bind all parameters
|
||||
// into the MYSQL_BIND structure
|
||||
memset(bind, 0, sizeof(*bind) * (top_level_msg.field_count));
|
||||
top_level_msg.bind(bind, msg);
|
||||
|
||||
// Insert the binded row
|
||||
if (mysql_stmt_bind_param(stmt, bind)) {
|
||||
LOG << "mysql_stmt_bind_param() failed: " << mysql_stmt_error(stmt) << std::endl;
|
||||
delete[] bind;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mysql_stmt_execute(stmt)) {
|
||||
LOG << "mysql_stmt_execute() failed: " << mysql_stmt_error(stmt) << std::endl;
|
||||
delete[] bind;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* Increment the selector */
|
||||
unsigned i = selector.size() - 1;
|
||||
selector[i] ++;
|
||||
|
||||
while (i > 0 && field_size_at_pos(msg, selector, i) <= selector[i]) {
|
||||
selector[i] = 0;
|
||||
i--;
|
||||
selector[i] ++;
|
||||
}
|
||||
if (i == 0) break;
|
||||
}
|
||||
|
||||
delete[] bind;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
206
src/core/util/DatabaseProtobufAdapter.hpp
Normal file
206
src/core/util/DatabaseProtobufAdapter.hpp
Normal file
@ -0,0 +1,206 @@
|
||||
#ifndef __COMM_PROTOBUF_DATABASE_ADAPTER_H__
|
||||
#define __COMM_PROTOBUF_DATABASE_ADAPTER_H__
|
||||
|
||||
#include <vector>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
#include <google/protobuf/message.h>
|
||||
#include "DatabaseCampaignMessage.pb.h"
|
||||
#include "util/Database.hpp"
|
||||
#include "util/StringJoiner.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace fail {
|
||||
|
||||
class DatabaseProtobufAdapter {
|
||||
Database *db;
|
||||
MYSQL_STMT *stmt;
|
||||
|
||||
void error_create_table();
|
||||
|
||||
/** \class TypeBridge
|
||||
A type bridge bridges the gap between a protobuf type and the
|
||||
sql database. It defines how the result table is defined, and
|
||||
how the message types are mapped onto SQL types. The whole
|
||||
message is mapped into a top level TypeBridge_message */
|
||||
struct TypeBridge {
|
||||
bool primary_key; // !< Should the field be put into the
|
||||
// !< primary key
|
||||
const google::protobuf::FieldDescriptor *desc;
|
||||
|
||||
virtual ~TypeBridge() {};
|
||||
|
||||
TypeBridge(const google::protobuf::FieldDescriptor *desc);
|
||||
/* The field name in the SQL Table */
|
||||
virtual std::string name() { return desc->name(); }
|
||||
/* The expression in the create stmt for this type. */
|
||||
virtual std::string sql_create_stmt()
|
||||
{ return name() + " " + sql_type() + (desc->is_required() ? " NOT NULL": ""); };
|
||||
|
||||
/* The mapped type */
|
||||
virtual std::string sql_type() = 0;
|
||||
virtual int element_size() { return 0; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *) { };
|
||||
|
||||
/* A common function that handles NULL values for fields */
|
||||
bool insert_null(MYSQL_BIND *bind, const google::protobuf::Message *msg) {
|
||||
const google::protobuf::Reflection *ref = msg->GetReflection();
|
||||
if (!ref->HasField(*msg, desc)) {
|
||||
bind->buffer_type = MYSQL_TYPE_NULL;
|
||||
return true; // handled
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg) = 0;
|
||||
};
|
||||
|
||||
struct TypeBridge_repeated : TypeBridge {
|
||||
TypeBridge *inner;
|
||||
char *buffer;
|
||||
TypeBridge_repeated(const google::protobuf::FieldDescriptor *desc, TypeBridge *inner)
|
||||
: TypeBridge(desc), inner(inner), buffer(0) {};
|
||||
virtual std::string sql_type() { return "blob"; };
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_int32 : TypeBridge {
|
||||
int32_t buffer;
|
||||
TypeBridge_int32(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
virtual std::string sql_type() { return "INT"; };
|
||||
virtual int element_size() { return 4; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_uint32 : TypeBridge {
|
||||
uint32_t buffer;
|
||||
TypeBridge_uint32(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
|
||||
virtual std::string sql_type() { return "INT"; };
|
||||
virtual int element_size() { return 4; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_int64 : TypeBridge {
|
||||
int64_t buffer;
|
||||
TypeBridge_int64(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
virtual std::string sql_type() { return "BIGINT"; };
|
||||
virtual int element_size() { return 8; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_uint64 : TypeBridge {
|
||||
uint64_t buffer;
|
||||
TypeBridge_uint64(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
|
||||
virtual std::string sql_type() { return "BIGINT"; };
|
||||
virtual int element_size() { return 8; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
struct TypeBridge_double : TypeBridge {
|
||||
double buffer;
|
||||
TypeBridge_double(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
|
||||
virtual std::string sql_type() { return "DOUBLE"; };
|
||||
virtual int element_size() { return 8; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
struct TypeBridge_float : TypeBridge {
|
||||
float buffer;
|
||||
TypeBridge_float(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
|
||||
virtual std::string sql_type() { return "FLOAT"; };
|
||||
virtual int element_size() { return 4; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
struct TypeBridge_bool : TypeBridge {
|
||||
bool buffer;
|
||||
TypeBridge_bool(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
|
||||
virtual std::string sql_type() { return "TINYINT"; };
|
||||
virtual int element_size() { return 1; };
|
||||
virtual void copy_to(const google::protobuf::Message *msg, int i, void *);
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_string : TypeBridge {
|
||||
TypeBridge_string(const google::protobuf::FieldDescriptor *desc)
|
||||
: TypeBridge(desc){};
|
||||
virtual std::string sql_type() { return "TEXT"; };
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_enum : TypeBridge {
|
||||
int32_t size;
|
||||
TypeBridge_enum(const google::protobuf::FieldDescriptor *desc) : TypeBridge(desc) {};
|
||||
virtual std::string sql_type();
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
};
|
||||
|
||||
struct TypeBridge_message : TypeBridge {
|
||||
const google::protobuf::Descriptor *msg_type;
|
||||
int nesting_level;
|
||||
int field_count;
|
||||
std::vector<TypeBridge *> types;
|
||||
std::vector<int> *selector;
|
||||
|
||||
/* Pointer to the toplevel message */
|
||||
TypeBridge_message *parent;
|
||||
|
||||
TypeBridge_message * top_level_msg() {
|
||||
TypeBridge_message *p = this;
|
||||
while (p->parent != 0) p = p->parent;
|
||||
return p;
|
||||
}
|
||||
|
||||
std::vector<TypeBridge_message *> repeated_message_stack;
|
||||
|
||||
TypeBridge_message(const google::protobuf::FieldDescriptor *desc,
|
||||
const google::protobuf::Descriptor *msg_type,
|
||||
TypeBridge_message *parent)
|
||||
: TypeBridge(desc), msg_type(msg_type), parent(parent) {
|
||||
if (parent)
|
||||
nesting_level = parent->nesting_level+1;
|
||||
else
|
||||
nesting_level = 0;
|
||||
};
|
||||
virtual std::string sql_create_stmt();
|
||||
virtual std::string sql_type() { return ""; };
|
||||
virtual void bind(MYSQL_BIND *bind, const google::protobuf::Message *msg);
|
||||
/* Returns the number of enclosed fields */
|
||||
int gatherTypes(StringJoiner &insert_stmt, StringJoiner &primary_key);
|
||||
};
|
||||
|
||||
TypeBridge_message top_level_msg;
|
||||
|
||||
std::string result_table_name;
|
||||
|
||||
int field_size_at_pos(const google::protobuf::Message *msg, std::vector<int> selector, int pos);
|
||||
|
||||
|
||||
public:
|
||||
DatabaseProtobufAdapter() : db(0), top_level_msg(0, 0, 0) {}
|
||||
void set_database_handle(Database *db) { this->db = db; }
|
||||
void create_table(const google::protobuf::Descriptor *);
|
||||
bool insert_row(const google::protobuf::Message *msg);
|
||||
std::string result_table() { return result_table_name; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
69
src/core/util/StringJoiner.hpp
Normal file
69
src/core/util/StringJoiner.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef __UTIL_STRINGJOINER_H
|
||||
#define __UTIL_STRINGJOINER_H
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace fail {
|
||||
/**
|
||||
* \brief Helper subclass of std::deque<std::string> for convenient
|
||||
* concatenating strings with a separator.
|
||||
*
|
||||
* The behaviour is similar to the python list().join(",") construct.
|
||||
*
|
||||
*/
|
||||
struct StringJoiner : public std::deque<std::string> {
|
||||
/**
|
||||
* \brief join all strings in the container,
|
||||
*
|
||||
* Join all the collected strings to one string. The separator is
|
||||
* inserted between each element.
|
||||
*/
|
||||
std::string join(const char *j) {
|
||||
std::stringstream ss;
|
||||
if (size() == 0)
|
||||
return "";
|
||||
|
||||
ss << front();
|
||||
|
||||
std::deque<std::string>::const_iterator i = begin() + 1;
|
||||
|
||||
while (i != end()) {
|
||||
ss << j << *i;
|
||||
i++;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief append strings to list.
|
||||
*
|
||||
* Appends the given value to the list of values if it isn't the
|
||||
* empty string. "" will be ignored.
|
||||
*/
|
||||
void push_back(const value_type &x) {
|
||||
if (x.compare("") == 0)
|
||||
return;
|
||||
std::deque<value_type>::push_back(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief append strings to list.
|
||||
*
|
||||
* Appends the given value to the list of values if it isn't the
|
||||
* empty string. "" will be ignored.
|
||||
*/
|
||||
void push_front(const value_type &x) {
|
||||
if (x.compare("") == 0)
|
||||
return;
|
||||
std::deque<value_type>::push_front(x);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user