This commit is contained in:
WolverinDEV
2019-08-16 16:13:52 +02:00
parent bae6a56ed3
commit aa3de59652
14 changed files with 454 additions and 279 deletions
+239 -71
View File
@@ -44,7 +44,8 @@ if(!result && result.msg().find(ignore) == string::npos){
#define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")");
#define CURRENT_VERSION 11
#define CURRENT_DATABASE_VERSION 11
#define CURRENT_PERMISSION_VERSION 0
#define CLIENT_UID_LENGTH "64"
#define CLIENT_NAME_LENGTH "128"
@@ -62,6 +63,22 @@ struct alive_watch {
}
};
#define db_version(new_version) \
do { \
if(!this->change_database_version(new_version)) { \
error = "failed to update database version"; \
return false; \
} \
} while(0)
#define perm_version(new_version) \
do { \
if(!this->change_permission_version(new_version)) { \
error = "failed to update permission version"; \
return false; \
} \
} while(0)
bool SqlDataManager::initialize(std::string& error) {
if(ts::config::database::url.find("sqlite://") == 0)
this->manager = new sql::sqlite::SqliteManager();
@@ -77,11 +94,9 @@ bool SqlDataManager::initialize(std::string& error) {
return false;
}
string command_append_utf8;
if(manager->getType() == sql::TYPE_MYSQL) {
sql::command(this->manager, "SET NAMES utf8").execute();
//sql::command(this->manager, "DEFAULT CHARSET=utf8").execute();
command_append_utf8 = " CHARACTER SET=utf8";
} else if(manager->getType() == sql::TYPE_SQLITE) {
if(!config::database::sqlite::journal_mode.empty())
sql::command(this->manager, "PRAGMA journal_mode=" + config::database::sqlite::journal_mode + ";").execute();
@@ -95,33 +110,79 @@ bool SqlDataManager::initialize(std::string& error) {
sql::command(this->manager, "PRAGMA encoding = \"UTF-8\";").execute();
}
this->detectVersion();
//general stuff
if(this->version != CURRENT_VERSION) {
auto timestamp_start = system_clock::now();
logMessage(LOG_GENERAL, "Upgrading database from version " + to_string(this->version) + " to " + to_string(CURRENT_VERSION) + ". This could take a moment!");
if(manager->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql(),"BEGIN TRANSACTION;").execute();
if(!result) {
error = "failed to begin transaction (" + result.fmtStr() + ")";
return false;
}
/* begin transaction, if available */
if(manager->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql(),"BEGIN TRANSACTION;").execute();
if(!result) {
error = "failed to begin transaction (" + result.fmtStr() + ")";
return false;
}
}
alive_watch rollback_watch([&]{
if(manager->getType() == sql::TYPE_SQLITE) {
auto result = sql::command(this->sql(), "ROLLBACK;").execute();
if (!result) {
logCritical(LOG_GENERAL, "Failed to rollback database after transaction.");
return;
}
debugMessage(LOG_GENERAL, "Rollbacked database successfully.");
alive_watch rollback_watch([&]{
if(manager->getType() == sql::TYPE_SQLITE) {
auto result = sql::command(this->sql(), "ROLLBACK;").execute();
if (!result) {
logCritical(LOG_GENERAL, "Failed to rollback database after transaction.");
return;
}
debugMessage(LOG_GENERAL, "Rollbacked database successfully.");
}
});
});
switch (this->version) {
if(!this->detect_versions()) {
error = "failed to detect database/permission version";
return false;
}
if(!this->update_database(error)) {
error = "failed to upgrade database: " + error;
return false;
}
//Advanced locked test
{
bool property_exists = false;
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([](bool& flag, int, string*, string*) { flag = true; }, property_exists);
sql::result res;
if(!property_exists) {
res = sql::command(this->sql(), "INSERT INTO `general` (`key`, `value`) VALUES (:key, :value);", variable{":key", "lock_test"}, variable{":value", "UPDATE ME!"}).execute();
} else {
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
}
if(!res) {
if(res.msg().find("database is locked") != string::npos) error = "database is locked";
else error = "Failed to execute lock test! Command result: " + res.fmtStr();
return false;
}
}
if(!this->update_permissions(error)) {
error = "failed to upgrade permissions: " + error;
return false;
}
rollback_watch.do_notify = false; /* transaction was successful */
if(manager->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql(), "COMMIT;").execute();
if(!result) {
error = "failed to commit changes";
return false;
}
}
return true;
}
bool SqlDataManager::update_database(std::string &error) {
if(this->_database_version != CURRENT_DATABASE_VERSION) {
string command_append_utf8 = manager->getType() == sql::TYPE_MYSQL ? " CHARACTER SET=utf8" : "";
sql::result result;
auto timestamp_start = system_clock::now();
logMessage(LOG_GENERAL, "Upgrading database from version " + to_string(this->_database_version) + " to " + to_string(CURRENT_DATABASE_VERSION) + ". This could take a moment!");
switch (this->_database_version) {
case -1:
CREATE_TABLE("general", "`key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
CREATE_TABLE("servers", "`serverId` INT NOT NULL, `host` TEXT NOT NULL, `port` INT", command_append_utf8);
@@ -143,10 +204,9 @@ bool SqlDataManager::initialize(std::string& error) {
CREATE_TABLE("letters", "`serverId` INT NOT NULL, `letterId` INTEGER NOT NULL PRIMARY KEY, `sender` VARCHAR(" CLIENT_UID_LENGTH "), `receiver` VARCHAR(" CLIENT_UID_LENGTH "), `created` INT, `subject` TEXT, `message` TEXT, `read` INT", command_append_utf8);
CREATE_TABLE("musicbots", "`serverId` INT, `botId` INT, `uniqueId` VARCHAR(" CLIENT_UID_LENGTH "), `owner` INT", command_append_utf8);
this->changeVersion(0);
db_version(0);
case 0:
CREATE_INDEX("general", "key");
CREATE_INDEX("general", "key");
CREATE_INDEX("servers", "serverId");
CREATE_INDEX2R("assignedGroups", "serverId", "cldbid");
@@ -175,33 +235,33 @@ bool SqlDataManager::initialize(std::string& error) {
CREATE_INDEX("letters", "serverId");
CREATE_INDEX("letters", "letterId");
CREATE_INDEX("musicbots", "serverId");
this->changeVersion(1);
db_version(1);
case 1:
sql::command(this->sql(), "UPDATE `properties` SET `type` = :type WHERE `serverId` = 0 AND `id` = 0", variable{":type", property::PropertyType::PROP_TYPE_INSTANCE}).execute();
this->changeVersion(2);
db_version(2);
case 2:
sql::command(this->sql(), "ALTER TABLE permissions ADD flag_skip BOOL;").execute();
sql::command(this->sql(), "ALTER TABLE permissions ADD flag_negate BOOL;").execute();
this->changeVersion(3);
db_version(3);
case 3:
EXECUTE("Failed to update ban table", "ALTER TABLE `bannedClients` ADD COLUMN `triggered` INT DEFAULT 0;");
EXECUTE("Failed to update ban table", "ALTER TABLE `bannedClients` ADD COLUMN `triggered` INT DEFAULT 0;");
CREATE_TABLE("ban_trigger", "`server_id` INT, `ban_id` INT, `unique_id` VARCHAR(" CLIENT_UID_LENGTH "), `hardware_id` VARCHAR(" CLIENT_UID_LENGTH "), `name` VARCHAR(" CLIENT_NAME_LENGTH "), `ip` VARCHAR(128), `timestamp` BIGINT", command_append_utf8);
CREATE_INDEX2R("ban_trigger", "server_id", "ban_id");
this->changeVersion(4);
db_version(4);
case 4:
sql::command(this->sql(), "ALTER TABLE queries ADD server INT;").execute();
this->changeVersion(5);
db_version(5);
case 5:
CREATE_TABLE("playlists", "`serverId` INT NOT NULL, `playlist_id` INT", command_append_utf8);
CREATE_TABLE("playlists", "`serverId` INT NOT NULL, `playlist_id` INT", command_append_utf8);
CREATE_INDEX("playlists", "serverId");
CREATE_TABLE("playlist_songs", "`serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT", command_append_utf8);
CREATE_INDEX2R("playlist_songs", "serverId", "playlist_id");
this->changeVersion(6);
db_version(6);
sql::command(this->sql(), "UPDATE `permissions ` SET `permId` = `b_client_music_create_temporary` WHERE `permId` = `b_client_music_create`").execute();
@@ -318,14 +378,14 @@ ROLLBACK;
sql::command(this->sql(), "DELETE FROM `properties` WHERE `type` = 1 AND NOT (`serverId` IN (SELECT `serverId` FROM servers) OR `serverId` = 0);").execute();
}
this->changeVersion(8);
db_version(8);
case 8:
result = sql::command(this->sql(), "UPDATE `queries` SET `server` = 0 WHERE `server` IS NULL").execute();
if(!result) {
error = "Failed to drop null query entries (" + result.fmtStr() + ")";
return false;
}
this->changeVersion(9);
db_version(9);
case 9:
//
//"UPDATE `permissions` SET `id` = :id WHERE `type` = :channel_type" ;permission::SQL_PERM_CHANNEL
@@ -344,22 +404,14 @@ ROLLBACK;
return false;
}
}
this->changeVersion(10);
db_version(10);
case 10:
CREATE_TABLE("conversations", "`server_id` INT, `channel_id` INT, `conversation_id` INT, `file_path` TEXT", command_append_utf8);
CREATE_TABLE("conversation_blocks", "`server_id` INT, `conversation_id` INT, `begin_timestamp` INT, `end_timestamp` INT, `block_offset` INT, `flags` INT", command_append_utf8);
CREATE_INDEX("conversations", "server_id");
CREATE_INDEX2R("conversation_blocks", "server_id", "conversation_id");
this->changeVersion(11);
db_version(11);
default:
if(manager->getType() == sql::TYPE_SQLITE) {
result = sql::command(this->sql(), "COMMIT;").execute();
if(!result) {
error = "failed to commit changes";
return false;
}
}
rollback_watch.do_notify = false; /* transaction was successful */
break;
}
@@ -367,21 +419,106 @@ ROLLBACK;
logMessage(LOG_GENERAL, "Database upgrade took {}ms", duration_cast<milliseconds>(timestamp_end - timestamp_start).count());
}
//Advanced locked test
{
bool property_exists = false;
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([](bool& flag, int, string*, string*) { flag = true; }, property_exists);
sql::result res;
if(!property_exists) {
res = sql::command(this->sql(), "INSERT INTO `general` (`key`, `value`) VALUES (:key, :value);", variable{":key", "lock_test"}, variable{":value", "UPDATE ME!"}).execute();
} else {
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
}
if(!res) {
if(res.msg().find("database is locked") != string::npos) error = "database is locked";
else error = "Failed to execute lock test! Command result: " + res.fmtStr();
return false;
return true;
}
bool SqlDataManager::update_permissions(std::string &error) {
if(this->_permissions_version != CURRENT_PERMISSION_VERSION) {
sql::result result;
auto timestamp_start = system_clock::now();
logMessage(LOG_GENERAL, "Upgrading permissions from version " + to_string(this->_permissions_version) + " to " + to_string(CURRENT_PERMISSION_VERSION) + ". This could take a moment!");
const auto auto_update = [&](permission::update::GroupUpdateType update_type, const std::string& permission, permission::v2::PermissionFlaggedValue value, bool skip, bool negate, permission::v2::PermissionFlaggedValue granted) {
/*
INSERT [OR IGNORE | IGNORE] INTO `perms` (serverId, type, id, channelId, permId, value, grant, flag_skip, flag_negate)
SELECT DISTINCT `permissions`.`serverId`, 0, `groupId`, 0, :name, :value, :grant, :skip, :negate FROM groups
INNER JOIN `permissions`
ON permissions.permId = 'i_group_auto_update_type' AND permissions.channelId = 0 AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = :update_type;
*/
std::string query = "INSERT ";
if(this->sql()->getType() == sql::TYPE_MYSQL)
query += "IGNORE ";
else
query += "OR IGNORE ";
query += "INTO `permissions` (serverId, type, id, channelId, permId, value, grant, flag_skip, flag_negate) ";
query += string() + "SELECT DISTINCT `permissions`.`serverId`, 0, `groupId`, 0, "
+ "'" + permission + "', "
+ to_string(value.has_value ? value.value : -2) + ", "
+ to_string(granted.has_value ? granted.value : -2) + ", "
+ to_string(skip) + ", "
+ to_string(negate) + " FROM groups ";
query += "INNER JOIN `permissions` ";
query += "ON permissions.permId = 'i_group_auto_update_type' AND permissions.channelId = 0 AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = " + to_string(update_type);
logTrace(LOG_GENERAL, "Executing sql update: {}", query);
auto result = sql::command(this->sql(), query).execute();
if(!result) {
error = "failed to auto update permission " + permission + " for type " + to_string(update_type) + ": " + result.fmtStr();
return false;
}
return true;
};
switch (this->_permissions_version) {
case -1:
/* initial setup, or first introduce of 1.4.0. Default stuff will be loaded from the template file so we only run updates here */
if(!auto_update(permission::update::QUERY_ADMIN, "b_client_is_priority_speaker", {-2, false}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_virtualserver_modify_country_code", {1, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_ignore_subscribe_power", {1, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_ignore_description_view_power", {1, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "i_max_playlist_size", {1, false}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "i_max_playlists", {1, false}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "i_channel_create_modify_conversation_history_length", {1, false}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_create_modify_conversation_history_unlimited", {1, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_create_modify_conversation_private", {1, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_client_is_priority_speaker", {-2, false}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_virtualserver_modify_country_code", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_channel_ignore_subscribe_power", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_channel_ignore_description_view_power", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "i_max_playlist_size", {1, false}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "i_max_playlists", {1, false}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "i_channel_create_modify_conversation_history_length", {1, false}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_channel_create_modify_conversation_history_unlimited", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "b_channel_create_modify_conversation_private", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::SERVER_NORMAL, "i_max_playlist_size", {50, true}, false, false, {-2, false}))
return false;
if(!auto_update(permission::update::SERVER_NORMAL, "i_channel_create_modify_conversation_history_length", {15000, true}, false, false, {-2, false}))
return false;
if(!auto_update(permission::update::SERVER_GUEST, "i_max_playlist_size", {10, true}, false, false, {-2, false}))
return false;
perm_version(0);
default:
break;
}
auto timestamp_end = system_clock::now();
logMessage(LOG_GENERAL, "Permission upgrade took {}ms", duration_cast<milliseconds>(timestamp_end - timestamp_start).count());
}
return true;
}
@@ -392,23 +529,54 @@ void SqlDataManager::finalize() {
this->manager = nullptr;
}
void SqlDataManager::detectVersion() {
sql::command(this->manager, "SELECT `value` FROM `general` WHERE `key`= :key", variable{":key", "data_version"}).query([&](int length, char** values, char**){
this->version = atoi(values[0]);
this->version_present = false;
bool SqlDataManager::detect_versions() {
auto result = sql::command(this->manager, "SELECT `value` FROM `general` WHERE `key`= :key", variable{":key", "data_version"}).query([&](int length, char** values, char**){
this->_database_version = atoi(values[0]);
this->database_version_present = true;
return 0;
});
result = sql::command(this->manager, "SELECT `value` FROM `general` WHERE `key`= :key", variable{":key", "permissions_version"}).query([&](int length, char** values, char**){
this->_permissions_version = atoi(values[0]);
this->permissions_version_present = true;
return 0;
});
return true;
}
void SqlDataManager::changeVersion(int version) {
bool SqlDataManager::change_database_version(int version) {
string command;
if(this->version_present)
if(this->database_version_present)
command = "UPDATE `general` SET `value`= :version WHERE `key`= :key;";
else
command = "INSERT INTO `general` (`key`, `value`) VALUES (:key, :version);";
auto result = sql::command(this->manager, command, variable{":version", version}, variable{":key", "data_version"}).execute();
if(!result) logError("Could not update SQL version. (" + result.fmtStr() + ")");
else this->version_present = true;
this->version = version;
if(!result) {
logError(LOG_INSTANCE, "Could not update SQL database version. (" + result.fmtStr() + ")");
return false;
}
this->database_version_present = true;
this->_database_version = version;
return true;
}
bool SqlDataManager::change_permission_version(int version) {
string command;
if(this->permissions_version_present)
command = "UPDATE `general` SET `value`= :version WHERE `key`= :key;";
else
command = "INSERT INTO `general` (`key`, `value`) VALUES (:key, :version);";
auto result = sql::command(this->manager, command, variable{":version", version}, variable{":key", "permissions_version"}).execute();
if(!result) {
logError(LOG_INSTANCE, "Could not update SQL permissions version. (" + result.fmtStr() + ")");
return false;
}
this->permissions_version_present = true;
this->_permissions_version = version;
return true;
}
+12 -5
View File
@@ -9,18 +9,25 @@ namespace ts {
SqlDataManager();
virtual ~SqlDataManager();
int getVersion() { return this->version; }
inline int get_database_version() const { return this->_database_version; }
inline int get_permissions_version() const { return this->_database_version; }
bool initialize(std::string&);
void finalize();
sql::SqlManager* sql() { return this->manager; }
private:
sql::SqlManager* manager = nullptr;
int version = -1;
bool version_present = false;
int _database_version = -1;
int _permissions_version = -1;
bool database_version_present = false;
bool permissions_version_present = false;
void detectVersion();
void changeVersion(int);
bool detect_versions();
bool change_database_version(int);
bool change_permission_version(int);
bool update_database(std::string& /* errror */);
bool update_permissions(std::string& /* errror */);
};
}
}