// returns true if class has db fields bool create_fields_table(DCClass* dcc) { stringstream ss; ss << "CREATE TABLE IF NOT EXISTS fields_" << dcc->get_name() << "(object_id INT NOT NULL PRIMARY KEY"; int db_field_count = 0; for(int i = 0; i < dcc->get_num_inherited_fields(); ++i) { DCField* field = dcc->get_inherited_field(i); if(field->is_db() && !field->as_molecular_field()) { db_field_count += 1; // TODO: Store SimpleParameters and fields with 1 SimpleParameter // as a simpler type. // NOTE: This might be a lot easier if the DCParser was modified // such that atomic fields containing only 1 DCSimpleParameter // element are initialized as a DCSimpleField subclass of DCAtomicField. // TODO: Also see if you can't find a convenient way to get the max length of // for example a string field, and use a VARCHAR(len) instead of TEXT. // Same for blobs with VARBINARY. ss << "," << field->get_name() << " TEXT"; } } if(db_field_count > 0) { ss << ");"; m_sql << ss.str(); return true; } return false; }
void del_fields_in_table(uint32_t id, DCClass* dcc, const vector<DCField*> &fields) { string name; for(auto it = fields.begin(); it != fields.end(); ++it) { DCField* field = *it; if(field->is_db()) { m_sql << "UPDATE fields_" << dcc->get_name() << " SET " << field->get_name() << "=NULL WHERE object_id=" << id << ";"; } } }
void get_all_from_table(uint32_t id, DCClass* dcc, map<DCField*, vector<uint8_t> > &fields) { string value; indicator ind; for(int i = 0; i < dcc->get_num_inherited_fields(); ++i) { DCField* field = dcc->get_inherited_field(i); if(field->is_db()) { m_sql << "SELECT " << field->get_name() << " FROM fields_" << dcc->get_name() << " WHERE object_id=" << id << ";", into(value, ind); if(ind == i_ok) { string packed_data = field->parse_string(value); fields[field] = vector<uint8_t>(packed_data.begin(), packed_data.end()); } } } }
void get_fields_from_table(uint32_t id, DCClass* dcc, const vector<DCField*> &fields, map<DCField*, vector<uint8_t> > &values) { string value; indicator ind; for(auto it = fields.begin(); it != fields.end(); ++it) { DCField* field = *it; if(field->is_db()) { m_sql << "SELECT " << field->get_name() << " FROM fields_" << dcc->get_name() << " WHERE object_id=" << id << ";", into(value, ind); if(ind == i_ok) { string packed_data = field->parse_string(value); values[field] = vector<uint8_t>(packed_data.begin(), packed_data.end()); } } } }
virtual void handle_datagram(Datagram &in_dg, DatagramIterator &dgi) { channel_t sender = dgi.read_uint64(); unsigned short msg_type = dgi.read_uint16(); switch(msg_type) { case DBSERVER_CREATE_STORED_OBJECT: { unsigned int context = dgi.read_uint32(); // Start response with generic header Datagram resp; resp.add_server_header(sender, m_control_channel, DBSERVER_CREATE_STORED_OBJECT_RESP); resp.add_uint32(context); // Get DistributedClass unsigned short dc_id = dgi.read_uint16(); DCClass *dcc = gDCF->get_class(dc_id); if(!dcc) { m_log->error() << "Invalid DCClass when creating object: #" << dc_id << std::endl; resp.add_uint32(0); send(resp); return; } // Unpack fields to be passed to database DatabaseObject dbo(dc_id); unsigned short field_count = dgi.read_uint16(); m_log->spam() << "Unpacking fields..." << std::endl; try { for(unsigned int i = 0; i < field_count; ++i) { unsigned short field_id = dgi.read_uint16(); DCField *field = dcc->get_field_by_index(field_id); if(field) { if(field->is_db()) { dgi.unpack_field(field, dbo.fields[field]); } else { std::string tmp; dgi.unpack_field(field, tmp); } } } } catch(std::exception &e) { m_log->error() << "Error while unpacking fields, msg may be truncated. e.what(): " << e.what() << std::endl; resp.add_uint32(0); send(resp); return; } // Check for required fields, and populate with defaults m_log->spam() << "Checking all required fields exist..." << std::endl; for(int i = 0; i < dcc->get_num_inherited_fields(); ++i) { DCField *field = dcc->get_inherited_field(i); if(field->is_required() && field->is_db() && !field->as_molecular_field()) { if(dbo.fields.find(field) == dbo.fields.end()) { if(!field->has_default_value()) { m_log->error() << "Field " << field->get_name() << " missing when trying to create " "object of type " << dcc->get_name(); resp.add_uint32(0); send(resp); return; } else { dbo.fields[field] = field->get_default_value(); } } } } // Create object in database m_log->spam() << "Creating stored object..." << std::endl; unsigned int do_id = m_db_engine->create_object(dbo); if(do_id == 0 || do_id < m_min_id || do_id > m_max_id) { m_log->error() << "Ran out of DistributedObject ids while creating new object." << std::endl; resp.add_uint32(0); send(resp); return; } m_log->spam() << "... created with ID: " << do_id << std::endl; resp.add_uint32(do_id); send(resp); } break; case DBSERVER_SELECT_STORED_OBJECT_ALL: { unsigned int context = dgi.read_uint32(); Datagram resp; resp.add_server_header(sender, m_control_channel, DBSERVER_SELECT_STORED_OBJECT_ALL_RESP); resp.add_uint32(context); unsigned int do_id = dgi.read_uint32(); DatabaseObject dbo; if(m_db_engine->get_object(do_id, dbo)) { resp.add_uint8(1); resp.add_uint16(dbo.dc_id); resp.add_uint16(dbo.fields.size()); for(auto it = dbo.fields.begin(); it != dbo.fields.end(); ++it) { resp.add_uint16(it->first->get_number()); resp.add_data(it->second); } } else { resp.add_uint8(0); } send(resp); } break; case DBSERVER_DELETE_STORED_OBJECT: { if(dgi.read_uint32() == DBSERVER_DELETE_STORED_OBJECT_VERIFY_CODE) { unsigned int do_id = dgi.read_uint32(); m_db_engine->delete_object(do_id); m_log->debug() << "Deleted object with ID: " << do_id << std::endl; } else { m_log->warning() << "Wrong delete verify code." << std::endl; } } break; default: m_log->error() << "Recieved unknown MsgType: " << msg_type << std::endl; }; }
void DBStateServer::handle_datagram(Datagram &in_dg, DatagramIterator &dgi) { channel_t sender = dgi.read_uint64(); uint16_t msgtype = dgi.read_uint16(); switch(msgtype) { case DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS: { handle_activate(dgi, false); break; } case DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER: { handle_activate(dgi, true); break; } case DBSS_OBJECT_DELETE_DISK: { uint32_t do_id = dgi.read_uint32(); // If object exists broadcast the delete message auto obj_keyval = m_objs.find(do_id); if(obj_keyval != m_objs.end()) { DistributedObject* obj = obj_keyval->second; std::set<channel_t> targets; // Add location to broadcast if(obj->get_location()) { targets.insert(obj->get_location()); } // Add AI to broadcast if(obj->get_ai()) { targets.insert(obj->get_ai()); } // Add owner to broadcast if(obj->get_owner()) { targets.insert(obj->get_owner()); } // Build and send datagram Datagram dg(targets, sender, DBSS_OBJECT_DELETE_DISK); dg.add_uint32(do_id); send(dg); } // Send delete to database Datagram dg(m_db_channel, do_id, DBSERVER_OBJECT_DELETE); dg.add_uint32(do_id); send(dg); break; } case STATESERVER_OBJECT_SET_FIELD: { uint32_t do_id = dgi.read_uint32(); uint32_t field_id = dgi.read_uint16(); DCField* field = g_dcf->get_field_by_index(field_id); if(field && field->is_db()) { m_log->trace() << "Forwarding SetField for field with id " << field_id << ", on object " << do_id << " to database." << std::endl; Datagram dg(m_db_channel, do_id, DBSERVER_OBJECT_SET_FIELD); dg.add_uint32(do_id); dg.add_uint16(field_id); dg.add_data(dgi.read_remainder()); send(dg); } break; } case STATESERVER_OBJECT_SET_FIELDS: { uint32_t do_id = dgi.read_uint32(); uint16_t field_count = dgi.read_uint16(); std::unordered_map<DCField*, std::vector<uint8_t> > db_fields; for(uint16_t i = 0; i < field_count; ++i) { uint16_t field_id = dgi.read_uint16(); DCField* field = g_dcf->get_field_by_index(field_id); if(!field) { m_log->warning() << "Received invalid field in SetFields" " with id " << field_id << std::endl; break; } if(field->is_db()) { dgi.unpack_field(field, db_fields[field]); } } if(db_fields.size() > 0) { m_log->trace() << "Forwarding SetFields on object " << do_id << " to database." << std::endl; Datagram dg(m_db_channel, do_id, DBSERVER_OBJECT_SET_FIELDS); dg.add_uint32(do_id); dg.add_uint16(field_count); for(auto it = db_fields.begin(); it != db_fields.end(); ++it) { dg.add_uint16(it->first->get_number()); dg.add_data(it->second); } send(dg); } break; } case STATESERVER_OBJECT_GET_FIELD: { uint32_t r_context = dgi.read_uint32(); uint32_t r_do_id = dgi.read_uint32(); uint16_t field_id = dgi.read_uint16(); // If object is active or loading, the Object or Loader will handle it if(m_objs.find(r_do_id) != m_objs.end() || m_loading.find(r_do_id) != m_loading.end()) { break; } m_log->trace() << "Received GetField for field with id " << field_id << " on inactive object with id " << r_do_id << std::endl; // Check field is "ram db" or "required" DCField* field = g_dcf->get_field_by_index(field_id); if(!field || !(field->is_required() || (field->is_ram() && field->is_db()))) { Datagram dg(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP); dg.add_uint32(r_context); dg.add_uint8(false); send(dg); } if(field->is_db()) { // Get context for db query uint32_t db_context = m_next_context++; // Prepare reponse datagram if(m_context_datagrams.find(db_context) == m_context_datagrams.end()) { m_context_datagrams[db_context].add_server_header(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP); } m_context_datagrams[db_context].add_uint32(r_context); // Send query to database Datagram dg(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_FIELD); dg.add_uint32(db_context); dg.add_uint32(r_do_id); dg.add_uint16(field_id); send(dg); } else // Field is required and not-db { Datagram dg = Datagram(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP); dg.add_uint32(r_context); dg.add_uint8(true); dg.add_uint16(field_id); dg.add_data(field->get_default_value()); send(dg); } break; } case DBSERVER_OBJECT_GET_FIELD_RESP: { uint32_t db_context = dgi.read_uint32(); // Check context auto dg_keyval = m_context_datagrams.find(db_context); if(dg_keyval == m_context_datagrams.end()) { break; // Not meant for me, handled by LoadingObject } m_log->trace() << "Received GetFieldResp from database." << std::endl; // Get the datagram from the db_context Datagram dg(dg_keyval->second); // Cleanup the context m_context_datagrams.erase(db_context); // Check to make sure the datagram is appropriate DatagramIterator check_dgi = DatagramIterator(dg); uint16_t resp_type = check_dgi.get_msg_type(); if(resp_type != STATESERVER_OBJECT_GET_FIELD_RESP) { if(resp_type == STATESERVER_OBJECT_GET_FIELDS_RESP) { m_log->warning() << "Received GetFieldsResp, but expecting GetFieldResp." << std::endl; } else if(resp_type == STATESERVER_OBJECT_GET_ALL_RESP) { m_log->warning() << "Received GetAllResp, but expecting GetFieldResp." << std::endl; } break; } // Add database field payload to response (don't know dclass, so must copy payload) and send dg.add_data(dgi.read_remainder()); send(dg); break; } case STATESERVER_OBJECT_GET_FIELDS: { uint32_t r_context = dgi.read_uint32(); uint32_t r_do_id = dgi.read_uint32(); uint16_t field_count = dgi.read_uint16(); // If object is active or loading, the Object or Loader will handle it if(m_objs.find(r_do_id) != m_objs.end() || m_loading.find(r_do_id) != m_loading.end()) { break; } m_log->trace() << "Received GetFields for inactive object with id " << r_do_id << std::endl; // Read requested fields from datagram std::list<DCField*> db_fields; // Ram|required db fields in request std::list<DCField*> ram_fields; // Ram|required but not-db fields in request for(uint16_t i = 0; i < field_count; ++i) { uint16_t field_id = dgi.read_uint16(); DCField* field = g_dcf->get_field_by_index(field_id); if(!field) { Datagram dg(sender, r_do_id, STATESERVER_OBJECT_GET_FIELDS_RESP); dg.add_uint32(r_context); dg.add_uint8(false); send(dg); } else if(field->is_ram() || field->is_required()) { if(field->is_db()) { db_fields.push_back(field); } else { ram_fields.push_back(field); } } } if(db_fields.size()) { // Get context for db query uint32_t db_context = m_next_context++; // Prepare reponse datagram if(m_context_datagrams.find(db_context) == m_context_datagrams.end()) { m_context_datagrams[db_context].add_server_header(sender, r_do_id, STATESERVER_OBJECT_GET_FIELDS_RESP); } m_context_datagrams[db_context].add_uint32(r_context); m_context_datagrams[db_context].add_uint8(true); m_context_datagrams[db_context].add_uint16(ram_fields.size() + db_fields.size()); for(auto it = ram_fields.begin(); it != ram_fields.end(); ++it) { m_context_datagrams[db_context].add_uint16((*it)->get_number()); m_context_datagrams[db_context].add_data((*it)->get_default_value()); } // Send query to database Datagram dg(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_FIELDS); dg.add_uint32(db_context); dg.add_uint32(r_do_id); dg.add_uint16(db_fields.size()); for(auto it = db_fields.begin(); it != db_fields.end(); ++it) { dg.add_uint16((*it)->get_number()); } send(dg); } else // If no database fields exist { Datagram dg = Datagram(sender, r_do_id, STATESERVER_OBJECT_GET_FIELDS_RESP); dg.add_uint32(r_context); dg.add_uint8(true); dg.add_uint16(ram_fields.size()); for(auto it = ram_fields.begin(); it != ram_fields.end(); ++it) { dg.add_uint16((*it)->get_number()); dg.add_data((*it)->get_default_value()); } send(dg); } break; } case DBSERVER_OBJECT_GET_FIELDS_RESP: { uint32_t db_context = dgi.read_uint32(); // Check context auto dg_keyval = m_context_datagrams.find(db_context); if(dg_keyval == m_context_datagrams.end()) { break; // Not meant for me, handled by LoadingObject } // Get the datagram from the db_context Datagram dg(dg_keyval->second); // Cleanup the context m_context_datagrams.erase(db_context); // Check to make sure the datagram is appropriate DatagramIterator check_dgi = DatagramIterator(dg); uint16_t resp_type = check_dgi.get_msg_type(); if(resp_type != STATESERVER_OBJECT_GET_FIELDS_RESP) { if(resp_type == STATESERVER_OBJECT_GET_FIELD_RESP) { m_log->warning() << "Received GetFieldResp, but expecting GetFieldsResp." << std::endl; } else if(resp_type == STATESERVER_OBJECT_GET_ALL_RESP) { m_log->warning() << "Received GetAllResp, but expecting GetFieldsResp." << std::endl; } break; } m_log->trace() << "Received GetFieldResp from database." << std::endl; // Add database field payload to response (don't know dclass, so must copy payload). if(dgi.read_uint8() == true) { dgi.read_uint16(); // Discard field count dg.add_data(dgi.read_remainder()); } send(dg); break; } case STATESERVER_OBJECT_GET_ALL: { uint32_t r_context = dgi.read_uint32(); uint32_t r_do_id = dgi.read_uint32(); // If object is active or loading, the Object or Loader will handle it if(m_objs.find(r_do_id) != m_objs.end() || m_loading.find(r_do_id) != m_loading.end()) { break; } m_log->trace() << "Received GetAll for inactive object with id " << r_do_id << std::endl; // Get context for db query, and remember caller with it uint32_t db_context = m_next_context++; if(m_context_datagrams.find(db_context) == m_context_datagrams.end()) { m_context_datagrams[db_context].add_server_header(sender, r_do_id, STATESERVER_OBJECT_GET_ALL_RESP); } m_context_datagrams[db_context].add_uint32(r_context); m_context_datagrams[db_context].add_uint32(r_do_id); m_context_datagrams[db_context].add_uint64(INVALID_CHANNEL); // Location // Cache the do_id --> context in case we get a dbss_activate m_inactive_loads[r_do_id].insert(r_context); // Send query to database Datagram dg(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_ALL); dg.add_uint32(db_context); dg.add_uint32(r_do_id); send(dg); break; } case DBSERVER_OBJECT_GET_ALL_RESP: { uint32_t db_context = dgi.read_uint32(); // Check context auto dg_keyval = m_context_datagrams.find(db_context); if(dg_keyval == m_context_datagrams.end()) { break; // Not meant for me, handled by LoadingObject } // Get the datagram from the db_context Datagram dg(dg_keyval->second); // Cleanup the context m_context_datagrams.erase(db_context); // Check to make sure the datagram is appropriate DatagramIterator check_dgi = DatagramIterator(dg); uint16_t resp_type = check_dgi.get_msg_type(); if(resp_type != STATESERVER_OBJECT_GET_ALL_RESP) { if(resp_type == STATESERVER_OBJECT_GET_FIELD_RESP) { m_log->warning() << "Received GetFieldResp, but expecting GetAllResp." << std::endl; } else if(resp_type == STATESERVER_OBJECT_GET_FIELDS_RESP) { m_log->warning() << "Received GetFieldsResp, but expecting GetAllResp." << std::endl; } break; } // Get do_id from datagram check_dgi.seek_payload(); check_dgi.skip(8 + 4); // skip over sender and context to do_id; uint32_t do_id = check_dgi.read_uint32(); // Remove cached loading operation if(m_inactive_loads[do_id].size() > 1) { m_inactive_loads[do_id].erase(db_context); } else { m_inactive_loads.erase(do_id); } m_log->trace() << "Received GetAllResp from database." << std::endl; // If object not found, just cleanup the context map if(dgi.read_uint8() != true) { break; // Object not found } // Read object class uint16_t dc_id = dgi.read_uint16(); if(!dc_id) { m_log->error() << "Received object from database with unknown dclass" << " - id:" << dc_id << std::endl; break; } DCClass* r_dclass = g_dcf->get_class(dc_id); // Get fields from database std::unordered_map<DCField*, std::vector<uint8_t> > required_fields; std::map<DCField*, std::vector<uint8_t> > ram_fields; if(!unpack_db_fields(dgi, r_dclass, required_fields, ram_fields)) { m_log->error() << "Error while unpacking fields from database." << std::endl; break; } // Add class to response dg.add_uint16(r_dclass->get_number()); // Add required fields to datagram int dcc_field_count = r_dclass->get_num_inherited_fields(); for(int i = 0; i < dcc_field_count; ++i) { DCField *field = r_dclass->get_inherited_field(i); if(!field->as_molecular_field() && field->is_required()) { auto req_it = required_fields.find(field); if(req_it != required_fields.end()) { dg.add_data(req_it->second); } else { dg.add_data(field->get_default_value()); } } } // Add ram fields to datagram dg.add_uint16(ram_fields.size()); for(auto it = ram_fields.begin(); it != ram_fields.end(); ++it) { dg.add_uint16(it->first->get_number()); dg.add_data(it->second); } // Send response back to caller send(dg); break; } default: { if(msgtype < STATESERVER_MSGTYPE_MIN || msgtype > DBSERVER_MSGTYPE_MAX) { m_log->warning() << "Received unknown message of type " << msgtype << std::endl; } else { m_log->trace() << "Ignoring stateserver or database message" << " of type " << msgtype << std::endl; } } } }
bool set_fields_if_equals(uint32_t do_id, const map<DCField*, vector<uint8_t> > &equals, map<DCField*, vector<uint8_t> > &values) { // Get class from the objects table DCClass* dcc = get_class(do_id); if(!dcc) { return false; // Object does not exist } bool stored = is_storable(dcc->get_number()); if(!stored) { return false; // Class has no database fields } bool failed = false; string value; indicator ind; vector<DCField*> null_fields; try { m_sql.begin(); // Start transaction for(auto it = equals.begin(); it != equals.end(); ++it) { DCField* field = it->first; if(field->is_db()) { m_sql << "SELECT " << field->get_name() << " FROM fields_" << dcc->get_name() << " WHERE object_id=" << do_id << ";", into(value, ind); if(ind != i_ok) { null_fields.push_back(field); failed = true; continue; } string packed_equal(it->second.begin(), it->second.end()); string equal = field->format_data(packed_equal, false); if(value == equal) { string packed_data(values[field].begin(), values[field].end()); string insert = field->format_data(packed_data, false); m_sql << "UPDATE fields_" << dcc->get_name() << " SET " << field->get_name() << "='" << insert << "' WHERE object_id=" << do_id << ";"; \ } else { failed = true; } value = field->parse_string(value); values[field] = vector<uint8_t>(value.begin(), value.end()); } } if(failed) { for(auto it = null_fields.begin(); it != null_fields.end(); ++it) { values.erase(*it); } m_sql.rollback(); // Revert transaction return false; } else { m_sql.commit(); // End transaction return true; } } catch(const exception &e) { m_sql.rollback(); // Revert transaction values.clear(); return false; } }
bool set_fields_if_empty(uint32_t do_id, map<DCField*, vector<uint8_t> > &values) { // Get class from the objects table DCClass* dcc = get_class(do_id); if(!dcc) { values.clear(); return false; // Object does not exist } bool stored = is_storable(dcc->get_number()); if(!stored) { values.clear(); return false; // Class has no database fields } bool failed = false; string value; indicator ind; try { m_sql.begin(); // Start transaction for(auto it = values.begin(); it != values.end(); ++it) { DCField* field = it->first; if(field->is_db()) { m_sql << "SELECT " << field->get_name() << " FROM fields_" << dcc->get_name() << " WHERE object_id=" << do_id << ";", into(value, ind); if(ind != i_null) { failed = true; string packed_data = field->parse_string(value); values[field] = vector<uint8_t>(packed_data.begin(), packed_data.end()); continue; } string packed_data(it->second.begin(), it->second.end()); value = it->first->format_data(packed_data, false); m_sql << "UPDATE fields_" << dcc->get_name() << " SET " << field->get_name() << "='" << value << "' WHERE object_id=" << do_id << ";"; } } if(failed) { m_sql.rollback(); // Revert transaction } else { m_sql.commit(); // End transaction } } catch(const exception &e) { m_sql.rollback(); // Revert transaction values.clear(); return false; } }