diff --git a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs index 88a07cd3e..a26fb45b7 100644 --- a/Arrowgene.Ddon.Cli/Command/ServerCommand.cs +++ b/Arrowgene.Ddon.Cli/Command/ServerCommand.cs @@ -102,6 +102,7 @@ public CommandResultType Run(CommandParameter parameter) if (_database == null) { + _setting.DatabaseSetting ??= new DatabaseSetting(); _database = DdonDatabaseBuilder.Build(_setting.DatabaseSetting); } diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj index 5dff9fa84..82bb7f672 100644 --- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj +++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj @@ -14,6 +14,8 @@ + + diff --git a/Arrowgene.Ddon.Database/DatabaseSetting.cs b/Arrowgene.Ddon.Database/DatabaseSetting.cs index 63ced11b4..cf85c5e3c 100644 --- a/Arrowgene.Ddon.Database/DatabaseSetting.cs +++ b/Arrowgene.Ddon.Database/DatabaseSetting.cs @@ -3,7 +3,6 @@ using System.Runtime.Serialization; using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared; -using Arrowgene.Ddon.Shared.Network; namespace Arrowgene.Ddon.Database { @@ -12,40 +11,61 @@ public class DatabaseSetting { public DatabaseSetting() { - Type = DatabaseType.SQLite; - SqLiteFolder = Path.Combine(Util.ExecutingDirectory(), "Files/Database"); + Type = "sqlite"; + DatabaseFolder = Path.Combine(Util.ExecutingDirectory(), "Files/Database"); Host = "localhost"; Port = 3306; Database = "Ddon"; User = string.Empty; Password = string.Empty; - WipeOnStartup = true; + WipeOnStartup = false; string envDbType = Environment.GetEnvironmentVariable("DB_TYPE"); - switch (envDbType) + if (!string.IsNullOrEmpty(envDbType)) { - case "sqlite": - Type = DatabaseType.SQLite; - break; + Type = envDbType; + } + string envDbFolder = Environment.GetEnvironmentVariable("DB_FOLDER"); + if (!string.IsNullOrEmpty(envDbFolder)) + { + DatabaseFolder = envDbFolder; + } + string envDbHost = Environment.GetEnvironmentVariable("DB_HOST"); + if (!string.IsNullOrEmpty(envDbHost)) + { + Host = envDbHost; + } + string envDbPort = Environment.GetEnvironmentVariable("DB_PORT"); + if (!string.IsNullOrEmpty(envDbPort)) + { + Port = Convert.ToInt16(envDbPort); + } + string envDbDatabase = Environment.GetEnvironmentVariable("DB_DATABASE"); + if (!string.IsNullOrEmpty(envDbDatabase)) + { + Database = envDbDatabase; } - string envDbUser = Environment.GetEnvironmentVariable("DB_USER"); if (!string.IsNullOrEmpty(envDbUser)) { User = envDbUser; } - string envDbPass = Environment.GetEnvironmentVariable("DB_PASS"); if (!string.IsNullOrEmpty(envDbPass)) { Password = envDbPass; } + string envDbWipeOnStartup = Environment.GetEnvironmentVariable("DB_WIPE_ON_STARTUP"); + if (!string.IsNullOrEmpty(envDbWipeOnStartup)) + { + WipeOnStartup = Convert.ToBoolean(envDbWipeOnStartup); + } } public DatabaseSetting(DatabaseSetting databaseSettings) { Type = databaseSettings.Type; - SqLiteFolder = databaseSettings.SqLiteFolder; + DatabaseFolder = databaseSettings.DatabaseFolder; Host = databaseSettings.Host; Port = databaseSettings.Port; User = databaseSettings.User; @@ -54,9 +74,9 @@ public DatabaseSetting(DatabaseSetting databaseSettings) WipeOnStartup = databaseSettings.WipeOnStartup; } - [DataMember(Order = 0)] public DatabaseType Type { get; set; } + [DataMember(Order = 0)] public string Type { get; set; } - [DataMember(Order = 1)] public string SqLiteFolder { get; set; } + [DataMember(Order = 1)] public string DatabaseFolder { get; set; } [DataMember(Order = 2)] public string Host { get; set; } @@ -68,6 +88,6 @@ public DatabaseSetting(DatabaseSetting databaseSettings) [DataMember(Order = 6)] public string Database { get; set; } - [DataMember(Order = 6)] public bool WipeOnStartup { get; set; } + [DataMember(Order = 7)] public bool WipeOnStartup { get; set; } } } diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs index 5c8770252..3a045f9aa 100644 --- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs +++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Text; +using System.Text.RegularExpressions; using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Database.Sql; using Arrowgene.Logging; @@ -9,46 +11,75 @@ namespace Arrowgene.Ddon.Database public static class DdonDatabaseBuilder { private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder)); + private const string DefaultSchemaFile = "Script/schema_sqlite.sql"; public static IDatabase Build(DatabaseSetting settings) { - IDatabase database = null; - switch (settings.Type) + Enum.TryParse(settings.Type, true, out DatabaseType dbType); + IDatabase database = dbType switch { - case DatabaseType.SQLite: - string sqLitePath = Path.Combine(settings.SqLiteFolder, $"db.v{DdonSqLiteDb.Version}.sqlite"); - database = BuildSqLite(settings.SqLiteFolder, sqLitePath, settings.WipeOnStartup); - break; - } + DatabaseType.SQLite => BuildSqLite(settings.DatabaseFolder, settings.WipeOnStartup), + DatabaseType.PostgreSQL => BuildPostgres(settings.DatabaseFolder, settings.Host, settings.User, settings.Password, settings.Database, settings.WipeOnStartup), + DatabaseType.MariaDb => BuildMariaDB(settings.DatabaseFolder, settings.Host, settings.User, settings.Password, settings.Database, settings.WipeOnStartup), + _ => throw new ArgumentOutOfRangeException($"Unknown database type '{settings.Type}' encountered!") + }; if (database == null) { Logger.Error("Database could not be created, exiting..."); Environment.Exit(1); } + else + { + Logger.Info($"Database of type '${dbType.ToString()}' has been created."); + Logger.Info($"Database path: {settings.DatabaseFolder}"); + } return database; } - public static DdonSqLiteDb BuildSqLite(string sqLiteFolder, string sqLitePath, bool deleteIfExists) + public static DdonSqLiteDb BuildSqLite(string databaseFolder, bool wipeOnStartup) + { + string sqLitePath = Path.Combine(databaseFolder, $"db.v{DdonSqLiteDb.Version}.sqlite"); + DdonSqLiteDb db = new DdonSqLiteDb(sqLitePath, wipeOnStartup); + if (db.CreateDatabase()) + { + string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile); + String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8); + + db.Execute(schema); + } + + return db; + } + + public static DdonPostgresDb BuildPostgres(string databaseFolder, string host, string user, string password, string database, bool wipeOnStartup) { - if (deleteIfExists) + DdonPostgresDb db = new DdonPostgresDb(host, user, password, database, wipeOnStartup); + if (db.CreateDatabase()) { - try - { - File.Delete(sqLitePath); - } - catch (Exception) - { - // ignored - } + string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile); + String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8); + schema = Regex.Replace(schema, "(\\s)DATETIME(\\s|,)", "$1TIMESTAMP WITH TIME ZONE$2"); + schema = Regex.Replace(schema, "(\\s)INTEGER PRIMARY KEY AUTOINCREMENT(\\s|,)", "$1SERIAL PRIMARY KEY$2"); + schema = Regex.Replace(schema, "(\\s)BLOB(\\s|,)", "$1BYTEA$2"); + + db.Execute(schema); } - DdonSqLiteDb db = new DdonSqLiteDb(sqLitePath); + return db; + } + + public static DdonMariaDb BuildMariaDB(string databaseFolder, string host, string user, string password, string database, bool wipeOnStartup) + { + DdonMariaDb db = new DdonMariaDb(host, user, password, database, wipeOnStartup); if (db.CreateDatabase()) { - ScriptRunner scriptRunner = new ScriptRunner(db); - scriptRunner.Run(Path.Combine(sqLiteFolder, "Script/schema_sqlite.sql")); + string schemaFilePath = Path.Combine(databaseFolder, DefaultSchemaFile); + String schema = File.ReadAllText(schemaFilePath, Encoding.UTF8); + schema = Regex.Replace(schema, "(\\s)AUTOINCREMENT(\\s|,)", "$1AUTO_INCREMENT$2"); + + db.Execute(schema); } return db; diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql index 83e489fe4..35e69fae4 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql @@ -1,419 +1,420 @@ -CREATE TABLE IF NOT EXISTS `setting` +CREATE TABLE IF NOT EXISTS setting ( - `key` TEXT NOT NULL, - `value` TEXT NOT NULL, - PRIMARY KEY (`key`) + "key" VARCHAR(32) NOT NULL, + "value" TEXT NOT NULL, + PRIMARY KEY ("key") ); -CREATE TABLE IF NOT EXISTS `account` +CREATE TABLE IF NOT EXISTS account ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `name` TEXT NOT NULL, - `normal_name` TEXT NOT NULL, - `hash` TEXT NOT NULL, - `mail` TEXT NOT NULL, - `mail_verified` INTEGER NOT NULL, - `mail_verified_at` DATETIME DEFAULT NULL, - `mail_token` TEXT DEFAULT NULL, - `password_token` TEXT DEFAULT NULL, - `login_token` TEXT DEFAULT NULL, - `login_token_created` DATETIME DEFAULT NULL, - `state` INTEGER NOT NULL, - `last_login` DATETIME DEFAULT NULL, - `created` DATETIME NOT NULL, - CONSTRAINT `uq_account_name` UNIQUE (`name`), - CONSTRAINT `uq_account_normal_name` UNIQUE (`normal_name`), - CONSTRAINT `uq_account_login_token` UNIQUE (`login_token`), - CONSTRAINT `uq_account_mail` UNIQUE (`mail`) + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "name" TEXT NOT NULL, + "normal_name" TEXT NOT NULL, + "hash" TEXT NOT NULL, + "mail" TEXT NOT NULL, + "mail_verified" BOOLEAN NOT NULL, + "mail_verified_at" DATETIME DEFAULT NULL, + "mail_token" TEXT DEFAULT NULL, + "password_token" TEXT DEFAULT NULL, + "login_token" TEXT DEFAULT NULL, + "login_token_created" DATETIME DEFAULT NULL, + "state" INTEGER NOT NULL, + "last_login" DATETIME DEFAULT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_account_name UNIQUE ("name"), + CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"), + CONSTRAINT uq_account_login_token UNIQUE ("login_token"), + CONSTRAINT uq_account_mail UNIQUE ("mail") ); -CREATE TABLE IF NOT EXISTS `ddon_character_common` +CREATE TABLE IF NOT EXISTS ddon_character_common ( - `character_common_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `job` TINYINT NOT NULL, - `hide_equip_head` BIT NOT NULL, - `hide_equip_lantern` BIT NOT NULL, - `jewelry_slot_num` TINYINT NOT NULL + "character_common_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "job" SMALLINT NOT NULL, + "hide_equip_head" BOOLEAN NOT NULL, + "hide_equip_lantern" BOOLEAN NOT NULL, + "jewelry_slot_num" SMALLINT NOT NULL ); -CREATE TABLE IF NOT EXISTS `ddon_character` +CREATE TABLE IF NOT EXISTS ddon_character ( - `character_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `character_common_id` INTEGER NOT NULL, - `account_id` INTEGER NOT NULL, - `version` INTEGER NOT NULL, - `first_name` TEXT NOT NULL, - `last_name` TEXT NOT NULL, - `created` DATETIME NOT NULL, - `my_pawn_slot_num` TINYINT NOT NULL, - `rental_pawn_slot_num` TINYINT NOT NULL, - `hide_equip_head_pawn` BIT NOT NULL, - `hide_equip_lantern_pawn` BIT NOT NULL, - `arisen_profile_share_range` TINYINT NOT NULL, - CONSTRAINT `fk_character_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE, - CONSTRAINT `fk_character_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE + "character_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "character_common_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "version" INTEGER NOT NULL, + "first_name" TEXT NOT NULL, + "last_name" TEXT NOT NULL, + "created" DATETIME NOT NULL, + "my_pawn_slot_num" SMALLINT NOT NULL, + "rental_pawn_slot_num" SMALLINT NOT NULL, + "hide_equip_head_pawn" BOOLEAN NOT NULL, + "hide_equip_lantern_pawn" BOOLEAN NOT NULL, + "arisen_profile_share_range" SMALLINT NOT NULL, + CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_pawn` +CREATE TABLE IF NOT EXISTS ddon_pawn ( - `pawn_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `character_common_id` INTEGER NOT NULL, - `character_id` INTEGER NOT NULL, - `name` TEXT NOT NULL, - `hm_type` TINYINT NOT NULL, - `pawn_type` TINYINT NOT NULL, - CONSTRAINT `fk_character_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE, - CONSTRAINT `fk_character_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "pawn_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "character_common_id" INTEGER NOT NULL, + "character_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "hm_type" SMALLINT NOT NULL, + "pawn_type" SMALLINT NOT NULL, + CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_edit_info` +CREATE TABLE IF NOT EXISTS ddon_edit_info ( - `character_common_id` INTEGER PRIMARY KEY NOT NULL, - `sex` BIT NOT NULL, - `voice` TINYINT NOT NULL, - `voice_pitch` SMALLINT NOT NULL, - `personality` TINYINT NOT NULL, - `speech_freq` TINYINT NOT NULL, - `body_type` TINYINT NOT NULL, - `hair` TINYINT NOT NULL, - `beard` TINYINT NOT NULL, - `makeup` TINYINT NOT NULL, - `scar` TINYINT NOT NULL, - `eye_preset_no` TINYINT NOT NULL, - `nose_preset_no` TINYINT NOT NULL, - `mouth_preset_no` TINYINT NOT NULL, - `eyebrow_tex_no` TINYINT NOT NULL, - `color_skin` TINYINT NOT NULL, - `color_hair` TINYINT NOT NULL, - `color_beard` TINYINT NOT NULL, - `color_eyebrow` TINYINT NOT NULL, - `color_r_eye` TINYINT NOT NULL, - `color_l_eye` TINYINT NOT NULL, - `color_makeup` TINYINT NOT NULL, - `sokutobu` SMALLINT NOT NULL, - `hitai` SMALLINT NOT NULL, - `mimi_jyouge` SMALLINT NOT NULL, - `kannkaku` SMALLINT NOT NULL, - `mabisasi_jyouge` SMALLINT NOT NULL, - `hanakuchi_jyouge` SMALLINT NOT NULL, - `ago_saki_haba` SMALLINT NOT NULL, - `ago_zengo` SMALLINT NOT NULL, - `ago_saki_jyouge` SMALLINT NOT NULL, - `hitomi_ookisa` SMALLINT NOT NULL, - `me_ookisa` SMALLINT NOT NULL, - `me_kaiten` SMALLINT NOT NULL, - `mayu_kaiten` SMALLINT NOT NULL, - `mimi_ookisa` SMALLINT NOT NULL, - `mimi_muki` SMALLINT NOT NULL, - `elf_mimi` SMALLINT NOT NULL, - `miken_takasa` SMALLINT NOT NULL, - `miken_haba` SMALLINT NOT NULL, - `hohobone_ryou` SMALLINT NOT NULL, - `hohobone_jyouge` SMALLINT NOT NULL, - `hohoniku` SMALLINT NOT NULL, - `erahone_jyouge` SMALLINT NOT NULL, - `erahone_haba` SMALLINT NOT NULL, - `hana_jyouge` SMALLINT NOT NULL, - `hana_haba` SMALLINT NOT NULL, - `hana_takasa` SMALLINT NOT NULL, - `hana_kakudo` SMALLINT NOT NULL, - `kuchi_haba` SMALLINT NOT NULL, - `kuchi_atsusa` SMALLINT NOT NULL, - `eyebrow_uv_offset_x` SMALLINT NOT NULL, - `eyebrow_uv_offset_y` SMALLINT NOT NULL, - `wrinkle` SMALLINT NOT NULL, - `wrinkle_albedo_blend_rate` SMALLINT NOT NULL, - `wrinkle_detail_normal_power` SMALLINT NOT NULL, - `muscle_albedo_blend_rate` SMALLINT NOT NULL, - `muscle_detail_normal_power` SMALLINT NOT NULL, - `height` SMALLINT NOT NULL, - `head_size` SMALLINT NOT NULL, - `neck_offset` SMALLINT NOT NULL, - `neck_scale` SMALLINT NOT NULL, - `upper_body_scale_x` SMALLINT NOT NULL, - `belly_size` SMALLINT NOT NULL, - `teat_scale` SMALLINT NOT NULL, - `tekubi_size` SMALLINT NOT NULL, - `koshi_offset` SMALLINT NOT NULL, - `koshi_size` SMALLINT NOT NULL, - `ankle_offset` SMALLINT NOT NULL, - `fat` SMALLINT NOT NULL, - `muscle` SMALLINT NOT NULL, - `motion_filter` SMALLINT NOT NULL, - CONSTRAINT `fk_edit_info_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "sex" SMALLINT NOT NULL, + "voice" SMALLINT NOT NULL, + "voice_pitch" SMALLINT NOT NULL, + "personality" SMALLINT NOT NULL, + "speech_freq" SMALLINT NOT NULL, + "body_type" SMALLINT NOT NULL, + "hair" SMALLINT NOT NULL, + "beard" SMALLINT NOT NULL, + "makeup" SMALLINT NOT NULL, + "scar" SMALLINT NOT NULL, + "eye_preset_no" SMALLINT NOT NULL, + "nose_preset_no" SMALLINT NOT NULL, + "mouth_preset_no" SMALLINT NOT NULL, + "eyebrow_tex_no" SMALLINT NOT NULL, + "color_skin" SMALLINT NOT NULL, + "color_hair" SMALLINT NOT NULL, + "color_beard" SMALLINT NOT NULL, + "color_eyebrow" SMALLINT NOT NULL, + "color_r_eye" SMALLINT NOT NULL, + "color_l_eye" SMALLINT NOT NULL, + "color_makeup" SMALLINT NOT NULL, + "sokutobu" SMALLINT NOT NULL, + "hitai" SMALLINT NOT NULL, + "mimi_jyouge" SMALLINT NOT NULL, + "kannkaku" SMALLINT NOT NULL, + "mabisasi_jyouge" SMALLINT NOT NULL, + "hanakuchi_jyouge" SMALLINT NOT NULL, + "ago_saki_haba" SMALLINT NOT NULL, + "ago_zengo" SMALLINT NOT NULL, + "ago_saki_jyouge" SMALLINT NOT NULL, + "hitomi_ookisa" SMALLINT NOT NULL, + "me_ookisa" SMALLINT NOT NULL, + "me_kaiten" SMALLINT NOT NULL, + "mayu_kaiten" SMALLINT NOT NULL, + "mimi_ookisa" SMALLINT NOT NULL, + "mimi_muki" SMALLINT NOT NULL, + "elf_mimi" SMALLINT NOT NULL, + "miken_takasa" SMALLINT NOT NULL, + "miken_haba" SMALLINT NOT NULL, + "hohobone_ryou" SMALLINT NOT NULL, + "hohobone_jyouge" SMALLINT NOT NULL, + "hohoniku" SMALLINT NOT NULL, + "erahone_jyouge" SMALLINT NOT NULL, + "erahone_haba" SMALLINT NOT NULL, + "hana_jyouge" SMALLINT NOT NULL, + "hana_haba" SMALLINT NOT NULL, + "hana_takasa" SMALLINT NOT NULL, + "hana_kakudo" SMALLINT NOT NULL, + "kuchi_haba" SMALLINT NOT NULL, + "kuchi_atsusa" SMALLINT NOT NULL, + "eyebrow_uv_offset_x" SMALLINT NOT NULL, + "eyebrow_uv_offset_y" SMALLINT NOT NULL, + "wrinkle" SMALLINT NOT NULL, + "wrinkle_albedo_blend_rate" SMALLINT NOT NULL, + "wrinkle_detail_normal_power" SMALLINT NOT NULL, + "muscle_albedo_blend_rate" SMALLINT NOT NULL, + "muscle_detail_normal_power" SMALLINT NOT NULL, + "height" SMALLINT NOT NULL, + "head_size" SMALLINT NOT NULL, + "neck_offset" SMALLINT NOT NULL, + "neck_scale" SMALLINT NOT NULL, + "upper_body_scale_x" SMALLINT NOT NULL, + "belly_size" SMALLINT NOT NULL, + "teat_scale" SMALLINT NOT NULL, + "tekubi_size" SMALLINT NOT NULL, + "koshi_offset" SMALLINT NOT NULL, + "koshi_size" SMALLINT NOT NULL, + "ankle_offset" SMALLINT NOT NULL, + "fat" SMALLINT NOT NULL, + "muscle" SMALLINT NOT NULL, + "motion_filter" SMALLINT NOT NULL, + CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_status_info` +CREATE TABLE IF NOT EXISTS ddon_status_info ( - `character_common_id` INTEGER PRIMARY KEY NOT NULL, - `hp` INT NOT NULL, - `stamina` INT NOT NULL, - `revive_point` TINYINT NOT NULL, - `max_hp` INT NOT NULL, - `max_stamina` INT NOT NULL, - `white_hp` INT NOT NULL, - `gain_hp` INT NOT NULL, - `gain_stamina` INT NOT NULL, - `gain_attack` INT NOT NULL, - `gain_defense` INT NOT NULL, - `gain_magic_attack` INT NOT NULL, - `gain_magic_defense` INT NOT NULL, - CONSTRAINT `fk_status_info_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "hp" INTEGER NOT NULL, + "stamina" INTEGER NOT NULL, + "revive_point" SMALLINT NOT NULL, + "max_hp" INTEGER NOT NULL, + "max_stamina" INTEGER NOT NULL, + "white_hp" INTEGER NOT NULL, + "gain_hp" INTEGER NOT NULL, + "gain_stamina" INTEGER NOT NULL, + "gain_attack" INTEGER NOT NULL, + "gain_defense" INTEGER NOT NULL, + "gain_magic_attack" INTEGER NOT NULL, + "gain_magic_defense" INTEGER NOT NULL, + CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_character_matching_profile` +CREATE TABLE IF NOT EXISTS ddon_character_matching_profile ( - `character_id` INTEGER PRIMARY KEY NOT NULL, - `entry_job` TINYINT NOT NULL, - `entry_job_level` INT NOT NULL, - `current_job` TINYINT NOT NULL, - `current_job_level` INT NOT NULL, - `objective_type1` INT NOT NULL, - `objective_type2` INT NOT NULL, - `play_style` INT NOT NULL, - `comment` TEXT NOT NULL, - `is_join_party` TINYINT NOT NULL, - CONSTRAINT `fk_matching_profile_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER PRIMARY KEY NOT NULL, + "entry_job" SMALLINT NOT NULL, + "entry_job_level" INTEGER NOT NULL, + "current_job" SMALLINT NOT NULL, + "current_job_level" INTEGER NOT NULL, + objective_type1 INTEGER NOT NULL, + objective_type2 INTEGER NOT NULL, + "play_style" INTEGER NOT NULL, + "comment" TEXT NOT NULL, + "is_join_party" BOOLEAN NOT NULL, + CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_character_arisen_profile` +CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile ( - `character_id` INTEGER PRIMARY KEY NOT NULL, - `background_id` TINYINT NOT NULL, - `title_uid` INT NOT NULL, - `title_index` INT NOT NULL, - `motion_id` SMALLINT NOT NULL, - `motion_frame_no` INT NOT NULL, - CONSTRAINT `fk_arisen_profile_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER PRIMARY KEY NOT NULL, + "background_id" SMALLINT NOT NULL, + "title_uid" INTEGER NOT NULL, + "title_index" INTEGER NOT NULL, + "motion_id" SMALLINT NOT NULL, + "motion_frame_no" INTEGER NOT NULL, + CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_character_job_data` +CREATE TABLE IF NOT EXISTS ddon_character_job_data ( - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `exp` INT NOT NULL, - `job_point` INT NOT NULL, - `lv` INT NOT NULL, - `atk` SMALLINT NOT NULL, - `def` SMALLINT NOT NULL, - `m_atk` SMALLINT NOT NULL, - `m_def` SMALLINT NOT NULL, - `strength` SMALLINT NOT NULL, - `down_power` SMALLINT NOT NULL, - `shake_power` SMALLINT NOT NULL, - `stun_power` SMALLINT NOT NULL, - `consitution` SMALLINT NOT NULL, - `guts` SMALLINT NOT NULL, - `fire_resist` TINYINT NOT NULL, - `ice_resist` TINYINT NOT NULL, - `thunder_resist` TINYINT NOT NULL, - `holy_resist` TINYINT NOT NULL, - `dark_resist` TINYINT NOT NULL, - `spread_resist` TINYINT NOT NULL, - `freeze_resist` TINYINT NOT NULL, - `shock_resist` TINYINT NOT NULL, - `absorb_resist` TINYINT NOT NULL, - `dark_elm_resist` TINYINT NOT NULL, - `poison_resist` TINYINT NOT NULL, - `slow_resist` TINYINT NOT NULL, - `sleep_resist` TINYINT NOT NULL, - `stun_resist` TINYINT NOT NULL, - `wet_resist` TINYINT NOT NULL, - `oil_resist` TINYINT NOT NULL, - `seal_resist` TINYINT NOT NULL, - `curse_resist` TINYINT NOT NULL, - `soft_resist` TINYINT NOT NULL, - `stone_resist` TINYINT NOT NULL, - `gold_resist` TINYINT NOT NULL, - `fire_reduce_resist` TINYINT NOT NULL, - `ice_reduce_resist` TINYINT NOT NULL, - `thunder_reduce_resist` TINYINT NOT NULL, - `holy_reduce_resist` TINYINT NOT NULL, - `dark_reduce_resist` TINYINT NOT NULL, - `atk_down_resist` TINYINT NOT NULL, - `def_down_resist` TINYINT NOT NULL, - `m_atk_down_resist` TINYINT NOT NULL, - `m_def_down_resist` TINYINT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`), - CONSTRAINT `fk_character_job_data_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "exp" INTEGER NOT NULL, + "job_point" INTEGER NOT NULL, + "lv" INTEGER NOT NULL, + "atk" SMALLINT NOT NULL, + "def" SMALLINT NOT NULL, + "m_atk" SMALLINT NOT NULL, + "m_def" SMALLINT NOT NULL, + "strength" SMALLINT NOT NULL, + "down_power" SMALLINT NOT NULL, + "shake_power" SMALLINT NOT NULL, + "stun_power" SMALLINT NOT NULL, + "consitution" SMALLINT NOT NULL, + "guts" SMALLINT NOT NULL, + "fire_resist" SMALLINT NOT NULL, + "ice_resist" SMALLINT NOT NULL, + "thunder_resist" SMALLINT NOT NULL, + "holy_resist" SMALLINT NOT NULL, + "dark_resist" SMALLINT NOT NULL, + "spread_resist" SMALLINT NOT NULL, + "freeze_resist" SMALLINT NOT NULL, + "shock_resist" SMALLINT NOT NULL, + "absorb_resist" SMALLINT NOT NULL, + "dark_elm_resist" SMALLINT NOT NULL, + "poison_resist" SMALLINT NOT NULL, + "slow_resist" SMALLINT NOT NULL, + "sleep_resist" SMALLINT NOT NULL, + "stun_resist" SMALLINT NOT NULL, + "wet_resist" SMALLINT NOT NULL, + "oil_resist" SMALLINT NOT NULL, + "seal_resist" SMALLINT NOT NULL, + "curse_resist" SMALLINT NOT NULL, + "soft_resist" SMALLINT NOT NULL, + "stone_resist" SMALLINT NOT NULL, + "gold_resist" SMALLINT NOT NULL, + "fire_reduce_resist" SMALLINT NOT NULL, + "ice_reduce_resist" SMALLINT NOT NULL, + "thunder_reduce_resist" SMALLINT NOT NULL, + "holy_reduce_resist" SMALLINT NOT NULL, + "dark_reduce_resist" SMALLINT NOT NULL, + "atk_down_resist" SMALLINT NOT NULL, + "def_down_resist" SMALLINT NOT NULL, + "m_atk_down_resist" SMALLINT NOT NULL, + "m_def_down_resist" SMALLINT NOT NULL, + CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job), + CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_storage` +CREATE TABLE IF NOT EXISTS ddon_storage ( - `character_id` INTEGER NOT NULL, - `storage_type` TINYINT NOT NULL, - `slot_max` SMALLINT NOT NULL, - `item_sort` BLOB NOT NULL, - PRIMARY KEY (`character_id`, `storage_type`), - CONSTRAINT `fk_storage_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_max" SMALLINT NOT NULL, + "item_sort" BLOB NOT NULL, + CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type), + CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_wallet_point` +CREATE TABLE IF NOT EXISTS ddon_wallet_point ( - `character_id` INTEGER NOT NULL, - `type` TINYINT NOT NULL, - `value` INTEGER NOT NULL, - PRIMARY KEY (`character_id`, `type`), - CONSTRAINT `fk_wallet_point_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "value" INTEGER NOT NULL, + CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type), + CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_item` +CREATE TABLE IF NOT EXISTS ddon_item ( - `uid` TEXT NOT NULL, - `item_id` INT NOT NULL, - `unk3` TINYINT NOT NULL, - `color` TINYINT NOT NULL, - `plus_value` TINYINT NOT NULL, - PRIMARY KEY (`uid`) + -- See Item.cs, uid is at most of size 8. + "uid" VARCHAR(8) NOT NULL, + "item_id" INTEGER NOT NULL, + unk3 SMALLINT NOT NULL, + "color" SMALLINT NOT NULL, + "plus_value" SMALLINT NOT NULL, + PRIMARY KEY ("uid") ); -CREATE TABLE IF NOT EXISTS `ddon_storage_item` +CREATE TABLE IF NOT EXISTS ddon_storage_item ( - `item_uid` TEXT NOT NULL, - `character_id` INTEGER NOT NULL, - `storage_type` TINYINT NOT NULL, - `slot_no` SMALLINT NOT NULL, - `item_num` INT NOT NULL, - PRIMARY KEY (`character_id`, `storage_type`, `slot_no`), - CONSTRAINT `fk_storage_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE, - CONSTRAINT `fk_storage_item_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "item_uid" VARCHAR(8) NOT NULL, + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "item_num" INTEGER NOT NULL, + CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no), + CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_equip_item` +CREATE TABLE IF NOT EXISTS ddon_equip_item ( - `item_uid` TEXT NOT NULL, - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `equip_type` TINYINT NOT NULL, - `equip_slot` SMALLINT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `equip_type`, `equip_slot`), - CONSTRAINT `fk_equip_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE, - CONSTRAINT `fk_equip_item_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_type" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot), + CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_equip_job_item` +CREATE TABLE IF NOT EXISTS ddon_equip_job_item ( - `item_uid` TEXT NOT NULL, - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `equip_slot` SMALLINT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `equip_slot`), - CONSTRAINT `fk_equip_job_item_item_uid` FOREIGN KEY (`item_uid`) REFERENCES `ddon_item` (`uid`) ON DELETE CASCADE, - CONSTRAINT `fk_equip_job_item_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot), + CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_normal_skill_param` +CREATE TABLE IF NOT EXISTS ddon_normal_skill_param ( - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `skill_no` INT NOT NULL, - `index` INT NOT NULL, - `pre_skill_no` INT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `skill_no`), - CONSTRAINT `fk_normal_skill_param_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_no" INTEGER NOT NULL, + "index" INTEGER NOT NULL, + "pre_skill_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no), + CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_learned_custom_skill` +CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill ( - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `skill_id` INT NOT NULL, - `skill_lv` TINYINT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `skill_id`), - CONSTRAINT `fk_learned_custom_skill_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + "skill_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, skill_id), + CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_equipped_custom_skill` +CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill ( - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `slot_no` TINYINT NOT NULL, - `skill_id` INT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `slot_no`), - CONSTRAINT `fk_equipped_custom_skill_character_common_id` FOREIGN KEY (`character_common_id`, `job`, `skill_id`) REFERENCES `ddon_learned_custom_skill` (`character_common_id`, `job`, `skill_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no), + CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_learned_ability` +CREATE TABLE IF NOT EXISTS ddon_learned_ability ( - `character_common_id` INTEGER NOT NULL, - `job` TINYINT NOT NULL, - `ability_id` INT NOT NULL, - `ability_lv` TINYINT NOT NULL, - PRIMARY KEY (`character_common_id`, `job`, `ability_id`), - CONSTRAINT `fk_learned_ability_character_common_id` FOREIGN KEY (`character_common_id`) REFERENCES `ddon_character_common` (`character_common_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + "ability_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, ability_id), + CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_equipped_ability` +CREATE TABLE IF NOT EXISTS ddon_equipped_ability ( - `character_common_id` INTEGER NOT NULL, - `equipped_to_job` TINYINT NOT NULL, - `job` TINYINT NOT NULL, - `slot_no` TINYINT NOT NULL, - `ability_id` INT NOT NULL, - PRIMARY KEY (`character_common_id`, `equipped_to_job`, `slot_no`), - CONSTRAINT `fk_equipped_ability_character_common_id` FOREIGN KEY (`character_common_id`, `job`, `ability_id`) REFERENCES `ddon_learned_ability` (`character_common_id`, `job`, `ability_id`) ON DELETE CASCADE + "character_common_id" INTEGER NOT NULL, + "equipped_to_job" SMALLINT NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no), + CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_shortcut` +CREATE TABLE IF NOT EXISTS ddon_shortcut ( - `character_id` INTEGER NOT NULL, - `page_no` INTEGER NOT NULL, - `button_no` INTEGER NOT NULL, - `shortcut_id` INTEGER NOT NULL, - `u32_data` INTEGER NOT NULL, - `f32_data` INTEGER NOT NULL, - `exex_type` TINYINT NOT NULL, - PRIMARY KEY (`character_id`, `page_no`, `button_no`), - CONSTRAINT `fk_shortcut_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "shortcut_id" INTEGER NOT NULL, + u32_data INTEGER NOT NULL, + f32_data INTEGER NOT NULL, + "exex_type" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_communication_shortcut` +CREATE TABLE IF NOT EXISTS ddon_communication_shortcut ( - `character_id` INTEGER NOT NULL, - `page_no` INTEGER NOT NULL, - `button_no` INTEGER NOT NULL, - `type` TINYINT NOT NULL, - `category` TINYINT NOT NULL, - `id` INTEGER NOT NULL, - PRIMARY KEY (`character_id`, `page_no`, `button_no`), - CONSTRAINT `fk_communication_shortcut_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) ON DELETE CASCADE + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "category" SMALLINT NOT NULL, + "id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_pawn_reaction` +CREATE TABLE IF NOT EXISTS ddon_pawn_reaction ( - `pawn_id` INTEGER NOT NULL, - `reaction_type` TINYINT NOT NULL, - `motion_no` INTEGER NOT NULL, - PRIMARY KEY (`pawn_id`,`reaction_type`), - CONSTRAINT `fk_pawn_reaction_pawn_id` FOREIGN KEY (`pawn_id`) REFERENCES `ddon_pawn` (`pawn_id`) ON DELETE CASCADE + "pawn_id" INTEGER NOT NULL, + "reaction_type" SMALLINT NOT NULL, + "motion_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type), + CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_sp_skill` +CREATE TABLE IF NOT EXISTS ddon_sp_skill ( - `pawn_id` INTEGER NOT NULL, - `sp_skill_id` TINYINT NOT NULL, - `sp_skill_lv` TINYINT NOT NULL, - PRIMARY KEY (`pawn_id`), - CONSTRAINT `fk_sp_skill_pawn_id` FOREIGN KEY (`pawn_id`) REFERENCES `ddon_pawn` (`pawn_id`) ON DELETE CASCADE + "pawn_id" INTEGER NOT NULL, + "sp_skill_id" SMALLINT NOT NULL, + "sp_skill_lv" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"), + CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS `ddon_game_token` +CREATE TABLE IF NOT EXISTS ddon_game_token ( - `account_id` INTEGER PRIMARY KEY NOT NULL, - `character_id` INTEGER NOT NULL, - `token` TEXT NOT NULL, - `created` DATETIME NOT NULL, - CONSTRAINT `uq_game_token_token` UNIQUE (`token`), - CONSTRAINT `fk_game_token_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`), - CONSTRAINT `fk_game_token_character_id` FOREIGN KEY (`character_id`) REFERENCES `ddon_character` (`character_id`) + "account_id" INTEGER PRIMARY KEY NOT NULL, + "character_id" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_game_token_token UNIQUE ("token"), + CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"), + CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ); -CREATE TABLE IF NOT EXISTS `ddon_connection` +CREATE TABLE IF NOT EXISTS ddon_connection ( - `server_id` INTEGER NOT NULL, - `account_id` INTEGER NOT NULL, - `type` INTEGER NOT NULL, - `created` DATETIME NOT NULL, - CONSTRAINT `uq_connection_server_id_account_id` UNIQUE (`server_id`, `account_id`), - CONSTRAINT `fk_game_token_account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) -); \ No newline at end of file + "server_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "type" INTEGER NOT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id), + CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") +); diff --git a/Arrowgene.Ddon.Database/Model/DatabaseType.cs b/Arrowgene.Ddon.Database/Model/DatabaseType.cs index 50bafb857..4add44d0a 100644 --- a/Arrowgene.Ddon.Database/Model/DatabaseType.cs +++ b/Arrowgene.Ddon.Database/Model/DatabaseType.cs @@ -3,5 +3,7 @@ public enum DatabaseType { SQLite, + PostgreSQL, + MariaDb } } diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs index 2e6ed9bb6..8920455a4 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDb.cs @@ -8,11 +8,12 @@ namespace Arrowgene.Ddon.Database.Sql.Core /// /// Implementation of Ddon database operations. /// - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqlDb)); + private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqlDb)); public DdonSqlDb() { @@ -34,13 +35,13 @@ public static string BuildQueryField(string table, params string[][] fieldLists) string field = fieldList[j]; if(table != null) { - sb.Append('`'); + sb.Append('\"'); sb.Append(table); - sb.Append("`."); + sb.Append("\"."); } - sb.Append('`'); + sb.Append('\"'); sb.Append(field); - sb.Append('`'); + sb.Append('\"'); if (j < fieldList.Length - 1) { sb.Append(", "); @@ -52,6 +53,11 @@ public static string BuildQueryField(string table, params string[][] fieldLists) } public static string BuildQueryUpdate(params string[][] fieldLists) + { + return BuildQueryUpdateWithPrefix("@", fieldLists); + } + + protected static string BuildQueryUpdateWithPrefix(string prefix, params string[][] fieldLists) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < fieldLists.Length; i++) @@ -60,7 +66,7 @@ public static string BuildQueryUpdate(params string[][] fieldLists) for (int j = 0; j < fieldList.Length; j++) { string field = fieldList[j]; - sb.Append($"`{field}`=@{field}"); + sb.Append($"\"{field}\"={prefix}{field}"); if (j < fieldList.Length - 1) { sb.Append(", "); diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs index 9ab0776fa..714a502c6 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbAccount.cs @@ -1,24 +1,26 @@ using System; using System.Data.Common; +using System.Text; using Arrowgene.Ddon.Database.Model; namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] AccountFields = new string[] { "name", "normal_name", "hash", "mail", "mail_verified", "mail_verified_at", "mail_token", "password_token", "login_token", "login_token_created", "state", "last_login", "created" }; - - private static readonly string SqlInsertAccount = $"INSERT INTO `account` ({BuildQueryField(AccountFields)}) VALUES ({BuildQueryInsert(AccountFields)});"; - private static readonly string SqlSelectAccountById = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `id`=@id;"; - private static readonly string SqlSelectAccountByName = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `normal_name`=@normal_name;"; - private static readonly string SqlSelectAccountByLoginToken = $"SELECT `id`, {BuildQueryField(AccountFields)} FROM `account` WHERE `login_token`=@login_token;"; - private static readonly string SqlUpdateAccount = $"UPDATE `account` SET {BuildQueryUpdate(AccountFields)} WHERE `id`=@id;"; - private const string SqlDeleteAccount = "DELETE FROM `account` WHERE `id`=@id;"; + + private static readonly string SqlInsertAccount = $"INSERT INTO \"account\" ({BuildQueryField(AccountFields)}) VALUES ({BuildQueryInsert(AccountFields)});"; + private static readonly string SqlSelectAccountById = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"id\"=@id;"; + private static readonly string SqlSelectAccountByName = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"normal_name\"=@normal_name;"; + private static readonly string SqlSelectAccountByLoginToken = $"SELECT \"id\", {BuildQueryField(AccountFields)} FROM \"account\" WHERE \"login_token\"=@login_token;"; + private static readonly string SqlUpdateAccount = $"UPDATE \"account\" SET {BuildQueryUpdate(AccountFields)} WHERE \"id\"=@id;"; + private const string SqlDeleteAccount = "DELETE FROM \"account\" WHERE \"id\"=@id;"; public Account CreateAccount(string name, string mail, string hash) { @@ -28,7 +30,14 @@ public Account CreateAccount(string name, string mail, string hash) account.Mail = mail; account.Hash = hash; account.State = AccountStateType.User; - account.Created = DateTime.Now; + account.Created = DateTime.UtcNow; + account.MailToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(mail)); + account.PasswordToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(name)); + account.LoginToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(name)); + account.LoginTokenCreated = DateTime.UtcNow; + account.MailVerifiedAt = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + account.LastAuthentication = DateTime.UtcNow; + Logger.Info($"Creating account: {account}"); int rowsAffected = ExecuteNonQuery(SqlInsertAccount, command => { AddParameter(command, "@name", account.Name); @@ -50,11 +59,11 @@ public Account CreateAccount(string name, string mail, string hash) return null; } - account.Id = (int) autoIncrement; - + account.Id = (int)autoIncrement; + return account; } - + public Account SelectAccountByName(string accountName) { accountName = accountName.ToLowerInvariant(); @@ -128,7 +137,7 @@ public bool DeleteAccount(int accountId) return rowsAffected > NoRowsAffected; } - private Account ReadAccount(DbDataReader reader) + private Account ReadAccount(TReader reader) { Account account = new Account(); account.Id = GetInt32(reader, "id"); @@ -142,7 +151,7 @@ private Account ReadAccount(DbDataReader reader) account.PasswordToken = GetStringNullable(reader, "password_token"); account.LoginToken = GetStringNullable(reader, "login_token"); account.LoginTokenCreated = GetDateTimeNullable(reader, "login_token_created"); - account.State = (AccountStateType) GetInt32(reader, "state"); + account.State = (AccountStateType)GetInt32(reader, "state"); account.LastAuthentication = GetDateTimeNullable(reader, "last_login"); account.Created = GetDateTime(reader, "created"); return account; diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs index 1f1099179..8ca2643ce 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacter.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; using System.Data.Common; +using Arrowgene.Ddon.Database.Model; using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] CharacterFields = new string[] { @@ -26,46 +28,46 @@ public abstract partial class DdonSqlDb : SqlDb "character_id", "background_id", "title_uid", "title_index", "motion_id", "motion_frame_no" }; - private readonly string SqlInsertCharacter = $"INSERT INTO `ddon_character` ({BuildQueryField(CharacterFields)}) VALUES ({BuildQueryInsert(CharacterFields)});"; - private static readonly string SqlUpdateCharacter = $"UPDATE `ddon_character` SET {BuildQueryUpdate(CharacterFields)} WHERE `character_id` = @character_id;"; - private static readonly string SqlSelectCharacter = $"SELECT `ddon_character`.`character_id`, {BuildQueryField(CharacterFields)} FROM `ddon_character` WHERE `character_id` = @character_id;"; - private static readonly string SqlSelectCharactersByAccountId = $"SELECT `ddon_character`.`character_id`, {BuildQueryField(CharacterFields)} FROM `ddon_character` WHERE `account_id` = @account_id;"; - private readonly string SqlSelectAllCharacterData = $"SELECT `ddon_character`.`character_id`, {BuildQueryField("ddon_character", CharacterFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} " - + "FROM `ddon_character` " - + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_character_matching_profile` ON `ddon_character_matching_profile`.`character_id` = `ddon_character`.`character_id` " - + "LEFT JOIN `ddon_character_arisen_profile` ON `ddon_character_arisen_profile`.`character_id` = `ddon_character`.`character_id` " - + "WHERE `ddon_character`.`character_id` = @character_id"; - private readonly string SqlSelectAllCharactersDataByAccountId = $"SELECT `ddon_character`.`character_id`, {BuildQueryField("ddon_character", CharacterFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} " - + "FROM `ddon_character` " - + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_character`.`character_common_id` " - + "LEFT JOIN `ddon_character_matching_profile` ON `ddon_character_matching_profile`.`character_id` = `ddon_character`.`character_id` " - + "LEFT JOIN `ddon_character_arisen_profile` ON `ddon_character_arisen_profile`.`character_id` = `ddon_character`.`character_id` " - + "WHERE `account_id` = @account_id"; - private const string SqlDeleteCharacter = "DELETE FROM `ddon_character_common` WHERE EXISTS (SELECT 1 FROM `ddon_character` WHERE `ddon_character_common`.`character_common_id`=`ddon_character`.`character_common_id` AND `character_id`=@character_id);"; - - - private readonly string SqlInsertCharacterMatchingProfile = $"INSERT INTO `ddon_character_matching_profile` ({BuildQueryField(CDataMatchingProfileFields)}) VALUES ({BuildQueryInsert(CDataMatchingProfileFields)});"; - private static readonly string SqlUpdateCharacterMatchingProfile = $"UPDATE `ddon_character_matching_profile` SET {BuildQueryUpdate(CDataMatchingProfileFields)} WHERE `character_id` = @character_id;"; - private static readonly string SqlSelectCharacterMatchingProfile = $"SELECT {BuildQueryField(CDataMatchingProfileFields)} FROM `ddon_character_matching_profile` WHERE `character_id` = @character_id;"; - private const string SqlDeleteCharacterMatchingProfile = "DELETE FROM `ddon_character_matching_profile` WHERE `character_id`=@character_id;"; - - - private readonly string SqlInsertCharacterArisenProfile = $"INSERT INTO `ddon_character_arisen_profile` ({BuildQueryField(CDataArisenProfileFields)}) VALUES ({BuildQueryInsert(CDataArisenProfileFields)});"; - private static readonly string SqlUpdateCharacterArisenProfile = $"UPDATE `ddon_character_arisen_profile` SET {BuildQueryUpdate(CDataArisenProfileFields)} WHERE `character_id` = @character_id;"; - private static readonly string SqlSelectCharacterArisenProfile = $"SELECT {BuildQueryField(CDataArisenProfileFields)} FROM `ddon_character_arisen_profile` WHERE `character_id` = @character_id;"; - private const string SqlDeleteCharacterArisenProfile = "DELETE FROM `ddon_character_arisen_profile` WHERE `character_id`=@character_id;"; + private readonly string SqlInsertCharacter = $"INSERT INTO \"ddon_character\" ({BuildQueryField(CharacterFields)}) VALUES ({BuildQueryInsert(CharacterFields)});"; + private static readonly string SqlUpdateCharacter = $"UPDATE \"ddon_character\" SET {BuildQueryUpdate(CharacterFields)} WHERE \"character_id\" = @character_id;"; + private static readonly string SqlSelectCharacter = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField(CharacterFields)} FROM \"ddon_character\" WHERE \"character_id\" = @character_id;"; + private static readonly string SqlSelectCharactersByAccountId = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField(CharacterFields)} FROM \"ddon_character\" WHERE \"account_id\" = @account_id;"; + private readonly string SqlSelectAllCharacterData = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField("ddon_character", CharacterFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} " + + "FROM \"ddon_character\" " + + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_character_matching_profile\" ON \"ddon_character_matching_profile\".\"character_id\" = \"ddon_character\".\"character_id\" " + + "LEFT JOIN \"ddon_character_arisen_profile\" ON \"ddon_character_arisen_profile\".\"character_id\" = \"ddon_character\".\"character_id\" " + + "WHERE \"ddon_character\".\"character_id\" = @character_id"; + private readonly string SqlSelectAllCharactersDataByAccountId = $"SELECT \"ddon_character\".\"character_id\", {BuildQueryField("ddon_character", CharacterFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}, {BuildQueryField("ddon_character_matching_profile", CDataMatchingProfileFields)}, {BuildQueryField("ddon_character_arisen_profile", CDataArisenProfileFields)} " + + "FROM \"ddon_character\" " + + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_character\".\"character_common_id\" " + + "LEFT JOIN \"ddon_character_matching_profile\" ON \"ddon_character_matching_profile\".\"character_id\" = \"ddon_character\".\"character_id\" " + + "LEFT JOIN \"ddon_character_arisen_profile\" ON \"ddon_character_arisen_profile\".\"character_id\" = \"ddon_character\".\"character_id\" " + + "WHERE \"account_id\" = @account_id"; + private const string SqlDeleteCharacter = "DELETE FROM \"ddon_character_common\" WHERE EXISTS (SELECT 1 FROM \"ddon_character\" WHERE \"ddon_character_common\".\"character_common_id\"=\"ddon_character\".\"character_common_id\" AND \"character_id\"=@character_id);"; + + + private readonly string SqlInsertCharacterMatchingProfile = $"INSERT INTO \"ddon_character_matching_profile\" ({BuildQueryField(CDataMatchingProfileFields)}) VALUES ({BuildQueryInsert(CDataMatchingProfileFields)});"; + private static readonly string SqlUpdateCharacterMatchingProfile = $"UPDATE \"ddon_character_matching_profile\" SET {BuildQueryUpdate(CDataMatchingProfileFields)} WHERE \"character_id\" = @character_id;"; + private static readonly string SqlSelectCharacterMatchingProfile = $"SELECT {BuildQueryField(CDataMatchingProfileFields)} FROM \"ddon_character_matching_profile\" WHERE \"character_id\" = @character_id;"; + private const string SqlDeleteCharacterMatchingProfile = "DELETE FROM \"ddon_character_matching_profile\" WHERE \"character_id\"=@character_id;"; + + + private readonly string SqlInsertCharacterArisenProfile = $"INSERT INTO \"ddon_character_arisen_profile\" ({BuildQueryField(CDataArisenProfileFields)}) VALUES ({BuildQueryInsert(CDataArisenProfileFields)});"; + private static readonly string SqlUpdateCharacterArisenProfile = $"UPDATE \"ddon_character_arisen_profile\" SET {BuildQueryUpdate(CDataArisenProfileFields)} WHERE \"character_id\" = @character_id;"; + private static readonly string SqlSelectCharacterArisenProfile = $"SELECT {BuildQueryField(CDataArisenProfileFields)} FROM \"ddon_character_arisen_profile\" WHERE \"character_id\" = @character_id;"; + private const string SqlDeleteCharacterArisenProfile = "DELETE FROM \"ddon_character_arisen_profile\" WHERE \"character_id\"=@character_id;"; public bool CreateCharacter(Character character) { return ExecuteInTransaction(conn => { - character.Created = DateTime.Now; + character.Created = DateTime.UtcNow; ExecuteNonQuery(conn, SqlInsertCharacterCommon, command => { AddParameter(command, character); }, out long commonId); character.CommonId = (uint) commonId; @@ -86,14 +88,14 @@ public bool CreateCharacter(Character character) public bool UpdateCharacterBaseInfo(Character character) { - return UpdateCharacterBaseInfo(null, character); + using TCon connection = OpenNewConnection(); + return UpdateCharacterBaseInfo(connection, character); } public bool UpdateCharacterBaseInfo(TCon conn, Character character) { int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacter, command => { - AddParameter(command, "@character_id", character.CharacterId); AddParameter(command, character); }); @@ -102,14 +104,14 @@ public bool UpdateCharacterBaseInfo(TCon conn, Character character) public bool UpdateCharacterMatchingProfile(Character character) { - return UpdateCharacterMatchingProfile(null, character); + using TCon connection = OpenNewConnection(); + return UpdateCharacterMatchingProfile(connection, character); } public bool UpdateCharacterMatchingProfile(TCon conn, Character character) { int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterMatchingProfile, command => { - AddParameter(command, "@character_id", character.CharacterId); AddParameter(command, character); }); @@ -118,14 +120,14 @@ public bool UpdateCharacterMatchingProfile(TCon conn, Character character) public bool UpdateCharacterArisenProfile(Character character) { - return UpdateCharacterArisenProfile(null, character); + using TCon connection = OpenNewConnection(); + return UpdateCharacterArisenProfile(connection, character); } public bool UpdateCharacterArisenProfile(TCon conn, Character character) { int characterUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterArisenProfile, command => { - AddParameter(command, "@character_id", character.CharacterId); AddParameter(command, character); }); @@ -153,7 +155,8 @@ public Character SelectCharacter(uint characterId) public List SelectCharactersByAccountId(int accountId) { List characters = new List(); - ExecuteInTransaction(conn => { + ExecuteInTransaction(conn => + { ExecuteReader(conn, SqlSelectAllCharactersDataByAccountId, command => { AddParameter(command, "@account_id", accountId); }, reader => { @@ -161,10 +164,12 @@ public List SelectCharactersByAccountId(int accountId) { Character character = ReadAllCharacterData(reader); characters.Add(character); - - QueryCharacterData(conn, character); } }); + foreach (var character in characters) + { + QueryCharacterData(conn, character); + } }); return characters; } @@ -212,30 +217,30 @@ private void QueryCharacterData(TCon conn, Character character) Tuple tuple = ReadStorage(reader); character.Storage.addStorage(tuple.Item1, tuple.Item2); } - - ExecuteReader(conn, SqlSelectStorageItemsByCharacter, - command2 => { AddParameter(command2, "@character_id", character.CharacterId); }, - reader2 => + }); + ExecuteReader(conn, SqlSelectStorageItemsByCharacter, + command2 => { AddParameter(command2, "@character_id", character.CharacterId); }, + reader2 => + { + while(reader2.Read()) { - while(reader2.Read()) - { - string UId = GetString(reader2, "item_uid"); - StorageType storageType = (StorageType) GetByte(reader2, "storage_type"); - ushort slot = GetUInt16(reader2, "slot_no"); - uint itemNum = GetUInt32(reader2, "item_num"); - - ExecuteReader(conn, SqlSelectItem, - command3 => { AddParameter(command3, "@uid", UId); }, - reader3 => + string UId = GetString(reader2, "item_uid"); + StorageType storageType = (StorageType) GetByte(reader2, "storage_type"); + ushort slot = GetUInt16(reader2, "slot_no"); + uint itemNum = GetUInt32(reader2, "item_num"); + + using TCon connection = OpenNewConnection(); + ExecuteReader(connection, SqlSelectItem, + command3 => { AddParameter(command3, "@uid", UId); }, + reader3 => + { + if(reader3.Read()) { - if(reader3.Read()) - { - Item item = ReadItem(reader3); - character.Storage.setStorageItem(item, itemNum, storageType, slot); - } - }); - } - }); + Item item = ReadItem(reader3); + character.Storage.setStorageItem(item, itemNum, storageType, slot); + } + }); + } }); // Wallet Points @@ -256,34 +261,22 @@ private void StoreCharacterData(TCon conn, Character character) foreach(CDataShortCut shortcut in character.ShortCutList) { - ExecuteNonQuery(conn, SqlReplaceShortcut, command => - { - AddParameter(command, character.CharacterId, shortcut); - }); + ReplaceShortcut(conn, character.CharacterId, shortcut); } foreach(CDataCommunicationShortCut communicationShortcut in character.CommunicationShortCutList) { - ExecuteNonQuery(conn, SqlReplaceCommunicationShortcut, command => - { - AddParameter(command, character.CharacterId, communicationShortcut); - }); + ReplaceCommunicationShortcut(conn, character.CharacterId, communicationShortcut); } foreach(StorageType storageType in character.Storage.getAllStorages().Keys) { - ExecuteNonQuery(conn, SqlReplaceStorage, command => - { - AddParameter(command, character.CharacterId, storageType, character.Storage.getStorage(storageType)); - }); + ReplaceStorage(conn, character.CharacterId, storageType, character.Storage.getStorage(storageType)); } foreach(CDataWalletPoint walletPoint in character.WalletPointList) { - ExecuteNonQuery(conn, SqlReplaceWalletPoint, command => - { - AddParameter(command, character.CharacterId, walletPoint); - }); + ReplaceWalletPoint(conn, character.CharacterId, walletPoint); } } @@ -327,7 +320,7 @@ private void CreateItems(TCon conn, Character character) } } - private Character ReadAllCharacterData(DbDataReader reader) + private Character ReadAllCharacterData(TReader reader) { Character character = new Character(); diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs index 411d5f6d8..78a87bc28 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterCommon.cs @@ -8,9 +8,10 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] CharacterCommonFields = new string[] { @@ -40,29 +41,29 @@ public abstract partial class DdonSqlDb : SqlDb }; - private readonly string SqlInsertCharacterCommon = $"INSERT INTO `ddon_character_common` ({BuildQueryField(CharacterCommonFields)}) VALUES ({BuildQueryInsert(CharacterCommonFields)});"; - private readonly string SqlUpdateCharacterCommon = $"UPDATE `ddon_character_common` SET {BuildQueryUpdate(CharacterCommonFields)} WHERE `character_common_id` = @character_common_id;"; + private readonly string SqlInsertCharacterCommon = $"INSERT INTO \"ddon_character_common\" ({BuildQueryField(CharacterCommonFields)}) VALUES ({BuildQueryInsert(CharacterCommonFields)});"; + private readonly string SqlUpdateCharacterCommon = $"UPDATE \"ddon_character_common\" SET {BuildQueryUpdate(CharacterCommonFields)} WHERE \"character_common_id\" = @character_common_id;"; - private readonly string SqlInsertEditInfo = $"INSERT INTO `ddon_edit_info` ({BuildQueryField(CDataEditInfoFields)}) VALUES ({BuildQueryInsert(CDataEditInfoFields)});"; - private static readonly string SqlUpdateEditInfo = $"UPDATE `ddon_edit_info` SET {BuildQueryUpdate(CDataEditInfoFields)} WHERE `character_common_id` = @character_common_id;"; - private static readonly string SqlSelectEditInfo = $"SELECT {BuildQueryField(CDataEditInfoFields)} FROM `ddon_edit_info` WHERE `character_common_id` = @character_common_id;"; - private const string SqlDeleteEditInfo = "DELETE FROM `ddon_edit_info` WHERE `character_common_id`=@character_common_id;"; + private readonly string SqlInsertEditInfo = $"INSERT INTO \"ddon_edit_info\" ({BuildQueryField(CDataEditInfoFields)}) VALUES ({BuildQueryInsert(CDataEditInfoFields)});"; + private static readonly string SqlUpdateEditInfo = $"UPDATE \"ddon_edit_info\" SET {BuildQueryUpdate(CDataEditInfoFields)} WHERE \"character_common_id\" = @character_common_id;"; + private static readonly string SqlSelectEditInfo = $"SELECT {BuildQueryField(CDataEditInfoFields)} FROM \"ddon_edit_info\" WHERE \"character_common_id\" = @character_common_id;"; + private const string SqlDeleteEditInfo = "DELETE FROM \"ddon_edit_info\" WHERE \"character_common_id\"=@character_common_id;"; - private readonly string SqlInsertStatusInfo = $"INSERT INTO `ddon_status_info` ({BuildQueryField(CDataStatusInfoFields)}) VALUES ({BuildQueryInsert(CDataStatusInfoFields)});"; - private static readonly string SqlUpdateStatusInfo = $"UPDATE `ddon_status_info` SET {BuildQueryUpdate(CDataStatusInfoFields)} WHERE `character_common_id` = @character_common_id;"; - private static readonly string SqlSelectStatusInfo = $"SELECT {BuildQueryField(CDataStatusInfoFields)} FROM `ddon_status_info` WHERE `character_common_id` = @character_common_id;"; - private const string SqlDeleteStatusInfo = "DELETE FROM `ddon_status_info` WHERE `character_common_id`=@character_common_id;"; + private readonly string SqlInsertStatusInfo = $"INSERT INTO \"ddon_status_info\" ({BuildQueryField(CDataStatusInfoFields)}) VALUES ({BuildQueryInsert(CDataStatusInfoFields)});"; + private static readonly string SqlUpdateStatusInfo = $"UPDATE \"ddon_status_info\" SET {BuildQueryUpdate(CDataStatusInfoFields)} WHERE \"character_common_id\" = @character_common_id;"; + private static readonly string SqlSelectStatusInfo = $"SELECT {BuildQueryField(CDataStatusInfoFields)} FROM \"ddon_status_info\" WHERE \"character_common_id\" = @character_common_id;"; + private const string SqlDeleteStatusInfo = "DELETE FROM \"ddon_status_info\" WHERE \"character_common_id\"=@character_common_id;"; public bool UpdateCharacterCommonBaseInfo(CharacterCommon common) { - return UpdateCharacterCommonBaseInfo(null, common); + using TCon connection = OpenNewConnection(); + return UpdateCharacterCommonBaseInfo(connection, common); } public bool UpdateCharacterCommonBaseInfo(TCon conn, CharacterCommon common) { int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateCharacterCommon, command => { - AddParameter(command, "@character_common_id", common.CommonId); AddParameter(command, common); }); @@ -71,14 +72,14 @@ public bool UpdateCharacterCommonBaseInfo(TCon conn, CharacterCommon common) public bool UpdateEditInfo(CharacterCommon common) { - return UpdateEditInfo(null, common); + using TCon connection = OpenNewConnection(); + return UpdateEditInfo(connection, common); } public bool UpdateEditInfo(TCon conn, CharacterCommon common) { int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateEditInfo, command => { - AddParameter(command, "@character_common_id", common.CommonId); AddParameter(command, common); }); @@ -87,14 +88,14 @@ public bool UpdateEditInfo(TCon conn, CharacterCommon common) public bool UpdateStatusInfo(CharacterCommon common) { - return UpdateStatusInfo(null, common); + using TCon connection = OpenNewConnection(); + return UpdateStatusInfo(connection, common); } public bool UpdateStatusInfo(TCon conn, CharacterCommon common) { int commonUpdateRowsAffected = ExecuteNonQuery(conn, SqlUpdateStatusInfo, command => { - AddParameter(command, "@character_common_id", common.CommonId); AddParameter(command, common); }); @@ -125,7 +126,9 @@ private void QueryCharacterCommonData(TCon conn, CharacterCommon common) JobId job = (JobId) GetByte(reader, "job"); EquipType equipType = (EquipType) GetByte(reader, "equip_type"); byte equipSlot = GetByte(reader, "equip_slot"); - ExecuteReader(conn, SqlSelectItem, + + using TCon connection = OpenNewConnection(); + ExecuteReader(connection, SqlSelectItem, command2 => { AddParameter(command2, "@uid", UId); }, reader2 => { @@ -148,7 +151,9 @@ private void QueryCharacterCommonData(TCon conn, CharacterCommon common) string UId = GetString(reader, "item_uid"); JobId job = (JobId) GetByte(reader, "job"); byte equipSlot = GetByte(reader, "equip_slot"); - ExecuteReader(conn, SqlSelectItem, + + using TCon connection = OpenNewConnection(); + ExecuteReader(connection, SqlSelectItem, command2 => { AddParameter(command2, "@uid", UId); }, reader2 => { @@ -228,18 +233,12 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common) { foreach(CDataCharacterJobData characterJobData in common.CharacterJobDataList) { - ExecuteNonQuery(conn, SqlReplaceCharacterJobData, command => - { - AddParameter(command, common.CommonId, characterJobData); - }); + ReplaceCharacterJobData(conn, common.CommonId, characterJobData); } foreach(CDataNormalSkillParam normalSkillParam in common.LearnedNormalSkills) { - ExecuteNonQuery(conn, SqlReplaceNormalSkillParam, command => - { - AddParameter(command, common.CommonId, normalSkillParam); - }); + ReplaceNormalSkillParam(conn, common.CommonId, normalSkillParam); } foreach(CustomSkill learnedSkills in common.LearnedCustomSkills) @@ -258,10 +257,7 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common) byte slotNo = (byte)(i+1); if(skill != null) { - ExecuteNonQuery(conn, SqlReplaceEquippedCustomSkill, command => - { - AddParameter(command, common.CommonId, slotNo, skill); - }); + ReplaceEquippedCustomSkill(conn, common.CommonId, slotNo, skill); } } } @@ -283,16 +279,13 @@ private void StoreCharacterCommonData(TCon conn, CharacterCommon common) byte slotNo = (byte)(i+1); if(ability != null) { - ExecuteNonQuery(conn, SqlReplaceEquippedAbility, command => - { - AddParameter(command, common.CommonId, equippedToJob, slotNo, ability); - }); + ReplaceEquippedAbility(conn, common.CommonId, equippedToJob, slotNo, ability); } } } } - private void ReadAllCharacterCommonData(DbDataReader reader, CharacterCommon common) + private void ReadAllCharacterCommonData(TReader reader, CharacterCommon common) { common.CommonId = GetUInt32(reader, "character_common_id"); common.Job = (JobId) GetByte(reader, "job"); @@ -481,4 +474,4 @@ private void AddParameter(TCom command, CharacterCommon common) AddParameter(command, "@gain_magic_defense", common.StatusInfo.GainMagicDefense); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs index efe1b9bdd..d5d5a8936 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCharacterJobData.cs @@ -4,11 +4,12 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] CDataCharacterJobDataFields = new string[] + protected static readonly string[] CDataCharacterJobDataFields = new string[] { "character_common_id", "job", "exp", "job_point", "lv", "atk", "def", "m_atk", "m_def", "strength", "down_power", "shake_power", "stun_power", "consitution", "guts", "fire_resist", "ice_resist", "thunder_resist", "holy_resist", "dark_resist", "spread_resist", @@ -18,30 +19,74 @@ public abstract partial class DdonSqlDb : SqlDb "m_atk_down_resist", "m_def_down_resist" }; - private readonly string SqlInsertCharacterJobData = $"INSERT INTO `ddon_character_job_data` ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});"; - private readonly string SqlReplaceCharacterJobData = $"INSERT OR REPLACE INTO `ddon_character_job_data` ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});"; - private static readonly string SqlUpdateCharacterJobData = $"UPDATE `ddon_character_job_data` SET {BuildQueryUpdate(CDataCharacterJobDataFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job;"; - private static readonly string SqlSelectCharacterJobData = $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM `ddon_character_job_data` WHERE `character_common_id` = @character_common_id AND `job` = @job;"; - private static readonly string SqlSelectCharacterJobDataByCharacter = $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM `ddon_character_job_data` WHERE `character_common_id` = @character_common_id;"; - private const string SqlDeleteCharacterJobData = "DELETE FROM `ddon_character_job_data` WHERE `character_common_id`=@character_common_id AND `job` = @job;"; - + private readonly string SqlInsertCharacterJobData = + $"INSERT INTO \"ddon_character_job_data\" ({BuildQueryField(CDataCharacterJobDataFields)}) VALUES ({BuildQueryInsert(CDataCharacterJobDataFields)});"; + + protected virtual string SqlInsertIfNotExistsCharacterJobData { get; } = + $"INSERT INTO \"ddon_character_job_data\" ({BuildQueryField(CDataCharacterJobDataFields)}) SELECT {BuildQueryInsert(CDataCharacterJobDataFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job);"; + + private static readonly string SqlUpdateCharacterJobData = + $"UPDATE \"ddon_character_job_data\" SET {BuildQueryUpdate(CDataCharacterJobDataFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;"; + + private static readonly string SqlSelectCharacterJobData = + $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;"; + + private static readonly string SqlSelectCharacterJobDataByCharacter = + $"SELECT {BuildQueryField(CDataCharacterJobDataFields)} FROM \"ddon_character_job_data\" WHERE \"character_common_id\" = @character_common_id;"; + + private const string SqlDeleteCharacterJobData = "DELETE FROM \"ddon_character_job_data\" WHERE \"character_common_id\"=@character_common_id AND \"job\" = @job;"; + public bool ReplaceCharacterJobData(uint commonId, CDataCharacterJobData replacedCharacterJobData) { - return ExecuteNonQuery(SqlReplaceCharacterJobData, command => + using TCon connection = OpenNewConnection(); + return ReplaceCharacterJobData(connection, commonId, replacedCharacterJobData); + } + + public bool ReplaceCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData replacedCharacterJobData) + { + Logger.Debug("Inserting character job data."); + if (!InsertIfNotExistsCharacterJobData(connection, commonId, replacedCharacterJobData)) { - AddParameter(command, commonId, replacedCharacterJobData); - }) == 1; + Logger.Debug("Character job data already exists, replacing."); + return UpdateCharacterJobData(connection, commonId, replacedCharacterJobData); + } + return true; + } + + public bool InsertCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData) + { + using TCon connection = OpenNewConnection(); + return InsertCharacterJobData(connection, commonId, updatedCharacterJobData); + } + + public bool InsertCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData) + { + return ExecuteNonQuery(connection, SqlInsertCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1; } + public bool InsertIfNotExistsCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsCharacterJobData(connection, commonId, updatedCharacterJobData); + } + + public bool InsertIfNotExistsCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1; + } + public bool UpdateCharacterJobData(uint commonId, CDataCharacterJobData updatedCharacterJobData) { - return ExecuteNonQuery(SqlUpdateCharacterJobData, command => - { - AddParameter(command, commonId, updatedCharacterJobData); - }) == 1; + using TCon connection = OpenNewConnection(); + return UpdateCharacterJobData(connection, commonId, updatedCharacterJobData); + } + + public bool UpdateCharacterJobData(TCon connection, uint commonId, CDataCharacterJobData updatedCharacterJobData) + { + return ExecuteNonQuery(connection, SqlUpdateCharacterJobData, command => { AddParameter(command, commonId, updatedCharacterJobData); }) == 1; } - private CDataCharacterJobData ReadCharacterJobData(DbDataReader reader) + private CDataCharacterJobData ReadCharacterJobData(TReader reader) { CDataCharacterJobData characterJobData = new CDataCharacterJobData(); characterJobData.Job = (JobId) GetByte(reader, "job"); @@ -140,4 +185,4 @@ private void AddParameter(TCom command, uint commonId, CDataCharacterJobData cha AddParameter(command, "m_def_down_resist", characterJobData.MDefDownResist); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs index 429b86e4b..1f1a741cf 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbCommunicationShortcut.cs @@ -3,24 +3,45 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] CommunicationShortcutFields = new string[] + protected static readonly string[] CommunicationShortcutFields = new string[] { "character_id", "page_no", "button_no", "type", "category", "id" }; - private readonly string SqlInsertCommunicationShortcut = $"INSERT INTO `ddon_communication_shortcut` ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});"; - private readonly string SqlReplaceCommunicationShortcut = $"INSERT OR REPLACE INTO `ddon_communication_shortcut` ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});"; - private static readonly string SqlUpdateCommunicationShortcut = $"UPDATE `ddon_communication_shortcut` SET {BuildQueryUpdate(CommunicationShortcutFields)} WHERE `character_id`=@old_character_id AND `page_no`=@old_page_no AND `button_no`=@old_button_no"; - private static readonly string SqlSelectCommunicationShortcuts = $"SELECT {BuildQueryField(CommunicationShortcutFields)} FROM `ddon_communication_shortcut` WHERE `character_id`=@character_id;"; - private const string SqlDeleteCommunicationShortcut = "DELETE FROM `ddon_communication_shortcut` WHERE `character_id`=@character_id AND `page_no`=@page_no AND `button_no`=@button_no"; + private readonly string SqlInsertCommunicationShortcut = $"INSERT INTO \"ddon_communication_shortcut\" ({BuildQueryField(CommunicationShortcutFields)}) VALUES ({BuildQueryInsert(CommunicationShortcutFields)});"; + private readonly string SqlInsertIfNotExistsCommunicationShortcut = $"INSERT INTO \"ddon_communication_shortcut\" ({BuildQueryField(CommunicationShortcutFields)}) SELECT {BuildQueryInsert(CommunicationShortcutFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no);"; + private static readonly string SqlUpdateCommunicationShortcut = $"UPDATE \"ddon_communication_shortcut\" SET {BuildQueryUpdate(CommunicationShortcutFields)} WHERE \"character_id\"=@old_character_id AND \"page_no\"=@old_page_no AND \"button_no\"=@old_button_no"; + private static readonly string SqlSelectCommunicationShortcuts = $"SELECT {BuildQueryField(CommunicationShortcutFields)} FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id;"; + private const string SqlDeleteCommunicationShortcut = "DELETE FROM \"ddon_communication_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no"; + public bool InsertIfNotExistsCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsCommunicationShortcut(connection, characterId, communicationShortcut); + } + + public bool InsertIfNotExistsCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsCommunicationShortcut, command => + { + AddParameter(command, characterId, communicationShortcut); + }) == 1; + } + public bool InsertCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut) { - return ExecuteNonQuery(SqlInsertCommunicationShortcut, command => + using TCon connection = OpenNewConnection(); + return InsertCommunicationShortcut(connection, characterId, communicationShortcut); + } + + public bool InsertCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut) + { + return ExecuteNonQuery(connection, SqlInsertCommunicationShortcut, command => { AddParameter(command, characterId, communicationShortcut); }) == 1; @@ -28,16 +49,30 @@ public bool InsertCommunicationShortcut(uint characterId, CDataCommunicationShor public bool ReplaceCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut) { - ExecuteNonQuery(SqlReplaceCommunicationShortcut, command => + using TCon connection = OpenNewConnection(); + return ReplaceCommunicationShortcut(connection, characterId, communicationShortcut); + } + + public bool ReplaceCommunicationShortcut(TCon connection, uint characterId, CDataCommunicationShortCut communicationShortcut) + { + Logger.Debug("Inserting communication shortcut."); + if (!InsertIfNotExistsCommunicationShortcut(connection, characterId, communicationShortcut)) { - AddParameter(command, characterId, communicationShortcut); - }); + Logger.Debug("Communication shortcut already exists, replacing."); + return UpdateCommunicationShortcut(connection, characterId, communicationShortcut.PageNo, communicationShortcut.ButtonNo, communicationShortcut); + } return true; } public bool UpdateCommunicationShortcut(uint characterId, uint oldPageNo, uint oldButtonNo, CDataCommunicationShortCut updatedCommunicationShortcut) { - return ExecuteNonQuery(SqlUpdateCommunicationShortcut, command => + using TCon connection = OpenNewConnection(); + return UpdateCommunicationShortcut(connection, characterId, oldPageNo, oldButtonNo, updatedCommunicationShortcut); + } + + public bool UpdateCommunicationShortcut(TCon connection, uint characterId, uint oldPageNo, uint oldButtonNo, CDataCommunicationShortCut updatedCommunicationShortcut) + { + return ExecuteNonQuery(connection, SqlUpdateCommunicationShortcut, command => { AddParameter(command, characterId, updatedCommunicationShortcut); AddParameter(command, "@old_character_id", characterId); @@ -56,7 +91,7 @@ public bool DeleteCommunicationShortcut(uint characterId, uint pageNo, uint butt }) == 1; } - private CDataCommunicationShortCut ReadCommunicationShortCut(DbDataReader reader) + private CDataCommunicationShortCut ReadCommunicationShortCut(TReader reader) { CDataCommunicationShortCut shortcut = new CDataCommunicationShortCut(); shortcut.PageNo = GetUInt32(reader, "page_no"); @@ -77,4 +112,4 @@ private void AddParameter(TCom command, uint characterId, CDataCommunicationShor AddParameter(command, "id", shortcut.Id); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs index c9a0e6f77..95d1e7521 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbConnection.cs @@ -4,24 +4,25 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private const string SqlInsertConnection = - "INSERT INTO `ddon_connection` (`server_id`, `account_id`, `type`, `created`) VALUES (@server_id, @account_id, @type, @created);"; + "INSERT INTO \"ddon_connection\" (\"server_id\", \"account_id\", \"type\", \"created\") VALUES (@server_id, @account_id, @type, @created);"; private const string SqlSelectConnectionsByAccountId = - "SELECT `server_id`, `account_id`, `type`, `created` FROM `ddon_connection` WHERE `account_id` = @account_id;"; + "SELECT \"server_id\", \"account_id\", \"type\", \"created\" FROM \"ddon_connection\" WHERE \"account_id\" = @account_id;"; private const string SqlDeleteConnectionsByAccountId = - "DELETE FROM `ddon_connection` WHERE `account_id`=@account_id;"; + "DELETE FROM \"ddon_connection\" WHERE \"account_id\"=@account_id;"; private const string SqlDeleteConnectionsByServerId = - "DELETE FROM `ddon_connection` WHERE `server_id`=@server_id;"; + "DELETE FROM \"ddon_connection\" WHERE \"server_id\"=@server_id;"; private const string SqlDeleteConnection = - "DELETE FROM `ddon_connection` WHERE `server_id`=@server_id AND `account_id`=@account_id;"; + "DELETE FROM \"ddon_connection\" WHERE \"server_id\"=@server_id AND \"account_id\"=@account_id;"; public bool InsertConnection(Connection connection) { diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs index 05c64b910..e6442357c 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipItem.cs @@ -4,50 +4,76 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] CDataEquipItemFields = new string[] + protected static readonly string[] CDataEquipItemFields = new string[] { "item_uid", "character_common_id", "job", "equip_type", "equip_slot" }; - private readonly string SqlInsertEquipItem = $"INSERT INTO `ddon_equip_item` ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});"; - private readonly string SqlReplaceEquipItem = $"INSERT OR REPLACE INTO `ddon_equip_item` ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});"; - private static readonly string SqlUpdateEquipItem = $"UPDATE `ddon_equip_item` SET {BuildQueryUpdate(CDataEquipItemFields)} WHERE `item_uid`=@item_uid AND `character_common_id`=@character_common_id AND `job`=@job AND `equip_type`=@equip_type AND `equip_slot`=@equip_slot;"; - private static readonly string SqlSelectEquipItemByCharacter = $"SELECT {BuildQueryField(CDataEquipItemFields)} FROM `ddon_equip_item` WHERE `character_common_id`=@character_common_id;"; - private static readonly string SqlDeleteEquipItem = "DELETE FROM `ddon_equip_item` WHERE `item_uid`=@item_uid AND `character_common_id`=@character_common_id AND `job`=@job AND `equip_type`=@equip_type AND `equip_slot`=@equip_slot;"; - - public bool InsertEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) + private readonly string SqlInsertEquipItem = $"INSERT INTO \"ddon_equip_item\" ({BuildQueryField(CDataEquipItemFields)}) VALUES ({BuildQueryInsert(CDataEquipItemFields)});"; + private readonly string SqlInsertIfNotExistsEquipItem = $"INSERT INTO \"ddon_equip_item\" ({BuildQueryField(CDataEquipItemFields)}) SELECT {BuildQueryInsert(CDataEquipItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equip_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot);"; + private static readonly string SqlUpdateEquipItem = $"UPDATE \"ddon_equip_item\" SET {BuildQueryUpdate(CDataEquipItemFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot;"; + private static readonly string SqlSelectEquipItemByCharacter = $"SELECT {BuildQueryField(CDataEquipItemFields)} FROM \"ddon_equip_item\" WHERE \"character_common_id\"=@character_common_id;"; + private static readonly string SqlDeleteEquipItem = "DELETE FROM \"ddon_equip_item\" WHERE \"item_uid\"=@item_uid AND \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_type\"=@equip_type AND \"equip_slot\"=@equip_slot;"; + + public bool InsertIfNotExistsEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { - return ExecuteNonQuery(conn, SqlInsertEquipItem, command => + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsEquipItem(connection, commonId, job, equipType, equipSlot, itemUId); + } + + public bool InsertIfNotExistsEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) + { + return ExecuteNonQuery(conn, SqlInsertIfNotExistsEquipItem, command => { AddParameter(command, commonId, job, equipType, equipSlot, itemUId); }) == 1; - } - + } + public bool InsertEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { - return this.InsertEquipItem(null, commonId, job, equipType, equipSlot, itemUId); + using TCon connection = OpenNewConnection(); + return InsertEquipItem(connection, commonId, job, equipType, equipSlot, itemUId); } - - public bool ReplaceEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) + + public bool InsertEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { - return ExecuteNonQuery(conn, SqlReplaceEquipItem, command => + return ExecuteNonQuery(conn, SqlInsertEquipItem, command => { AddParameter(command, commonId, job, equipType, equipSlot, itemUId); }) == 1; } - + public bool ReplaceEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { - return this.ReplaceEquipItem(null, commonId, job, equipType, equipSlot, itemUId); + using TCon connection = OpenNewConnection(); + return ReplaceEquipItem(connection, commonId, job, equipType, equipSlot, itemUId); + } + + public bool ReplaceEquipItem(TCon conn, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) + { + Logger.Debug("Inserting equip item."); + if (!InsertIfNotExistsEquipItem(conn, commonId, job, equipType, equipSlot, itemUId)) + { + Logger.Debug("Equip item already exists, replacing."); + return UpdateEquipItem(conn, commonId, job, equipType, equipSlot, itemUId); + } + return true; } public bool UpdateEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { - return ExecuteNonQuery(SqlUpdateEquipItem, command => + using TCon connection = OpenNewConnection(); + return UpdateEquipItem(connection, commonId, job, equipType, equipSlot, itemUId); + } + + public bool UpdateEquipItem(TCon connection, uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) + { + return ExecuteNonQuery(connection, SqlUpdateEquipItem, command => { AddParameter(command, commonId, job, equipType, equipSlot, itemUId); }) == 1; @@ -70,4 +96,4 @@ private void AddParameter(TCom command, uint commonId, JobId job, EquipType equi AddParameter(command, "equip_slot", equipSlot); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs index a090cf1a7..9506bdf99 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs @@ -3,41 +3,49 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] CDataEquipJobItemFields = new string[] + protected static readonly string[] CDataEquipJobItemFields = new string[] { "item_uid", "character_common_id", "job", "equip_slot" }; - private readonly string SqlInsertEquipJobItem = $"INSERT INTO `ddon_equip_job_item` ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});"; - private readonly string SqlReplaceEquipJobItem = $"INSERT OR REPLACE INTO `ddon_equip_job_item` ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});"; - private static readonly string SqlUpdateEquipJobItem = $"UPDATE `ddon_equip_job_item` SET {BuildQueryUpdate(CDataEquipJobItemFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job AND `equip_slot`=@equip_slot;"; - private static readonly string SqlSelectEquipJobItems = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM `ddon_equip_job_item` WHERE `character_common_id` = @character_common_id AND `job` = @job;"; - private static readonly string SqlSelectEquipJobItemsByCharacter = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM `ddon_equip_job_item` WHERE `character_common_id` = @character_common_id;"; - private const string SqlDeleteEquipJobItem = "DELETE FROM `ddon_equip_job_item` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `equip_slot`=@equip_slot;"; + private readonly string SqlInsertEquipJobItem = $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});"; + private readonly string SqlInsertIfNotExistsEquipJobItem = $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) SELECT {BuildQueryInsert(CDataEquipJobItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job);"; + private static readonly string SqlUpdateEquipJobItem = $"UPDATE \"ddon_equip_job_item\" SET {BuildQueryUpdate(CDataEquipJobItemFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"equip_slot\"=@equip_slot;"; + private static readonly string SqlSelectEquipJobItems = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;"; + private static readonly string SqlSelectEquipJobItemsByCharacter = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id;"; + private const string SqlDeleteEquipJobItem = "DELETE FROM \"ddon_equip_job_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_slot\"=@equip_slot;"; + + public bool InsertIfNotExistsEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsEquipJobItem(connection, itemUId, commonId, job, slotNo); + } - public bool InsertEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo) + public bool InsertIfNotExistsEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo) { - return ExecuteNonQuery(conn, SqlInsertEquipJobItem, command => + return ExecuteNonQuery(conn, SqlInsertIfNotExistsEquipJobItem, command => { AddParameter(command, "item_uid", itemUId); AddParameter(command, "character_common_id", commonId); AddParameter(command, "job", (byte) job); AddParameter(command, "equip_slot", slotNo); }) == 1; - } - + } + public bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) { - return InsertEquipJobItem(null, itemUId, commonId, job, slotNo); + using TCon connection = OpenNewConnection(); + return InsertEquipJobItem(connection, itemUId, commonId, job, slotNo); } - public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo) + public bool InsertEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo) { - return ExecuteNonQuery(conn, SqlReplaceEquipJobItem, command => + return ExecuteNonQuery(conn, SqlInsertEquipJobItem, command => { AddParameter(command, "item_uid", itemUId); AddParameter(command, "character_common_id", commonId); @@ -48,7 +56,19 @@ public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId public bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) { - return ReplaceEquipJobItem(null, itemUId, commonId, job, slotNo); + using TCon connection = OpenNewConnection(); + return ReplaceEquipJobItem(connection, itemUId, commonId, job, slotNo); + } + + public bool ReplaceEquipJobItem(TCon conn, string itemUId, uint commonId, JobId job, ushort slotNo) + { + Logger.Debug("Inserting equp job item."); + if (!InsertIfNotExistsEquipJobItem(conn, itemUId, commonId, job, slotNo)) + { + Logger.Debug("Equip job item already exists, replacing."); + return UpdateEquipJobItem(conn, itemUId, commonId, job, slotNo); + } + return true; } public bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo) @@ -60,5 +80,22 @@ public bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo) AddParameter(command, "equip_slot", slotNo); }) == 1; } + + public bool UpdateEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) + { + using TCon connection = OpenNewConnection(); + return UpdateEquipJobItem(connection, itemUId, commonId, job, slotNo); + } + + public bool UpdateEquipJobItem(TCon connection, string itemUId, uint commonId, JobId job, ushort slotNo) + { + return ExecuteNonQuery(connection, SqlUpdateEquipJobItem, command => + { + AddParameter(command, "item_uid", itemUId); + AddParameter(command, "character_common_id", commonId); + AddParameter(command, "job", (byte) job); + AddParameter(command, "equip_slot", slotNo); + }) == 1; + } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs index 74849e564..cd1deeec4 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedAbility.cs @@ -4,25 +4,46 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] EquippedAbilityFields = new string[] + protected static readonly string[] EquippedAbilityFields = new string[] { "character_common_id", "equipped_to_job", "job", "slot_no", "ability_id" }; - private readonly string SqlInsertEquippedAbility = $"INSERT INTO `ddon_equipped_ability` ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});"; - private readonly string SqlReplaceEquippedAbility = $"INSERT OR REPLACE INTO `ddon_equipped_ability` ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});"; - private static readonly string SqlUpdateEquippedAbility = $"UPDATE `ddon_equipped_ability` SET {BuildQueryUpdate(EquippedAbilityFields)} WHERE `character_common_id`=@old_character_common_id AND `equipped_to_job`=@old_equipped_to_job AND `slot_no`=@old_slot_no;"; - private static readonly string SqlSelectEquippedAbilities = $"SELECT {BuildQueryField(EquippedAbilityFields)} FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id ORDER BY equipped_to_job, slot_no;"; - private const string SqlDeleteEquippedAbility = "DELETE FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id AND `equipped_to_job`=@equipped_to_job AND `slot_no`=@slot_no;"; - private const string SqlDeleteEquippedAbilities = "DELETE FROM `ddon_equipped_ability` WHERE `character_common_id`=@character_common_id AND `equipped_to_job`=@equipped_to_job;"; + private readonly string SqlInsertEquippedAbility = $"INSERT INTO \"ddon_equipped_ability\" ({BuildQueryField(EquippedAbilityFields)}) VALUES ({BuildQueryInsert(EquippedAbilityFields)});"; + private readonly string SqlInsertIfNotExistsEquippedAbility = $"INSERT INTO \"ddon_equipped_ability\" ({BuildQueryField(EquippedAbilityFields)}) SELECT {BuildQueryInsert(EquippedAbilityFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job AND \"slot_no\"=@slot_no);"; + private static readonly string SqlUpdateEquippedAbility = $"UPDATE \"ddon_equipped_ability\" SET {BuildQueryUpdate(EquippedAbilityFields)} WHERE \"character_common_id\"=@old_character_common_id AND \"equipped_to_job\"=@old_equipped_to_job AND \"slot_no\"=@old_slot_no;"; + private static readonly string SqlSelectEquippedAbilities = $"SELECT {BuildQueryField(EquippedAbilityFields)} FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id ORDER BY equipped_to_job, slot_no;"; + private const string SqlDeleteEquippedAbility = "DELETE FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job AND \"slot_no\"=@slot_no;"; + private const string SqlDeleteEquippedAbilities = "DELETE FROM \"ddon_equipped_ability\" WHERE \"character_common_id\"=@character_common_id AND \"equipped_to_job\"=@equipped_to_job;"; + public bool InsertIfNotExistsEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsEquippedAbility(connection, commonId, equippedToJob, slotNo, ability); + } + + public bool InsertIfNotExistsEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsEquippedAbility, command => + { + AddParameter(command, commonId, equippedToJob, slotNo, ability); + }) == 1; + } + public bool InsertEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability) { - return ExecuteNonQuery(SqlInsertEquippedAbility, command => + using TCon connection = OpenNewConnection(); + return InsertEquippedAbility(connection, commonId, equippedToJob, slotNo, ability); + } + + public bool InsertEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability) + { + return ExecuteNonQuery(connection, SqlInsertEquippedAbility, command => { AddParameter(command, commonId, equippedToJob, slotNo, ability); }) == 1; @@ -30,42 +51,48 @@ public bool InsertEquippedAbility(uint commonId, JobId equippedToJob, byte slotN public bool ReplaceEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo, Ability ability) { - ExecuteNonQuery(SqlReplaceEquippedAbility, command => + using TCon connection = OpenNewConnection(); + return ReplaceEquippedAbility(connection, commonId, equippedToJob, slotNo, ability); + } + + public bool ReplaceEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo, Ability ability) + { + Logger.Debug("Inserting equipped ability."); + if (!InsertIfNotExistsEquippedAbility(connection, commonId, equippedToJob, slotNo, ability)) { - AddParameter(command, commonId, equippedToJob, slotNo, ability); - }); + Logger.Debug("Equipped ability already exists, replacing."); + return UpdateEquippedAbility(connection, commonId, equippedToJob, slotNo, equippedToJob, slotNo, ability); + } return true; } - + public bool ReplaceEquippedAbilities(uint commonId, JobId equippedToJob, List abilities) { return ExecuteInTransaction(connection => { // Remove previously equipped abilities - ExecuteNonQuery(connection, SqlDeleteEquippedAbilities, command => - { - AddParameter(command, "@character_common_id", commonId); - AddParameter(command, "@equipped_to_job", (byte) equippedToJob); - }); - + DeleteEquippedAbilities(connection, commonId, equippedToJob); // Insert new ones for(byte i = 0; i < abilities.Count; i++) { Ability ability = abilities[i]; byte slotNo = (byte)(i+1); - ExecuteNonQuery(connection, SqlInsertEquippedAbility, command => - { - AddParameter(command, commonId, equippedToJob, slotNo, ability); - }); + InsertEquippedAbility(connection, commonId, equippedToJob, slotNo, ability); } }); } - public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedability) + public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedAbility) + { + using TCon connection = OpenNewConnection(); + return UpdateEquippedAbility(connection, commonId, oldEquippedToJob, oldSlotNo, equippedToJob, slotNo, updatedAbility); + } + + public bool UpdateEquippedAbility(TCon connection, uint commonId, JobId oldEquippedToJob, byte oldSlotNo, JobId equippedToJob, byte slotNo, Ability updatedAbility) { - return ExecuteNonQuery(SqlDeleteEquippedAbility, command => + return ExecuteNonQuery(connection, SqlUpdateEquippedAbility, command => { - AddParameter(command, commonId, equippedToJob, slotNo, updatedability); + AddParameter(command, commonId, equippedToJob, slotNo, updatedAbility); AddParameter(command, "@old_character_common_id", commonId); AddParameter(command, "@old_equipped_to_job", (byte) oldEquippedToJob); AddParameter(command, "@old_slot_no", oldSlotNo); @@ -74,7 +101,13 @@ public bool UpdateEquippedAbility(uint commonId, JobId oldEquippedToJob, byte ol public bool DeleteEquippedAbility(uint commonId, JobId equippedToJob, byte slotNo) { - return ExecuteNonQuery(SqlDeleteEquippedAbility, command => + using TCon connection = OpenNewConnection(); + return DeleteEquippedAbility(connection, commonId, equippedToJob, slotNo); + } + + public bool DeleteEquippedAbility(TCon connection, uint commonId, JobId equippedToJob, byte slotNo) + { + return ExecuteNonQuery(connection, SqlDeleteEquippedAbility, command => { AddParameter(command, "@character_common_id", commonId); AddParameter(command, "@equipped_to_job", (byte) equippedToJob); @@ -84,7 +117,13 @@ public bool DeleteEquippedAbility(uint commonId, JobId equippedToJob, byte slotN public bool DeleteEquippedAbilities(uint commonId, JobId equippedToJob) { - return ExecuteNonQuery(SqlDeleteEquippedAbilities, command => + using TCon connection = OpenNewConnection(); + return DeleteEquippedAbilities(connection, commonId, equippedToJob); + } + + public bool DeleteEquippedAbilities(TCon connection, uint commonId, JobId equippedToJob) + { + return ExecuteNonQuery(connection, SqlDeleteEquippedAbilities, command => { AddParameter(command, "@character_common_id", commonId); AddParameter(command, "@equipped_to_job", (byte) equippedToJob); @@ -101,4 +140,4 @@ private void AddParameter(TCom command, uint commonId, JobId equippedToJob, byte AddParameter(command, "ability_lv", ability.AbilityLv); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs index 1fdbe3e00..026534ba8 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquippedCustomSkill.cs @@ -3,24 +3,45 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] EquippedCustomSkillFields = new string[] + protected static readonly string[] EquippedCustomSkillFields = new string[] { "character_common_id", "job", "slot_no", "skill_id" }; - private readonly string SqlInsertEquippedCustomSkill = $"INSERT INTO `ddon_equipped_custom_skill` ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});"; - private readonly string SqlReplaceEquippedCustomSkill = $"INSERT OR REPLACE INTO `ddon_equipped_custom_skill` ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});"; - private static readonly string SqlUpdateEquippedCustomSkill = $"UPDATE `ddon_equipped_custom_skill` SET {BuildQueryUpdate(EquippedCustomSkillFields)} WHERE `character_common_id`=@old_character_common_id AND `job`=@old_job AND `slot_no`=@old_slot_no;"; - private static readonly string SqlSelectEquippedCustomSkills = $"SELECT {BuildQueryField(EquippedCustomSkillFields)} FROM `ddon_equipped_custom_skill` WHERE `character_common_id`=@character_common_id;"; - private const string SqlDeleteEquippedCustomSkill = "DELETE FROM `ddon_equipped_custom_skill` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `slot_no`=@slot_no;"; + private readonly string SqlInsertEquippedCustomSkill = $"INSERT INTO \"ddon_equipped_custom_skill\" ({BuildQueryField(EquippedCustomSkillFields)}) VALUES ({BuildQueryInsert(EquippedCustomSkillFields)});"; + private readonly string SqlInsertIfNotExistsEquippedCustomSkill = $"INSERT INTO \"ddon_equipped_custom_skill\" ({BuildQueryField(EquippedCustomSkillFields)}) SELECT {BuildQueryInsert(EquippedCustomSkillFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"slot_no\"=@slot_no);"; + private static readonly string SqlUpdateEquippedCustomSkill = $"UPDATE \"ddon_equipped_custom_skill\" SET {BuildQueryUpdate(EquippedCustomSkillFields)} WHERE \"character_common_id\"=@old_character_common_id AND \"job\"=@old_job AND \"slot_no\"=@old_slot_no;"; + private static readonly string SqlSelectEquippedCustomSkills = $"SELECT {BuildQueryField(EquippedCustomSkillFields)} FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id;"; + private const string SqlDeleteEquippedCustomSkill = "DELETE FROM \"ddon_equipped_custom_skill\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"slot_no\"=@slot_no;"; + public bool InsertIfNotExistsEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsEquippedCustomSkill(connection, commonId, slotNo, skill); + } + + public bool InsertIfNotExistsEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsEquippedCustomSkill, command => + { + AddParameter(command, commonId, slotNo, skill); + }) == 1; + } + public bool InsertEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill) { - return ExecuteNonQuery(SqlInsertEquippedCustomSkill, command => + using TCon connection = OpenNewConnection(); + return InsertEquippedCustomSkill(connection, commonId, slotNo, skill); + } + + public bool InsertEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill) + { + return ExecuteNonQuery(connection, SqlInsertEquippedCustomSkill, command => { AddParameter(command, commonId, slotNo, skill); }) == 1; @@ -28,18 +49,32 @@ public bool InsertEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill sk public bool ReplaceEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill) { - ExecuteNonQuery(SqlReplaceEquippedCustomSkill, command => + using TCon connection = OpenNewConnection(); + return ReplaceEquippedCustomSkill(connection, commonId, slotNo, skill); + } + + public bool ReplaceEquippedCustomSkill(TCon connection, uint commonId, byte slotNo, CustomSkill skill) + { + Logger.Debug("Inserting equipped custom skill."); + if (!InsertIfNotExistsEquippedCustomSkill(connection, commonId, slotNo, skill)) { - AddParameter(command, commonId, slotNo, skill); - }); + Logger.Debug("Equipped custom skill already exists, replacing."); + return UpdateEquippedCustomSkill(connection, commonId, skill.Job, slotNo, slotNo, skill); + } return true; } public bool UpdateEquippedCustomSkill(uint commonId, JobId oldJob, byte oldSlotNo, byte slotNo, CustomSkill updatedSkill) { - return ExecuteNonQuery(SqlDeleteEquippedCustomSkill, command => + using TCon connection = OpenNewConnection(); + return UpdateEquippedCustomSkill(connection, commonId, oldJob, oldSlotNo, slotNo, updatedSkill); + } + + public bool UpdateEquippedCustomSkill(TCon connection, uint commonId, JobId oldJob, byte oldSlotNo, byte slotNo, CustomSkill updatedSkill) + { + return ExecuteNonQuery(connection, SqlUpdateEquippedCustomSkill, command => { - AddParameter(command, commonId, updatedSkill); + AddParameter(command, commonId, slotNo, updatedSkill); AddParameter(command, "@old_character_common_id", commonId); AddParameter(command, "@old_job", (byte) oldJob); AddParameter(command, "@old_slot_no", oldSlotNo); @@ -64,4 +99,4 @@ private void AddParameter(TCom command, uint commonId, byte slotNo, CustomSkill AddParameter(command, "skill_id", skill.SkillId); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs index 57510f7dc..c417fafb4 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbGameToken.cs @@ -3,16 +3,17 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private const string SqlInsertToken = "INSERT INTO `ddon_game_token` (`account_id`, `character_id`, `token`, `created`) VALUES (@account_id, @character_id, @token, @created);"; - private const string SqlUpdateToken = "UPDATE `ddon_game_token` SET `character_id`=@character_id, `token`=@token, `created`=@created WHERE `account_id` = @account_id;"; - private const string SqlSelectTokenByAccountId = "SELECT `token`, `account_id`, `character_id`, `token`, `created` FROM `ddon_game_token` WHERE `account_id` = @account_id;"; - private const string SqlDeleteTokenByAccountId = "DELETE FROM `ddon_game_token` WHERE `account_id`=@account_id;"; - private const string SqlSelectToken = "SELECT `token`, `account_id`, `character_id`, `token`, `created` FROM `ddon_game_token` WHERE `token` = @token;"; - private const string SqlDeleteToken = "DELETE FROM `ddon_game_token` WHERE `token`=@token;"; + private const string SqlInsertToken = "INSERT INTO \"ddon_game_token\" (\"account_id\", \"character_id\", \"token\", \"created\") VALUES (@account_id, @character_id, @token, @created);"; + private const string SqlUpdateToken = "UPDATE \"ddon_game_token\" SET \"character_id\"=@character_id, \"token\"=@token, \"created\"=@created WHERE \"account_id\" = @account_id;"; + private const string SqlSelectTokenByAccountId = "SELECT \"token\", \"account_id\", \"character_id\", \"token\", \"created\" FROM \"ddon_game_token\" WHERE \"account_id\" = @account_id;"; + private const string SqlDeleteTokenByAccountId = "DELETE FROM \"ddon_game_token\" WHERE \"account_id\"=@account_id;"; + private const string SqlSelectToken = "SELECT \"token\", \"account_id\", \"character_id\", \"token\", \"created\" FROM \"ddon_game_token\" WHERE \"token\" = @token;"; + private const string SqlDeleteToken = "DELETE FROM \"ddon_game_token\" WHERE \"token\"=@token;"; public bool SetToken(GameToken token) { diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs index 63ced654c..38cf20c04 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbItem.cs @@ -1,25 +1,24 @@ -using System; -using System.Collections.Generic; using System.Data.Common; -using System.Linq; -using System.Threading.Tasks; using Arrowgene.Ddon.Shared.Model; namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] ItemFields = new string[] + protected static readonly string[] ItemFields = new string[] { "uid", "item_id", "unk3", "color", "plus_value" }; // Items don't get updated or deleted once created as the same row is shared among all players. // Making a distinction wouldn't make sense, as upgrading/changin crests would generate a new item with a different UID - private readonly string SqlInsertOrIgnoreItem = $"INSERT OR IGNORE INTO `ddon_item` ({BuildQueryField(ItemFields)}) VALUES ({BuildQueryInsert(ItemFields)});"; - private static readonly string SqlSelectItem = $"SELECT {BuildQueryField(ItemFields)} FROM `ddon_item` WHERE `uid`=@uid;"; + protected virtual string SqlInsertOrIgnoreItem { get; } = + $"INSERT INTO \"ddon_item\" ({BuildQueryField(ItemFields)}) SELECT {BuildQueryInsert(ItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_item\" WHERE \"uid\"=@uid);"; + + private static readonly string SqlSelectItem = $"SELECT {BuildQueryField(ItemFields)} FROM \"ddon_item\" WHERE \"uid\"=@uid;"; public bool InsertItem(TCon conn, Item item) { @@ -31,7 +30,8 @@ public bool InsertItem(TCon conn, Item item) public bool InsertItem(Item item) { - return this.InsertItem(null, item); + using TCon connection = OpenNewConnection(); + return InsertItem(connection, item); } public Item SelectItem(string uid) @@ -52,7 +52,7 @@ public Item SelectItem(string uid) return item; } - private Item ReadItem(DbDataReader reader) + private Item ReadItem(TReader reader) { Item item = new Item(); item.UId = GetString(reader, "uid"); @@ -72,4 +72,4 @@ private void AddParameter(TCom command, Item item) AddParameter(command, "plus_value", item.PlusValue); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs index 2a0c06e11..3fda89559 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedAbility.cs @@ -4,9 +4,10 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] LearnedAbilityFields = new string[] @@ -14,9 +15,9 @@ public abstract partial class DdonSqlDb : SqlDb "character_common_id", "job", "ability_id", "ability_lv" }; - private readonly string SqlInsertLearnedAbility = $"INSERT INTO `ddon_learned_ability` ({BuildQueryField(LearnedAbilityFields)}) VALUES ({BuildQueryInsert(LearnedAbilityFields)});"; - private readonly string SqlUpdateLearnedAbility = $"UPDATE `ddon_learned_ability` SET {BuildQueryUpdate(LearnedAbilityFields)} WHERE `character_common_id`=@character_common_id AND `job`=@job AND `ability_id`=@ability_id;"; - private static readonly string SqlSelectLearnedAbilities = $"SELECT {BuildQueryField(LearnedAbilityFields)} FROM `ddon_learned_ability` WHERE `character_common_id`=@character_common_id;"; + private readonly string SqlInsertLearnedAbility = $"INSERT INTO \"ddon_learned_ability\" ({BuildQueryField(LearnedAbilityFields)}) VALUES ({BuildQueryInsert(LearnedAbilityFields)});"; + private readonly string SqlUpdateLearnedAbility = $"UPDATE \"ddon_learned_ability\" SET {BuildQueryUpdate(LearnedAbilityFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"ability_id\"=@ability_id;"; + private static readonly string SqlSelectLearnedAbilities = $"SELECT {BuildQueryField(LearnedAbilityFields)} FROM \"ddon_learned_ability\" WHERE \"character_common_id\"=@character_common_id;"; public bool InsertLearnedAbility(uint commonId, Ability ability) { @@ -36,7 +37,7 @@ public bool UpdateLearnedAbility(uint commonId, Ability ability) return true; } - private Ability ReadLearnedAbility(DbDataReader reader) + private Ability ReadLearnedAbility(TReader reader) { Ability ability = new Ability(); ability.Job = (JobId) GetByte(reader, "job"); @@ -53,4 +54,4 @@ private void AddParameter(TCom command, uint commonId, Ability ability) AddParameter(command, "ability_lv", ability.AbilityLv); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs index 6b86fed91..3d0f68705 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbLearnedCustomSkill.cs @@ -3,18 +3,19 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] LearnedCustomSkillFields = new string[] { "character_common_id", "job", "skill_id", "skill_lv" }; - private readonly string SqlInsertLearnedCustomSkill = $"INSERT INTO `ddon_learned_custom_skill` ({BuildQueryField(LearnedCustomSkillFields)}) VALUES ({BuildQueryInsert(LearnedCustomSkillFields)});"; - private readonly string SqlUpdateLearnedCustomSkill = $"UPDATE `ddon_learned_custom_skill` SET {BuildQueryUpdate(LearnedCustomSkillFields)} WHERE `character_common_id`=@character_common_id AND `job`=@job AND `skill_id`=@skill_id;"; - private static readonly string SqlSelectLearnedCustomSkills = $"SELECT {BuildQueryField(LearnedCustomSkillFields)} FROM `ddon_learned_custom_skill` WHERE `character_common_id`=@character_common_id;"; + private readonly string SqlInsertLearnedCustomSkill = $"INSERT INTO \"ddon_learned_custom_skill\" ({BuildQueryField(LearnedCustomSkillFields)}) VALUES ({BuildQueryInsert(LearnedCustomSkillFields)});"; + private readonly string SqlUpdateLearnedCustomSkill = $"UPDATE \"ddon_learned_custom_skill\" SET {BuildQueryUpdate(LearnedCustomSkillFields)} WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"skill_id\"=@skill_id;"; + private static readonly string SqlSelectLearnedCustomSkills = $"SELECT {BuildQueryField(LearnedCustomSkillFields)} FROM \"ddon_learned_custom_skill\" WHERE \"character_common_id\"=@character_common_id;"; public bool InsertLearnedCustomSkill(uint commonId, CustomSkill skill) { @@ -34,7 +35,7 @@ public bool UpdateLearnedCustomSkill(uint commonId, CustomSkill updatedSkill) return true; } - private CustomSkill ReadLearnedCustomSkill(DbDataReader reader) + private CustomSkill ReadLearnedCustomSkill(TReader reader) { CustomSkill skill = new CustomSkill(); skill.Job = (JobId) GetByte(reader, "job"); @@ -51,4 +52,4 @@ private void AddParameter(TCom command, uint commonId, CustomSkill skill) AddParameter(command, "skill_lv", skill.SkillLv); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs index c52c0cee9..29235cb03 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbNormalSkillParam.cs @@ -4,25 +4,96 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] CDataNormalSkillParamFields = new string[] + protected static readonly string[] CDataNormalSkillParamFields = new string[] { "character_common_id", "job", "skill_no", "index", "pre_skill_no" }; - private readonly string SqlInsertNormalSkillParam = $"INSERT INTO `ddon_normal_skill_param` ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});"; - private readonly string SqlReplaceNormalSkillParam = $"INSERT OR REPLACE INTO `ddon_normal_skill_param` ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});"; - private static readonly string SqlUpdateNormalSkillParam = $"UPDATE `ddon_normal_skill_param` SET {BuildQueryUpdate(CDataNormalSkillParamFields)} WHERE `character_common_id` = @character_common_id AND `job` = @job AND `skill_no`=@skill_no;"; - private static readonly string SqlSelectNormalSkillParam = $"SELECT {BuildQueryField(CDataNormalSkillParamFields)} FROM `ddon_normal_skill_param` WHERE `character_common_id` = @character_common_id;"; - private const string SqlDeleteNormalSkillParam = "DELETE FROM `ddon_normal_skill_param` WHERE `character_common_id`=@character_common_id AND `job`=@job AND `skill_no`=@skill_no;"; + private readonly string SqlInsertNormalSkillParam = $"INSERT INTO \"ddon_normal_skill_param\" ({BuildQueryField(CDataNormalSkillParamFields)}) VALUES ({BuildQueryInsert(CDataNormalSkillParamFields)});"; + private readonly string SqlInsertIfNotExistsNormalSkillParam = $"INSERT INTO \"ddon_normal_skill_param\" ({BuildQueryField(CDataNormalSkillParamFields)}) SELECT {BuildQueryInsert(CDataNormalSkillParamFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"skill_no\"=@skill_no);"; + private static readonly string SqlUpdateNormalSkillParam = $"UPDATE \"ddon_normal_skill_param\" SET {BuildQueryUpdate(CDataNormalSkillParamFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"skill_no\"=@skill_no;"; + private static readonly string SqlSelectNormalSkillParam = $"SELECT {BuildQueryField(CDataNormalSkillParamFields)} FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\" = @character_common_id;"; + private const string SqlDeleteNormalSkillParam = "DELETE FROM \"ddon_normal_skill_param\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"skill_no\"=@skill_no;"; - private CDataNormalSkillParam ReadNormalSkillParam(DbDataReader reader) + public bool InsertIfNotExistsNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsNormalSkillParam(connection, commonId, normalSkillParam); + } + + public bool InsertIfNotExistsNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam) + { + return ExecuteNonQuery(conn, SqlInsertIfNotExistsNormalSkillParam, command => + { + AddParameter(command, commonId, normalSkillParam); + }) == 1; + } + + public bool InsertNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam) + { + using TCon connection = OpenNewConnection(); + return InsertNormalSkillParam(connection, commonId, normalSkillParam); + } + + public bool InsertNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam) + { + return ExecuteNonQuery(conn, SqlInsertNormalSkillParam, command => + { + AddParameter(command, commonId, normalSkillParam); + }) == 1; + } + + public bool ReplaceNormalSkillParam(uint commonId, CDataNormalSkillParam normalSkillParam) + { + using TCon connection = OpenNewConnection(); + return ReplaceNormalSkillParam(connection, commonId, normalSkillParam); + } + + public bool ReplaceNormalSkillParam(TCon conn, uint commonId, CDataNormalSkillParam normalSkillParam) + { + Logger.Debug("Inserting storage item."); + if (!InsertIfNotExistsNormalSkillParam(conn, commonId, normalSkillParam)) + { + Logger.Debug("Storage item already exists, replacing."); + return UpdateNormalSkillParam(conn, commonId, normalSkillParam.Job, normalSkillParam.SkillNo, normalSkillParam); + } + + return true; + } + + public bool UpdateNormalSkillParam(uint commonId, JobId job, uint skillNo, CDataNormalSkillParam normalSkillParam) + { + using TCon connection = OpenNewConnection(); + return UpdateNormalSkillParam(connection, commonId, job, skillNo, normalSkillParam); + } + + public bool UpdateNormalSkillParam(TCon connection, uint commonId, JobId job, uint skillNo, CDataNormalSkillParam normalSkillParam) + { + return ExecuteNonQuery(connection, SqlUpdateNormalSkillParam, command => + { + AddParameter(command, commonId, normalSkillParam); + }) == 1; + } + + public bool DeleteNormalSkillParam(uint commonId, JobId job, uint skillNo) + { + return ExecuteNonQuery(SqlDeleteNormalSkillParam, command => + { + AddParameter(command, "@character_common_id", commonId); + AddParameter(command, "@job", (byte)job); + AddParameter(command, "@skill_no", skillNo); + }) == 1; + } + + private CDataNormalSkillParam ReadNormalSkillParam(TReader reader) { CDataNormalSkillParam normalSkillParam = new CDataNormalSkillParam(); - normalSkillParam.Job = (JobId) GetByte(reader, "job"); + normalSkillParam.Job = (JobId)GetByte(reader, "job"); normalSkillParam.SkillNo = GetUInt32(reader, "skill_no"); normalSkillParam.Index = GetUInt32(reader, "index"); normalSkillParam.PreSkillNo = GetUInt32(reader, "pre_skill_no"); @@ -32,10 +103,10 @@ private CDataNormalSkillParam ReadNormalSkillParam(DbDataReader reader) private void AddParameter(TCom command, uint commonId, CDataNormalSkillParam normalSkillParam) { AddParameter(command, "character_common_id", commonId); - AddParameter(command, "job", (byte) normalSkillParam.Job); + AddParameter(command, "job", (byte)normalSkillParam.Job); AddParameter(command, "skill_no", normalSkillParam.SkillNo); AddParameter(command, "index", normalSkillParam.Index); AddParameter(command, "pre_skill_no", normalSkillParam.PreSkillNo); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs index 9f60f434b..c67a02413 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbPawn.cs @@ -5,54 +5,55 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { private static readonly string[] PawnFields = new string[] { "character_common_id", "character_id", "name", "hm_type", "pawn_type" }; - private static readonly string[] CDataPawnReactionFields = new string[] + protected static readonly string[] CDataPawnReactionFields = new string[] { "pawn_id", "reaction_type", "motion_no" }; - private static readonly string[] CDataSpSkillFields = new string[] + protected static readonly string[] CDataSpSkillFields = new string[] { "pawn_id", "sp_skill_id", "sp_skill_lv" }; - private readonly string SqlInsertPawn = $"INSERT INTO `ddon_pawn` ({BuildQueryField(PawnFields)}) VALUES ({BuildQueryInsert(PawnFields)});"; - private static readonly string SqlUpdatePawn = $"UPDATE `ddon_pawn` SET {BuildQueryUpdate(PawnFields)} WHERE `pawn_id` = @pawn_id;"; - private static readonly string SqlSelectPawn = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField(PawnFields)} FROM `ddon_pawn` WHERE `pawn_id` = @pawn_id;"; - private static readonly string SqlSelectPawnsByCharacterId = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField(PawnFields)} FROM `ddon_pawn` WHERE `character_id` = @character_id;"; - private readonly string SqlSelectAllPawnData = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField("ddon_pawn", PawnFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}" - + "FROM `ddon_pawn` " - + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "WHERE `ddon_pawn`.`pawn_id` = @pawn_id"; - private readonly string SqlSelectAllPawnsDataByCharacterId = $"SELECT `ddon_pawn`.`pawn_id`, {BuildQueryField("ddon_pawn", PawnFields)}, `ddon_character_common`.`character_common_id`, {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}" - + "FROM `ddon_pawn` " - + "LEFT JOIN `ddon_character_common` ON `ddon_character_common`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "LEFT JOIN `ddon_edit_info` ON `ddon_edit_info`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "LEFT JOIN `ddon_status_info` ON `ddon_status_info`.`character_common_id` = `ddon_pawn`.`character_common_id` " - + "WHERE `character_id` = @character_id"; - private const string SqlDeletePawn = "DELETE FROM `ddon_character_common` WHERE EXISTS (SELECT 1 FROM `ddon_pawn` WHERE `pawn_id`=@pawn_id);"; - - private readonly string SqlInsertPawnReaction = $"INSERT INTO `ddon_pawn_reaction` ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});"; - private readonly string SqlReplacePawnReaction = $"REPLACE INTO `ddon_pawn_reaction` ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});"; - private static readonly string SqlUpdatePawnReaction = $"UPDATE `ddon_pawn_reaction` SET {BuildQueryUpdate(CDataPawnReactionFields)} WHERE `pawn_id` = @pawn_id AND `reaction_type`=@reaction_type;"; - private static readonly string SqlSelectPawnReactionByPawnId = $"SELECT {BuildQueryField(CDataPawnReactionFields)} FROM `ddon_pawn_reaction` WHERE `pawn_id` = @pawn_id;"; - private const string SqlDeletePawnReaction = "DELETE FROM `ddon_pawn_reaction` WHERE `pawn_id`=@pawn_id AND `reaction_type`=@reaction_type;"; - - private readonly string SqlInsertSpSkill = $"INSERT INTO `ddon_sp_skill` ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});"; - private readonly string SqlReplaceSpSkill = $"REPLACE INTO `ddon_sp_skill` ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});"; - private static readonly string SqlUpdateSpSkill = $"UPDATE `ddon_sp_skill` SET {BuildQueryUpdate(CDataSpSkillFields)} WHERE `pawn_id` = @pawn_id AND `sp_skill_id`=@sp_skill_id;"; - private static readonly string SqlSelectSpSkillByPawnId = $"SELECT {BuildQueryField(CDataSpSkillFields)} FROM `ddon_sp_skill` WHERE `pawn_id` = @pawn_id;"; - private const string SqlDeleteSpSkill = "DELETE FROM `ddon_sp_skill` WHERE `pawn_id`=@pawn_id AND `sp_skill_id`=@sp_skill_id;"; + private readonly string SqlInsertPawn = $"INSERT INTO \"ddon_pawn\" ({BuildQueryField(PawnFields)}) VALUES ({BuildQueryInsert(PawnFields)});"; + private static readonly string SqlUpdatePawn = $"UPDATE \"ddon_pawn\" SET {BuildQueryUpdate(PawnFields)} WHERE \"pawn_id\" = @pawn_id;"; + private static readonly string SqlSelectPawn = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField(PawnFields)} FROM \"ddon_pawn\" WHERE \"pawn_id\" = @pawn_id;"; + private static readonly string SqlSelectPawnsByCharacterId = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField(PawnFields)} FROM \"ddon_pawn\" WHERE \"character_id\" = @character_id;"; + private readonly string SqlSelectAllPawnData = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField("ddon_pawn", PawnFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}" + + "FROM \"ddon_pawn\" " + + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "WHERE \"ddon_pawn\".\"pawn_id\" = @pawn_id"; + private readonly string SqlSelectAllPawnsDataByCharacterId = $"SELECT \"ddon_pawn\".\"pawn_id\", {BuildQueryField("ddon_pawn", PawnFields)}, \"ddon_character_common\".\"character_common_id\", {BuildQueryField("ddon_character_common", CharacterCommonFields)}, {BuildQueryField("ddon_edit_info", CDataEditInfoFields)}, {BuildQueryField("ddon_status_info", CDataStatusInfoFields)}" + + "FROM \"ddon_pawn\" " + + "LEFT JOIN \"ddon_character_common\" ON \"ddon_character_common\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "LEFT JOIN \"ddon_edit_info\" ON \"ddon_edit_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "LEFT JOIN \"ddon_status_info\" ON \"ddon_status_info\".\"character_common_id\" = \"ddon_pawn\".\"character_common_id\" " + + "WHERE \"character_id\" = @character_id"; + private const string SqlDeletePawn = "DELETE FROM \"ddon_character_common\" WHERE EXISTS (SELECT 1 FROM \"ddon_pawn\" WHERE \"pawn_id\"=@pawn_id);"; + + private readonly string SqlInsertPawnReaction = $"INSERT INTO \"ddon_pawn_reaction\" ({BuildQueryField(CDataPawnReactionFields)}) VALUES ({BuildQueryInsert(CDataPawnReactionFields)});"; + private readonly string SqlInsertIfNotExistsPawnReaction = $"INSERT INTO \"ddon_pawn_reaction\" ({BuildQueryField(CDataPawnReactionFields)}) SELECT {BuildQueryInsert(CDataPawnReactionFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\"=@pawn_id AND \"reaction_type\"=@reaction_type);"; + private static readonly string SqlUpdatePawnReaction = $"UPDATE \"ddon_pawn_reaction\" SET {BuildQueryUpdate(CDataPawnReactionFields)} WHERE \"pawn_id\" = @pawn_id AND \"reaction_type\"=@reaction_type;"; + private static readonly string SqlSelectPawnReactionByPawnId = $"SELECT {BuildQueryField(CDataPawnReactionFields)} FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\" = @pawn_id;"; + private const string SqlDeletePawnReaction = "DELETE FROM \"ddon_pawn_reaction\" WHERE \"pawn_id\"=@pawn_id AND \"reaction_type\"=@reaction_type;"; + + private readonly string SqlInsertSpSkill = $"INSERT INTO \"ddon_sp_skill\" ({BuildQueryField(CDataSpSkillFields)}) VALUES ({BuildQueryInsert(CDataSpSkillFields)});"; + private readonly string SqlInsertIfNotExistsSpSkill = $"INSERT INTO \"ddon_sp_skill\" ({BuildQueryField(CDataSpSkillFields)}) SELECT {BuildQueryInsert(CDataSpSkillFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_sp_skill\" WHERE \"pawn_id\" = @pawn_id);"; + private static readonly string SqlUpdateSpSkill = $"UPDATE \"ddon_sp_skill\" SET {BuildQueryUpdate(CDataSpSkillFields)} WHERE \"pawn_id\" = @pawn_id AND \"sp_skill_id\"=@sp_skill_id;"; + private static readonly string SqlSelectSpSkillByPawnId = $"SELECT {BuildQueryField(CDataSpSkillFields)} FROM \"ddon_sp_skill\" WHERE \"pawn_id\" = @pawn_id;"; + private const string SqlDeleteSpSkill = "DELETE FROM \"ddon_sp_skill\" WHERE \"pawn_id\"=@pawn_id AND \"sp_skill_id\"=@sp_skill_id;"; public bool CreatePawn(Pawn pawn) { @@ -94,7 +95,8 @@ public Pawn SelectPawn(uint pawnId) public List SelectPawnsByCharacterId(uint characterId) { List pawns = new List(); - ExecuteInTransaction(conn => { + ExecuteInTransaction(conn => + { ExecuteReader(conn, SqlSelectAllPawnsDataByCharacterId, command => { AddParameter(command, "@character_id", characterId); }, reader => { @@ -102,10 +104,12 @@ public List SelectPawnsByCharacterId(uint characterId) { Pawn pawn = ReadAllPawnData(reader); pawns.Add(pawn); - - QueryPawnData(conn, pawn); } }); + foreach (var pawn in pawns) + { + QueryPawnData(conn, pawn); + } }); return pawns; } @@ -113,13 +117,17 @@ public List SelectPawnsByCharacterId(uint characterId) public bool DeletePawn(uint pawnId) { int rowsAffected = ExecuteNonQuery(SqlDeletePawn, - command => { AddParameter(command, "@pawn_id", pawnId); }); + command => + { + AddParameter(command, "@pawn_id", pawnId); + }); return rowsAffected > NoRowsAffected; } public bool UpdatePawnBaseInfo(Pawn pawn) { - return UpdatePawnBaseInfo(null, pawn); + using TCon connection = OpenNewConnection(); + return UpdatePawnBaseInfo(connection, pawn); } public bool UpdatePawnBaseInfo(TCon conn, Pawn pawn) @@ -163,18 +171,12 @@ private void StorePawnData(TCon conn, Pawn pawn) foreach (CDataPawnReaction pawnReaction in pawn.PawnReactionList) { - ExecuteNonQuery(conn, SqlReplacePawnReaction, command => - { - AddParameter(command, pawn.PawnId, pawnReaction); - }); + ReplacePawnReaction(conn, pawn.PawnId, pawnReaction); } foreach (CDataSpSkill spSkill in pawn.SpSkillList) { - ExecuteNonQuery(conn, SqlReplaceSpSkill, command => - { - AddParameter(command, pawn.PawnId, spSkill); - }); + ReplaceSpSkill(conn, pawn.PawnId, spSkill); } } @@ -200,8 +202,144 @@ private void CreateItems(TCon conn, Pawn pawn) } } } + + public bool InsertIfNotExistsSpSkill(uint pawnId, CDataSpSkill spSkill) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsSpSkill(connection, pawnId, spSkill); + } + + public bool InsertIfNotExistsSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill) + { + return ExecuteNonQuery(conn, SqlInsertIfNotExistsSpSkill, command => + { + AddParameter(command, pawnId, spSkill); + }) == 1; + } + + public bool InsertSpSkill(uint pawnId, CDataSpSkill spSkill) + { + using TCon connection = OpenNewConnection(); + return InsertSpSkill(connection, pawnId, spSkill); + } + + public bool InsertSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill) + { + return ExecuteNonQuery(conn, SqlInsertSpSkill, command => + { + AddParameter(command, pawnId, spSkill); + }) == 1; + } + + public bool ReplaceSpSkill(uint pawnId, CDataSpSkill spSkill) + { + using TCon connection = OpenNewConnection(); + return ReplaceSpSkill(connection, pawnId, spSkill); + } + + public bool ReplaceSpSkill(TCon conn, uint pawnId, CDataSpSkill spSkill) + { + Logger.Debug("Inserting SP Skill."); + if (!InsertIfNotExistsSpSkill(conn, pawnId, spSkill)) + { + Logger.Debug("SP skill already exists, replacing."); + return UpdateSpSkill(conn, pawnId, spSkill); + } + return true; + } + + public bool UpdateSpSkill(uint pawnId, CDataSpSkill spSkill) + { + using TCon connection = OpenNewConnection(); + return UpdateSpSkill(connection, pawnId, spSkill); + } + + public bool UpdateSpSkill(TCon connection, uint pawnId, CDataSpSkill spSkill) + { + return ExecuteNonQuery(connection, SqlUpdateSpSkill, command => + { + AddParameter(command, pawnId, spSkill); + }) == 1; + } + + public bool DeleteSpSkill(uint pawnId, byte spSkillId) + { + return ExecuteNonQuery(SqlDeleteSpSkill, command => + { + AddParameter(command, "@pawn_id", pawnId); + AddParameter(command, "@sp_skill_id", spSkillId); + }) == 1; + } + + public bool InsertIfNotExistsPawnReaction(uint pawnId, CDataPawnReaction pawnReaction) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsPawnReaction(connection, pawnId, pawnReaction); + } + + public bool InsertIfNotExistsPawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction) + { + return ExecuteNonQuery(conn, SqlInsertIfNotExistsPawnReaction, command => + { + AddParameter(command, pawnId, pawnReaction); + }) == 1; + } + + public bool InsertPawnReaction(uint pawnId, CDataPawnReaction pawnReaction) + { + using TCon connection = OpenNewConnection(); + return InsertPawnReaction(connection, pawnId, pawnReaction); + } + + public bool InsertPawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction) + { + return ExecuteNonQuery(conn, SqlInsertPawnReaction, command => + { + AddParameter(command, pawnId, pawnReaction); + }) == 1; + } + + public bool ReplacePawnReaction(uint pawnId, CDataPawnReaction pawnReaction) + { + using TCon connection = OpenNewConnection(); + return ReplacePawnReaction(connection, pawnId, pawnReaction); + } + + public bool ReplacePawnReaction(TCon conn, uint pawnId, CDataPawnReaction pawnReaction) + { + Logger.Debug("Inserting pawn reaction."); + if (!InsertIfNotExistsPawnReaction(conn, pawnId, pawnReaction)) + { + Logger.Debug("Pawn reaction already exists, replacing."); + return UpdatePawnReaction(conn, pawnId, pawnReaction); + } + return true; + } + + public bool UpdatePawnReaction(uint pawnId, CDataPawnReaction pawnReaction) + { + using TCon connection = OpenNewConnection(); + return UpdatePawnReaction(connection, pawnId, pawnReaction); + } + + public bool UpdatePawnReaction(TCon connection, uint pawnId, CDataPawnReaction pawnReaction) + { + return ExecuteNonQuery(connection, SqlUpdatePawnReaction, command => + { + AddParameter(command, pawnId, pawnReaction); + }) == 1; + } + + public bool DeleteNormalSkillParam(uint pawnId, byte reactionType) + { + return ExecuteNonQuery(SqlDeletePawnReaction, command => + { + AddParameter(command, "@pawn_id", pawnId); + AddParameter(command, "@reaction_type", reactionType); + }) == 1; + } - private Pawn ReadAllPawnData(DbDataReader reader) + private Pawn ReadAllPawnData(TReader reader) { Pawn pawn = new Pawn(); @@ -227,7 +365,7 @@ private void AddParameter(TCom command, Pawn pawn) AddParameter(command, "@pawn_type", pawn.PawnType); } - private CDataPawnReaction ReadPawnReaction(DbDataReader reader) + private CDataPawnReaction ReadPawnReaction(TReader reader) { CDataPawnReaction pawnReaction = new CDataPawnReaction(); pawnReaction.ReactionType = GetByte(reader, "reaction_type"); @@ -242,7 +380,7 @@ private void AddParameter(TCom command, uint pawnId, CDataPawnReaction pawnReact AddParameter(command, "motion_no", pawnReaction.MotionNo); } - private CDataSpSkill ReadSpSkill(DbDataReader reader) + private CDataSpSkill ReadSpSkill(TReader reader) { CDataSpSkill spSkill = new CDataSpSkill(); spSkill.SpSkillId = GetByte(reader, "sp_skill_id"); @@ -257,4 +395,4 @@ private void AddParameter(TCom command, uint pawnId, CDataSpSkill spSkill) AddParameter(command, "sp_skill_lv", spSkill.SpSkillLv); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs index e6e216ece..f4a2d8acf 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbSetting.cs @@ -2,14 +2,15 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private const string SqlSelectSetting = "SELECT `value` FROM `setting` WHERE `key` = @key;"; - private const string SqlInsertSetting = "INSERT INTO `setting` (`key`, `value`) VALUES (@key, @value);"; - private const string SqlUpdateSetting = "UPDATE `setting` SET `value`=@value WHERE `key`=@key;"; - private const string SqlDeleteSetting = "DELETE FROM `setting` WHERE `key`=@key;"; + private const string SqlSelectSetting = "SELECT \"value\" FROM \"setting\" WHERE \"key\" = @key;"; + private const string SqlInsertSetting = "INSERT INTO \"setting\" (\"key\", \"value\") VALUES (@key, @value);"; + private const string SqlUpdateSetting = "UPDATE \"setting\" SET \"value\"=@value WHERE \"key\"=@key;"; + private const string SqlDeleteSetting = "DELETE FROM \"setting\" WHERE \"key\"=@key;"; public bool SetSetting(string key, string value) diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs index b0b6e5f77..2d6e82903 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbShortcut.cs @@ -3,24 +3,45 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] ShortcutFields = new string[] + protected static readonly string[] ShortcutFields = new string[] { "character_id", "page_no", "button_no", "shortcut_id", "u32_data", "f32_data", "exex_type" }; - private readonly string SqlInsertShortcut = $"INSERT INTO `ddon_shortcut` ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});"; - private readonly string SqlReplaceShortcut = $"INSERT OR REPLACE INTO `ddon_shortcut` ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});"; - private static readonly string SqlUpdateShortcut = $"UPDATE `ddon_shortcut` SET {BuildQueryUpdate(ShortcutFields)} WHERE `character_id`=@old_character_id AND `page_no`=@old_page_no AND `button_no`=@old_button_no"; - private static readonly string SqlSelectShortcuts = $"SELECT {BuildQueryField(ShortcutFields)} FROM `ddon_shortcut` WHERE `character_id`=@character_id;"; - private const string SqlDeleteShortcut = "DELETE FROM `ddon_shortcut` WHERE `character_id`=@character_id AND `page_no`=@page_no AND `button_no`=@button_no"; + private readonly string SqlInsertShortcut = $"INSERT INTO \"ddon_shortcut\" ({BuildQueryField(ShortcutFields)}) VALUES ({BuildQueryInsert(ShortcutFields)});"; + private readonly string SqlInsertIfNotExistsShortcut = $"INSERT INTO \"ddon_shortcut\" ({BuildQueryField(ShortcutFields)}) SELECT {BuildQueryInsert(ShortcutFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no);"; + private static readonly string SqlUpdateShortcut = $"UPDATE \"ddon_shortcut\" SET {BuildQueryUpdate(ShortcutFields)} WHERE \"character_id\"=@old_character_id AND \"page_no\"=@old_page_no AND \"button_no\"=@old_button_no"; + private static readonly string SqlSelectShortcuts = $"SELECT {BuildQueryField(ShortcutFields)} FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id;"; + private const string SqlDeleteShortcut = "DELETE FROM \"ddon_shortcut\" WHERE \"character_id\"=@character_id AND \"page_no\"=@page_no AND \"button_no\"=@button_no"; + public bool InsertIfNotExistsShortcut(uint characterId, CDataShortCut shortcut) + { + using TCon connection = OpenNewConnection(); + return InsertShortcut(connection, characterId, shortcut); + } + + public bool InsertIfNotExistsShortcut(TCon connection, uint characterId, CDataShortCut shortcut) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsShortcut, command => + { + AddParameter(command, characterId, shortcut); + }) == 1; + } + public bool InsertShortcut(uint characterId, CDataShortCut shortcut) { - return ExecuteNonQuery(SqlInsertShortcut, command => + using TCon connection = OpenNewConnection(); + return InsertShortcut(connection, characterId, shortcut); + } + + public bool InsertShortcut(TCon connection, uint characterId, CDataShortCut shortcut) + { + return ExecuteNonQuery(connection, SqlInsertShortcut, command => { AddParameter(command, characterId, shortcut); }) == 1; @@ -28,16 +49,29 @@ public bool InsertShortcut(uint characterId, CDataShortCut shortcut) public bool ReplaceShortcut(uint characterId, CDataShortCut shortcut) { - ExecuteNonQuery(SqlReplaceShortcut, command => + using TCon connection = OpenNewConnection(); + return ReplaceShortcut(connection, characterId, shortcut); + } + + public bool ReplaceShortcut(TCon connection, uint characterId, CDataShortCut shortcut) + { + Logger.Debug("Inserting shortcut."); + if (!InsertIfNotExistsShortcut(connection, characterId, shortcut)) { - AddParameter(command, characterId, shortcut); - }); + Logger.Debug("Shortcut already exists, replacing."); + return UpdateShortcut(connection, characterId, shortcut.PageNo, shortcut.ButtonNo, shortcut); + } return true; } public bool UpdateShortcut(uint characterId, uint oldPageNo, uint oldButtonNo, CDataShortCut updatedShortcut) { - return ExecuteNonQuery(SqlDeleteShortcut, command => + using TCon connection = OpenNewConnection(); + return UpdateShortcut(connection, characterId, oldPageNo, oldButtonNo, updatedShortcut); + } + public bool UpdateShortcut(TCon connection, uint characterId, uint oldPageNo, uint oldButtonNo, CDataShortCut updatedShortcut) + { + return ExecuteNonQuery(connection, SqlUpdateShortcut, command => { AddParameter(command, characterId, updatedShortcut); AddParameter(command, "@old_character_id", characterId); @@ -56,7 +90,7 @@ public bool DeleteShortcut(uint characterId, uint pageNo, uint buttonNo) }) == 1; } - private CDataShortCut ReadShortCut(DbDataReader reader) + private CDataShortCut ReadShortCut(TReader reader) { CDataShortCut shortcut = new CDataShortCut(); shortcut.PageNo = GetUInt32(reader, "page_no"); @@ -79,4 +113,4 @@ private void AddParameter(TCom command, uint characterId, CDataShortCut shortcut AddParameter(command, "exex_type", shortcut.ExexType); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs index 7caea38c9..93cdc2b8a 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorage.cs @@ -5,25 +5,63 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] StorageFields = new string[] + protected static readonly string[] StorageFields = new string[] { "character_id", "storage_type", "slot_max", "item_sort" }; - private static readonly string SqlInsertStorage = $"INSERT INTO `ddon_storage` ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});"; - private static readonly string SqlReplaceStorage = $"INSERT OR REPLACE INTO `ddon_storage` ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});"; - private static readonly string SqlUpdateStorage = $"UPDATE `ddon_storage` SET {BuildQueryUpdate(StorageFields)} WHERE `storage_type` = @storage_type AND `character_id` = @character_id;"; - private static readonly string SqlSelectStorage = $"SELECT {BuildQueryField(StorageFields)} FROM `ddon_storage` WHERE `storage_type` = @storage_type AND `character_id` = @character_id;"; - private static readonly string SqlSelectAllStoragesByCharacter = $"SELECT {BuildQueryField(StorageFields)} FROM `ddon_storage` WHERE `character_id` = @character_id;"; - private static readonly string SqlDeleteStorage = "DELETE FROM `ddon_storage` WHERE `storage_type`=@storage_type AND `character_id` = @character_id;"; + private static readonly string SqlInsertStorage = $"INSERT INTO \"ddon_storage\" ({BuildQueryField(StorageFields)}) VALUES ({BuildQueryInsert(StorageFields)});"; + private static readonly string SqlInsertIfNotExistsStorage = $"INSERT INTO \"ddon_storage\" ({BuildQueryField(StorageFields)}) SELECT {BuildQueryInsert(StorageFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_storage\" WHERE \"storage_type\"=@storage_type AND \"character_id\" = @character_id);"; + private static readonly string SqlUpdateStorage = $"UPDATE \"ddon_storage\" SET {BuildQueryUpdate(StorageFields)} WHERE \"storage_type\" = @storage_type AND \"character_id\" = @character_id;"; + private static readonly string SqlSelectStorage = $"SELECT {BuildQueryField(StorageFields)} FROM \"ddon_storage\" WHERE \"storage_type\" = @storage_type AND \"character_id\" = @character_id;"; + private static readonly string SqlSelectAllStoragesByCharacter = $"SELECT {BuildQueryField(StorageFields)} FROM \"ddon_storage\" WHERE \"character_id\" = @character_id;"; + private static readonly string SqlDeleteStorage = "DELETE FROM \"ddon_storage\" WHERE \"storage_type\"=@storage_type AND \"character_id\" = @character_id;"; + public bool ReplaceStorage(uint characterId, StorageType storageType, Storage storage) + { + using TCon connection = OpenNewConnection(); + return ReplaceStorage(connection, characterId, storageType, storage); + } + + public bool ReplaceStorage(TCon connection, uint characterId, StorageType storageType, Storage storage) + { + Logger.Debug("Inserting storage."); + if (!InsertIfNotExistsStorage(connection, characterId, storageType, storage)) + { + Logger.Debug("Storage already exists, replacing."); + return UpdateStorage(connection, characterId, storageType, storage); + } + return true; + } + + public bool InsertIfNotExistsStorage(uint characterId, StorageType storageType, Storage storage) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsStorage(connection, characterId, storageType, storage); + } + + public bool InsertIfNotExistsStorage(TCon connection, uint characterId, StorageType storageType, Storage storage) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsStorage, command => + { + AddParameter(command, characterId, storageType, storage); + }) == 1; + } + public bool InsertStorage(uint characterId, StorageType storageType, Storage storage) { - return ExecuteNonQuery(SqlInsertStorage, command => + using TCon connection = OpenNewConnection(); + return InsertStorage(connection, characterId, storageType, storage); + } + + public bool InsertStorage(TCon connection, uint characterId, StorageType storageType, Storage storage) + { + return ExecuteNonQuery(connection, SqlInsertStorage, command => { AddParameter(command, characterId, storageType, storage); }) == 1; @@ -31,7 +69,13 @@ public bool InsertStorage(uint characterId, StorageType storageType, Storage sto public bool UpdateStorage(uint characterId, StorageType storageType, Storage storage) { - return ExecuteNonQuery(SqlUpdateStorage, command => + using TCon connection = OpenNewConnection(); + return UpdateStorage(connection, characterId, storageType, storage); + } + + public bool UpdateStorage(TCon connection, uint characterId, StorageType storageType, Storage storage) + { + return ExecuteNonQuery(connection, SqlUpdateStorage, command => { AddParameter(command, characterId, storageType, storage); }) == 1; @@ -46,8 +90,7 @@ public bool DeleteStorage(uint characterId, StorageType storageType) }) == 1; } - - private Tuple ReadStorage(DbDataReader reader) + private Tuple ReadStorage(TReader reader) { StorageType storageType = (StorageType) GetByte(reader, "storage_type"); ushort slotMax = GetUInt16(reader, "slot_max"); @@ -63,4 +106,4 @@ private void AddParameter(TCom command, uint characterId, StorageType storageTyp AddParameter(command, "item_sort", storage.SortData); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs index cfc9183bc..9dddad98d 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbStorageItem.cs @@ -4,25 +4,27 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] StorageItemFields = new string[] + protected static readonly string[] StorageItemFields = new string[] { "item_uid", "character_id", "storage_type", "slot_no", "item_num" }; - private static readonly string SqlInsertStorageItem = $"INSERT INTO `ddon_storage_item` ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});"; - private static readonly string SqlReplaceStorageItem = $"INSERT OR REPLACE INTO `ddon_storage_item` ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});"; - private static readonly string SqlSelectStorageItemsByUId = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `item_uid`=@item_uid;"; - private static readonly string SqlSelectStorageItemsByCharacter = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `character_id`=@character_id;"; - private static readonly string SqlSelectStorageItemsByCharacterAndStorageType = $"SELECT {BuildQueryField(StorageItemFields)} FROM `ddon_storage_item` WHERE `character_id`=@character_id AND `storage_type`=@storage_type;"; - private static readonly string SqlDeleteStorageItem = "DELETE FROM `ddon_storage_item` WHERE `character_id`=@character_id AND `storage_type`=@storage_type AND `slot_no`=@slot_no;"; - - public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + private static readonly string SqlInsertStorageItem = $"INSERT INTO \"ddon_storage_item\" ({BuildQueryField(StorageItemFields)}) VALUES ({BuildQueryInsert(StorageItemFields)});"; + private static readonly string SqlInsertIfNotExistsStorageItem = $"INSERT INTO \"ddon_storage_item\" ({BuildQueryField(StorageItemFields)}) SELECT {BuildQueryInsert(StorageItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no);"; + private static readonly string SqlSelectStorageItemsByUId = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"item_uid\"=@item_uid;"; + private static readonly string SqlSelectStorageItemsByCharacter = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id;"; + private static readonly string SqlSelectStorageItemsByCharacterAndStorageType = $"SELECT {BuildQueryField(StorageItemFields)} FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type;"; + private static readonly string SqlDeleteStorageItem = "DELETE FROM \"ddon_storage_item\" WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no;"; + private static readonly string SqlUpdateStorageItem = $"UPDATE \"ddon_storage_item\" SET {BuildQueryUpdate(StorageItemFields)} WHERE \"character_id\"=@character_id AND \"storage_type\"=@storage_type AND \"slot_no\"=@slot_no;"; + + public bool InsertIfNotExistsStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) { - return ExecuteNonQuery(conn, SqlInsertStorageItem, command => + return ExecuteNonQuery(conn, SqlInsertIfNotExistsStorageItem, command => { AddParameter(command, "character_id", characterId); AddParameter(command, "storage_type", (byte) storageType); @@ -32,14 +34,15 @@ public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageTy }) == 1; } - public bool InsertStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + public bool InsertIfNotExistsStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) { - return this.InsertStorageItem(null, characterId, storageType, slotNo, itemUId, itemNum); + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum); } - - public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + + public bool InsertStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) { - return ExecuteNonQuery(conn, SqlReplaceStorageItem, command => + return ExecuteNonQuery(conn, SqlInsertStorageItem, command => { AddParameter(command, "character_id", characterId); AddParameter(command, "storage_type", (byte) storageType); @@ -49,9 +52,27 @@ public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageT }) == 1; } + public bool InsertStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + { + using TCon connection = OpenNewConnection(); + return InsertStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum); + } + + public bool ReplaceStorageItem(TCon conn, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + { + Logger.Debug("Inserting storage item."); + if (!InsertIfNotExistsStorageItem(conn, characterId, storageType, slotNo, itemUId, itemNum)) + { + Logger.Debug("Storage item already exists, replacing."); + return UpdateStorageItem(conn, characterId, storageType, slotNo, itemUId, itemNum); + } + return true; + } + public bool ReplaceStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) { - return this.ReplaceStorageItem(null, characterId, storageType, slotNo, itemUId, itemNum); + using TCon connection = OpenNewConnection(); + return ReplaceStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum); } public bool DeleteStorageItem(uint characterId, StorageType storageType, ushort slotNo) @@ -63,5 +84,23 @@ public bool DeleteStorageItem(uint characterId, StorageType storageType, ushort AddParameter(command, "slot_no", slotNo); }) == 1; } + + public bool UpdateStorageItem(uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + { + using TCon connection = OpenNewConnection(); + return UpdateStorageItem(connection, characterId, storageType, slotNo, itemUId, itemNum); + } + + public bool UpdateStorageItem(TCon connection, uint characterId, StorageType storageType, ushort slotNo, string itemUId, uint itemNum) + { + return ExecuteNonQuery(connection, SqlUpdateStorageItem, command => + { + AddParameter(command, "character_id", characterId); + AddParameter(command, "storage_type", (byte) storageType); + AddParameter(command, "slot_no", slotNo); + AddParameter(command, "item_uid", itemUId); + AddParameter(command, "item_num", itemNum); + }) == 1; + } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs index 04b63fbd0..af7c6b316 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbWalletPoint.cs @@ -4,24 +4,45 @@ namespace Arrowgene.Ddon.Database.Sql.Core { - public abstract partial class DdonSqlDb : SqlDb + public abstract partial class DdonSqlDb : SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - private static readonly string[] WalletPointFields = new string[] + protected static readonly string[] WalletPointFields = new string[] { "character_id", "type", "value" }; - private readonly string SqlInsertWalletPoint = $"INSERT INTO `ddon_wallet_point` ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});"; - private readonly string SqlReplaceWalletPoint = $"INSERT OR REPLACE INTO `ddon_wallet_point` ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});"; - private static readonly string SqlUpdateWalletPoint = $"UPDATE `ddon_wallet_point` SET {BuildQueryUpdate(WalletPointFields)} WHERE `character_id`=@character_id AND `type`=@type"; - private static readonly string SqlSelectWalletPoints = $"SELECT {BuildQueryField(WalletPointFields)} FROM `ddon_wallet_point` WHERE `character_id`=@character_id;"; - private const string SqlDeleteWalletPoint = "DELETE FROM `ddon_wallet_point` WHERE `character_id`=@character_id AND `type`=@type"; + private readonly string SqlInsertWalletPoint = $"INSERT INTO \"ddon_wallet_point\" ({BuildQueryField(WalletPointFields)}) VALUES ({BuildQueryInsert(WalletPointFields)});"; + private readonly string SqlInsertIfNotExistsWalletPoint = $"INSERT INTO \"ddon_wallet_point\" ({BuildQueryField(WalletPointFields)}) SELECT {BuildQueryInsert(WalletPointFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id AND \"type\"=@type);"; + private static readonly string SqlUpdateWalletPoint = $"UPDATE \"ddon_wallet_point\" SET {BuildQueryUpdate(WalletPointFields)} WHERE \"character_id\"=@character_id AND \"type\"=@type"; + private static readonly string SqlSelectWalletPoints = $"SELECT {BuildQueryField(WalletPointFields)} FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id;"; + private const string SqlDeleteWalletPoint = "DELETE FROM \"ddon_wallet_point\" WHERE \"character_id\"=@character_id AND \"type\"=@type"; + public bool InsertIfNotExistsWalletPoint(uint characterId, CDataWalletPoint walletPoint) + { + using TCon connection = OpenNewConnection(); + return InsertIfNotExistsWalletPoint(connection, characterId, walletPoint); + } + + public bool InsertIfNotExistsWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint) + { + return ExecuteNonQuery(connection, SqlInsertIfNotExistsWalletPoint, command => + { + AddParameter(command, characterId, walletPoint); + }) == 1; + } + public bool InsertWalletPoint(uint characterId, CDataWalletPoint walletPoint) { - return ExecuteNonQuery(SqlInsertWalletPoint, command => + using TCon connection = OpenNewConnection(); + return InsertWalletPoint(connection, characterId, walletPoint); + } + + public bool InsertWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint) + { + return ExecuteNonQuery(connection, SqlInsertWalletPoint, command => { AddParameter(command, characterId, walletPoint); }) == 1; @@ -29,16 +50,30 @@ public bool InsertWalletPoint(uint characterId, CDataWalletPoint walletPoint) public bool ReplaceWalletPoint(uint characterId, CDataWalletPoint walletPoint) { - ExecuteNonQuery(SqlReplaceWalletPoint, command => + using TCon connection = OpenNewConnection(); + return ReplaceWalletPoint(connection, characterId, walletPoint); + } + + public bool ReplaceWalletPoint(TCon connection, uint characterId, CDataWalletPoint walletPoint) + { + Logger.Debug("Inserting wallet point."); + if (!InsertIfNotExistsWalletPoint(connection, characterId, walletPoint)) { - AddParameter(command, characterId, walletPoint); - }); + Logger.Debug("Wallet point already exists, replacing."); + return UpdateWalletPoint(connection, characterId, walletPoint); + } return true; } public bool UpdateWalletPoint(uint characterId, CDataWalletPoint updatedWalletPoint) { - return ExecuteNonQuery(SqlUpdateWalletPoint, command => + using TCon connection = OpenNewConnection(); + return UpdateWalletPoint(connection, characterId, updatedWalletPoint); + } + + public bool UpdateWalletPoint(TCon connection, uint characterId, CDataWalletPoint updatedWalletPoint) + { + return ExecuteNonQuery(connection, SqlUpdateWalletPoint, command => { AddParameter(command, characterId, updatedWalletPoint); }) == 1; @@ -53,7 +88,7 @@ public bool DeleteWalletPoint(uint characterId, WalletType type) }) == 1; } - private CDataWalletPoint ReadWalletPoint(DbDataReader reader) + private CDataWalletPoint ReadWalletPoint(TReader reader) { CDataWalletPoint walletPoint = new CDataWalletPoint(); walletPoint.Type = (WalletType) GetByte(reader, "type"); @@ -68,4 +103,4 @@ private void AddParameter(TCom command, uint characterId, CDataWalletPoint walle AddParameter(command, "value", walletPoint.Value); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs b/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs new file mode 100644 index 000000000..c2b425d8f --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/DdonMariaDb.cs @@ -0,0 +1,79 @@ +using System; +using Arrowgene.Ddon.Database.Sql.Core; +using Arrowgene.Logging; +using MySqlConnector; + +namespace Arrowgene.Ddon.Database.Sql +{ + public class DdonMariaDb : DdonSqlDb, IDatabase + { + private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonMariaDb)); + + private string _connectionString; + + public DdonMariaDb(string host, string user, string password, string database, bool wipeOnStartup) + { + _connectionString = BuildConnectionString(host, user, password, database); + if (wipeOnStartup) + { + Logger.Info($"WipeOnStartup is currently not supported."); + } + } + + public bool CreateDatabase() + { + if (_connectionString == null) + { + Logger.Error($"Failed to build connection string"); + return false; + } + + ReusableConnection = new MySqlConnection(_connectionString); + return true; + } + + private string BuildConnectionString(string host, string user, string password, string database) + { + MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder + { + Server = host, + UserID = user, + Password = password, + Database = database, + IgnoreCommandTransaction = true, + Pooling = true + }; + string connectionString = builder.ToString(); + Logger.Info($"Connection String: {connectionString}"); + return connectionString; + } + + protected override MySqlConnection OpenNewConnection() + { + MySqlConnection connection = new MySqlConnection(_connectionString); + connection.Open(); + return connection; + } + + protected override MySqlCommand Command(string query, MySqlConnection connection) + { + return new MySqlCommand(query, connection); + } + + /// + /// Always returns the first generated ID in a multi-statement environment. Ideally should be used on a per-connection basis. + /// https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id + /// + protected override long AutoIncrement(MySqlConnection connection, MySqlCommand command) + { + return command.LastInsertedId; + } + + public override int Upsert(string table, string[] columns, object[] values, string whereColumn, + object whereValue, + out long autoIncrement) + { + throw new NotImplementedException(); + } + } +} diff --git a/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs b/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs new file mode 100644 index 000000000..56f44af4c --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/DdonPostgresDb.cs @@ -0,0 +1,118 @@ +using System; +using System.Data; +using Arrowgene.Ddon.Database.Sql.Core; +using Arrowgene.Logging; +using Npgsql; + +namespace Arrowgene.Ddon.Database.Sql +{ + public class DdonPostgresDb : DdonSqlDb, IDatabase + { + private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonPostgresDb)); + + private string _connectionString; + private NpgsqlDataSource _dataSource; + + public DdonPostgresDb(string host, string user, string password, string database, bool wipeOnStartup) + { + _connectionString = BuildConnectionString(host, user, password, database); + if (wipeOnStartup) + { + Logger.Info($"WipeOnStartup is currently not supported."); + } + } + + public bool CreateDatabase() + { + if (_connectionString == null) + { + Logger.Error($"Failed to build connection string"); + return false; + } + + if (_dataSource == null) + { + var dataSourceBuilder = new NpgsqlDataSourceBuilder(_connectionString); + dataSourceBuilder.EnableParameterLogging(); + _dataSource = dataSourceBuilder.Build(); + } + + ReusableConnection = _dataSource.OpenConnection(); + return true; + } + + private string BuildConnectionString(string host, string user, string password, string database) + { + NpgsqlConnectionStringBuilder builder = new NpgsqlConnectionStringBuilder + { + Host = host, + Username = user, + Password = password, + Database = database, + Pooling = true + }; + string connectionString = builder.ToString(); + Logger.Info($"Connection String: {connectionString}"); + return connectionString; + } + + protected override NpgsqlConnection OpenNewConnection() + { + return _dataSource.OpenConnection(); + } + + protected override NpgsqlCommand Command(string query, NpgsqlConnection connection) + { + return new NpgsqlCommand(query, connection); + } + + /// + /// Safe within the same connection session (transaction?), but unsafe if triggers are involved. + /// https://stackoverflow.com/questions/2944297/postgresql-function-for-last-inserted-id + /// + protected override long AutoIncrement(NpgsqlConnection connection, NpgsqlCommand command) + { + using NpgsqlCommand lastIdCommand = new NpgsqlCommand("SELECT LASTVAL();", connection); + return (long)lastIdCommand.ExecuteScalar(); + } + + public override int Upsert(string table, string[] columns, object[] values, string whereColumn, + object whereValue, + out long autoIncrement) + { + throw new NotImplementedException(); + } + + protected override void AddParameter(NpgsqlCommand command, string name, DateTime? value) + { + if (value.HasValue) + { + AddParameter(command, name, DateTime.SpecifyKind(value.Value, DateTimeKind.Utc), DbType.DateTime); + } + else + { + AddParameter(command, name, value, DbType.DateTime); + } + } + + protected override void AddParameter(NpgsqlCommand command, string name, DateTime value) + { + AddParameter(command, name, DateTime.SpecifyKind(value, DateTimeKind.Utc), DbType.DateTime); + } + + protected override DateTime GetDateTime(NpgsqlDataReader reader, string column) + { + return DateTime.SpecifyKind(reader.GetDateTime(reader.GetOrdinal(column)), DateTimeKind.Utc); + } + + protected override DateTime? GetDateTimeNullable(NpgsqlDataReader reader, int ordinal) + { + if (reader.IsDBNull(ordinal)) + { + return null; + } + + return DateTime.SpecifyKind(reader.GetDateTime(ordinal), DateTimeKind.Utc); + } + } +} diff --git a/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs b/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs index a40aa0ecf..fac928bce 100644 --- a/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs +++ b/Arrowgene.Ddon.Database/Sql/DdonSqLiteDb.cs @@ -6,10 +6,7 @@ namespace Arrowgene.Ddon.Database.Sql { - /// - /// SQLite Ddon database. - /// - public class DdonSqLiteDb : DdonSqlDb, IDatabase + public class DdonSqLiteDb : DdonSqlDb, IDatabase { private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonSqLiteDb)); @@ -21,11 +18,22 @@ public class DdonSqLiteDb : DdonSqlDb, IDatabas private string _connectionString; private SQLiteConnection _memoryConnection; - public DdonSqLiteDb(string databasePath) + public DdonSqLiteDb(string databasePath, bool wipeOnStartup) { _memoryConnection = null; _databasePath = databasePath; - Logger.Info($"Database Path: {_databasePath}"); + if (wipeOnStartup) + { + try + { + File.Delete(_databasePath); + Logger.Info($"Database has been wiped."); + } + catch (Exception) + { + Logger.Error($"Failed to wipe database."); + } + } } public bool CreateDatabase() @@ -37,6 +45,8 @@ public bool CreateDatabase() return false; } + ReusableConnection = new SQLiteConnection(_connectionString); + if (_databasePath == MemoryDatabasePath) { throw new NotSupportedException("Connections are utilized via `using`, disposing the connection. In Memory DB only available for lifetime of connection"); @@ -59,22 +69,24 @@ public bool CreateDatabase() private string BuildConnectionString(string source) { - SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder(); - builder.DataSource = source; - builder.Version = 3; - builder.ForeignKeys = true; - // Set ADO.NET conformance flag https://system.data.sqlite.org/index.html/info/e36e05e299 - builder.Flags = builder.Flags & SQLiteConnectionFlags.StrictConformance; + SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder + { + DataSource = source, + Version = 3, + ForeignKeys = true, + Pooling = true, + // Set ADO.NET conformance flag https://system.data.sqlite.org/index.html/info/e36e05e299 + Flags = SQLiteConnectionFlags.Default | SQLiteConnectionFlags.StrictConformance + }; string connectionString = builder.ToString(); Logger.Info($"Connection String: {connectionString}"); return connectionString; } - protected override SQLiteConnection Connection() + protected override SQLiteConnection OpenNewConnection() { - SQLiteConnection connection = new SQLiteConnection(_connectionString); - return connection.OpenAndReturn(); + return new SQLiteConnection(_connectionString).OpenAndReturn(); } protected override SQLiteCommand Command(string query, SQLiteConnection connection) diff --git a/Arrowgene.Ddon.Database/Sql/SqlDb.cs b/Arrowgene.Ddon.Database/Sql/SqlDb.cs index 3922f4b0b..134e0e957 100644 --- a/Arrowgene.Ddon.Database/Sql/SqlDb.cs +++ b/Arrowgene.Ddon.Database/Sql/SqlDb.cs @@ -9,18 +9,41 @@ namespace Arrowgene.Ddon.Database.Sql /// /// Operations for SQL type databases. /// - public abstract class SqlDb + public abstract class SqlDb where TCon : DbConnection where TCom : DbCommand + where TReader : DbDataReader { - public const int NoRowsAffected = 0; - public const long NoAutoIncrement = 0; + protected const int NoRowsAffected = 0; + protected const long NoAutoIncrement = 0; - public SqlDb() + protected abstract TCon OpenNewConnection(); + + protected virtual TCon ReusableConnection { get; set; } + + /// + /// Reusing connections can usually only be done in special cases, depending on the database engine: + /// - One operation at a time. + /// - Not thread-safe. + /// If unsure, check DB engine connector documentation or prefer to use . + /// + /// An opened, prior-existing connection + protected virtual TCon OpenExistingConnection() { + switch (ReusableConnection.State) + { + case ConnectionState.Closed: + ReusableConnection.Open(); + break; + case ConnectionState.Broken: + ReusableConnection.Close(); + ReusableConnection.Open(); + break; + } + + return ReusableConnection; } - protected abstract TCon Connection(); protected abstract TCom Command(string query, TCon connection); protected abstract long AutoIncrement(TCon connection, TCom command); @@ -29,54 +52,47 @@ public abstract int Upsert(string table, string[] columns, object[] values, stri public bool ExecuteInTransaction(Action action) { - using (TCon connection = Connection()) + using TCon connection = OpenNewConnection(); + using DbTransaction transaction = connection.BeginTransaction(); + try { - try - { - Execute(connection, "BEGIN TRANSACTION"); - action(connection); - Execute(connection, "COMMIT"); - return true; - } - catch (Exception ex) - { - Execute(connection, "ROLLBACK"); - Exception(ex); - return false; - } + action(connection); + transaction.Commit(); + return true; + } + catch (Exception ex) + { + transaction.Rollback(); + connection.Close(); + Exception(ex); + return false; + } + finally + { + connection.Close(); } } public int ExecuteNonQuery(string query, Action nonQueryAction) { - return ExecuteNonQuery(null, query, nonQueryAction); + using TCon connection = OpenNewConnection(); + try + { + return ExecuteNonQuery(connection, query, nonQueryAction); + } + finally + { + connection.Close(); + } } - public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction) + public int ExecuteNonQuery(TCon conn, string query, Action nonQueryAction) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - int rowsAffected = 0; - using (TCom command = Command(query, connection)) - { - nonQueryAction(command); - rowsAffected = command.ExecuteNonQuery(); - } - - if (autoCloseConnection) - { - connection.Close(); - } - - return rowsAffected; + using TCom command = Command(query, conn); + nonQueryAction(command); + return command.ExecuteNonQuery(); } catch (Exception ex) { @@ -87,34 +103,25 @@ public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction public int ExecuteNonQuery(string query, Action nonQueryAction, out long autoIncrement) { - return ExecuteNonQuery(null, query, nonQueryAction, out autoIncrement); + using TCon connection = OpenNewConnection(); + try + { + return ExecuteNonQuery(connection, query, nonQueryAction, out autoIncrement); + } + finally + { + connection.Close(); + } } - public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction, out long autoIncrement) + public int ExecuteNonQuery(TCon conn, string query, Action nonQueryAction, out long autoIncrement) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - int rowsAffected = 0; - using (TCom command = Command(query, connection)) - { - nonQueryAction(command); - rowsAffected = command.ExecuteNonQuery(); - autoIncrement = AutoIncrement(connection, command); - } - - if (autoCloseConnection) - { - connection.Close(); - } - + using TCom command = Command(query, conn); + nonQueryAction(command); + var rowsAffected = command.ExecuteNonQuery(); + autoIncrement = AutoIncrement(conn, command); return rowsAffected; } catch (Exception ex) @@ -125,36 +132,27 @@ public int ExecuteNonQuery(TCon? conn, string query, Action nonQueryAction } } - public void ExecuteReader(string query, Action nonQueryAction, Action readAction) + public void ExecuteReader(string query, Action nonQueryAction, Action readAction) { - ExecuteReader(null, query, nonQueryAction, readAction); + using TCon connection = OpenNewConnection(); + try + { + ExecuteReader(connection, query, nonQueryAction, readAction); + } + finally + { + connection.Close(); + } } - public void ExecuteReader(TCon? conn, string query, Action nonQueryAction, Action readAction) + public void ExecuteReader(TCon conn, string query, Action nonQueryAction, Action readAction) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - using (TCom command = Command(query, connection)) - { - nonQueryAction(command); - using (DbDataReader reader = command.ExecuteReader()) - { - readAction(reader); - } - } - - if (autoCloseConnection) - { - connection.Close(); - } + using TCom command = Command(query, conn); + nonQueryAction(command); + using TReader reader = (TReader)command.ExecuteReader(); + readAction(reader); } catch (Exception ex) { @@ -162,35 +160,26 @@ public void ExecuteReader(TCon? conn, string query, Action nonQueryAction, } } - public void ExecuteReader(string query, Action readAction) + public void ExecuteReader(string query, Action readAction) { - ExecuteReader(null, query, readAction); + using TCon connection = OpenNewConnection(); + try + { + ExecuteReader(connection, query, readAction); + } + finally + { + connection.Close(); + } } - public void ExecuteReader(TCon? conn, string query, Action readAction) + public void ExecuteReader(TCon conn, string query, Action readAction) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - using (TCom command = Command(query, connection)) - { - using (DbDataReader reader = command.ExecuteReader()) - { - readAction(reader); - } - } - - if (autoCloseConnection) - { - connection.Close(); - } + using TCom command = Command(query, conn); + using TReader reader = (TReader)command.ExecuteReader(); + readAction(reader); } catch (Exception ex) { @@ -200,30 +189,23 @@ public void ExecuteReader(TCon? conn, string query, Action readAct public void Execute(string query) { - Execute(null, query); + using TCon connection = OpenNewConnection(); + try + { + Execute(connection, query); + } + finally + { + connection.Close(); + } } public void Execute(TCon? conn, string query) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - using (TCom command = Command(query, connection)) - { - command.ExecuteNonQuery(); - } - - if (autoCloseConnection) - { - connection.Close(); - } + using TCom command = Command(query, conn); + command.ExecuteNonQuery(); } catch (Exception ex) { @@ -233,29 +215,22 @@ public void Execute(TCon? conn, string query) public string ServerVersion() { - return ServerVersion(null); + using TCon connection = OpenNewConnection(); + try + { + return ServerVersion(connection); + } + finally + { + connection.Close(); + } } - public string ServerVersion(TCon? conn) + public string ServerVersion(TCon conn) { try { - bool autoCloseConnection = false; - TCon? connection = conn; - if (connection == null) - { - autoCloseConnection = true; - connection = Connection(); - } - - string serverVersion = connection.ServerVersion; - - if (autoCloseConnection) - { - connection.Close(); - } - - return serverVersion; + return conn.ServerVersion; } catch (Exception ex) { @@ -309,9 +284,14 @@ protected void AddParameter(TCom command, string name, byte value) AddParameter(command, name, value, DbType.Byte); } + protected void AddParameter(TCom command, string name, UInt16 value) + { + AddParameter(command, name, (short)value, DbType.Int16); + } + protected void AddParameter(TCom command, string name, UInt32 value) { - AddParameter(command, name, value, DbType.UInt32); + AddParameter(command, name, (int)value, DbType.Int32); } protected void AddParameterEnumInt32(TCom command, string name, T value) where T : Enum @@ -319,12 +299,12 @@ protected void AddParameterEnumInt32(TCom command, string name, T value) wher AddParameter(command, name, (Int32)(object)value, DbType.Int32); } - protected void AddParameter(TCom command, string name, DateTime? value) + protected virtual void AddParameter(TCom command, string name, DateTime? value) { AddParameter(command, name, value, DbType.DateTime); } - protected void AddParameter(TCom command, string name, DateTime value) + protected virtual void AddParameter(TCom command, string name, DateTime value) { AddParameter(command, name, value, DbType.DateTime); } @@ -339,7 +319,7 @@ protected void AddParameter(TCom command, string name, bool value) AddParameter(command, name, value, DbType.Boolean); } - protected DateTime? GetDateTimeNullable(DbDataReader reader, int ordinal) + protected virtual DateTime? GetDateTimeNullable(TReader reader, int ordinal) { if (reader.IsDBNull(ordinal)) { @@ -349,7 +329,7 @@ protected void AddParameter(TCom command, string name, bool value) return reader.GetDateTime(ordinal); } - protected string? GetStringNullable(DbDataReader reader, int ordinal) + protected string? GetStringNullable(TReader reader, int ordinal) { if (reader.IsDBNull(ordinal)) { @@ -359,9 +339,9 @@ protected void AddParameter(TCom command, string name, bool value) return reader.GetString(ordinal); } - protected byte[]? GetBytesNullable(DbDataReader reader, int ordinal, int size) + protected byte[]? GetBytesNullable(TReader reader, int ordinal, int size) { - if(reader.IsDBNull(ordinal)) + if (reader.IsDBNull(ordinal)) { return null; } @@ -371,76 +351,76 @@ protected void AddParameter(TCom command, string name, bool value) return buffer; } - protected int GetInt32(DbDataReader reader, string column) + protected int GetInt32(TReader reader, string column) { return reader.GetInt32(reader.GetOrdinal(column)); } - protected uint GetUInt32(DbDataReader reader, string column) + protected uint GetUInt32(TReader reader, string column) { return (uint)reader.GetInt32(reader.GetOrdinal(column)); } - protected byte GetByte(DbDataReader reader, string column) + protected byte GetByte(TReader reader, string column) { return reader.GetByte(reader.GetOrdinal(column)); } - protected short GetInt16(DbDataReader reader, string column) + protected short GetInt16(TReader reader, string column) { return reader.GetInt16(reader.GetOrdinal(column)); } - protected ushort GetUInt16(DbDataReader reader, string column) + protected ushort GetUInt16(TReader reader, string column) { return (ushort)reader.GetInt16(reader.GetOrdinal(column)); } - protected float GetFloat(DbDataReader reader, string column) + protected float GetFloat(TReader reader, string column) { return reader.GetFloat(reader.GetOrdinal(column)); } - protected string GetString(DbDataReader reader, string column) + protected string GetString(TReader reader, string column) { return reader.GetString(reader.GetOrdinal(column)); } - protected bool GetBoolean(DbDataReader reader, string column) + protected bool GetBoolean(TReader reader, string column) { return reader.GetBoolean(reader.GetOrdinal(column)); } - protected T GetEnumInt32(DbDataReader reader, string column) where T : Enum + protected T GetEnumInt32(TReader reader, string column) where T : Enum { return (T)(object)reader.GetInt32(reader.GetOrdinal(column)); } - protected DateTime GetDateTime(DbDataReader reader, string column) + protected virtual DateTime GetDateTime(TReader reader, string column) { return reader.GetDateTime(reader.GetOrdinal(column)); } - protected byte[] GetBytes(DbDataReader reader, string column, int size) + protected byte[] GetBytes(TReader reader, string column, int size) { byte[] buffer = new byte[size]; reader.GetBytes(reader.GetOrdinal(column), 0, buffer, 0, size); return buffer; } - protected DateTime? GetDateTimeNullable(DbDataReader reader, string column) + protected DateTime? GetDateTimeNullable(TReader reader, string column) { int ordinal = reader.GetOrdinal(column); return GetDateTimeNullable(reader, ordinal); } - protected string? GetStringNullable(DbDataReader reader, string column) + protected string? GetStringNullable(TReader reader, string column) { int ordinal = reader.GetOrdinal(column); return GetStringNullable(reader, ordinal); } - protected byte[]? GetBytesNullable(DbDataReader reader, string column, int size) + protected byte[]? GetBytesNullable(TReader reader, string column, int size) { int ordinal = reader.GetOrdinal(column); return GetBytesNullable(reader, ordinal, size); diff --git a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs index e01636542..5a63d41f9 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs @@ -208,9 +208,8 @@ private void EquipItem(DdonGameServer server, GameClient client, CharacterCommon uint itemToEquipNum = tuple.item.Item2; ushort storageSlotNo = tuple.slot; - Item? previouslyEquippedItem = characterToEquipTo.Equipment.GetEquipItem(characterToEquipTo.Job, equipType, equipSlot); + Item? previouslyEquippedItem = characterToEquipTo.Equipment.SetEquipItem(itemToEquip, characterToEquipTo.Job, equipType, equipSlot); - characterToEquipTo.Equipment.SetEquipItem(itemToEquip, characterToEquipTo.Job, equipType, equipSlot); server.Database.ReplaceEquipItem(characterToEquipTo.CommonId, characterToEquipTo.Job, equipType, equipSlot, itemToEquip.UId); if(previouslyEquippedItem != null) @@ -307,4 +306,4 @@ private void EquipItem(DdonGameServer server, GameClient client, CharacterCommon }); } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs b/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs index 08fea911d..146d876ec 100644 --- a/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs +++ b/Arrowgene.Ddon.GameServer/Chat/Log/ChatMessageLogEntry.cs @@ -12,7 +12,7 @@ public ChatMessageLogEntry() public ChatMessageLogEntry(Character character, ChatMessage chatMessage) { - DateTime = DateTime.Now; + DateTime = DateTime.UtcNow; FirstName = character.FirstName; LastName = character.LastName; CharacterId = character.CharacterId; diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs index c427a2e5c..4d1fa6a1c 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CharacterChargeRevivePointHandler.cs @@ -19,7 +19,7 @@ public override void Handle(GameClient client, StructurePacket connections = Database.SelectConnectionsByAccountId(account.Id); if (connections.Count > 0) diff --git a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs index 54ebc034c..cecb249a2 100644 --- a/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/WarpAreaWarpHandler.cs @@ -42,7 +42,7 @@ public override void Handle(GameClient client, StructurePacket TimeSpan.FromSeconds(60)) { res.Result = 1; // I guess } @@ -30,4 +30,4 @@ public override void Handle(GameClient client, StructurePacket p } client.LastWarpPointId = packet.Structure.DestPointId; - client.LastWarpDateTime = DateTime.Now; + client.LastWarpDateTime = DateTime.UtcNow; } } -} \ No newline at end of file +} diff --git a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs index 6b3cd4953..92ebee144 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyGroup.cs @@ -195,7 +195,7 @@ public ErrorRes Accept(GameClient client) return ErrorRes.Fail; } - TimeSpan invitationAge = DateTime.Now - invitation.Date; + TimeSpan invitationAge = DateTime.UtcNow - invitation.Date; if (invitationAge > TimeSpan.FromSeconds(PartyManager.InvitationTimeoutSec)) { Logger.Error(client, $"[PartyId:{Id}][Accept] invitation expired"); diff --git a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs index 9879325ea..58b9d47f0 100644 --- a/Arrowgene.Ddon.GameServer/Party/PartyManager.cs +++ b/Arrowgene.Ddon.GameServer/Party/PartyManager.cs @@ -37,7 +37,7 @@ public bool InviteParty(GameClient invitee, GameClient host, PartyGroup party) invitation.Invitee = invitee; invitation.Host = host; invitation.Party = party; - invitation.Date = DateTime.Now; + invitation.Date = DateTime.UtcNow; if (!_invites.TryAdd(invitee, invitation)) { Logger.Error(invitee, $"Already has pending invite)"); diff --git a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs index d322d1ac7..e1a8875a9 100644 --- a/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs +++ b/Arrowgene.Ddon.LoginServer/Handler/ClientLoginHandler.cs @@ -25,7 +25,7 @@ public ClientLoginHandler(DdonLoginServer server) : base(server) public override void Handle(LoginClient client, StructurePacket packet) { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; client.SetChallengeCompleted(true); string oneTimeToken = packet.Structure.OneTimeToken; diff --git a/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs b/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs index aa80978d4..5879012cb 100644 --- a/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs +++ b/Arrowgene.Ddon.LoginServer/Handler/ClientPingHandler.cs @@ -20,7 +20,7 @@ public ClientPingHandler(DdonLoginServer server) : base(server) public override void Handle(LoginClient client, IPacket packet) { - client.PingTime = DateTime.Now; + client.PingTime = DateTime.UtcNow; ServerRes res = new ServerRes(PacketId.L2C_PING_RES); client.Send(res); } diff --git a/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs b/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs index b43649b35..40ef4b754 100644 --- a/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs +++ b/Arrowgene.Ddon.Rpc/Command/ChatCommand.cs @@ -12,7 +12,7 @@ public class ChatCommand : IRpcCommand public ChatCommand() { - _since = DateTime.MinValue; + _since = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); } public ChatCommand(DateTime since) diff --git a/Arrowgene.Ddon.Shared/Model/Character.cs b/Arrowgene.Ddon.Shared/Model/Character.cs index 6735de11b..fa3ef9e24 100644 --- a/Arrowgene.Ddon.Shared/Model/Character.cs +++ b/Arrowgene.Ddon.Shared/Model/Character.cs @@ -11,7 +11,7 @@ public Character():base() { FirstName = string.Empty; LastName = string.Empty; - Created = DateTime.MinValue; + Created = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); PlayPointList = Enum.GetValues(typeof(JobId)).Cast().Select(job => new CDataJobPlayPoint() { Job = job, diff --git a/Arrowgene.Ddon.Shared/Model/GameToken.cs b/Arrowgene.Ddon.Shared/Model/GameToken.cs index ddc106cc1..4ce730bad 100644 --- a/Arrowgene.Ddon.Shared/Model/GameToken.cs +++ b/Arrowgene.Ddon.Shared/Model/GameToken.cs @@ -32,7 +32,7 @@ public static GameToken GenerateGameToken(int accountId, uint characterId) GameToken token = new GameToken(); token.Token = sb.ToString(); - token.Created = DateTime.Now; + token.Created = DateTime.UtcNow; token.AccountId = accountId; token.CharacterId = characterId; return token; diff --git a/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs b/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs index d2c8241b6..645bb583c 100644 --- a/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs +++ b/Arrowgene.Ddon.Test/Database/CharacterDatabaseTest.cs @@ -18,7 +18,7 @@ public void TestCharacterOperations() { DatabaseSetting setting = new DatabaseSetting(); setting.WipeOnStartup = true; - IDatabase database = DdonDatabaseBuilder.BuildSqLite(setting.SqLiteFolder, "character.test", true); + IDatabase database = DdonDatabaseBuilder.BuildSqLite(setting.DatabaseFolder, true); Account account = database.CreateAccount("test", "test", "test"); Assert.NotNull(account); diff --git a/Arrowgene.Ddon.WebServer/AccountRoute.cs b/Arrowgene.Ddon.WebServer/AccountRoute.cs index 00238b0eb..a7fb60753 100644 --- a/Arrowgene.Ddon.WebServer/AccountRoute.cs +++ b/Arrowgene.Ddon.WebServer/AccountRoute.cs @@ -107,7 +107,7 @@ private string CreateToken(string name, string password) } account.LoginToken = GameToken.GenerateLoginToken(); - account.LoginTokenCreated = DateTime.Now; + account.LoginTokenCreated = DateTime.UtcNow; _database.UpdateAccount(account); return account.LoginToken; } diff --git a/Arrowgene.Ddon.config.local_dev.json b/Arrowgene.Ddon.config.local_dev.json index 631525115..995d6be20 100644 --- a/Arrowgene.Ddon.config.local_dev.json +++ b/Arrowgene.Ddon.config.local_dev.json @@ -147,15 +147,5 @@ "UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit", "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html" }, - "DatabaseSetting": { - "Type": 0, - "SqLiteFolder": "\/var\/ddon\/server\/Files\/Database", - "Host": "localhost", - "Port": 3306, - "User": "", - "Password": "", - "Database": "Ddon", - "WipeOnStartup": false - }, "AssetPath": "\/var\/ddon\/server\/Files\/Assets" } \ No newline at end of file diff --git a/Arrowgene.Ddon.config.mariadb.local_dev.json b/Arrowgene.Ddon.config.mariadb.local_dev.json new file mode 100644 index 000000000..995d6be20 --- /dev/null +++ b/Arrowgene.Ddon.config.mariadb.local_dev.json @@ -0,0 +1,151 @@ +{ + "WebServerSetting": { + "PublicWebEndPoint": { + "IpAddress": "0.0.0.0", + "Port": 52099, + "IsHttps": false, + "HttpsCertPath": "", + "HttpsCertPw": "", + "SslProtocols": 0, + "DomainName": "" + }, + "WebSetting": { + "ServerHeader": "", + "WebEndpoints": [ + { + "IpAddress": "0.0.0.0", + "Port": 52099, + "IsHttps": false, + "HttpsCertPath": "", + "HttpsCertPw": "", + "SslProtocols": 0, + "DomainName": "" + } + ], + "WebFolder": "\/var\/ddon\/server\/Files\/www" + } + }, + "GameServerSetting": { + "ServerSetting": { + "Id": 10, + "Name": "Game", + "ListenIpAddress": "0.0.0.0", + "ServerPort": 52000, + "LogLevel": 0, + "LogUnknownPackets": true, + "LogOutgoingPackets": true, + "LogOutgoingPacketPayload": false, + "LogIncomingPackets": true, + "LogIncomingPacketPayload": false, + "LogIncomingPacketStructure": false, + "LogOutgoingPacketStructure": false, + "ServerSocketSettings": { + "Identity": "Game", + "MaxConnections": 100, + "NumSimultaneouslyWriteOperations": 100, + "BufferSize": 2000, + "Retries": 10, + "MaxUnitOfOrder": 1, + "MaxSimultaneousSendsPerClient": 1, + "SocketTimeoutSeconds": -1, + "SocketSettings": { + "Backlog": 5, + "DontFragment": true, + "DualMode": false, + "ExclusiveAddressUse": false, + "NoDelay": false, + "UseOnlyOverlappedIo": false, + "ReceiveBufferSize": 8192, + "ReceiveTimeout": 0, + "SendBufferSize": 8192, + "SendTimeout": 0, + "Ttl": 32, + "LingerEnabled": false, + "LingerTime": 30, + "SocketOptions": [ + { + "Level": "Socket", + "Name": 4, + "Value": false + } + ] + } + } + } + }, + "LoginServerSetting": { + "ServerSetting": { + "Id": 1, + "Name": "Login", + "ListenIpAddress": "0.0.0.0", + "ServerPort": 52100, + "LogLevel": 0, + "LogUnknownPackets": true, + "LogOutgoingPackets": true, + "LogOutgoingPacketPayload": false, + "LogIncomingPackets": true, + "LogIncomingPacketPayload": false, + "LogIncomingPacketStructure": false, + "LogOutgoingPacketStructure": false, + "ServerSocketSettings": { + "Identity": "Login", + "MaxConnections": 100, + "NumSimultaneouslyWriteOperations": 100, + "BufferSize": 2000, + "Retries": 10, + "MaxUnitOfOrder": 1, + "MaxSimultaneousSendsPerClient": 1, + "SocketTimeoutSeconds": -1, + "SocketSettings": { + "Backlog": 5, + "DontFragment": true, + "DualMode": false, + "ExclusiveAddressUse": false, + "NoDelay": false, + "UseOnlyOverlappedIo": false, + "ReceiveBufferSize": 8192, + "ReceiveTimeout": 0, + "SendBufferSize": 8192, + "SendTimeout": 0, + "Ttl": 32, + "LingerEnabled": false, + "LingerTime": 30, + "SocketOptions": [ + { + "Level": "Socket", + "Name": 4, + "Value": false + } + ] + } + } + }, + "AccountRequired": false, + "JobLevelMax": 65, + "ClanMemberMax": 100, + "CharacterNumMax": 4, + "EnableVisualEquip": true, + "FriendListMax": 200, + "NoOperationTimeOutTime": 14400, + "UrlApiA": "http:\/\/localhost:52099\/link\/api", + "UrlApiB": "http:\/\/localhost:52099\/link\/api", + "UrlCampaign": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/slide.html", + "UrlCampaignBanner": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/bnr01.html?", + "UrlChargeA": "http:\/\/localhost:52099\/sp_ingame\/charge\/", + "UrlChargeB": "http:\/\/localhost:52099\/sp_ingame\/charge\/", + "UrlChargeCallback": "http:\/\/localhost:52099\/opening\/entry\/ddo\/cog_callback\/charge", + "UrlCompanionImage": "http:\/\/localhost:52099\/", + "UrlIndex": "http:\/\/localhost:52099\/sp_ingame\/link\/index.html", + "UrlManual": "http:\/\/localhost:52099\/manual_nfb\/", + "UrlPhotoupAuthorize": "http:\/\/localhost:52099\/api\/photoup\/authorize", + "UrlSample10": "http:\/\/sample10.html", + "UrlSample9": "http:\/\/sample09.html", + "UrlShopAttention": "http:\/\/localhost:52099\/shop\/ingame\/attention?", + "UrlShopCounterA": "http:\/\/localhost:52099\/shop\/ingame\/counter?", + "UrlShopCounterB": "http:\/\/localhost:52099\/shop\/ingame\/counter?", + "UrlShopDetail": "http:\/\/localhost:52099\/shop\/ingame\/stone\/detail", + "UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit", + "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html" + }, + "AssetPath": "\/var\/ddon\/server\/Files\/Assets" +} \ No newline at end of file diff --git a/Arrowgene.Ddon.config.psql.local_dev.json b/Arrowgene.Ddon.config.psql.local_dev.json new file mode 100644 index 000000000..995d6be20 --- /dev/null +++ b/Arrowgene.Ddon.config.psql.local_dev.json @@ -0,0 +1,151 @@ +{ + "WebServerSetting": { + "PublicWebEndPoint": { + "IpAddress": "0.0.0.0", + "Port": 52099, + "IsHttps": false, + "HttpsCertPath": "", + "HttpsCertPw": "", + "SslProtocols": 0, + "DomainName": "" + }, + "WebSetting": { + "ServerHeader": "", + "WebEndpoints": [ + { + "IpAddress": "0.0.0.0", + "Port": 52099, + "IsHttps": false, + "HttpsCertPath": "", + "HttpsCertPw": "", + "SslProtocols": 0, + "DomainName": "" + } + ], + "WebFolder": "\/var\/ddon\/server\/Files\/www" + } + }, + "GameServerSetting": { + "ServerSetting": { + "Id": 10, + "Name": "Game", + "ListenIpAddress": "0.0.0.0", + "ServerPort": 52000, + "LogLevel": 0, + "LogUnknownPackets": true, + "LogOutgoingPackets": true, + "LogOutgoingPacketPayload": false, + "LogIncomingPackets": true, + "LogIncomingPacketPayload": false, + "LogIncomingPacketStructure": false, + "LogOutgoingPacketStructure": false, + "ServerSocketSettings": { + "Identity": "Game", + "MaxConnections": 100, + "NumSimultaneouslyWriteOperations": 100, + "BufferSize": 2000, + "Retries": 10, + "MaxUnitOfOrder": 1, + "MaxSimultaneousSendsPerClient": 1, + "SocketTimeoutSeconds": -1, + "SocketSettings": { + "Backlog": 5, + "DontFragment": true, + "DualMode": false, + "ExclusiveAddressUse": false, + "NoDelay": false, + "UseOnlyOverlappedIo": false, + "ReceiveBufferSize": 8192, + "ReceiveTimeout": 0, + "SendBufferSize": 8192, + "SendTimeout": 0, + "Ttl": 32, + "LingerEnabled": false, + "LingerTime": 30, + "SocketOptions": [ + { + "Level": "Socket", + "Name": 4, + "Value": false + } + ] + } + } + } + }, + "LoginServerSetting": { + "ServerSetting": { + "Id": 1, + "Name": "Login", + "ListenIpAddress": "0.0.0.0", + "ServerPort": 52100, + "LogLevel": 0, + "LogUnknownPackets": true, + "LogOutgoingPackets": true, + "LogOutgoingPacketPayload": false, + "LogIncomingPackets": true, + "LogIncomingPacketPayload": false, + "LogIncomingPacketStructure": false, + "LogOutgoingPacketStructure": false, + "ServerSocketSettings": { + "Identity": "Login", + "MaxConnections": 100, + "NumSimultaneouslyWriteOperations": 100, + "BufferSize": 2000, + "Retries": 10, + "MaxUnitOfOrder": 1, + "MaxSimultaneousSendsPerClient": 1, + "SocketTimeoutSeconds": -1, + "SocketSettings": { + "Backlog": 5, + "DontFragment": true, + "DualMode": false, + "ExclusiveAddressUse": false, + "NoDelay": false, + "UseOnlyOverlappedIo": false, + "ReceiveBufferSize": 8192, + "ReceiveTimeout": 0, + "SendBufferSize": 8192, + "SendTimeout": 0, + "Ttl": 32, + "LingerEnabled": false, + "LingerTime": 30, + "SocketOptions": [ + { + "Level": "Socket", + "Name": 4, + "Value": false + } + ] + } + } + }, + "AccountRequired": false, + "JobLevelMax": 65, + "ClanMemberMax": 100, + "CharacterNumMax": 4, + "EnableVisualEquip": true, + "FriendListMax": 200, + "NoOperationTimeOutTime": 14400, + "UrlApiA": "http:\/\/localhost:52099\/link\/api", + "UrlApiB": "http:\/\/localhost:52099\/link\/api", + "UrlCampaign": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/slide.html", + "UrlCampaignBanner": "http:\/\/localhost:52099\/sp_ingame\/campaign\/bnr\/bnr01.html?", + "UrlChargeA": "http:\/\/localhost:52099\/sp_ingame\/charge\/", + "UrlChargeB": "http:\/\/localhost:52099\/sp_ingame\/charge\/", + "UrlChargeCallback": "http:\/\/localhost:52099\/opening\/entry\/ddo\/cog_callback\/charge", + "UrlCompanionImage": "http:\/\/localhost:52099\/", + "UrlIndex": "http:\/\/localhost:52099\/sp_ingame\/link\/index.html", + "UrlManual": "http:\/\/localhost:52099\/manual_nfb\/", + "UrlPhotoupAuthorize": "http:\/\/localhost:52099\/api\/photoup\/authorize", + "UrlSample10": "http:\/\/sample10.html", + "UrlSample9": "http:\/\/sample09.html", + "UrlShopAttention": "http:\/\/localhost:52099\/shop\/ingame\/attention?", + "UrlShopCounterA": "http:\/\/localhost:52099\/shop\/ingame\/counter?", + "UrlShopCounterB": "http:\/\/localhost:52099\/shop\/ingame\/counter?", + "UrlShopDetail": "http:\/\/localhost:52099\/shop\/ingame\/stone\/detail", + "UrlShopStoneLimit": "http:\/\/localhost:52099\/shop\/ingame\/stone\/limit", + "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html" + }, + "AssetPath": "\/var\/ddon\/server\/Files\/Assets" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2adf609b8..68b6c5118 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,9 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env WORKDIR /App COPY . ./ -RUN dotnet publish Arrowgene.Ddon.Cli/Arrowgene.Ddon.Cli.csproj /p:Version=1.0.0.0 -p:PublishReadyToRun=true /p:DebugType=None /p:DebugSymbols=false -r linux-x64 --self-contained false -c Release -o out +# Runtime can be e.g. linux-x64 or linux-arm64, see officially supported Runtime Identifiers +ARG RUNTIME +RUN dotnet publish Arrowgene.Ddon.Cli/Arrowgene.Ddon.Cli.csproj /p:Version=1.0.0.0 -p:PublishReadyToRun=true /p:DebugType=None /p:DebugSymbols=false -r ${RUNTIME:-linux-x64} --self-contained false -c Release -o out FROM mcr.microsoft.com/dotnet/aspnet:6.0 #RUN apt-get update && apt-get install -y apt-transport-https && rm -rf /var/lib/apt/lists/* diff --git a/README.adoc b/README.adoc new file mode 100644 index 000000000..408cf0cab --- /dev/null +++ b/README.adoc @@ -0,0 +1,188 @@ +:toc: +:toclevels: 1 +:toc-placement!: + += Dragons Dogma Online - Server + +image::https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/actions/workflows/build.yaml/badge.svg[] + +Server Emulator for the Game Dragons Dogma Online. + +''' + +toc::[] + +''' + +== Disclaimer +The project is intended for educational purpose only. + +== Developer Setup + +. Clone the repository +.. `git clone https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline.git` +. Install .NET 6.0 SDK or later https://dotnet.microsoft.com/download +. Use your IDE of choice +.. *Visual Studio* +... Open the `DragonsDogmaOnline.sln`-file +... Note: Minimum version of "Visual Studio 2022" or later. +.. *VS Code* +... Download IDE: https://code.visualstudio.com/download +... C# Plugin: https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp +... Open the Project Folder: `\Arrowgene.DragonsDogmaOnline` +.. *IntelliJ Rider* +... https://www.jetbrains.com/rider/ +... Note: Minimum version of "IntelliJ Rider 2021.3" or later. +... Open the `DragonsDogmaOnline.sln`-file +. Debug the Project +.. Run the `Ddon.Cli`-Project with arguments `server start` + +== Deployment + +The application (server) requires the (ASP).NET runtime 6 to function. Alternatively the SDK also works and is required when building from source. + +On a high level, a total of four components are required to successfully connect a client: + +. Web server +. Login server +. Game server +. Database + +All of these components are provided & automatically started up in the default developer setup outlined above. + +As the developer setup relies on an embedded https://www.sqlite.org/index.html[SQLite] database, this might not suite all production needs (e.g. the embedded SQLite setup currently lacks ARM binaries). Thus, two more relational databases are supported: + +. https://mariadb.org/[MariaDB] +. https://www.postgresql.org/[PostgreSQL] + +=== Container setup + +It is also possible to run a containerized setup. +A xref:./Dockerfile[Dockerfile] is provided which encapsulates everything required to build & publish the server from source. Three separate docker-compose files are provided depending on the desired database. None of these actually reference any image and instead rely on ad-hoc source code builds as the image is currently not publicly available. + +. xref:./docker-compose.yml[SQLite-enabled docker-compose] +.. `docker-compose up` +. xref:./docker-compose.mariadb.yml[MariaDB-enabled docker-compose] +.. `docker-compose -f docker-compose.mariadb.yml up` +. xref:./docker-compose.psql.yml[PostgreSQL-enabled docker-compose] +.. `docker-compose -f docker-compose.psql.yml up` + +==== Useful run commands + +* Force rebuild `docker-compose up --build` +* Clean up & delete persistent volumes (data) as well `docker-compose down -v` +* Only build the image `docker-compose build` +* Build for other runtimes, e.g. arm64 `RUNTIME=linux-arm64 docker-compose build` +* All-in-one clean & rebuild `docker-compose down -v && docker-compose up --build` +* All-in-one clean & rebuild & arm64 `RUNTIME=linux-arm64 docker-compose down -v && docker-compose up --build` + +== Server +With default configuration the server will listen on following ports: + +[source] +---- +52099 - http/download +52000 - tcp/gameserver +52100 - tcp/loginserver +---- + +ensure that no other local services listen on these ports. + +== Client +Launch the client with the following args: + +`"DDO.exe" "addr=localhost port=52100 token=00000000000000000000 DL=http://127.0.0.1:52099/win/ LVer=03.04.003.20181115.0 RVer=3040008"` + +== Progress + +=== Login Server +* [x] Account +* [x] Character Creation + +=== Game Server +==== Party Management (Party List) +* [ ] Party Members +** [x] View Arisen Profile +** [ ] Send Tell +** [ ] Send Friend Request +** [ ] View Status and Equipment +** [x] Promote to Party Leader +** [x] Kick from Party +** [ ] Invite to Group Chat +** [x] Disband Party +** [ ] Invite to Entryboard +** [ ] Follow with Autorun +** [ ] Cancel Party Invite +** [ ] Decline Party Invite +** [ ] View Party List +** [x] Leave +** [ ] Invite Directly to Clan +* [ ] Main Pawns +** [ ] View Pawn Profile +** [x] Invite to Party +** [x] Kick from Party +** [ ] View Status and Equipment +* [ ] Support Pawns +* [ ] Party Search +** [ ] Search +** [ ] Simple Request +* [ ] Player Search +** [ ] View Arisen Profile +** [x] Invite to Party +** [ ] Send Tell +** [ ] Send Friend Request +** [ ] Invite to Group Chat +** [ ] Invite to Entryboard +** [x] Search + +== Guidelines +=== Git +==== Workflow +The work on this project should happen via `feature-branches` + +Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release. +When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point. +The essence of a feature branch is that it exists as long as the feature is in development, +but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment). + +1. Create a new `feature/feature-name` or `fix/bug-fix-name` branch from master +2. Push all your changes to that branch +3. Create a Pull Request to merge that branch into `master` + +=== Best Practise +- Do not use Console.WriteLine etc., use the specially designed logger. +- Own the Code: extract solutions, discard libraries. +- Annotate functions with documentation comments (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments). + +=== C# Coding Standards and Naming Conventions + +[options="header"] +|========================================================= +| Object Name | Notation | Char Mask | Underscores +| Class name | PascalCase | [A-z][0-9] | No +| Constructor name | PascalCase | [A-z][0-9] | No +| Method name | PascalCase | [A-z][0-9] | No +| Method arguments | camelCase | [A-z][0-9] | No +| Local variables | camelCase | [A-z][0-9] | No +| Constants name | PascalCase | [A-z][0-9] | No +| Field name | _camelCase | [A-z][0-9] | Yes +| Properties name | PascalCase | [A-z][0-9] | No +| Delegate name | PascalCase | [A-z] | No +| Enum type name | PascalCase | [A-z] | No +|========================================================= + +== Attribution +=== Contributors / Making It Happening +Let me preface with that this work could not exist without the excellent work of various individuals +- Ando - Reverse Engineering & Tooling (Session Splitter, Camellia Key Cracker) +- David - Reverse Engineering (unpacking PC Executable, defeating Anti Debug and CRC checks) +- The White Dragon Temple +- Nothilvien [@sebastian-heinz](https://github.com/sebastian-heinz) - Reverse Engineering & Server Code + +(if you have been forgotten please reach out) + +=== 3rd Parties and Libraries +- System.Data.SQLite (https://system.data.sqlite.org/) +- KaitaiStruct.Runtime.Csharp (https://kaitai.io/) +- Arrowgene.Networking (https://github.com/sebastian-heinz/Arrowgene.Networking) +- .NET Standard (https://github.com/dotnet/standard) diff --git a/README.md b/README.md deleted file mode 100644 index e552198ce..000000000 --- a/README.md +++ /dev/null @@ -1,159 +0,0 @@ -Dragons Dogma Online - Server -=== -Server Emulator for the Game Dragons Dogma Online. - -## Table of contents -- [Disclaimer](#disclaimer) -- [Setup](#setup) - - [Visual Studio](#visual-studio) - - [VS Code](#vs-code) - - [IntelliJ Rider](#intellij-rider) -- [Server](#server) -- [Client](#client) -- [Progress](#progress) -- [Guidelines](#guidelines) -- [Attribution](#attribution) - - [Contributers](#contributers) - - [3rd Parties and Libraries](#3rd-parties-and-libraries) - -# Disclaimer -The project is intended for educational purpose only. - -# Setup -## 1) Clone the repository -`git clone https://github.com/sebastian-heinz/ddo-server.git` - -## 2) Install .Net 6.0 SDK or later -https://dotnet.microsoft.com/download - -## 3) Use your IDE of choice: - -## 3.1) Visual Studio -### Notice: -Minimum version of "Visual Studio 2022" or later. - -### Open Project: -Open the `DragonsDogmaOnline.sln`-file - -## 3.2) VS Code -Download IDE: https://code.visualstudio.com/download -C# Plugin: https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp - -### Open Project: -Open the Project Folder: -`\Arrowgene.DragonsDogmaOnline` - -## 3.3) IntelliJ Rider -https://www.jetbrains.com/rider/ -### Notice: -Minimum version of "IntelliJ Rider 2021.3" or later. - -### Open Project: -Open the `DragonsDogmaOnline.sln`-file - -## 4) Debug the Project -Run the `Ddon.Cli`-Project - -# Server -With default configuration the server will listen on following ports: -``` -52099 - http/download -52000 - tcp/gameserver -52100 - tcp/loginserver -``` -ensure that no other local services listen on these ports. - -# Client -Launch the client with the following args: -`"DDO.exe" "addr=localhost port=52100 token=00000000000000000000 DL=http://127.0.0.1:52099/win/ LVer=03.04.003.20181115.0 RVer=3040008"` - -# Progress - -## Login Server -- [x] Account -- [x] Character Creation - -## Game Server -### Party Management (Party List) -- [ ] Party Members - - [x] View Arisen Profile - - [ ] Send Tell - - [ ] Send Friend Request - - [ ] View Status and Equipment - - [x] Promote to Party Leader - - [x] Kick from Party - - [ ] Invite to Group Chat - - [x] Disband Party - - [ ] Invite to Entryboard - - [ ] Follow with Autorun - - [ ] Cancel Party Invite - - [ ] Decline Party Invite - - [ ] View Party List - - [x] Leave - - [ ] Invite Directly to Clan -- [ ] Main Pawns - - [ ] View Pawn Profile - - [x] Invite to Party - - [x] Kick from Party - - [ ] View Status and Equipment -- [ ] Support Pawns -- [ ] Party Search - - [ ] Search - - [ ] Simple Request -- [ ] Player Search - - [ ] View Arisen Profile - - [x] Invite to Party - - [ ] Send Tell - - [ ] Send Friend Request - - [ ] Invite to Group Chat - - [ ] Invite to Entryboard - - [x] Search - -# Guidelines -## Git -### Workflow -The work on this project should happen via `feature-branches` - -Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release. -When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point. -The essence of a feature branch is that it exists as long as the feature is in development, -but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment). - -1) Create a new `feature/feature-name` or `fix/bug-fix-name` branch from master -2) Push all your changes to that branch -3) Create a Pull Request to merge that branch into `master` - -## Best Practise -- Do not use Console.WriteLine etc, use the specially designed logger. -- Own the Code: extract solutions, discard libraries. -- Annotate functions with documentation comments (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments). - -## C# Coding Standards and Naming Conventions -| Object Name | Notation | Char Mask | Underscores | -|:--------------------------|:------------|:-------------------|:------------| -| Class name | PascalCase | [A-z][0-9] | No | -| Constructor name | PascalCase | [A-z][0-9] | No | -| Method name | PascalCase | [A-z][0-9] | No | -| Method arguments | camelCase | [A-z][0-9] | No | -| Local variables | camelCase | [A-z][0-9] | No | -| Constants name | PascalCase | [A-z][0-9] | No | -| Field name | _camelCase | [A-z][0-9] | Yes | -| Properties name | PascalCase | [A-z][0-9] | No | -| Delegate name | PascalCase | [A-z] | No | -| Enum type name | PascalCase | [A-z] | No | - -# Attribution -## Contributors / Making It Happening -Let me preface with that this work could not exist without the excellent work of various individuals -- Ando - Reverse Engineering & Tooling (Session Splitter, Camellia Key Cracker) -- David - Reverse Engineering (unpacking PC Executable, defeating Anti Debug and CRC checks) -- The White Dragon Temple -- Nothilvien [@sebastian-heinz](https://github.com/sebastian-heinz) - Reverse Engineering & Server Code - -(if you have been forgotten please reach out) - -## 3rd Parties and Libraries -- System.Data.SQLite (https://system.data.sqlite.org/) -- KaitaiStruct.Runtime.Csharp (https://kaitai.io/) -- Arrowgene.Networking (https://github.com/sebastian-heinz/Arrowgene.Networking) -- .NET Standard (https://github.com/dotnet/standard) diff --git a/deploy/Arrowgene.Ddon.config.json b/deploy/Arrowgene.Ddon.config.json index 354d9b4c5..fc7012078 100644 --- a/deploy/Arrowgene.Ddon.config.json +++ b/deploy/Arrowgene.Ddon.config.json @@ -133,8 +133,8 @@ "UrlSupportIndex": "http:\/\/localhost:52099\/sp_ingame\/support\/index.html" }, "DatabaseSetting": { - "Type": 0, - "SqLiteFolder": "\/var\/ddon\/server\/Files\/Database", + "Type": "sqlite", + "DatabaseFolder": "\/var\/ddon\/server\/Files\/Database", "Host": "localhost", "Port": 3306, "User": "", diff --git a/deploy/mariadb/docker-compose.yml b/deploy/mariadb/docker-compose.yml new file mode 100644 index 000000000..a2648be4a --- /dev/null +++ b/deploy/mariadb/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +volumes: + ddon-mariadb-volume: + +services: + mariadb: + container_name: ddon-mariadb + image: mariadb:11 + ports: + - "3306:3306" + restart: no + volumes: + - ddon-mariadb-volume:/var/lib/mysql +# - $PWD/schema_mariadb.sql:/docker-entrypoint-initdb.d/schema_mariadb.sql:ro + - $PWD/mariadb.cnf:/etc/mysql/mariadb.conf.d/mariadb.cnf:ro + environment: + - MARIADB_USER=admin + - MARIADB_ROOT_PASSWORD=admin + - MARIADB_PASSWORD=admin + - MARIADB_DATABASE=mariadb + - LANG=C.UTF_8 + command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"] + healthcheck: + test: ["CMD-SHELL", "mariadb --defaults-file=/var/lib/mysql/.my-healthcheck.cnf --skip-column-names -h localhost -e \"select 1 from information_schema.ENGINES WHERE ENGINE='InnoDB' AND support in ('YES', 'DEFAULT', 'ENABLED')\""] + interval: 10s + timeout: 1s + retries: 3 diff --git a/deploy/mariadb/mariadb.cnf b/deploy/mariadb/mariadb.cnf new file mode 100644 index 000000000..aefa24bc5 --- /dev/null +++ b/deploy/mariadb/mariadb.cnf @@ -0,0 +1,7 @@ +[mysqld] +sql_mode="STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,ANSI_QUOTES" + +[mariadb] +log_warnings=3 +general_log +general_log_file=/tmp/queries.log diff --git a/deploy/mariadb/schema_mariadb.sql b/deploy/mariadb/schema_mariadb.sql new file mode 100644 index 000000000..578efda72 --- /dev/null +++ b/deploy/mariadb/schema_mariadb.sql @@ -0,0 +1,420 @@ +CREATE TABLE IF NOT EXISTS setting +( + "key" VARCHAR(32) NOT NULL, + "value" TEXT NOT NULL, + PRIMARY KEY ("key") +); + +CREATE TABLE IF NOT EXISTS account +( + "id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + "name" TEXT NOT NULL, + "normal_name" TEXT NOT NULL, + "hash" TEXT NOT NULL, + "mail" TEXT NOT NULL, + "mail_verified" BOOLEAN NOT NULL, + "mail_verified_at" DATETIME DEFAULT NULL, + "mail_token" TEXT DEFAULT NULL, + "password_token" TEXT DEFAULT NULL, + "login_token" TEXT DEFAULT NULL, + "login_token_created" DATETIME DEFAULT NULL, + "state" INTEGER NOT NULL, + "last_login" DATETIME DEFAULT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_account_name UNIQUE ("name"), + CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"), + CONSTRAINT uq_account_login_token UNIQUE ("login_token"), + CONSTRAINT uq_account_mail UNIQUE ("mail") +); + +CREATE TABLE IF NOT EXISTS ddon_character_common +( + "character_common_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + "job" SMALLINT NOT NULL, + "hide_equip_head" BOOLEAN NOT NULL, + "hide_equip_lantern" BOOLEAN NOT NULL, + "jewelry_slot_num" SMALLINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS ddon_character +( + "character_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + "character_common_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "version" INTEGER NOT NULL, + "first_name" TEXT NOT NULL, + "last_name" TEXT NOT NULL, + "created" DATETIME NOT NULL, + "my_pawn_slot_num" SMALLINT NOT NULL, + "rental_pawn_slot_num" SMALLINT NOT NULL, + "hide_equip_head_pawn" BOOLEAN NOT NULL, + "hide_equip_lantern_pawn" BOOLEAN NOT NULL, + "arisen_profile_share_range" SMALLINT NOT NULL, + CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_pawn +( + "pawn_id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, + "character_common_id" INTEGER NOT NULL, + "character_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "hm_type" SMALLINT NOT NULL, + "pawn_type" SMALLINT NOT NULL, + CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_edit_info +( + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "sex" SMALLINT NOT NULL, + "voice" SMALLINT NOT NULL, + "voice_pitch" SMALLINT NOT NULL, + "personality" SMALLINT NOT NULL, + "speech_freq" SMALLINT NOT NULL, + "body_type" SMALLINT NOT NULL, + "hair" SMALLINT NOT NULL, + "beard" SMALLINT NOT NULL, + "makeup" SMALLINT NOT NULL, + "scar" SMALLINT NOT NULL, + "eye_preset_no" SMALLINT NOT NULL, + "nose_preset_no" SMALLINT NOT NULL, + "mouth_preset_no" SMALLINT NOT NULL, + "eyebrow_tex_no" SMALLINT NOT NULL, + "color_skin" SMALLINT NOT NULL, + "color_hair" SMALLINT NOT NULL, + "color_beard" SMALLINT NOT NULL, + "color_eyebrow" SMALLINT NOT NULL, + "color_r_eye" SMALLINT NOT NULL, + "color_l_eye" SMALLINT NOT NULL, + "color_makeup" SMALLINT NOT NULL, + "sokutobu" SMALLINT NOT NULL, + "hitai" SMALLINT NOT NULL, + "mimi_jyouge" SMALLINT NOT NULL, + "kannkaku" SMALLINT NOT NULL, + "mabisasi_jyouge" SMALLINT NOT NULL, + "hanakuchi_jyouge" SMALLINT NOT NULL, + "ago_saki_haba" SMALLINT NOT NULL, + "ago_zengo" SMALLINT NOT NULL, + "ago_saki_jyouge" SMALLINT NOT NULL, + "hitomi_ookisa" SMALLINT NOT NULL, + "me_ookisa" SMALLINT NOT NULL, + "me_kaiten" SMALLINT NOT NULL, + "mayu_kaiten" SMALLINT NOT NULL, + "mimi_ookisa" SMALLINT NOT NULL, + "mimi_muki" SMALLINT NOT NULL, + "elf_mimi" SMALLINT NOT NULL, + "miken_takasa" SMALLINT NOT NULL, + "miken_haba" SMALLINT NOT NULL, + "hohobone_ryou" SMALLINT NOT NULL, + "hohobone_jyouge" SMALLINT NOT NULL, + "hohoniku" SMALLINT NOT NULL, + "erahone_jyouge" SMALLINT NOT NULL, + "erahone_haba" SMALLINT NOT NULL, + "hana_jyouge" SMALLINT NOT NULL, + "hana_haba" SMALLINT NOT NULL, + "hana_takasa" SMALLINT NOT NULL, + "hana_kakudo" SMALLINT NOT NULL, + "kuchi_haba" SMALLINT NOT NULL, + "kuchi_atsusa" SMALLINT NOT NULL, + "eyebrow_uv_offset_x" SMALLINT NOT NULL, + "eyebrow_uv_offset_y" SMALLINT NOT NULL, + "wrinkle" SMALLINT NOT NULL, + "wrinkle_albedo_blend_rate" SMALLINT NOT NULL, + "wrinkle_detail_normal_power" SMALLINT NOT NULL, + "muscle_albedo_blend_rate" SMALLINT NOT NULL, + "muscle_detail_normal_power" SMALLINT NOT NULL, + "height" SMALLINT NOT NULL, + "head_size" SMALLINT NOT NULL, + "neck_offset" SMALLINT NOT NULL, + "neck_scale" SMALLINT NOT NULL, + "upper_body_scale_x" SMALLINT NOT NULL, + "belly_size" SMALLINT NOT NULL, + "teat_scale" SMALLINT NOT NULL, + "tekubi_size" SMALLINT NOT NULL, + "koshi_offset" SMALLINT NOT NULL, + "koshi_size" SMALLINT NOT NULL, + "ankle_offset" SMALLINT NOT NULL, + "fat" SMALLINT NOT NULL, + "muscle" SMALLINT NOT NULL, + "motion_filter" SMALLINT NOT NULL, + CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_status_info +( + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "hp" INTEGER NOT NULL, + "stamina" INTEGER NOT NULL, + "revive_point" SMALLINT NOT NULL, + "max_hp" INTEGER NOT NULL, + "max_stamina" INTEGER NOT NULL, + "white_hp" INTEGER NOT NULL, + "gain_hp" INTEGER NOT NULL, + "gain_stamina" INTEGER NOT NULL, + "gain_attack" INTEGER NOT NULL, + "gain_defense" INTEGER NOT NULL, + "gain_magic_attack" INTEGER NOT NULL, + "gain_magic_defense" INTEGER NOT NULL, + CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_matching_profile +( + "character_id" INTEGER PRIMARY KEY NOT NULL, + "entry_job" SMALLINT NOT NULL, + "entry_job_level" INTEGER NOT NULL, + "current_job" SMALLINT NOT NULL, + "current_job_level" INTEGER NOT NULL, + objective_type1 INTEGER NOT NULL, + objective_type2 INTEGER NOT NULL, + "play_style" INTEGER NOT NULL, + "comment" TEXT NOT NULL, + "is_join_party" BOOLEAN NOT NULL, + CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile +( + "character_id" INTEGER PRIMARY KEY NOT NULL, + "background_id" SMALLINT NOT NULL, + "title_uid" INTEGER NOT NULL, + "title_index" INTEGER NOT NULL, + "motion_id" SMALLINT NOT NULL, + "motion_frame_no" INTEGER NOT NULL, + CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_job_data +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "exp" INTEGER NOT NULL, + "job_point" INTEGER NOT NULL, + "lv" INTEGER NOT NULL, + "atk" SMALLINT NOT NULL, + "def" SMALLINT NOT NULL, + "m_atk" SMALLINT NOT NULL, + "m_def" SMALLINT NOT NULL, + "strength" SMALLINT NOT NULL, + "down_power" SMALLINT NOT NULL, + "shake_power" SMALLINT NOT NULL, + "stun_power" SMALLINT NOT NULL, + "consitution" SMALLINT NOT NULL, + "guts" SMALLINT NOT NULL, + "fire_resist" SMALLINT NOT NULL, + "ice_resist" SMALLINT NOT NULL, + "thunder_resist" SMALLINT NOT NULL, + "holy_resist" SMALLINT NOT NULL, + "dark_resist" SMALLINT NOT NULL, + "spread_resist" SMALLINT NOT NULL, + "freeze_resist" SMALLINT NOT NULL, + "shock_resist" SMALLINT NOT NULL, + "absorb_resist" SMALLINT NOT NULL, + "dark_elm_resist" SMALLINT NOT NULL, + "poison_resist" SMALLINT NOT NULL, + "slow_resist" SMALLINT NOT NULL, + "sleep_resist" SMALLINT NOT NULL, + "stun_resist" SMALLINT NOT NULL, + "wet_resist" SMALLINT NOT NULL, + "oil_resist" SMALLINT NOT NULL, + "seal_resist" SMALLINT NOT NULL, + "curse_resist" SMALLINT NOT NULL, + "soft_resist" SMALLINT NOT NULL, + "stone_resist" SMALLINT NOT NULL, + "gold_resist" SMALLINT NOT NULL, + "fire_reduce_resist" SMALLINT NOT NULL, + "ice_reduce_resist" SMALLINT NOT NULL, + "thunder_reduce_resist" SMALLINT NOT NULL, + "holy_reduce_resist" SMALLINT NOT NULL, + "dark_reduce_resist" SMALLINT NOT NULL, + "atk_down_resist" SMALLINT NOT NULL, + "def_down_resist" SMALLINT NOT NULL, + "m_atk_down_resist" SMALLINT NOT NULL, + "m_def_down_resist" SMALLINT NOT NULL, + CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job), + CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_storage +( + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_max" SMALLINT NOT NULL, + "item_sort" BLOB NOT NULL, + CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type), + CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_wallet_point +( + "character_id" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "value" INTEGER NOT NULL, + CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type), + CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_item +( + -- See Item.cs, uid is at most of size 8. + "uid" VARCHAR(8) NOT NULL, + "item_id" INTEGER NOT NULL, + unk3 SMALLINT NOT NULL, + "color" SMALLINT NOT NULL, + "plus_value" SMALLINT NOT NULL, + PRIMARY KEY ("uid") +); + +CREATE TABLE IF NOT EXISTS ddon_storage_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "item_num" INTEGER NOT NULL, + CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no), + CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equip_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_type" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot), + CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equip_job_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot), + CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_normal_skill_param +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_no" INTEGER NOT NULL, + "index" INTEGER NOT NULL, + "pre_skill_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no), + CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + "skill_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, skill_id), + CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no), + CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_learned_ability +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + "ability_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, ability_id), + CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equipped_ability +( + "character_common_id" INTEGER NOT NULL, + "equipped_to_job" SMALLINT NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no), + CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_shortcut +( + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "shortcut_id" INTEGER NOT NULL, + u32_data INTEGER NOT NULL, + f32_data INTEGER NOT NULL, + "exex_type" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_communication_shortcut +( + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "category" SMALLINT NOT NULL, + "id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_pawn_reaction +( + "pawn_id" INTEGER NOT NULL, + "reaction_type" SMALLINT NOT NULL, + "motion_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type), + CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_sp_skill +( + "pawn_id" INTEGER NOT NULL, + "sp_skill_id" SMALLINT NOT NULL, + "sp_skill_lv" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"), + CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_game_token +( + "account_id" INTEGER PRIMARY KEY NOT NULL, + "character_id" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_game_token_token UNIQUE ("token"), + CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"), + CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") +); + +CREATE TABLE IF NOT EXISTS ddon_connection +( + "server_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "type" INTEGER NOT NULL, + "created" DATETIME NOT NULL, + CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id), + CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") +); diff --git a/deploy/postgresql/docker-compose.yml b/deploy/postgresql/docker-compose.yml new file mode 100644 index 000000000..c0ef0635d --- /dev/null +++ b/deploy/postgresql/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.8' + +volumes: + ddon-psql-data: + +services: + psql: + container_name: ddon-psql + image: postgres:16-alpine + ports: + - "5432:5432" + restart: no + volumes: + - ddon-psql-data:/var/lib/postgresql/data +# - $PWD/schema_postgres.sql:/docker-entrypoint-initdb.d/schema_postgres.sql:ro + - $PWD/postgresql.conf:/etc/postgresql/postgresql.conf:ro + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=root + - POSTGRES_DB=postgres + - POSTGRES_INITDB_ARGS=--auth=scram-sha-256 --lc-numeric=en_US.UTF-8 + command: postgres -c config_file=/etc/postgresql/postgresql.conf + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "postgres"] + interval: 10s + timeout: 1s + retries: 3 diff --git a/deploy/postgresql/postgresql.conf b/deploy/postgresql/postgresql.conf new file mode 100644 index 000000000..4a8f88f8a --- /dev/null +++ b/deploy/postgresql/postgresql.conf @@ -0,0 +1,841 @@ +# ----------------------------- +# PostgreSQL configuration file +# ----------------------------- +# +# This file consists of lines of the form: +# +# name = value +# +# (The "=" is optional.) Whitespace may be used. Comments are introduced with +# "#" anywhere on a line. The complete list of parameter names and allowed +# values can be found in the PostgreSQL documentation. +# +# The commented-out settings shown in this file represent the default values. +# Re-commenting a setting is NOT sufficient to revert it to the default value; +# you need to reload the server. +# +# This file is read on server startup and when the server receives a SIGHUP +# signal. If you edit the file on a running system, you have to SIGHUP the +# server for the changes to take effect, run "pg_ctl reload", or execute +# "SELECT pg_reload_conf()". Some parameters, which are marked below, +# require a server shutdown and restart to take effect. +# +# Any parameter can also be given as a command-line option to the server, e.g., +# "postgres -c log_connections=on". Some parameters can be changed at run time +# with the "SET" SQL command. +# +# Memory units: B = bytes Time units: us = microseconds +# kB = kilobytes ms = milliseconds +# MB = megabytes s = seconds +# GB = gigabytes min = minutes +# TB = terabytes h = hours +# d = days + + +#------------------------------------------------------------------------------ +# FILE LOCATIONS +#------------------------------------------------------------------------------ + +# The default values of these variables are driven from the -D command-line +# option or PGDATA environment variable, represented here as ConfigDir. + +#data_directory = 'ConfigDir' # use data in another directory + # (change requires restart) +#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file + # (change requires restart) +#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file + # (change requires restart) + +# If external_pid_file is not explicitly set, no extra PID file is written. +#external_pid_file = '' # write an extra PID file + # (change requires restart) + + +#------------------------------------------------------------------------------ +# CONNECTIONS AND AUTHENTICATION +#------------------------------------------------------------------------------ + +# - Connection Settings - + +listen_addresses = '*' # what IP address(es) to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +#port = 5432 # (change requires restart) +#max_connections = 100 # (change requires restart) +#reserved_connections = 0 # (change requires restart) +#superuser_reserved_connections = 3 # (change requires restart) +#unix_socket_directories = '/tmp' # comma-separated list of directories + # (change requires restart) +#unix_socket_group = '' # (change requires restart) +#unix_socket_permissions = 0777 # begin with 0 to use octal notation + # (change requires restart) +#bonjour = off # advertise server via Bonjour + # (change requires restart) +#bonjour_name = '' # defaults to the computer name + # (change requires restart) + +# - TCP settings - +# see "man tcp" for details + +#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; + # 0 selects the system default +#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; + # 0 selects the system default +#tcp_keepalives_count = 0 # TCP_KEEPCNT; + # 0 selects the system default +#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; + # 0 selects the system default + +#client_connection_check_interval = 0 # time between checks for client + # disconnection while running queries; + # 0 for never + +# - Authentication - + +#authentication_timeout = 1min # 1s-600s +#password_encryption = scram-sha-256 # scram-sha-256 or md5 +#scram_iterations = 4096 +#db_user_namespace = off + +# GSSAPI using Kerberos +#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab' +#krb_caseins_users = off +#gss_accept_delegation = off + +# - SSL - + +#ssl = off +#ssl_ca_file = '' +#ssl_cert_file = 'server.crt' +#ssl_crl_file = '' +#ssl_crl_dir = '' +#ssl_key_file = 'server.key' +#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers +#ssl_prefer_server_ciphers = on +#ssl_ecdh_curve = 'prime256v1' +#ssl_min_protocol_version = 'TLSv1.2' +#ssl_max_protocol_version = '' +#ssl_dh_params_file = '' +#ssl_passphrase_command = '' +#ssl_passphrase_command_supports_reload = off + + +#------------------------------------------------------------------------------ +# RESOURCE USAGE (except WAL) +#------------------------------------------------------------------------------ + +# - Memory - + +#shared_buffers = 128MB # min 128kB + # (change requires restart) +#huge_pages = try # on, off, or try + # (change requires restart) +#huge_page_size = 0 # zero for system default + # (change requires restart) +#temp_buffers = 8MB # min 800kB +#max_prepared_transactions = 0 # zero disables the feature + # (change requires restart) +# Caution: it is not advisable to set max_prepared_transactions nonzero unless +# you actively intend to use prepared transactions. +#work_mem = 4MB # min 64kB +#hash_mem_multiplier = 2.0 # 1-1000.0 multiplier on hash table work_mem +#maintenance_work_mem = 64MB # min 1MB +#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem +#logical_decoding_work_mem = 64MB # min 64kB +#max_stack_depth = 2MB # min 100kB +#shared_memory_type = mmap # the default is the first option + # supported by the operating system: + # mmap + # sysv + # windows + # (change requires restart) +#dynamic_shared_memory_type = posix # the default is usually the first option + # supported by the operating system: + # posix + # sysv + # windows + # mmap + # (change requires restart) +#min_dynamic_shared_memory = 0MB # (change requires restart) +#vacuum_buffer_usage_limit = 256kB # size of vacuum and analyze buffer access strategy ring; + # 0 to disable vacuum buffer access strategy; + # range 128kB to 16GB + +# - Disk - + +#temp_file_limit = -1 # limits per-process temp file space + # in kilobytes, or -1 for no limit + +# - Kernel Resources - + +#max_files_per_process = 1000 # min 64 + # (change requires restart) + +# - Cost-Based Vacuum Delay - + +#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) +#vacuum_cost_page_hit = 1 # 0-10000 credits +#vacuum_cost_page_miss = 2 # 0-10000 credits +#vacuum_cost_page_dirty = 20 # 0-10000 credits +#vacuum_cost_limit = 200 # 1-10000 credits + +# - Background Writer - + +#bgwriter_delay = 200ms # 10-10000ms between rounds +#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables +#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round +#bgwriter_flush_after = 0 # measured in pages, 0 disables + +# - Asynchronous Behavior - + +#backend_flush_after = 0 # measured in pages, 0 disables +#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching +#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching +#max_worker_processes = 8 # (change requires restart) +#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers +#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers +#max_parallel_workers = 8 # maximum number of max_worker_processes that + # can be used in parallel operations +#parallel_leader_participation = on +#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate + # (change requires restart) + + +#------------------------------------------------------------------------------ +# WRITE-AHEAD LOG +#------------------------------------------------------------------------------ + +# - Settings - + +#wal_level = replica # minimal, replica, or logical + # (change requires restart) +#fsync = on # flush data to disk for crash safety + # (turning this off can cause + # unrecoverable data corruption) +#synchronous_commit = on # synchronization level; + # off, local, remote_write, remote_apply, or on +#wal_sync_method = fsync # the default is the first option + # supported by the operating system: + # open_datasync + # fdatasync (default on Linux and FreeBSD) + # fsync + # fsync_writethrough + # open_sync +#full_page_writes = on # recover from partial page writes +#wal_log_hints = off # also do full page writes of non-critical updates + # (change requires restart) +#wal_compression = off # enables compression of full-page writes; + # off, pglz, lz4, zstd, or on +#wal_init_zero = on # zero-fill new WAL files +#wal_recycle = on # recycle WAL files +#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers + # (change requires restart) +#wal_writer_delay = 200ms # 1-10000 milliseconds +#wal_writer_flush_after = 1MB # measured in pages, 0 disables +#wal_skip_threshold = 2MB + +#commit_delay = 0 # range 0-100000, in microseconds +#commit_siblings = 5 # range 1-1000 + +# - Checkpoints - + +#checkpoint_timeout = 5min # range 30s-1d +#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0 +#checkpoint_flush_after = 0 # measured in pages, 0 disables +#checkpoint_warning = 30s # 0 disables +#max_wal_size = 1GB +#min_wal_size = 80MB + +# - Prefetching during recovery - + +#recovery_prefetch = try # prefetch pages referenced in the WAL? +#wal_decode_buffer_size = 512kB # lookahead window used for prefetching + # (change requires restart) + +# - Archiving - + +#archive_mode = off # enables archiving; off, on, or always + # (change requires restart) +#archive_library = '' # library to use to archive a WAL file + # (empty string indicates archive_command should + # be used) +#archive_command = '' # command to use to archive a WAL file + # placeholders: %p = path of file to archive + # %f = file name only + # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' +#archive_timeout = 0 # force a WAL file switch after this + # number of seconds; 0 disables + +# - Archive Recovery - + +# These are only used in recovery mode. + +#restore_command = '' # command to use to restore an archived WAL file + # placeholders: %p = path of file to restore + # %f = file name only + # e.g. 'cp /mnt/server/archivedir/%f %p' +#archive_cleanup_command = '' # command to execute at every restartpoint +#recovery_end_command = '' # command to execute at completion of recovery + +# - Recovery Target - + +# Set these only when performing a targeted recovery. + +#recovery_target = '' # 'immediate' to end recovery as soon as a + # consistent state is reached + # (change requires restart) +#recovery_target_name = '' # the named restore point to which recovery will proceed + # (change requires restart) +#recovery_target_time = '' # the time stamp up to which recovery will proceed + # (change requires restart) +#recovery_target_xid = '' # the transaction ID up to which recovery will proceed + # (change requires restart) +#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed + # (change requires restart) +#recovery_target_inclusive = on # Specifies whether to stop: + # just after the specified recovery target (on) + # just before the recovery target (off) + # (change requires restart) +#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID + # (change requires restart) +#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' + # (change requires restart) + + +#------------------------------------------------------------------------------ +# REPLICATION +#------------------------------------------------------------------------------ + +# - Sending Servers - + +# Set these on the primary and on any standby that will send replication data. + +#max_wal_senders = 10 # max number of walsender processes + # (change requires restart) +#max_replication_slots = 10 # max number of replication slots + # (change requires restart) +#wal_keep_size = 0 # in megabytes; 0 disables +#max_slot_wal_keep_size = -1 # in megabytes; -1 disables +#wal_sender_timeout = 60s # in milliseconds; 0 disables +#track_commit_timestamp = off # collect timestamp of transaction commit + # (change requires restart) + +# - Primary Server - + +# These settings are ignored on a standby server. + +#synchronous_standby_names = '' # standby servers that provide sync rep + # method to choose sync standbys, number of sync standbys, + # and comma-separated list of application_name + # from standby(s); '*' = all + +# - Standby Servers - + +# These settings are ignored on a primary server. + +#primary_conninfo = '' # connection string to sending server +#primary_slot_name = '' # replication slot on sending server +#hot_standby = on # "off" disallows queries during recovery + # (change requires restart) +#max_standby_archive_delay = 30s # max delay before canceling queries + # when reading WAL from archive; + # -1 allows indefinite delay +#max_standby_streaming_delay = 30s # max delay before canceling queries + # when reading streaming WAL; + # -1 allows indefinite delay +#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name + # is not set +#wal_receiver_status_interval = 10s # send replies at least this often + # 0 disables +#hot_standby_feedback = off # send info from standby to prevent + # query conflicts +#wal_receiver_timeout = 60s # time that receiver waits for + # communication from primary + # in milliseconds; 0 disables +#wal_retrieve_retry_interval = 5s # time to wait before retrying to + # retrieve WAL after a failed attempt +#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery + +# - Subscribers - + +# These settings are ignored on a publisher. + +#max_logical_replication_workers = 4 # taken from max_worker_processes + # (change requires restart) +#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers +#max_parallel_apply_workers_per_subscription = 2 # taken from max_logical_replication_workers + + +#------------------------------------------------------------------------------ +# QUERY TUNING +#------------------------------------------------------------------------------ + +# - Planner Method Configuration - + +#enable_async_append = on +#enable_bitmapscan = on +#enable_gathermerge = on +#enable_hashagg = on +#enable_hashjoin = on +#enable_incremental_sort = on +#enable_indexscan = on +#enable_indexonlyscan = on +#enable_material = on +#enable_memoize = on +#enable_mergejoin = on +#enable_nestloop = on +#enable_parallel_append = on +#enable_parallel_hash = on +#enable_partition_pruning = on +#enable_partitionwise_join = off +#enable_partitionwise_aggregate = off +#enable_presorted_aggregate = on +#enable_seqscan = on +#enable_sort = on +#enable_tidscan = on + +# - Planner Cost Constants - + +#seq_page_cost = 1.0 # measured on an arbitrary scale +#random_page_cost = 4.0 # same scale as above +#cpu_tuple_cost = 0.01 # same scale as above +#cpu_index_tuple_cost = 0.005 # same scale as above +#cpu_operator_cost = 0.0025 # same scale as above +#parallel_setup_cost = 1000.0 # same scale as above +#parallel_tuple_cost = 0.1 # same scale as above +#min_parallel_table_scan_size = 8MB +#min_parallel_index_scan_size = 512kB +#effective_cache_size = 4GB + +#jit_above_cost = 100000 # perform JIT compilation if available + # and query more expensive than this; + # -1 disables +#jit_inline_above_cost = 500000 # inline small functions if query is + # more expensive than this; -1 disables +#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if + # query is more expensive than this; + # -1 disables + +# - Genetic Query Optimizer - + +#geqo = on +#geqo_threshold = 12 +#geqo_effort = 5 # range 1-10 +#geqo_pool_size = 0 # selects default based on effort +#geqo_generations = 0 # selects default based on effort +#geqo_selection_bias = 2.0 # range 1.5-2.0 +#geqo_seed = 0.0 # range 0.0-1.0 + +# - Other Planner Options - + +#default_statistics_target = 100 # range 1-10000 +#constraint_exclusion = partition # on, off, or partition +#cursor_tuple_fraction = 0.1 # range 0.0-1.0 +#from_collapse_limit = 8 +#jit = on # allow JIT compilation +#join_collapse_limit = 8 # 1 disables collapsing of explicit + # JOIN clauses +#plan_cache_mode = auto # auto, force_generic_plan or + # force_custom_plan +#recursive_worktable_factor = 10.0 # range 0.001-1000000 + + +#------------------------------------------------------------------------------ +# REPORTING AND LOGGING +#------------------------------------------------------------------------------ + +# - Where to Log - + +log_destination = 'stderr' # Valid values are combinations of + # stderr, csvlog, jsonlog, syslog, and + # eventlog, depending on platform. + # csvlog and jsonlog require + # logging_collector to be on. + +# This is used when logging to stderr: +logging_collector = off # Enable capturing of stderr, jsonlog, + # and csvlog into log files. Required + # to be on for csvlogs and jsonlogs. + # (change requires restart) + +# These are only used if logging_collector is on: +#log_directory = 'log' # directory where log files are written, + # can be absolute or relative to PGDATA +#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, + # can include strftime() escapes +#log_file_mode = 0600 # creation mode for log files, + # begin with 0 to use octal notation +#log_rotation_age = 1d # Automatic rotation of logfiles will + # happen after that time. 0 disables. +#log_rotation_size = 10MB # Automatic rotation of logfiles will + # happen after that much log output. + # 0 disables. +#log_truncate_on_rotation = off # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +# These are relevant when logging to syslog: +#syslog_facility = 'LOCAL0' +#syslog_ident = 'postgres' +#syslog_sequence_numbers = on +#syslog_split_messages = on + +# This is only relevant when logging to eventlog (Windows): +# (change requires restart) +#event_source = 'PostgreSQL' + +# - When to Log - + +log_min_messages = info # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic + +log_min_error_statement = error # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic (effectively off) + +#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements + # and their durations, > 0 logs only + # statements running at least this number + # of milliseconds + +#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements + # and their durations, > 0 logs only a sample of + # statements running at least this number + # of milliseconds; + # sample fraction is determined by log_statement_sample_rate + +#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding + # log_min_duration_sample to be logged; + # 1.0 logs all such statements, 0.0 never logs + + +#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements + # are logged regardless of their duration; 1.0 logs all + # statements from all transactions, 0.0 never logs + +#log_startup_progress_interval = 10s # Time between progress updates for + # long-running startup operations. + # 0 disables the feature, > 0 indicates + # the interval in milliseconds. + +# - What to Log - + +#debug_print_parse = off +#debug_print_rewritten = off +#debug_print_plan = off +#debug_pretty_print = on +#log_autovacuum_min_duration = 10min # log autovacuum activity; + # -1 disables, 0 logs all actions and + # their durations, > 0 logs only + # actions running at least this number + # of milliseconds. +#log_checkpoints = on +log_connections = on +log_disconnections = on +log_duration = on +#log_error_verbosity = default # terse, default, or verbose messages +log_hostname = on +#log_line_prefix = '%m [%p] ' # special values: + # %a = application name + # %u = user name + # %d = database name + # %r = remote host and port + # %h = remote host + # %b = backend type + # %p = process ID + # %P = process ID of parallel group leader + # %t = timestamp without milliseconds + # %m = timestamp with milliseconds + # %n = timestamp with milliseconds (as a Unix epoch) + # %Q = query ID (0 if none or not computed) + # %i = command tag + # %e = SQL state + # %c = session ID + # %l = session line number + # %s = session start timestamp + # %v = virtual transaction ID + # %x = transaction ID (0 if none) + # %q = stop here in non-session + # processes + # %% = '%' + # e.g. '<%u%%%d> ' +#log_lock_waits = off # log lock waits >= deadlock_timeout +#log_recovery_conflict_waits = off # log standby recovery conflict waits + # >= deadlock_timeout +#log_parameter_max_length = -1 # when logging statements, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +#log_parameter_max_length_on_error = 0 # when logging an error, limit logged + # bind-parameter values to N bytes; + # -1 means print in full, 0 disables +log_statement = 'all' # none, ddl, mod, all +#log_replication_commands = off +#log_temp_files = -1 # log temporary files equal or larger + # than the specified size in kilobytes; + # -1 disables, 0 logs all temp files +#log_timezone = 'GMT' + +# - Process Title - + +#cluster_name = '' # added to process titles if nonempty + # (change requires restart) +#update_process_title = on + + +#------------------------------------------------------------------------------ +# STATISTICS +#------------------------------------------------------------------------------ + +# - Cumulative Query and Index Statistics - + +#track_activities = on +#track_activity_query_size = 1024 # (change requires restart) +track_counts = on +#track_io_timing = off +#track_wal_io_timing = off +#track_functions = none # none, pl, all +#stats_fetch_consistency = cache # cache, none, snapshot + + +# - Monitoring - + +#compute_query_id = auto +#log_statement_stats = off +#log_parser_stats = off +#log_planner_stats = off +#log_executor_stats = off + + +#------------------------------------------------------------------------------ +# AUTOVACUUM +#------------------------------------------------------------------------------ + +autovacuum = on # Enable autovacuum subprocess? 'on' + # requires track_counts to also be on. +#autovacuum_max_workers = 3 # max number of autovacuum subprocesses + # (change requires restart) +#autovacuum_naptime = 1min # time between autovacuum runs +#autovacuum_vacuum_threshold = 50 # min number of row updates before + # vacuum +#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts + # before vacuum; -1 disables insert + # vacuums +#autovacuum_analyze_threshold = 50 # min number of row updates before + # analyze +#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum +#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table + # size before insert vacuum +#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze +#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum + # (change requires restart) +#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age + # before forced vacuum + # (change requires restart) +#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for + # autovacuum, in milliseconds; + # -1 means use vacuum_cost_delay +#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for + # autovacuum, -1 means use + # vacuum_cost_limit + + +#------------------------------------------------------------------------------ +# CLIENT CONNECTION DEFAULTS +#------------------------------------------------------------------------------ + +# - Statement Behavior - + +#client_min_messages = notice # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # log + # notice + # warning + # error +#search_path = '"$user", public' # schema names +#row_security = on +#default_table_access_method = 'heap' +#default_tablespace = '' # a tablespace name, '' uses the default +#default_toast_compression = 'pglz' # 'pglz' or 'lz4' +#temp_tablespaces = '' # a list of tablespace names, '' uses + # only default tablespace +#check_function_bodies = on +#default_transaction_isolation = 'read committed' +#default_transaction_read_only = off +#default_transaction_deferrable = off +#session_replication_role = 'origin' +#statement_timeout = 0 # in milliseconds, 0 is disabled +#lock_timeout = 0 # in milliseconds, 0 is disabled +#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled +#idle_session_timeout = 0 # in milliseconds, 0 is disabled +#vacuum_freeze_table_age = 150000000 +#vacuum_freeze_min_age = 50000000 +#vacuum_failsafe_age = 1600000000 +#vacuum_multixact_freeze_table_age = 150000000 +#vacuum_multixact_freeze_min_age = 5000000 +#vacuum_multixact_failsafe_age = 1600000000 +#bytea_output = 'hex' # hex, escape +#xmlbinary = 'base64' +#xmloption = 'content' +#gin_pending_list_limit = 4MB +#createrole_self_grant = '' # set and/or inherit + +# - Locale and Formatting - + +#datestyle = 'iso, mdy' +#intervalstyle = 'postgres' +#timezone = 'GMT' +#timezone_abbreviations = 'Default' # Select the set of available time zone + # abbreviations. Currently, there are + # Default + # Australia (historical usage) + # India + # You can create your own file in + # share/timezonesets/. +#extra_float_digits = 1 # min -15, max 3; any value >0 actually + # selects precise output mode +#client_encoding = sql_ascii # actually, defaults to database + # encoding + +# These settings are initialized by initdb, but they can be changed. +#lc_messages = 'C' # locale for system error message + # strings +#lc_monetary = 'C' # locale for monetary formatting +#lc_numeric = 'C' # locale for number formatting +#lc_time = 'C' # locale for time formatting + +#icu_validation_level = warning # report ICU locale validation + # errors at the given level + +# default configuration for text search +#default_text_search_config = 'pg_catalog.simple' + +# - Shared Library Preloading - + +#local_preload_libraries = '' +#session_preload_libraries = '' +#shared_preload_libraries = '' # (change requires restart) +#jit_provider = 'llvmjit' # JIT library to use + +# - Other Defaults - + +#dynamic_library_path = '$libdir' +#gin_fuzzy_search_limit = 0 + + +#------------------------------------------------------------------------------ +# LOCK MANAGEMENT +#------------------------------------------------------------------------------ + +#deadlock_timeout = 1s +#max_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_transaction = 64 # min 10 + # (change requires restart) +#max_pred_locks_per_relation = -2 # negative values mean + # (max_pred_locks_per_transaction + # / -max_pred_locks_per_relation) - 1 +#max_pred_locks_per_page = 2 # min 0 + + +#------------------------------------------------------------------------------ +# VERSION AND PLATFORM COMPATIBILITY +#------------------------------------------------------------------------------ + +# - Previous PostgreSQL Versions - + +#array_nulls = on +#backslash_quote = safe_encoding # on, off, or safe_encoding +#escape_string_warning = on +#lo_compat_privileges = off +#quote_all_identifiers = off +#standard_conforming_strings = on +#synchronize_seqscans = on + +# - Other Platforms and Clients - + +#transform_null_equals = off + + +#------------------------------------------------------------------------------ +# ERROR HANDLING +#------------------------------------------------------------------------------ + +#exit_on_error = off # terminate session on any error? +#restart_after_crash = on # reinitialize after backend crash? +#data_sync_retry = off # retry or panic on failure to fsync + # data? + # (change requires restart) +#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+) + + +#------------------------------------------------------------------------------ +# CONFIG FILE INCLUDES +#------------------------------------------------------------------------------ + +# These options allow settings to be loaded from files other than the +# default postgresql.conf. Note that these are directives, not variable +# assignments, so they can usefully be given more than once. + +#include_dir = '...' # include files ending in '.conf' from + # a directory, e.g., 'conf.d' +#include_if_exists = '...' # include file only if it exists +#include = '...' # include file + + +#------------------------------------------------------------------------------ +# CUSTOMIZED OPTIONS +#------------------------------------------------------------------------------ + +# Add settings for extensions here + + +# https://pgtune.leopard.in.ua/#/ +# DB Version: 15 +# OS Type: linux +# DB Type: web +# Total Memory (RAM): 1 GB +# CPUs num: 1 +# Data Storage: ssd +max_connections = 200 +shared_buffers = 256MB +effective_cache_size = 768MB +maintenance_work_mem = 64MB +checkpoint_completion_target = 0.9 +wal_buffers = 7864kB +default_statistics_target = 100 +random_page_cost = 1.1 +effective_io_concurrency = 200 +work_mem = 655kB +min_wal_size = 1GB +max_wal_size = 4GB diff --git a/deploy/postgresql/schema_postgres.sql b/deploy/postgresql/schema_postgres.sql new file mode 100644 index 000000000..a5ff4d5aa --- /dev/null +++ b/deploy/postgresql/schema_postgres.sql @@ -0,0 +1,420 @@ +CREATE TABLE IF NOT EXISTS setting +( + "key" VARCHAR(32) NOT NULL, + "value" TEXT NOT NULL, + PRIMARY KEY ("key") +); + +CREATE TABLE IF NOT EXISTS account +( + "id" SERIAL PRIMARY KEY NOT NULL, + "name" TEXT NOT NULL, + "normal_name" TEXT NOT NULL, + "hash" TEXT NOT NULL, + "mail" TEXT NOT NULL, + "mail_verified" BOOLEAN NOT NULL, + "mail_verified_at" TIMESTAMP WITH TIME ZONE DEFAULT NULL, + "mail_token" TEXT DEFAULT NULL, + "password_token" TEXT DEFAULT NULL, + "login_token" TEXT DEFAULT NULL, + "login_token_created" TIMESTAMP WITH TIME ZONE DEFAULT NULL, + "state" INTEGER NOT NULL, + "last_login" TIMESTAMP WITH TIME ZONE DEFAULT NULL, + "created" TIMESTAMP WITH TIME ZONE NOT NULL, + CONSTRAINT uq_account_name UNIQUE ("name"), + CONSTRAINT uq_account_normal_name UNIQUE ("normal_name"), + CONSTRAINT uq_account_login_token UNIQUE ("login_token"), + CONSTRAINT uq_account_mail UNIQUE ("mail") +); + +CREATE TABLE IF NOT EXISTS ddon_character_common +( + "character_common_id" SERIAL PRIMARY KEY NOT NULL, + "job" SMALLINT NOT NULL, + "hide_equip_head" BOOLEAN NOT NULL, + "hide_equip_lantern" BOOLEAN NOT NULL, + "jewelry_slot_num" SMALLINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS ddon_character +( + "character_id" SERIAL PRIMARY KEY NOT NULL, + "character_common_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "version" INTEGER NOT NULL, + "first_name" TEXT NOT NULL, + "last_name" TEXT NOT NULL, + "created" TIMESTAMP WITH TIME ZONE NOT NULL, + "my_pawn_slot_num" SMALLINT NOT NULL, + "rental_pawn_slot_num" SMALLINT NOT NULL, + "hide_equip_head_pawn" BOOLEAN NOT NULL, + "hide_equip_lantern_pawn" BOOLEAN NOT NULL, + "arisen_profile_share_range" SMALLINT NOT NULL, + CONSTRAINT fk_character_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_pawn +( + "pawn_id" SERIAL PRIMARY KEY NOT NULL, + "character_common_id" INTEGER NOT NULL, + "character_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "hm_type" SMALLINT NOT NULL, + "pawn_type" SMALLINT NOT NULL, + CONSTRAINT fk_pawn_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE, + CONSTRAINT fk_character_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_edit_info +( + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "sex" SMALLINT NOT NULL, + "voice" SMALLINT NOT NULL, + "voice_pitch" SMALLINT NOT NULL, + "personality" SMALLINT NOT NULL, + "speech_freq" SMALLINT NOT NULL, + "body_type" SMALLINT NOT NULL, + "hair" SMALLINT NOT NULL, + "beard" SMALLINT NOT NULL, + "makeup" SMALLINT NOT NULL, + "scar" SMALLINT NOT NULL, + "eye_preset_no" SMALLINT NOT NULL, + "nose_preset_no" SMALLINT NOT NULL, + "mouth_preset_no" SMALLINT NOT NULL, + "eyebrow_tex_no" SMALLINT NOT NULL, + "color_skin" SMALLINT NOT NULL, + "color_hair" SMALLINT NOT NULL, + "color_beard" SMALLINT NOT NULL, + "color_eyebrow" SMALLINT NOT NULL, + "color_r_eye" SMALLINT NOT NULL, + "color_l_eye" SMALLINT NOT NULL, + "color_makeup" SMALLINT NOT NULL, + "sokutobu" SMALLINT NOT NULL, + "hitai" SMALLINT NOT NULL, + "mimi_jyouge" SMALLINT NOT NULL, + "kannkaku" SMALLINT NOT NULL, + "mabisasi_jyouge" SMALLINT NOT NULL, + "hanakuchi_jyouge" SMALLINT NOT NULL, + "ago_saki_haba" SMALLINT NOT NULL, + "ago_zengo" SMALLINT NOT NULL, + "ago_saki_jyouge" SMALLINT NOT NULL, + "hitomi_ookisa" SMALLINT NOT NULL, + "me_ookisa" SMALLINT NOT NULL, + "me_kaiten" SMALLINT NOT NULL, + "mayu_kaiten" SMALLINT NOT NULL, + "mimi_ookisa" SMALLINT NOT NULL, + "mimi_muki" SMALLINT NOT NULL, + "elf_mimi" SMALLINT NOT NULL, + "miken_takasa" SMALLINT NOT NULL, + "miken_haba" SMALLINT NOT NULL, + "hohobone_ryou" SMALLINT NOT NULL, + "hohobone_jyouge" SMALLINT NOT NULL, + "hohoniku" SMALLINT NOT NULL, + "erahone_jyouge" SMALLINT NOT NULL, + "erahone_haba" SMALLINT NOT NULL, + "hana_jyouge" SMALLINT NOT NULL, + "hana_haba" SMALLINT NOT NULL, + "hana_takasa" SMALLINT NOT NULL, + "hana_kakudo" SMALLINT NOT NULL, + "kuchi_haba" SMALLINT NOT NULL, + "kuchi_atsusa" SMALLINT NOT NULL, + "eyebrow_uv_offset_x" SMALLINT NOT NULL, + "eyebrow_uv_offset_y" SMALLINT NOT NULL, + "wrinkle" SMALLINT NOT NULL, + "wrinkle_albedo_blend_rate" SMALLINT NOT NULL, + "wrinkle_detail_normal_power" SMALLINT NOT NULL, + "muscle_albedo_blend_rate" SMALLINT NOT NULL, + "muscle_detail_normal_power" SMALLINT NOT NULL, + "height" SMALLINT NOT NULL, + "head_size" SMALLINT NOT NULL, + "neck_offset" SMALLINT NOT NULL, + "neck_scale" SMALLINT NOT NULL, + "upper_body_scale_x" SMALLINT NOT NULL, + "belly_size" SMALLINT NOT NULL, + "teat_scale" SMALLINT NOT NULL, + "tekubi_size" SMALLINT NOT NULL, + "koshi_offset" SMALLINT NOT NULL, + "koshi_size" SMALLINT NOT NULL, + "ankle_offset" SMALLINT NOT NULL, + "fat" SMALLINT NOT NULL, + "muscle" SMALLINT NOT NULL, + "motion_filter" SMALLINT NOT NULL, + CONSTRAINT fk_edit_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_status_info +( + "character_common_id" INTEGER PRIMARY KEY NOT NULL, + "hp" INTEGER NOT NULL, + "stamina" INTEGER NOT NULL, + "revive_point" SMALLINT NOT NULL, + "max_hp" INTEGER NOT NULL, + "max_stamina" INTEGER NOT NULL, + "white_hp" INTEGER NOT NULL, + "gain_hp" INTEGER NOT NULL, + "gain_stamina" INTEGER NOT NULL, + "gain_attack" INTEGER NOT NULL, + "gain_defense" INTEGER NOT NULL, + "gain_magic_attack" INTEGER NOT NULL, + "gain_magic_defense" INTEGER NOT NULL, + CONSTRAINT fk_status_info_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_matching_profile +( + "character_id" INTEGER PRIMARY KEY NOT NULL, + "entry_job" SMALLINT NOT NULL, + "entry_job_level" INTEGER NOT NULL, + "current_job" SMALLINT NOT NULL, + "current_job_level" INTEGER NOT NULL, + objective_type1 INTEGER NOT NULL, + objective_type2 INTEGER NOT NULL, + "play_style" INTEGER NOT NULL, + "comment" TEXT NOT NULL, + "is_join_party" BOOLEAN NOT NULL, + CONSTRAINT fk_matching_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_arisen_profile +( + "character_id" INTEGER PRIMARY KEY NOT NULL, + "background_id" SMALLINT NOT NULL, + "title_uid" INTEGER NOT NULL, + "title_index" INTEGER NOT NULL, + "motion_id" SMALLINT NOT NULL, + "motion_frame_no" INTEGER NOT NULL, + CONSTRAINT fk_arisen_profile_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_character_job_data +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "exp" INTEGER NOT NULL, + "job_point" INTEGER NOT NULL, + "lv" INTEGER NOT NULL, + "atk" SMALLINT NOT NULL, + "def" SMALLINT NOT NULL, + "m_atk" SMALLINT NOT NULL, + "m_def" SMALLINT NOT NULL, + "strength" SMALLINT NOT NULL, + "down_power" SMALLINT NOT NULL, + "shake_power" SMALLINT NOT NULL, + "stun_power" SMALLINT NOT NULL, + "consitution" SMALLINT NOT NULL, + "guts" SMALLINT NOT NULL, + "fire_resist" SMALLINT NOT NULL, + "ice_resist" SMALLINT NOT NULL, + "thunder_resist" SMALLINT NOT NULL, + "holy_resist" SMALLINT NOT NULL, + "dark_resist" SMALLINT NOT NULL, + "spread_resist" SMALLINT NOT NULL, + "freeze_resist" SMALLINT NOT NULL, + "shock_resist" SMALLINT NOT NULL, + "absorb_resist" SMALLINT NOT NULL, + "dark_elm_resist" SMALLINT NOT NULL, + "poison_resist" SMALLINT NOT NULL, + "slow_resist" SMALLINT NOT NULL, + "sleep_resist" SMALLINT NOT NULL, + "stun_resist" SMALLINT NOT NULL, + "wet_resist" SMALLINT NOT NULL, + "oil_resist" SMALLINT NOT NULL, + "seal_resist" SMALLINT NOT NULL, + "curse_resist" SMALLINT NOT NULL, + "soft_resist" SMALLINT NOT NULL, + "stone_resist" SMALLINT NOT NULL, + "gold_resist" SMALLINT NOT NULL, + "fire_reduce_resist" SMALLINT NOT NULL, + "ice_reduce_resist" SMALLINT NOT NULL, + "thunder_reduce_resist" SMALLINT NOT NULL, + "holy_reduce_resist" SMALLINT NOT NULL, + "dark_reduce_resist" SMALLINT NOT NULL, + "atk_down_resist" SMALLINT NOT NULL, + "def_down_resist" SMALLINT NOT NULL, + "m_atk_down_resist" SMALLINT NOT NULL, + "m_def_down_resist" SMALLINT NOT NULL, + CONSTRAINT pk_character_job_data PRIMARY KEY (character_common_id, job), + CONSTRAINT fk_character_job_data_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_storage +( + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_max" SMALLINT NOT NULL, + "item_sort" BYTEA NOT NULL, + CONSTRAINT pk_ddon_storage PRIMARY KEY (character_id, storage_type), + CONSTRAINT fk_storage_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_wallet_point +( + "character_id" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "value" INTEGER NOT NULL, + CONSTRAINT pk_ddon_wallet_point PRIMARY KEY (character_id, type), + CONSTRAINT fk_wallet_point_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_item +( + -- See Item.cs, uid is at most of size 8. + "uid" VARCHAR(8) NOT NULL, + "item_id" INTEGER NOT NULL, + unk3 SMALLINT NOT NULL, + "color" SMALLINT NOT NULL, + "plus_value" SMALLINT NOT NULL, + PRIMARY KEY ("uid") +); + +CREATE TABLE IF NOT EXISTS ddon_storage_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_id" INTEGER NOT NULL, + "storage_type" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "item_num" INTEGER NOT NULL, + CONSTRAINT pk_ddon_storage_item PRIMARY KEY (character_id, storage_type, slot_no), + CONSTRAINT fk_storage_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_storage_item_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equip_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_type" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_item PRIMARY KEY (character_common_id, job, equip_type, equip_slot), + CONSTRAINT fk_equip_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equip_job_item +( + "item_uid" VARCHAR(8) NOT NULL, + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "equip_slot" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_equip_job_item PRIMARY KEY (character_common_id, job, equip_slot), + CONSTRAINT fk_equip_job_item_item_uid FOREIGN KEY ("item_uid") REFERENCES ddon_item ("uid") ON DELETE CASCADE, + CONSTRAINT fk_equip_job_item_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_normal_skill_param +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_no" INTEGER NOT NULL, + "index" INTEGER NOT NULL, + "pre_skill_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_normal_skill_param PRIMARY KEY (character_common_id, job, skill_no), + CONSTRAINT fk_normal_skill_param_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_learned_custom_skill +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + "skill_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, skill_id), + CONSTRAINT fk_learned_custom_skill_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equipped_custom_skill +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "skill_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_custom_skill PRIMARY KEY (character_common_id, job, slot_no), + CONSTRAINT fk_equipped_custom_skill_character_common_id FOREIGN KEY (character_common_id, job, skill_id) REFERENCES ddon_learned_custom_skill (character_common_id, job, skill_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_learned_ability +( + "character_common_id" INTEGER NOT NULL, + "job" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + "ability_lv" SMALLINT NOT NULL, + PRIMARY KEY (character_common_id, job, ability_id), + CONSTRAINT fk_learned_ability_character_common_id FOREIGN KEY ("character_common_id") REFERENCES ddon_character_common ("character_common_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_equipped_ability +( + "character_common_id" INTEGER NOT NULL, + "equipped_to_job" SMALLINT NOT NULL, + "job" SMALLINT NOT NULL, + "slot_no" SMALLINT NOT NULL, + "ability_id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_equipped_ability PRIMARY KEY (character_common_id, equipped_to_job, slot_no), + CONSTRAINT fk_equipped_ability_character_common_id FOREIGN KEY (character_common_id, job, ability_id) REFERENCES ddon_learned_ability (character_common_id, job, ability_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_shortcut +( + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "shortcut_id" INTEGER NOT NULL, + u32_data INTEGER NOT NULL, + f32_data INTEGER NOT NULL, + "exex_type" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_communication_shortcut +( + "character_id" INTEGER NOT NULL, + "page_no" INTEGER NOT NULL, + "button_no" INTEGER NOT NULL, + "type" SMALLINT NOT NULL, + "category" SMALLINT NOT NULL, + "id" INTEGER NOT NULL, + CONSTRAINT pk_ddon_communication_shortcut PRIMARY KEY (character_id, page_no, button_no), + CONSTRAINT fk_communication_shortcut_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_pawn_reaction +( + "pawn_id" INTEGER NOT NULL, + "reaction_type" SMALLINT NOT NULL, + "motion_no" INTEGER NOT NULL, + CONSTRAINT pk_ddon_pawn_reaction PRIMARY KEY (pawn_id, reaction_type), + CONSTRAINT fk_pawn_reaction_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_sp_skill +( + "pawn_id" INTEGER NOT NULL, + "sp_skill_id" SMALLINT NOT NULL, + "sp_skill_lv" SMALLINT NOT NULL, + CONSTRAINT pk_ddon_sp_skill PRIMARY KEY ("pawn_id"), + CONSTRAINT fk_sp_skill_pawn_id FOREIGN KEY ("pawn_id") REFERENCES ddon_pawn ("pawn_id") ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ddon_game_token +( + "account_id" INTEGER PRIMARY KEY NOT NULL, + "character_id" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "created" TIMESTAMP WITH TIME ZONE NOT NULL, + CONSTRAINT uq_game_token_token UNIQUE ("token"), + CONSTRAINT fk_game_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id"), + CONSTRAINT fk_game_token_character_id FOREIGN KEY ("character_id") REFERENCES ddon_character ("character_id") +); + +CREATE TABLE IF NOT EXISTS ddon_connection +( + "server_id" INTEGER NOT NULL, + "account_id" INTEGER NOT NULL, + "type" INTEGER NOT NULL, + "created" TIMESTAMP WITH TIME ZONE NOT NULL, + CONSTRAINT uq_connection_server_id_account_id UNIQUE (server_id, account_id), + CONSTRAINT fk_connection_token_account_id FOREIGN KEY ("account_id") REFERENCES account ("id") +); diff --git a/docker-compose.mariadb.yml b/docker-compose.mariadb.yml new file mode 100644 index 000000000..94133e1b7 --- /dev/null +++ b/docker-compose.mariadb.yml @@ -0,0 +1,68 @@ +version: '3.8' + +volumes: + ddon-server-mariadb-volume: + +networks: + ddon-network: + +services: + app: + container_name: ddon-server + build: + context: . + args: + - RUNTIME=${RUNTIME} + restart: no + ports: + # Game server + - "52000:52000" + # Web server + - "52099:52099" + # Login server + - "52100:52100" + environment: + - DB_TYPE=mariadb + - DB_DATABASE=mariadb + - DB_FOLDER=/var/ddon/server/Files/Database + - DB_HOST=db + - DB_USER=admin + - DB_PASS=admin + - DB_PORT=3306 + - DB_WIPE_ON_STARTUP=false + volumes: + - ./Arrowgene.Ddon.config.mariadb.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro + - ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro + networks: + - ddon-network + # Workaround: cli expects to be able to process keyboard input + tty: true + depends_on: + db: + condition: service_healthy + command: /var/ddon/server/Arrowgene.Ddon.Cli server start + + db: + container_name: ddon-db + image: mariadb:11 + ports: + # Database + - "3306:3306" + restart: no + volumes: + - ddon-server-mariadb-volume:/var/lib/mysql + - $PWD/deploy/mariadb/mariadb.cnf:/etc/mysql/mariadb.conf.d/mariadb.cnf:ro + networks: + - ddon-network + environment: + - MARIADB_USER=admin + - MARIADB_ROOT_PASSWORD=admin + - MARIADB_PASSWORD=admin + - MARIADB_DATABASE=mariadb + - LANG=C.UTF_8 + command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"] + healthcheck: + test: ["CMD-SHELL", "mariadb --defaults-file=/var/lib/mysql/.my-healthcheck.cnf --skip-column-names -h localhost -e \"select 1 from information_schema.ENGINES WHERE ENGINE='InnoDB' AND support in ('YES', 'DEFAULT', 'ENABLED')\""] + interval: 3s + timeout: 3s + retries: 10 diff --git a/docker-compose.psql.yml b/docker-compose.psql.yml new file mode 100644 index 000000000..8c01354bf --- /dev/null +++ b/docker-compose.psql.yml @@ -0,0 +1,67 @@ +version: '3.8' + +volumes: + ddon-server-psql-volume: + +networks: + ddon-network: + +services: + app: + container_name: ddon-server + build: + context: . + args: + - RUNTIME=${RUNTIME} + restart: no + ports: + # Game server + - "52000:52000" + # Web server + - "52099:52099" + # Login server + - "52100:52100" + environment: + - DB_TYPE=postgresql + - DB_DATABASE=postgres + - DB_FOLDER=/var/ddon/server/Files/Database + - DB_HOST=db + - DB_USER=root + - DB_PASS=root + - DB_PORT=5432 + - DB_WIPE_ON_STARTUP=false + volumes: + - ./Arrowgene.Ddon.config.psql.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro + - ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro + networks: + - ddon-network + # Workaround: cli expects to be able to process keyboard input + tty: true + depends_on: + db: + condition: service_healthy + command: /var/ddon/server/Arrowgene.Ddon.Cli server start + + db: + container_name: ddon-db + image: postgres:16-alpine + ports: + # Database + - "5432:5432" + restart: no + volumes: + - ddon-server-psql-volume:/var/lib/postgresql/data + - $PWD/deploy/postgresql/postgresql.conf:/etc/postgresql/postgresql.conf:ro + networks: + - ddon-network + environment: + - POSTGRES_USER=root + - POSTGRES_PASSWORD=root + - POSTGRES_DB=postgres + - POSTGRES_INITDB_ARGS=--auth=scram-sha-256 --lc-numeric=en_US.UTF-8 + command: postgres -c config_file=/etc/postgresql/postgresql.conf + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "postgres"] + interval: 10s + timeout: 1s + retries: 3 diff --git a/docker-compose.yml b/docker-compose.yml index 8ffd66331..5b4e622b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,40 @@ version: '3.8' volumes: - ddon-server-database-volume: + ddon-server-sqlite-volume: + +networks: + ddon-network: services: app: container_name: ddon-server - build: . + build: + context: . + args: + - RUNTIME=${RUNTIME} restart: no ports: # Database - '3306:3306' # Game server - - '52000:52000' + - "52000:52000" # Web server - - '52099:52099' + - "52099:52099" # Login server - - '52100:52100' + - "52100:52100" + environment: + - DB_TYPE=sqlite + - DB_DATABASE=Ddon + - DB_FOLDER=/var/ddon/server/Files/Database + - DB_PORT=3306 + - DB_WIPE_ON_STARTUP=false volumes: - - ddon-server-database-volume:/var/ddon/server/Files/Database + - ddon-server-sqlite-volume:/var/ddon/server/Files/Database - ./Arrowgene.Ddon.config.local_dev.json:/var/ddon/server/Files/Arrowgene.Ddon.config.json:ro - ./Arrowgene.Ddon.Shared/Files/Assets:/var/ddon/server/Files/Assets:ro + networks: + - ddon-network # Workaround: cli expects to be able to process keyboard input tty: true command: /var/ddon/server/Arrowgene.Ddon.Cli server start