From 46c653293381dcc1188013d3e2c3f7587a199dc4 Mon Sep 17 00:00:00 2001 From: fusion32 Date: Fri, 10 Oct 2025 15:14:02 -0300 Subject: organize database configs and schema files + compilation guards --- Makefile | 16 ++- config.cfg | 51 +++++---- sql/README.txt | 10 -- sql/init.sql | 103 ------------------ sql/schema.sql | 274 ----------------------------------------------- sqlite/README.txt | 10 ++ sqlite/init.sql | 103 ++++++++++++++++++ sqlite/schema.sql | 274 +++++++++++++++++++++++++++++++++++++++++++++++ src/connections.cc | 2 +- src/database_mysql.cc | 6 ++ src/database_postgres.cc | 6 ++ src/database_pq.cc | 4 - src/database_sqlite.cc | 19 ++-- src/querymanager.cc | 148 +++++++++++++++++-------- src/querymanager.hh | 40 +++++-- 15 files changed, 589 insertions(+), 477 deletions(-) delete mode 100644 sql/README.txt delete mode 100644 sql/init.sql delete mode 100644 sql/schema.sql create mode 100644 sqlite/README.txt create mode 100644 sqlite/init.sql create mode 100644 sqlite/schema.sql create mode 100644 src/database_mysql.cc create mode 100644 src/database_postgres.cc delete mode 100644 src/database_pq.cc diff --git a/Makefile b/Makefile index 0ba1f30..0fca315 100644 --- a/Makefile +++ b/Makefile @@ -23,11 +23,17 @@ endif DATABASE ?= sqlite ifeq ($(DATABASE), sqlite) DATABASEOBJ = $(BUILDDIR)/database_sqlite.obj $(BUILDDIR)/sqlite3.obj + CFLAGS += -DDATABASE_SQLITE=1 else ifeq ($(DATABASE), postgres) - DATABASEOBJ = $(BUILDDIR)/database_pq.obj + DATABASEOBJ = $(BUILDDIR)/database_postgres.obj + CFLAGS += -DDATABASE_POSTGRESQL=1 LFLAGS += -lpq +else ifeq ($(DATABASE), mariadb) + DATABASEOBJ = $(BUILDDIR)/database_mysql.obj + CFLAGS += -DDATABASE_MYSQL=1 -DDATABASE_MARIADB=1 + LFLAGS += -lmariadb else - $(error Unsupported DATABASE: `$(DATABASE)`. Valid options are `sqlite` or `postgres`) + $(error Unsupported DATABASE: `$(DATABASE)`. Valid options are `sqlite`, `postgres`, or `mariadb`) endif $(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/connections.obj $(BUILDDIR)/hostcache.obj $(BUILDDIR)/query.obj $(BUILDDIR)/querymanager.obj $(BUILDDIR)/sha256.obj $(DATABASEOBJ) @@ -58,7 +64,11 @@ $(BUILDDIR)/database_sqlite.obj: $(SRCDIR)/database_sqlite.cc $(SRCDIR)/queryman @mkdir -p $(@D) $(CXX) -c $(CXXFLAGS) -o $@ $< -$(BUILDDIR)/database_pq.obj: $(SRCDIR)/database_pq.cc $(SRCDIR)/querymanager.hh +$(BUILDDIR)/database_postgres.obj: $(SRCDIR)/database_postgres.cc $(SRCDIR)/querymanager.hh + @mkdir -p $(@D) + $(CXX) -c $(CXXFLAGS) -o $@ $< + +$(BUILDDIR)/database_mysql.obj: $(SRCDIR)/database_mysql.cc $(SRCDIR)/querymanager.hh @mkdir -p $(@D) $(CXX) -c $(CXXFLAGS) -o $@ $< diff --git a/config.cfg b/config.cfg index 30ca4cd..076c535 100644 --- a/config.cfg +++ b/config.cfg @@ -1,23 +1,36 @@ # HostCache Config -MaxCachedHostNames = 100 -HostNameExpireTime = 30m +MaxCachedHostNames = 100 +HostNameExpireTime = 30m -# Database Config -MaxCachedStatements = 100 -DatabaseFile = "tibia.db" -# TODO(fusion): This is not great. Probably switch to database specific options? -#DatabaseHost = "localhost" -#DatabasePort = 5432 -#DatabaseUser = "postgres" -#DatabasePassword = "" -#DatabaseName = "tibia" -#DatabaseTLS = true +# SQLite Config +SQLite.File = "tibia.db" +SQLite.MaxCachedStatements = 100 + +# PostgreSQL Config +#PostgreSQL.UnixSocket = "" +#PostgreSQL.Host = "localhost" +#PostgreSQL.Port = 5432 +#PostgreSQL.User = "postgres" +#PostgreSQL.Password = "" +#PostgreSQL.Database = "tibia" +#PostgreSQL.TLS = true +#PostgreSQL.MaxCachedStatements = 100 + +# MySQL/MariaDB Config +#MySQL.UnixSocket = "" +#MySQL.Host = "localhost" +#MySQL.Port = 3306 +#MySQL.User = "mysql" +#MySQL.Password = "" +#MySQL.Database = "tibia" +#MySQL.TLS = true +#MySQL.MaxCachedStatements = 100 # Connection Config -QueryManagerPort = 7173 -QueryManagerPassword = "a6glaf0c" -QueryWorkerThreads = 1 -QueryBufferSize = 1M -QueryMaxAttempts = 3 -MaxConnections = 25 -MaxConnectionIdleTime = 5m +QueryManagerPort = 7173 +QueryManagerPassword = "a6glaf0c" +QueryWorkerThreads = 1 +QueryBufferSize = 1M +QueryMaxAttempts = 3 +MaxConnections = 25 +MaxConnectionIdleTime = 5m diff --git a/sql/README.txt b/sql/README.txt deleted file mode 100644 index 26f782a..0000000 --- a/sql/README.txt +++ /dev/null @@ -1,10 +0,0 @@ - The query manager will properly initialize and upgrade the database schema -based on files in this folder. The initial schema should be in `schema.sql` and -upgrades should be in `upgrade-N.sql` where N is a number starting from 1. The -`upgrade-N.sql` file should take the database from version N to N + 1. - The only restriction to these files is that they can't have transaction -statements ("BEGIN", "ROLLBACK", "COMMIT") because the query manager will -already bundle them into a transaction that also sets `user_version`, and -and nested transactions aren't allowed. - The current database version can be retrieved with the "PRAGMA user_version" -query in the SQLite shell. \ No newline at end of file diff --git a/sql/init.sql b/sql/init.sql deleted file mode 100644 index 589c4e3..0000000 --- a/sql/init.sql +++ /dev/null @@ -1,103 +0,0 @@ --- NOTE(fusion): The query manager WON'T automatically run this but the game --- server still requires at least the world config to be able to boot up. It --- is probably a good idea to keep this separated from `schema.sql` and then --- running it with `sqlite3 -echo tibia.db < sql/init.sql`, although it is --- not mandatory. --- Because this isn't automatically managed, all queries are wrapped in a --- transaction to avoid partial writes in case of errors. ---============================================================================== -BEGIN; - -INSERT INTO Worlds (WorldID, Name, Type, RebootTime, Host, Port, MaxPlayers, - PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer) - VALUES (1, 'Zanera', 0, 5, 'localhost', 7172, 1000, 100, 300, 100); - --- 111111/tibia -INSERT INTO Accounts (AccountID, Email, Auth) - VALUES (111111, '@tibia', X'206699cbc2fae1683118c873d746aa376049cb5923ef0980298bb7acbba527ec9e765668f7a338dffea34acf61a20efb654c1e9c62d35148dba2aeeef8dc7788'); - -INSERT INTO Characters (WorldID, CharacterID, AccountID, Name, Sex) - VALUES (1, 1, 111111, 'Gamemaster', 1), (1, 2, 111111, 'Player', 1); - -INSERT INTO CharacterRights (CharacterID, Right) - VALUES - (1, 'NOTATION'), - (1, 'NAMELOCK'), - (1, 'STATEMENT_REPORT'), - (1, 'BANISHMENT'), - (1, 'FINAL_WARNING'), - (1, 'IP_BANISHMENT'), - (1, 'KICK'), - (1, 'HOME_TELEPORT'), - (1, 'GAMEMASTER_BROADCAST'), - (1, 'ANONYMOUS_BROADCAST'), - (1, 'NO_BANISHMENT'), - (1, 'ALLOW_MULTICLIENT'), - (1, 'LOG_COMMUNICATION'), - (1, 'READ_GAMEMASTER_CHANNEL'), - (1, 'READ_TUTOR_CHANNEL'), - (1, 'HIGHLIGHT_HELP_CHANNEL'), - (1, 'SEND_BUGREPORTS'), - (1, 'NAME_INSULTING'), - (1, 'NAME_SENTENCE'), - (1, 'NAME_NONSENSICAL_LETTERS'), - (1, 'NAME_BADLY_FORMATTED'), - (1, 'NAME_NO_PERSON'), - (1, 'NAME_CELEBRITY'), - (1, 'NAME_COUNTRY'), - (1, 'NAME_FAKE_IDENTITY'), - (1, 'NAME_FAKE_POSITION'), - (1, 'STATEMENT_INSULTING'), - (1, 'STATEMENT_SPAMMING'), - (1, 'STATEMENT_ADVERT_OFFTOPIC'), - (1, 'STATEMENT_ADVERT_MONEY'), - (1, 'STATEMENT_NON_ENGLISH'), - (1, 'STATEMENT_CHANNEL_OFFTOPIC'), - (1, 'STATEMENT_VIOLATION_INCITING'), - (1, 'CHEATING_BUG_ABUSE'), - (1, 'CHEATING_GAME_WEAKNESS'), - (1, 'CHEATING_MACRO_USE'), - (1, 'CHEATING_MODIFIED_CLIENT'), - (1, 'CHEATING_HACKING'), - (1, 'CHEATING_MULTI_CLIENT'), - (1, 'CHEATING_ACCOUNT_TRADING'), - (1, 'CHEATING_ACCOUNT_SHARING'), - (1, 'GAMEMASTER_THREATENING'), - (1, 'GAMEMASTER_PRETENDING'), - (1, 'GAMEMASTER_INFLUENCE'), - (1, 'GAMEMASTER_FALSE_REPORTS'), - (1, 'KILLING_EXCESSIVE_UNJUSTIFIED'), - (1, 'DESTRUCTIVE_BEHAVIOUR'), - (1, 'SPOILING_AUCTION'), - (1, 'INVALID_PAYMENT'), - (1, 'TELEPORT_TO_CHARACTER'), - (1, 'TELEPORT_TO_MARK'), - (1, 'TELEPORT_VERTICAL'), - (1, 'TELEPORT_TO_COORDINATE'), - (1, 'LEVITATE'), - (1, 'SPECIAL_MOVEUSE'), - (1, 'MODIFY_GOSTRENGTH'), - (1, 'SHOW_COORDINATE'), - (1, 'RETRIEVE'), - (1, 'ENTER_HOUSES'), - (1, 'OPEN_NAMEDOORS'), - (1, 'INVULNERABLE'), - (1, 'UNLIMITED_MANA'), - (1, 'KEEP_INVENTORY'), - (1, 'ALL_SPELLS'), - (1, 'UNLIMITED_CAPACITY'), - (1, 'ATTACK_EVERYWHERE'), - (1, 'NO_LOGOUT_BLOCK'), - (1, 'GAMEMASTER_OUTFIT'), - (1, 'ILLUMINATE'), - (1, 'CHANGE_PROFESSION'), - (1, 'IGNORED_BY_MONSTERS'), - (1, 'SHOW_KEYHOLE_NUMBERS'), - (1, 'CREATE_OBJECTS'), - (1, 'CREATE_MONEY'), - (1, 'CREATE_MONSTERS'), - (1, 'CHANGE_SKILLS'), - (1, 'CLEANUP_FIELDS'), - (1, 'NO_STATISTICS'); - -COMMIT; diff --git a/sql/schema.sql b/sql/schema.sql deleted file mode 100644 index 677d778..0000000 --- a/sql/schema.sql +++ /dev/null @@ -1,274 +0,0 @@ --- Primary Tables ---============================================================================== -CREATE TABLE IF NOT EXISTS Worlds ( - WorldID INTEGER NOT NULL, - Name TEXT NOT NULL COLLATE NOCASE, - Type INTEGER NOT NULL, - RebootTime INTEGER NOT NULL, - Host TEXT NOT NULL, - Port INTEGER NOT NULL, - MaxPlayers INTEGER NOT NULL, - PremiumPlayerBuffer INTEGER NOT NULL, - MaxNewbies INTEGER NOT NULL, - PremiumNewbieBuffer INTEGER NOT NULL, - OnlineRecord INTEGER NOT NULL DEFAULT 0, - OnlineRecordTimestamp INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (WorldID), - UNIQUE (Name) -); - -CREATE TABLE IF NOT EXISTS Accounts ( - AccountID INTEGER NOT NULL, - Email TEXT NOT NULL COLLATE NOCASE, - Auth BLOB NOT NULL, - PremiumEnd INTEGER NOT NULL DEFAULT 0, - PendingPremiumDays INTEGER NOT NULL DEFAULT 0, - Deleted INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (AccountID), - UNIQUE (Email) -); - -CREATE TABLE IF NOT EXISTS Characters ( - WorldID INTEGER NOT NULL, - CharacterID INTEGER NOT NULL, - AccountID INTEGER NOT NULL, - Name TEXT NOT NULL COLLATE NOCASE, - Sex INTEGER NOT NULL, - Guild TEXT NOT NULL COLLATE NOCASE DEFAULT '', - Rank TEXT NOT NULL COLLATE NOCASE DEFAULT '', - Title TEXT NOT NULL COLLATE NOCASE DEFAULT '', - Level INTEGER NOT NULL DEFAULT 0, - Profession TEXT NOT NULL DEFAULT '', - Residence TEXT NOT NULL DEFAULT '', - LastLoginTime INTEGER NOT NULL DEFAULT 0, - TutorActivities INTEGER NOT NULL DEFAULT 0, - IsOnline INTEGER NOT NULL DEFAULT 0, - Deleted INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (CharacterID), - UNIQUE (Name) -); -CREATE INDEX IF NOT EXISTS CharactersWorldIndex - ON Characters(WorldID, IsOnline); -CREATE INDEX IF NOT EXISTS CharactersAccountIndex - ON Characters(AccountID, IsOnline); -CREATE INDEX IF NOT EXISTS CharactersGuildIndex - ON Characters(Guild, Rank); - -/* --- TODO(fusion): Have group rights instead of adding individual rights to characters? -ALTER TABLE Characters ADD GroupID INTEGER NOT NULL; -CREATE TABLE IF NOT EXISTS CharacterRights ( - GroupID INTEGER NOT NULL, - Right TEXT NOT NULL COLLATE NOCASE, - PRIMARY KEY(GroupID, Right) -); -*/ - -CREATE TABLE IF NOT EXISTS CharacterRights ( - CharacterID INTEGER NOT NULL, - Right TEXT NOT NULL COLLATE NOCASE, - PRIMARY KEY(CharacterID, Right) -); - -CREATE TABLE IF NOT EXISTS CharacterDeaths ( - CharacterID INTEGER NOT NULL, - Level INTEGER NOT NULL, - OffenderID INTEGER NOT NULL, - Remark TEXT NOT NULL, - Unjustified INTEGER NOT NULL, - Timestamp INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS CharacterDeathsCharacterIndex - ON CharacterDeaths(CharacterID, Level); -CREATE INDEX IF NOT EXISTS CharacterDeathsOffenderIndex - ON CharacterDeaths(OffenderID, Unjustified); - -CREATE TABLE IF NOT EXISTS Buddies ( - WorldID INTEGER NOT NULL, - AccountID INTEGER NOT NULL, - BuddyID INTEGER NOT NULL, - PRIMARY KEY (WorldID, AccountID, BuddyID) -); - -CREATE TABLE IF NOT EXISTS WorldInvitations ( - WorldID INTEGER NOT NULL, - CharacterID INTEGER NOT NULL, - PRIMARY KEY (WorldID, CharacterID) -); - -CREATE TABLE IF NOT EXISTS LoginAttempts ( - AccountID INTEGER NOT NULL, - IPAddress INTEGER NOT NULL, - Timestamp INTEGER NOT NULL, - Failed INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS LoginAttemptsAccountIndex - ON LoginAttempts(AccountID, Timestamp); -CREATE INDEX IF NOT EXISTS LoginAttemptsAddressIndex - ON LoginAttempts(IPAddress, Timestamp); - --- House Tables ---============================================================================== -CREATE TABLE IF NOT EXISTS Houses ( - WorldID INTEGER NOT NULL, - HouseID INTEGER NOT NULL, - Name TEXT NOT NULL, - Rent INTEGER NOT NULL, - Description TEXT NOT NULL, - Size INTEGER NOT NULL, - PositionX INTEGER NOT NULL, - PositionY INTEGER NOT NULL, - PositionZ INTEGER NOT NULL, - Town TEXT NOT NULL, - GuildHouse INTEGER NOT NULL, - PRIMARY KEY (WorldID, HouseID) -); - -CREATE TABLE IF NOT EXISTS HouseOwners ( - WorldID INTEGER NOT NULL, - HouseID INTEGER NOT NULL, - OwnerID INTEGER NOT NULL, - PaidUntil INTEGER NOT NULL, - PRIMARY KEY (WorldID, HouseID) -); - --- NOTE(fusion): An auction would have a non null `FinishTime` but it doesn't make --- sense to finish an auction just to restart it afterwards so it should only be --- set after the first bid, along with `BidderID` and `BidAmount`. -CREATE TABLE IF NOT EXISTS HouseAuctions ( - WorldID INTEGER NOT NULL, - HouseID INTEGER NOT NULL, - BidderID INTEGER DEFAULT NULL, - BidAmount INTEGER DEFAULT NULL, - FinishTime INTEGER DEFAULT NULL, - PRIMARY KEY (WorldID, HouseID) -); - -CREATE TABLE IF NOT EXISTS HouseTransfers ( - WorldID INTEGER NOT NULL, - HouseID INTEGER NOT NULL, - NewOwnerID INTEGER NOT NULL, - Price INTEGER NOT NULL, - PRIMARY KEY (WorldID, HouseID) -); - -CREATE TABLE IF NOT EXISTS HouseAuctionExclusions ( - CharacterID INTEGER NOT NULL, - Issued INTEGER NOT NULL, - Until INTEGER NOT NULL, - BanishmentID INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS HouseAuctionExclusionsIndex - ON HouseAuctionExclusions(CharacterID, Until); - -CREATE TABLE IF NOT EXISTS HouseAssignments ( - WorldID INTEGER NOT NULL, - HouseID INTEGER NOT NULL, - OwnerID INTEGER NOT NULL, - Price INTEGER NOT NULL, - Timestamp INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS HouseAssignmentsHouseIndex - ON HouseAssignments(WorldID, HouseID); -CREATE INDEX IF NOT EXISTS HouseAssignmentsTimeIndex - ON HouseAssignments(WorldID, Timestamp); -CREATE INDEX IF NOT EXISTS HouseAssignmentsOwnerIndex - ON HouseAssignments(OwnerID); - --- Banishment Tables ---============================================================================== -CREATE TABLE IF NOT EXISTS Banishments ( - BanishmentID INTEGER NOT NULL, - AccountID INTEGER NOT NULL, - IPAddress INTEGER NOT NULL, - GamemasterID INTEGER NOT NULL, - Reason TEXT NOT NULL, - Comment TEXT NOT NULL, - FinalWarning INTEGER NOT NULL, - Issued INTEGER NOT NULL, - Until INTEGER NOT NULL, - PRIMARY KEY (BanishmentID) -); -CREATE INDEX IF NOT EXISTS BanishmentsAccountIndex - ON Banishments(AccountID, Until, FinalWarning); - -CREATE TABLE IF NOT EXISTS IPBanishments ( - CharacterID INTEGER NOT NULL, - IPAddress INTEGER NOT NULL, - GamemasterID INTEGER NOT NULL, - Reason TEXT NOT NULL, - Comment TEXT NOT NULL, - Issued INTEGER NOT NULL, - Until INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS IPBanishmentsAddressIndex - ON IPBanishments(IPAddress); -CREATE INDEX IF NOT EXISTS IPBanishmentsCharacterIndex - ON IPBanishments(CharacterID); - -CREATE TABLE IF NOT EXISTS Namelocks ( - CharacterID INTEGER NOT NULL, - IPAddress INTEGER NOT NULL, - GamemasterID INTEGER NOT NULL, - Reason TEXT NOT NULL, - Comment TEXT NOT NULL, - Attempts INTEGER NOT NULL DEFAULT 0, - Approved INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY (CharacterID) -); - -CREATE TABLE IF NOT EXISTS Notations ( - CharacterID INTEGER NOT NULL, - IPAddress INTEGER NOT NULL, - GamemasterID INTEGER NOT NULL, - Reason TEXT NOT NULL, - Comment TEXT NOT NULL -); -CREATE INDEX IF NOT EXISTS NotationsCharacterIndex - ON Notations(CharacterID); - -CREATE TABLE IF NOT EXISTS Statements ( - WorldID INTEGER NOT NULL, - Timestamp INTEGER NOT NULL, - StatementID INTEGER NOT NULL, - CharacterID INTEGER NOT NULL, - Channel TEXT NOT NULL, - Text TEXT NOT NULL, - PRIMARY KEY (WorldID, Timestamp, StatementID) -); -CREATE INDEX IF NOT EXISTS StatementsCharacterIndex - ON Statements(CharacterID, Timestamp); - -CREATE TABLE IF NOT EXISTS ReportedStatements ( - WorldID INTEGER NOT NULL, - Timestamp INTEGER NOT NULL, - StatementID INTEGER NOT NULL, - CharacterID INTEGER NOT NULL, - BanishmentID INTEGER NOT NULL, - ReporterID INTEGER NOT NULL, - Reason TEXT NOT NULL, - Comment TEXT NOT NULL, - PRIMARY KEY (WorldID, Timestamp, StatementID) -); -CREATE INDEX IF NOT EXISTS ReportedStatementsCharacterIndex - ON ReportedStatements(CharacterID, Timestamp); -CREATE INDEX IF NOT EXISTS ReportedStatementsBanishmentIndex - ON ReportedStatements(BanishmentID); - --- Info Tables ---============================================================================== -CREATE TABLE IF NOT EXISTS KillStatistics ( - WorldID INTEGER NOT NULL, - RaceName TEXT NOT NULL COLLATE NOCASE, - TimesKilled INTEGER NOT NULL, - PlayersKilled INTEGER NOT NULL, - PRIMARY KEY (WorldID, RaceName) -); - -CREATE TABLE IF NOT EXISTS OnlineCharacters ( - WorldID INTEGER NOT NULL, - Name TEXT NOT NULL COLLATE NOCASE, - Level INTEGER NOT NULL, - Profession TEXT NOT NULL, - PRIMARY KEY (WorldID, Name) -); diff --git a/sqlite/README.txt b/sqlite/README.txt new file mode 100644 index 0000000..26f782a --- /dev/null +++ b/sqlite/README.txt @@ -0,0 +1,10 @@ + The query manager will properly initialize and upgrade the database schema +based on files in this folder. The initial schema should be in `schema.sql` and +upgrades should be in `upgrade-N.sql` where N is a number starting from 1. The +`upgrade-N.sql` file should take the database from version N to N + 1. + The only restriction to these files is that they can't have transaction +statements ("BEGIN", "ROLLBACK", "COMMIT") because the query manager will +already bundle them into a transaction that also sets `user_version`, and +and nested transactions aren't allowed. + The current database version can be retrieved with the "PRAGMA user_version" +query in the SQLite shell. \ No newline at end of file diff --git a/sqlite/init.sql b/sqlite/init.sql new file mode 100644 index 0000000..dc30adf --- /dev/null +++ b/sqlite/init.sql @@ -0,0 +1,103 @@ +-- NOTE(fusion): The query manager WON'T automatically run this but the game +-- server still requires at least the world config to be able to boot up. It +-- is probably a good idea to keep this separated from `schema.sql` and then +-- running it with `sqlite3 -echo tibia.db < sqlite/init.sql`, although it is +-- not mandatory. +-- Because this isn't automatically managed, all queries are wrapped in a +-- transaction to avoid partial writes in case of errors. +--============================================================================== +BEGIN; + +INSERT INTO Worlds (WorldID, Name, Type, RebootTime, Host, Port, MaxPlayers, + PremiumPlayerBuffer, MaxNewbies, PremiumNewbieBuffer) + VALUES (1, 'Zanera', 0, 5, 'localhost', 7172, 1000, 100, 300, 100); + +-- 111111/tibia +INSERT INTO Accounts (AccountID, Email, Auth) + VALUES (111111, '@tibia', X'206699cbc2fae1683118c873d746aa376049cb5923ef0980298bb7acbba527ec9e765668f7a338dffea34acf61a20efb654c1e9c62d35148dba2aeeef8dc7788'); + +INSERT INTO Characters (WorldID, CharacterID, AccountID, Name, Sex) + VALUES (1, 1, 111111, 'Gamemaster', 1), (1, 2, 111111, 'Player', 1); + +INSERT INTO CharacterRights (CharacterID, Right) + VALUES + (1, 'NOTATION'), + (1, 'NAMELOCK'), + (1, 'STATEMENT_REPORT'), + (1, 'BANISHMENT'), + (1, 'FINAL_WARNING'), + (1, 'IP_BANISHMENT'), + (1, 'KICK'), + (1, 'HOME_TELEPORT'), + (1, 'GAMEMASTER_BROADCAST'), + (1, 'ANONYMOUS_BROADCAST'), + (1, 'NO_BANISHMENT'), + (1, 'ALLOW_MULTICLIENT'), + (1, 'LOG_COMMUNICATION'), + (1, 'READ_GAMEMASTER_CHANNEL'), + (1, 'READ_TUTOR_CHANNEL'), + (1, 'HIGHLIGHT_HELP_CHANNEL'), + (1, 'SEND_BUGREPORTS'), + (1, 'NAME_INSULTING'), + (1, 'NAME_SENTENCE'), + (1, 'NAME_NONSENSICAL_LETTERS'), + (1, 'NAME_BADLY_FORMATTED'), + (1, 'NAME_NO_PERSON'), + (1, 'NAME_CELEBRITY'), + (1, 'NAME_COUNTRY'), + (1, 'NAME_FAKE_IDENTITY'), + (1, 'NAME_FAKE_POSITION'), + (1, 'STATEMENT_INSULTING'), + (1, 'STATEMENT_SPAMMING'), + (1, 'STATEMENT_ADVERT_OFFTOPIC'), + (1, 'STATEMENT_ADVERT_MONEY'), + (1, 'STATEMENT_NON_ENGLISH'), + (1, 'STATEMENT_CHANNEL_OFFTOPIC'), + (1, 'STATEMENT_VIOLATION_INCITING'), + (1, 'CHEATING_BUG_ABUSE'), + (1, 'CHEATING_GAME_WEAKNESS'), + (1, 'CHEATING_MACRO_USE'), + (1, 'CHEATING_MODIFIED_CLIENT'), + (1, 'CHEATING_HACKING'), + (1, 'CHEATING_MULTI_CLIENT'), + (1, 'CHEATING_ACCOUNT_TRADING'), + (1, 'CHEATING_ACCOUNT_SHARING'), + (1, 'GAMEMASTER_THREATENING'), + (1, 'GAMEMASTER_PRETENDING'), + (1, 'GAMEMASTER_INFLUENCE'), + (1, 'GAMEMASTER_FALSE_REPORTS'), + (1, 'KILLING_EXCESSIVE_UNJUSTIFIED'), + (1, 'DESTRUCTIVE_BEHAVIOUR'), + (1, 'SPOILING_AUCTION'), + (1, 'INVALID_PAYMENT'), + (1, 'TELEPORT_TO_CHARACTER'), + (1, 'TELEPORT_TO_MARK'), + (1, 'TELEPORT_VERTICAL'), + (1, 'TELEPORT_TO_COORDINATE'), + (1, 'LEVITATE'), + (1, 'SPECIAL_MOVEUSE'), + (1, 'MODIFY_GOSTRENGTH'), + (1, 'SHOW_COORDINATE'), + (1, 'RETRIEVE'), + (1, 'ENTER_HOUSES'), + (1, 'OPEN_NAMEDOORS'), + (1, 'INVULNERABLE'), + (1, 'UNLIMITED_MANA'), + (1, 'KEEP_INVENTORY'), + (1, 'ALL_SPELLS'), + (1, 'UNLIMITED_CAPACITY'), + (1, 'ATTACK_EVERYWHERE'), + (1, 'NO_LOGOUT_BLOCK'), + (1, 'GAMEMASTER_OUTFIT'), + (1, 'ILLUMINATE'), + (1, 'CHANGE_PROFESSION'), + (1, 'IGNORED_BY_MONSTERS'), + (1, 'SHOW_KEYHOLE_NUMBERS'), + (1, 'CREATE_OBJECTS'), + (1, 'CREATE_MONEY'), + (1, 'CREATE_MONSTERS'), + (1, 'CHANGE_SKILLS'), + (1, 'CLEANUP_FIELDS'), + (1, 'NO_STATISTICS'); + +COMMIT; diff --git a/sqlite/schema.sql b/sqlite/schema.sql new file mode 100644 index 0000000..677d778 --- /dev/null +++ b/sqlite/schema.sql @@ -0,0 +1,274 @@ +-- Primary Tables +--============================================================================== +CREATE TABLE IF NOT EXISTS Worlds ( + WorldID INTEGER NOT NULL, + Name TEXT NOT NULL COLLATE NOCASE, + Type INTEGER NOT NULL, + RebootTime INTEGER NOT NULL, + Host TEXT NOT NULL, + Port INTEGER NOT NULL, + MaxPlayers INTEGER NOT NULL, + PremiumPlayerBuffer INTEGER NOT NULL, + MaxNewbies INTEGER NOT NULL, + PremiumNewbieBuffer INTEGER NOT NULL, + OnlineRecord INTEGER NOT NULL DEFAULT 0, + OnlineRecordTimestamp INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (WorldID), + UNIQUE (Name) +); + +CREATE TABLE IF NOT EXISTS Accounts ( + AccountID INTEGER NOT NULL, + Email TEXT NOT NULL COLLATE NOCASE, + Auth BLOB NOT NULL, + PremiumEnd INTEGER NOT NULL DEFAULT 0, + PendingPremiumDays INTEGER NOT NULL DEFAULT 0, + Deleted INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (AccountID), + UNIQUE (Email) +); + +CREATE TABLE IF NOT EXISTS Characters ( + WorldID INTEGER NOT NULL, + CharacterID INTEGER NOT NULL, + AccountID INTEGER NOT NULL, + Name TEXT NOT NULL COLLATE NOCASE, + Sex INTEGER NOT NULL, + Guild TEXT NOT NULL COLLATE NOCASE DEFAULT '', + Rank TEXT NOT NULL COLLATE NOCASE DEFAULT '', + Title TEXT NOT NULL COLLATE NOCASE DEFAULT '', + Level INTEGER NOT NULL DEFAULT 0, + Profession TEXT NOT NULL DEFAULT '', + Residence TEXT NOT NULL DEFAULT '', + LastLoginTime INTEGER NOT NULL DEFAULT 0, + TutorActivities INTEGER NOT NULL DEFAULT 0, + IsOnline INTEGER NOT NULL DEFAULT 0, + Deleted INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (CharacterID), + UNIQUE (Name) +); +CREATE INDEX IF NOT EXISTS CharactersWorldIndex + ON Characters(WorldID, IsOnline); +CREATE INDEX IF NOT EXISTS CharactersAccountIndex + ON Characters(AccountID, IsOnline); +CREATE INDEX IF NOT EXISTS CharactersGuildIndex + ON Characters(Guild, Rank); + +/* +-- TODO(fusion): Have group rights instead of adding individual rights to characters? +ALTER TABLE Characters ADD GroupID INTEGER NOT NULL; +CREATE TABLE IF NOT EXISTS CharacterRights ( + GroupID INTEGER NOT NULL, + Right TEXT NOT NULL COLLATE NOCASE, + PRIMARY KEY(GroupID, Right) +); +*/ + +CREATE TABLE IF NOT EXISTS CharacterRights ( + CharacterID INTEGER NOT NULL, + Right TEXT NOT NULL COLLATE NOCASE, + PRIMARY KEY(CharacterID, Right) +); + +CREATE TABLE IF NOT EXISTS CharacterDeaths ( + CharacterID INTEGER NOT NULL, + Level INTEGER NOT NULL, + OffenderID INTEGER NOT NULL, + Remark TEXT NOT NULL, + Unjustified INTEGER NOT NULL, + Timestamp INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS CharacterDeathsCharacterIndex + ON CharacterDeaths(CharacterID, Level); +CREATE INDEX IF NOT EXISTS CharacterDeathsOffenderIndex + ON CharacterDeaths(OffenderID, Unjustified); + +CREATE TABLE IF NOT EXISTS Buddies ( + WorldID INTEGER NOT NULL, + AccountID INTEGER NOT NULL, + BuddyID INTEGER NOT NULL, + PRIMARY KEY (WorldID, AccountID, BuddyID) +); + +CREATE TABLE IF NOT EXISTS WorldInvitations ( + WorldID INTEGER NOT NULL, + CharacterID INTEGER NOT NULL, + PRIMARY KEY (WorldID, CharacterID) +); + +CREATE TABLE IF NOT EXISTS LoginAttempts ( + AccountID INTEGER NOT NULL, + IPAddress INTEGER NOT NULL, + Timestamp INTEGER NOT NULL, + Failed INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS LoginAttemptsAccountIndex + ON LoginAttempts(AccountID, Timestamp); +CREATE INDEX IF NOT EXISTS LoginAttemptsAddressIndex + ON LoginAttempts(IPAddress, Timestamp); + +-- House Tables +--============================================================================== +CREATE TABLE IF NOT EXISTS Houses ( + WorldID INTEGER NOT NULL, + HouseID INTEGER NOT NULL, + Name TEXT NOT NULL, + Rent INTEGER NOT NULL, + Description TEXT NOT NULL, + Size INTEGER NOT NULL, + PositionX INTEGER NOT NULL, + PositionY INTEGER NOT NULL, + PositionZ INTEGER NOT NULL, + Town TEXT NOT NULL, + GuildHouse INTEGER NOT NULL, + PRIMARY KEY (WorldID, HouseID) +); + +CREATE TABLE IF NOT EXISTS HouseOwners ( + WorldID INTEGER NOT NULL, + HouseID INTEGER NOT NULL, + OwnerID INTEGER NOT NULL, + PaidUntil INTEGER NOT NULL, + PRIMARY KEY (WorldID, HouseID) +); + +-- NOTE(fusion): An auction would have a non null `FinishTime` but it doesn't make +-- sense to finish an auction just to restart it afterwards so it should only be +-- set after the first bid, along with `BidderID` and `BidAmount`. +CREATE TABLE IF NOT EXISTS HouseAuctions ( + WorldID INTEGER NOT NULL, + HouseID INTEGER NOT NULL, + BidderID INTEGER DEFAULT NULL, + BidAmount INTEGER DEFAULT NULL, + FinishTime INTEGER DEFAULT NULL, + PRIMARY KEY (WorldID, HouseID) +); + +CREATE TABLE IF NOT EXISTS HouseTransfers ( + WorldID INTEGER NOT NULL, + HouseID INTEGER NOT NULL, + NewOwnerID INTEGER NOT NULL, + Price INTEGER NOT NULL, + PRIMARY KEY (WorldID, HouseID) +); + +CREATE TABLE IF NOT EXISTS HouseAuctionExclusions ( + CharacterID INTEGER NOT NULL, + Issued INTEGER NOT NULL, + Until INTEGER NOT NULL, + BanishmentID INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS HouseAuctionExclusionsIndex + ON HouseAuctionExclusions(CharacterID, Until); + +CREATE TABLE IF NOT EXISTS HouseAssignments ( + WorldID INTEGER NOT NULL, + HouseID INTEGER NOT NULL, + OwnerID INTEGER NOT NULL, + Price INTEGER NOT NULL, + Timestamp INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS HouseAssignmentsHouseIndex + ON HouseAssignments(WorldID, HouseID); +CREATE INDEX IF NOT EXISTS HouseAssignmentsTimeIndex + ON HouseAssignments(WorldID, Timestamp); +CREATE INDEX IF NOT EXISTS HouseAssignmentsOwnerIndex + ON HouseAssignments(OwnerID); + +-- Banishment Tables +--============================================================================== +CREATE TABLE IF NOT EXISTS Banishments ( + BanishmentID INTEGER NOT NULL, + AccountID INTEGER NOT NULL, + IPAddress INTEGER NOT NULL, + GamemasterID INTEGER NOT NULL, + Reason TEXT NOT NULL, + Comment TEXT NOT NULL, + FinalWarning INTEGER NOT NULL, + Issued INTEGER NOT NULL, + Until INTEGER NOT NULL, + PRIMARY KEY (BanishmentID) +); +CREATE INDEX IF NOT EXISTS BanishmentsAccountIndex + ON Banishments(AccountID, Until, FinalWarning); + +CREATE TABLE IF NOT EXISTS IPBanishments ( + CharacterID INTEGER NOT NULL, + IPAddress INTEGER NOT NULL, + GamemasterID INTEGER NOT NULL, + Reason TEXT NOT NULL, + Comment TEXT NOT NULL, + Issued INTEGER NOT NULL, + Until INTEGER NOT NULL +); +CREATE INDEX IF NOT EXISTS IPBanishmentsAddressIndex + ON IPBanishments(IPAddress); +CREATE INDEX IF NOT EXISTS IPBanishmentsCharacterIndex + ON IPBanishments(CharacterID); + +CREATE TABLE IF NOT EXISTS Namelocks ( + CharacterID INTEGER NOT NULL, + IPAddress INTEGER NOT NULL, + GamemasterID INTEGER NOT NULL, + Reason TEXT NOT NULL, + Comment TEXT NOT NULL, + Attempts INTEGER NOT NULL DEFAULT 0, + Approved INTEGER NOT NULL DEFAULT 0, + PRIMARY KEY (CharacterID) +); + +CREATE TABLE IF NOT EXISTS Notations ( + CharacterID INTEGER NOT NULL, + IPAddress INTEGER NOT NULL, + GamemasterID INTEGER NOT NULL, + Reason TEXT NOT NULL, + Comment TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS NotationsCharacterIndex + ON Notations(CharacterID); + +CREATE TABLE IF NOT EXISTS Statements ( + WorldID INTEGER NOT NULL, + Timestamp INTEGER NOT NULL, + StatementID INTEGER NOT NULL, + CharacterID INTEGER NOT NULL, + Channel TEXT NOT NULL, + Text TEXT NOT NULL, + PRIMARY KEY (WorldID, Timestamp, StatementID) +); +CREATE INDEX IF NOT EXISTS StatementsCharacterIndex + ON Statements(CharacterID, Timestamp); + +CREATE TABLE IF NOT EXISTS ReportedStatements ( + WorldID INTEGER NOT NULL, + Timestamp INTEGER NOT NULL, + StatementID INTEGER NOT NULL, + CharacterID INTEGER NOT NULL, + BanishmentID INTEGER NOT NULL, + ReporterID INTEGER NOT NULL, + Reason TEXT NOT NULL, + Comment TEXT NOT NULL, + PRIMARY KEY (WorldID, Timestamp, StatementID) +); +CREATE INDEX IF NOT EXISTS ReportedStatementsCharacterIndex + ON ReportedStatements(CharacterID, Timestamp); +CREATE INDEX IF NOT EXISTS ReportedStatementsBanishmentIndex + ON ReportedStatements(BanishmentID); + +-- Info Tables +--============================================================================== +CREATE TABLE IF NOT EXISTS KillStatistics ( + WorldID INTEGER NOT NULL, + RaceName TEXT NOT NULL COLLATE NOCASE, + TimesKilled INTEGER NOT NULL, + PlayersKilled INTEGER NOT NULL, + PRIMARY KEY (WorldID, RaceName) +); + +CREATE TABLE IF NOT EXISTS OnlineCharacters ( + WorldID INTEGER NOT NULL, + Name TEXT NOT NULL COLLATE NOCASE, + Level INTEGER NOT NULL, + Profession TEXT NOT NULL, + PRIMARY KEY (WorldID, Name) +); diff --git a/src/connections.cc b/src/connections.cc index b08b52a..0760514 100644 --- a/src/connections.cc +++ b/src/connections.cc @@ -612,7 +612,7 @@ void ProcessConnections(void){ ASSERT(NumFds > 0); int NumEvents = poll(Fds, NumFds, -1); if(NumEvents == -1){ - if(errno != ETIMEDOUT){ + if(errno != ETIMEDOUT && errno != EINTR){ LOG_ERR("Failed to poll connections: (%d) %s", errno, strerrordesc_np(errno)); } diff --git a/src/database_mysql.cc b/src/database_mysql.cc new file mode 100644 index 0000000..583bb7d --- /dev/null +++ b/src/database_mysql.cc @@ -0,0 +1,6 @@ +#if DATABASE_MYSQL +#include "querymanager.hh" + +// TODO + +#endif //DATABASE_MYSQL diff --git a/src/database_postgres.cc b/src/database_postgres.cc new file mode 100644 index 0000000..3b24967 --- /dev/null +++ b/src/database_postgres.cc @@ -0,0 +1,6 @@ +#if DATABASE_POSTGRESQL +#include "querymanager.hh" + +// TODO + +#endif //DATABASE_POSTGRESQL diff --git a/src/database_pq.cc b/src/database_pq.cc deleted file mode 100644 index 69cc45e..0000000 --- a/src/database_pq.cc +++ /dev/null @@ -1,4 +0,0 @@ -#include "querymanager.hh" - -// TODO(fusion): - diff --git a/src/database_sqlite.cc b/src/database_sqlite.cc index 8a5e526..37a62e0 100644 --- a/src/database_sqlite.cc +++ b/src/database_sqlite.cc @@ -1,3 +1,4 @@ +#if DATABASE_SQLITE #include "querymanager.hh" #include "sqlite3.h" @@ -43,8 +44,8 @@ public: static void EnsureStatementCache(TDatabase *Database){ if(Database->CachedStatements == NULL){ - ASSERT(g_Config.MaxCachedStatements > 0); - Database->MaxCachedStatements = g_Config.MaxCachedStatements; + ASSERT(g_Config.SQLite.MaxCachedStatements > 0); + Database->MaxCachedStatements = g_Config.SQLite.MaxCachedStatements; Database->CachedStatements = (TCachedStatement*)calloc( Database->MaxCachedStatements, sizeof(TCachedStatement)); } @@ -233,8 +234,8 @@ static bool InitDatabaseSchema(TDatabase *Database){ return false; } - if(!ExecFile(Database, "sql/schema.sql")){ - LOG_ERR("Failed to execute \"sql/schema.sql\""); + if(!ExecFile(Database, "sqlite/schema.sql")){ + LOG_ERR("Failed to execute \"sqlite/schema.sql\""); return false; } @@ -255,7 +256,7 @@ static bool UpgradeDatabaseSchema(TDatabase *Database, int UserVersion){ char FileName[256]; int NewVersion = UserVersion; while(true){ - snprintf(FileName, sizeof(FileName), "sql/upgrade-%d.sql", NewVersion); + snprintf(FileName, sizeof(FileName), "sqlite/upgrade-%d.sql", NewVersion); if(FileExists(FileName)){ NewVersion += 1; }else{ @@ -346,9 +347,9 @@ void DatabaseClose(TDatabase *Database){ TDatabase *DatabaseOpen(void){ TDatabase *Database = (TDatabase*)calloc(1, sizeof(TDatabase)); int Flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; - if(sqlite3_open_v2(g_Config.DatabaseFile, &Database->Handle, Flags, NULL) != SQLITE_OK){ + if(sqlite3_open_v2(g_Config.SQLite.File, &Database->Handle, Flags, NULL) != SQLITE_OK){ LOG_ERR("Failed to open database at \"%s\": %s\n", - g_Config.DatabaseFile, sqlite3_errmsg(Database->Handle)); + g_Config.SQLite.File, sqlite3_errmsg(Database->Handle)); DatabaseClose(Database); return NULL; } @@ -357,7 +358,7 @@ TDatabase *DatabaseOpen(void){ LOG_ERR("Failed to open database file \"%s\" with WRITE PERMISSIONS." " Make sure it has the appropriate permissions and is owned" " by the same user running the query manager.", - g_Config.DatabaseFile); + g_Config.SQLite.File); DatabaseClose(Database); return NULL; } @@ -2482,3 +2483,5 @@ bool CheckOnlineRecord(TDatabase *Database, int WorldID, int NumCharacters, bool *NewRecord = sqlite3_changes(Database->Handle) > 0; return true; } + +#endif //DATABASE_SQLITE diff --git a/src/querymanager.cc b/src/querymanager.cc index 6c1fffe..004bc35 100644 --- a/src/querymanager.cc +++ b/src/querymanager.cc @@ -67,6 +67,9 @@ int64 GetClockMonotonicMS(void){ QueryPerformanceFrequency(&Frequency); return (int64)((Counter.QuadPart * 1000) / Frequency.QuadPart); #else + // NOTE(fusion): The coarse monotonic clock has a larger resolution but is + // supposed to be faster, even avoiding system calls in some cases. It should + // be fine for millisecond precision which is what we're using. struct timespec Time; clock_gettime(CLOCK_MONOTONIC_COARSE, &Time); return ((int64)Time.tv_sec * 1000) @@ -374,22 +377,46 @@ bool ReadConfig(const char *FileName, TConfig *Config){ ReadIntegerConfig(&Config->MaxCachedHostNames, Val); }else if(StringEqCI(Key, "HostNameExpireTime")){ ReadDurationConfig(&Config->HostNameExpireTime, Val); - }else if(StringEqCI(Key, "MaxCachedStatements")){ - ReadIntegerConfig(&Config->MaxCachedStatements, Val); - }else if(StringEqCI(Key, "DatabaseFile")){ - ReadStringBufConfig(Config->DatabaseFile, Val); - }else if(StringEqCI(Key, "DatabaseHost")){ - ReadStringBufConfig(Config->DatabaseHost, Val); - }else if(StringEqCI(Key, "DatabasePort")){ - ReadIntegerConfig(&Config->DatabasePort, Val); - }else if(StringEqCI(Key, "DatabaseUser")){ - ReadStringBufConfig(Config->DatabaseUser, Val); - }else if(StringEqCI(Key, "DatabasePassword")){ - ReadStringBufConfig(Config->DatabasePassword, Val); - }else if(StringEqCI(Key, "DatabaseName")){ - ReadStringBufConfig(Config->DatabaseName, Val); - }else if(StringEqCI(Key, "DatabaseTLS")){ - ReadBooleanConfig(&Config->DatabaseTLS, Val); +#if DATABASE_SQLITE + }else if(StringEqCI(Key, "SQLite.File")){ + ReadStringBufConfig(Config->SQLite.File, Val); + }else if(StringEqCI(Key, "SQLite.MaxCachedStatements")){ + ReadIntegerConfig(&Config->SQLite.MaxCachedStatements, Val); +#elif DATABASE_POSTGRESQL + }else if(StringEqCI(Key, "PostgreSQL.UnixSocket")){ + ReadStringBufConfig(Config->PostgreSQL.UnixSocket, Val); + }else if(StringEqCI(Key, "PostgreSQL.Host")){ + ReadStringBufConfig(Config->PostgreSQL.Host, Val); + }else if(StringEqCI(Key, "PostgreSQL.Port")){ + ReadIntegerConfig(&Config->PostgreSQL.Port, Val); + }else if(StringEqCI(Key, "PostgreSQL.User")){ + ReadStringBufConfig(Config->PostgreSQL.User, Val); + }else if(StringEqCI(Key, "PostgreSQL.Password")){ + ReadStringBufConfig(Config->PostgreSQL.Password, Val); + }else if(StringEqCI(Key, "PostgreSQL.Database")){ + ReadStringBufConfig(Config->PostgreSQL.Database, Val); + }else if(StringEqCI(Key, "PostgreSQL.TLS")){ + ReadBooleanConfig(&Config->PostgreSQL.TLS, Val); + }else if(StringEqCI(Key, "PostgreSQL.MaxCachedStatements")){ + ReadIntegerConfig(&Config->PostgreSQL.MaxCachedStatements, Val); +#elif DATABASE_MYSQL + }else if(StringEqCI(Key, "MySQL.UnixSocket")){ + ReadStringBufConfig(Config->MySQL.UnixSocket, Val); + }else if(StringEqCI(Key, "MySQL.Host")){ + ReadStringBufConfig(Config->MySQL.Host, Val); + }else if(StringEqCI(Key, "MySQL.Port")){ + ReadIntegerConfig(&Config->MySQL.Port, Val); + }else if(StringEqCI(Key, "MySQL.User")){ + ReadStringBufConfig(Config->MySQL.User, Val); + }else if(StringEqCI(Key, "MySQL.Password")){ + ReadStringBufConfig(Config->MySQL.Password, Val); + }else if(StringEqCI(Key, "MySQL.Database")){ + ReadStringBufConfig(Config->MySQL.Database, Val); + }else if(StringEqCI(Key, "MySQL.TLS")){ + ReadBooleanConfig(&Config->MySQL.TLS, Val); + }else if(StringEqCI(Key, "MySQL.MaxCachedStatements")){ + ReadIntegerConfig(&Config->MySQL.MaxCachedStatements, Val); +#endif }else if(StringEqCI(Key, "QueryManagerPort")){ ReadIntegerConfig(&Config->QueryManagerPort, Val); }else if(StringEqCI(Key, "QueryManagerPassword")){ @@ -445,27 +472,41 @@ int main(int argc, const char **argv){ g_StartTimeMS = GetClockMonotonicMS(); // HostCache Config - g_Config.MaxCachedHostNames = 100; - g_Config.HostNameExpireTime = 30 * 60 * 1000; // milliseconds + g_Config.MaxCachedHostNames = 100; + g_Config.HostNameExpireTime = 30 * 60 * 1000; // milliseconds // Database Config - g_Config.MaxCachedStatements = 100; - StringBufCopy(g_Config.DatabaseFile, "tibia.db"); - StringBufCopy(g_Config.DatabaseHost, "localhost"); - g_Config.DatabasePort = 5432; - StringBufCopy(g_Config.DatabaseUser, "tibia"); - StringBufCopy(g_Config.DatabasePassword, ""); - StringBufCopy(g_Config.DatabaseName, ""); - g_Config.DatabaseTLS = true; +#if DATABASE_SQLITE + StringBufCopy(g_Config.SQLite.File, "tibia.db"); + g_Config.SQLite.MaxCachedStatements = 100; +#elif DATABASE_POSTGRESQL + StringBufCopy(g_Config.PostgreSQL.UnixSocket, ""); + StringBufCopy(g_Config.PostgreSQL.Host, "localhost"); + g_Config.PostgreSQL.Port = 5432; + StringBufCopy(g_Config.PostgreSQL.User, "postgres"); + StringBufCopy(g_Config.PostgreSQL.Password, ""); + StringBufCopy(g_Config.PostgreSQL.Database, "tibia"); + g_Config.PostgreSQL.TLS = true; + g_Config.PostgreSQL.MaxCachedStatements = 100; +#elif DATABASE_MYSQL + StringBufCopy(g_Config.MySQL.UnixSocket, ""); + StringBufCopy(g_Config.MySQL.Host, "localhost"); + g_Config.MySQL.Port = 5432; + StringBufCopy(g_Config.MySQL.User, "postgres"); + StringBufCopy(g_Config.MySQL.Password, ""); + StringBufCopy(g_Config.MySQL.Database, "tibia"); + g_Config.MySQL.TLS = true; + g_Config.MySQL.MaxCachedStatements = 100; +#endif // Connection Config - g_Config.QueryManagerPort = 7174; + g_Config.QueryManagerPort = 7174; StringBufCopy(g_Config.QueryManagerPassword, ""); - g_Config.QueryWorkerThreads = 1; - g_Config.QueryBufferSize = (int)MB(1); - g_Config.QueryMaxAttempts = 2; - g_Config.MaxConnections = 50; - g_Config.MaxConnectionIdleTime = 60 * 1000; // milliseconds + g_Config.QueryWorkerThreads = 1; + g_Config.QueryBufferSize = (int)MB(1); + g_Config.QueryMaxAttempts = 3; + g_Config.MaxConnections = 25; + g_Config.MaxConnectionIdleTime = 60 * 1000; // milliseconds LOG("Tibia Query Manager v0.2"); if(!ReadConfig("config.cfg", &g_Config)){ @@ -473,21 +514,34 @@ int main(int argc, const char **argv){ } // NOTE(fusion): Print config values for debugging purposes. - LOG("Max cached host names: %d", g_Config.MaxCachedHostNames); - LOG("Host name expire time: %dms", g_Config.HostNameExpireTime); - LOG("Max cached statements: %d", g_Config.MaxCachedStatements); - LOG("Database file: \"%s\"", g_Config.DatabaseFile); - LOG("Database host: \"%s\"", g_Config.DatabaseHost); - LOG("Database port: %d", g_Config.DatabasePort); - LOG("Database user: \"%s\"", g_Config.DatabaseUser); - LOG("Database name: \"%s\"", g_Config.DatabaseName); - LOG("Database TLS: %s", g_Config.DatabaseTLS ? "true" : "false"); - LOG("Query manager port: %d", g_Config.QueryManagerPort); - LOG("Query worker threads: %d", g_Config.QueryWorkerThreads); - LOG("Query buffer size: %d", g_Config.QueryBufferSize); - LOG("Query max attempts: %d", g_Config.QueryMaxAttempts); - LOG("Max connections: %d", g_Config.MaxConnections); - LOG("Max connection idle time: %dms", g_Config.MaxConnectionIdleTime); + LOG("Max cached host names: %d", g_Config.MaxCachedHostNames); + LOG("Host name expire time: %dms", g_Config.HostNameExpireTime); +#if DATABASE_SQLITE + LOG("SQLite file: \"%s\"", g_Config.SQLite.File); + LOG("SQLite max cached statements: %d", g_Config.SQLite.MaxCachedStatements); +#elif DATABASE_POSTGRESQL + LOG("PostgreSQL unix socket: \"%s\"", g_Config.PostgreSQL.UnixSocket); + LOG("PostgreSQL host: \"%s\"", g_Config.PostgreSQL.Host); + LOG("PostgreSQL port: %d", g_Config.PostgreSQL.Port); + LOG("PostgreSQL user: \"%s\"", g_Config.PostgreSQL.User); + LOG("PostgreSQL database: \"%s\"", g_Config.PostgreSQL.Database); + LOG("PostgreSQL TLS: %s", g_Config.PostgreSQL.TLS ? "true" : "false"); + LOG("PostgreSQL max cached statements: %d", g_Config.PostgreSQL.MaxCachedStatements); +#elif DATABASE_MYSQL + LOG("MySQL unix socket: \"%s\"", g_Config.MySQL.UnixSocket); + LOG("MySQL host: \"%s\"", g_Config.MySQL.Host); + LOG("MySQL port: %d", g_Config.MySQL.Port); + LOG("MySQL user: \"%s\"", g_Config.MySQL.User); + LOG("MySQL database: \"%s\"", g_Config.MySQL.Database); + LOG("MySQL TLS: %s", g_Config.MySQL.TLS ? "true" : "false"); + LOG("MySQL max cached statements: %d", g_Config.MySQL.MaxCachedStatements); +#endif + LOG("Query manager port: %d", g_Config.QueryManagerPort); + LOG("Query worker threads: %d", g_Config.QueryWorkerThreads); + LOG("Query buffer size: %d", g_Config.QueryBufferSize); + LOG("Query max attempts: %d", g_Config.QueryMaxAttempts); + LOG("Max connections: %d", g_Config.MaxConnections); + LOG("Max connection idle time: %dms", g_Config.MaxConnectionIdleTime); if(!CheckSHA256()){ return EXIT_FAILURE; diff --git a/src/querymanager.hh b/src/querymanager.hh index 15234e9..2ae12b1 100644 --- a/src/querymanager.hh +++ b/src/querymanager.hh @@ -75,20 +75,44 @@ typedef size_t usize; TRAP(); \ }while(0) +#if !DATABASE_SQLITE && !DATABASE_POSTGRESQL && !DATABASE_MYSQL +# error "No database system defined." +#endif + struct TConfig{ // HostCache Config int MaxCachedHostNames; int HostNameExpireTime; // Database Config - int MaxCachedStatements; - char DatabaseFile[100]; - char DatabaseHost[100]; - int DatabasePort; - char DatabaseUser[30]; - char DatabasePassword[30]; - char DatabaseName[30]; - bool DatabaseTLS; +#if DATABASE_SQLITE + struct{ + char File[100]; + int MaxCachedStatements; + } SQLite; +#elif DATABASE_POSTGRESQL + struct{ + char UnixSocket[100]; + char Host[100]; + int Port; + char User[30]; + char Password[30]; + char Database[30]; + bool TLS; + int MaxCachedStatements; + } PostgreSQL; +#elif DATABASE_MYSQL + struct{ + char UnixSocket[100]; + char Host[100]; + int Port; + char User[30]; + char Password[30]; + char Database[30]; + bool TLS; + int MaxCachedStatements; + } MySQL; +#endif // Connection Config int QueryManagerPort; -- cgit v1.2.3