From 00cc051dc69b1c8ed2c9a80ed29193d8c27f64fd Mon Sep 17 00:00:00 2001 From: Michael Green <84688932+michael-j-green@users.noreply.github.com> Date: Sun, 18 Jun 2023 23:08:02 +1000 Subject: [PATCH] feat: added metadata source field - this will determine the handling of the flags attribute --- .../RomSignatureObject.cs | 8 +++++ gaseous-server/Classes/ImportGames.cs | 8 +++-- gaseous-server/Classes/Metadata/Games.cs | 3 +- gaseous-server/Classes/Metadata/Platforms.cs | 3 +- gaseous-server/Classes/Roms.cs | 32 ++++++++++++------- .../Classes/SignatureIngestors/TOSEC.cs | 3 +- gaseous-server/Controllers/GamesController.cs | 18 +++++------ .../Controllers/SignaturesController.cs | 5 +-- gaseous-server/Models/Signatures_Games.cs | 8 +++++ gaseous-server/Program.cs | 7 ++-- .../Parsers/TosecParser.cs | 1 + gaseous-tools/Database/MySQL/gaseous-1000.sql | 14 ++++---- 12 files changed, 70 insertions(+), 40 deletions(-) diff --git a/gaseous-romsignatureobject/RomSignatureObject.cs b/gaseous-romsignatureobject/RomSignatureObject.cs index 5c04359..20021d2 100644 --- a/gaseous-romsignatureobject/RomSignatureObject.cs +++ b/gaseous-romsignatureobject/RomSignatureObject.cs @@ -72,6 +72,14 @@ namespace gaseous_romsignatureobject public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } + public SignatureSourceType SignatureSource { get; set; } + + public enum SignatureSourceType + { + None = 0, + TOSEC = 1 + } + public enum RomTypes { /// diff --git a/gaseous-server/Classes/ImportGames.cs b/gaseous-server/Classes/ImportGames.cs index e5cea16..d6b48b5 100644 --- a/gaseous-server/Classes/ImportGames.cs +++ b/gaseous-server/Classes/ImportGames.cs @@ -155,6 +155,7 @@ namespace gaseous_server.Classes ri.Md5 = hash.md5hash; ri.Sha1 = hash.sha1hash; ri.Size = fi.Length; + ri.SignatureSource = Models.Signatures_Games.RomItem.SignatureSourceType.None; } } @@ -205,13 +206,14 @@ namespace gaseous_server.Classes } // add to database - sql = "INSERT INTO games_roms (platformid, gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel, path) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @path); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sql = "INSERT INTO games_roms (platformid, gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel, path, metadatasource) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @path, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0)); dbDict.Add("gameid", Common.ReturnValueIfNull(determinedGame.Id, 0)); dbDict.Add("name", Common.ReturnValueIfNull(discoveredSignature.Rom.Name, "")); dbDict.Add("size", Common.ReturnValueIfNull(discoveredSignature.Rom.Size, 0)); dbDict.Add("crc", Common.ReturnValueIfNull(discoveredSignature.Rom.Crc, "")); dbDict.Add("developmentstatus", Common.ReturnValueIfNull(discoveredSignature.Rom.DevelopmentStatus, "")); + dbDict.Add("metadatasource", discoveredSignature.Rom.SignatureSource); if (discoveredSignature.Rom.flags != null) { @@ -245,7 +247,7 @@ namespace gaseous_server.Classes public static string ComputeROMPath(long RomId) { - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); // get metadata IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId); @@ -275,7 +277,7 @@ namespace gaseous_server.Classes public static void MoveGameFile(long RomId) { - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); string romPath = rom.Path; if (File.Exists(romPath)) diff --git a/gaseous-server/Classes/Metadata/Games.cs b/gaseous-server/Classes/Metadata/Games.cs index 539c2e5..016eed1 100644 --- a/gaseous-server/Classes/Metadata/Games.cs +++ b/gaseous-server/Classes/Metadata/Games.cs @@ -30,7 +30,8 @@ namespace gaseous_server.Classes.Metadata returnValue = new Game { Id = 0, - Name = "Unknown Title" + Name = "Unknown Title", + Slug = "Unknown" }; Storage.NewCacheValue(returnValue); diff --git a/gaseous-server/Classes/Metadata/Platforms.cs b/gaseous-server/Classes/Metadata/Platforms.cs index 97c618b..4e3fefa 100644 --- a/gaseous-server/Classes/Metadata/Platforms.cs +++ b/gaseous-server/Classes/Metadata/Platforms.cs @@ -32,7 +32,8 @@ namespace gaseous_server.Classes.Metadata returnValue = new Platform { Id = 0, - Name = "Unknown Platform" + Name = "Unknown Platform", + Slug = "Unknown" }; Storage.NewCacheValue(returnValue); diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index aed8ca7..f85fe68 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -6,7 +6,7 @@ namespace gaseous_server.Classes { public class Roms { - public static List GetRoms(long GameId) + public static List GetRoms(long GameId) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = "SELECT * FROM games_roms WHERE gameid = @id ORDER BY `name`"; @@ -16,7 +16,7 @@ namespace gaseous_server.Classes if (romDT.Rows.Count > 0) { - List romItems = new List(); + List romItems = new List(); foreach (DataRow romDR in romDT.Rows) { romItems.Add(BuildRom(romDR)); @@ -30,7 +30,7 @@ namespace gaseous_server.Classes } } - public static RomItem GetRom(long RomId) + public static GameRomItem GetRom(long RomId) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); string sql = "SELECT * FROM games_roms WHERE id = @id"; @@ -41,7 +41,7 @@ namespace gaseous_server.Classes if (romDT.Rows.Count > 0) { DataRow romDR = romDT.Rows[0]; - RomItem romItem = BuildRom(romDR); + GameRomItem romItem = BuildRom(romDR); return romItem; } else @@ -50,7 +50,7 @@ namespace gaseous_server.Classes } } - public static RomItem UpdateRom(long RomId, long PlatformId, long GameId) + public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId) { // ensure metadata for platformid is present IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId); @@ -66,14 +66,14 @@ namespace gaseous_server.Classes dbDict.Add("gameid", GameId); db.ExecuteCMD(sql, dbDict); - RomItem rom = GetRom(RomId); + GameRomItem rom = GetRom(RomId); return rom; } public static void DeleteRom(long RomId) { - RomItem rom = GetRom(RomId); + GameRomItem rom = GetRom(RomId); if (File.Exists(rom.Path)) { File.Delete(rom.Path); @@ -84,9 +84,9 @@ namespace gaseous_server.Classes db.ExecuteCMD(sql); } - private static RomItem BuildRom(DataRow romDR) + private static GameRomItem BuildRom(DataRow romDR) { - RomItem romItem = new RomItem + GameRomItem romItem = new GameRomItem { Id = (long)romDR["id"], PlatformId = (long)romDR["platformid"], @@ -101,12 +101,13 @@ namespace gaseous_server.Classes RomType = (int)romDR["romtype"], RomTypeMedia = (string)romDR["romtypemedia"], MediaLabel = (string)romDR["medialabel"], - Path = (string)romDR["path"] + Path = (string)romDR["path"], + Source = (GameRomItem.SourceType)(Int32)romDR["metadatasource"] }; return romItem; } - public class RomItem + public class GameRomItem { public long Id { get; set; } public long PlatformId { get; set; } @@ -122,7 +123,14 @@ namespace gaseous_server.Classes public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } public string? Path { get; set; } + public SourceType Source { get; set; } + + public enum SourceType + { + None = 0, + TOSEC = 1 + } } - } + } } diff --git a/gaseous-server/Classes/SignatureIngestors/TOSEC.cs b/gaseous-server/Classes/SignatureIngestors/TOSEC.cs index c1c46b4..72de44a 100644 --- a/gaseous-server/Classes/SignatureIngestors/TOSEC.cs +++ b/gaseous-server/Classes/SignatureIngestors/TOSEC.cs @@ -194,12 +194,13 @@ namespace gaseous_server.SignatureIngestors.TOSEC dbDict.Add("romtype", (int)romObject.RomType); dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); + dbDict.Add("metadatasource", Classes.Roms.GameRomItem.SourceType.TOSEC); sigDB = db.ExecuteCMD(sql, dbDict); if (sigDB.Rows.Count == 0) { // entry not present, insert it - sql = "INSERT INTO signatures_roms (gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; + sql = "INSERT INTO signatures_roms (gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel, metadatasource) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel, @metadatasource); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sigDB = db.ExecuteCMD(sql, dbDict); diff --git a/gaseous-server/Controllers/GamesController.cs b/gaseous-server/Controllers/GamesController.cs index 37371e9..ed99732 100644 --- a/gaseous-server/Controllers/GamesController.cs +++ b/gaseous-server/Controllers/GamesController.cs @@ -435,7 +435,7 @@ namespace gaseous_server.Controllers [HttpGet] [Route("{GameId}/roms")] - [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRom(long GameId) { @@ -443,7 +443,7 @@ namespace gaseous_server.Controllers { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); - List roms = Classes.Roms.GetRoms(GameId); + List roms = Classes.Roms.GetRoms(GameId); return Ok(roms); } @@ -455,7 +455,7 @@ namespace gaseous_server.Controllers [HttpGet] [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.RomItem), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRom(long GameId, long RomId) { @@ -463,7 +463,7 @@ namespace gaseous_server.Controllers { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); if (rom.GameId == GameId) { return Ok(rom); @@ -481,7 +481,7 @@ namespace gaseous_server.Controllers [HttpPatch] [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.RomItem), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId) { @@ -489,7 +489,7 @@ namespace gaseous_server.Controllers { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); if (rom.GameId == GameId) { rom = Classes.Roms.UpdateRom(RomId, NewPlatformId, NewGameId); @@ -508,7 +508,7 @@ namespace gaseous_server.Controllers [HttpDelete] [Route("{GameId}/roms/{RomId}")] - [ProducesResponseType(typeof(Classes.Roms.RomItem), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GameRomDelete(long GameId, long RomId) { @@ -516,7 +516,7 @@ namespace gaseous_server.Controllers { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); if (rom.GameId == GameId) { Classes.Roms.DeleteRom(RomId); @@ -543,7 +543,7 @@ namespace gaseous_server.Controllers { IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false); - Classes.Roms.RomItem rom = Classes.Roms.GetRom(RomId); + Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); if (rom.GameId != GameId) { return NotFound(); diff --git a/gaseous-server/Controllers/SignaturesController.cs b/gaseous-server/Controllers/SignaturesController.cs index 1e272d2..4d521dc 100644 --- a/gaseous-server/Controllers/SignaturesController.cs +++ b/gaseous-server/Controllers/SignaturesController.cs @@ -55,7 +55,7 @@ namespace gaseous_server.Controllers private List _GetSignature(string sqlWhere, string searchString) { Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT \n view_signatures_games.*,\n signatures_roms.id AS romid,\n signatures_roms.name AS romname,\n signatures_roms.size,\n signatures_roms.crc,\n signatures_roms.md5,\n signatures_roms.sha1,\n signatures_roms.developmentstatus,\n signatures_roms.flags,\n signatures_roms.romtype,\n signatures_roms.romtypemedia,\n signatures_roms.medialabel\nFROM\n signatures_roms\n INNER JOIN\n view_signatures_games ON signatures_roms.gameid = view_signatures_games.id WHERE " + sqlWhere; + string sql = "SELECT \n view_signatures_games.*,\n signatures_roms.id AS romid,\n signatures_roms.name AS romname,\n signatures_roms.size,\n signatures_roms.crc,\n signatures_roms.md5,\n signatures_roms.sha1,\n signatures_roms.developmentstatus,\n signatures_roms.flags,\n signatures_roms.romtype,\n signatures_roms.romtypemedia,\n signatures_roms.medialabel,\n signatures_roms.metadatasource\nFROM\n signatures_roms\n INNER JOIN\n view_signatures_games ON signatures_roms.gameid = view_signatures_games.id WHERE " + sqlWhere; Dictionary dbDict = new Dictionary(); dbDict.Add("searchString", searchString); @@ -94,7 +94,8 @@ namespace gaseous_server.Controllers flags = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)sigDbRow["flags"]), RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["romtype"], RomTypeMedia = (string)sigDbRow["romtypemedia"], - MediaLabel = (string)sigDbRow["medialabel"] + MediaLabel = (string)sigDbRow["medialabel"], + SignatureSource = (Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["metadatasource"] } }; GamesList.Add(gameItem); diff --git a/gaseous-server/Models/Signatures_Games.cs b/gaseous-server/Models/Signatures_Games.cs index 89f4524..0208008 100644 --- a/gaseous-server/Models/Signatures_Games.cs +++ b/gaseous-server/Models/Signatures_Games.cs @@ -133,6 +133,14 @@ namespace gaseous_server.Models public string? RomTypeMedia { get; set; } public string? MediaLabel { get; set; } + public SignatureSourceType SignatureSource { get; set; } + + public enum SignatureSourceType + { + None = 0, + TOSEC = 1 + } + public enum RomTypes { /// diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs index 542ea6c..49342f4 100644 --- a/gaseous-server/Program.cs +++ b/gaseous-server/Program.cs @@ -35,10 +35,7 @@ builder.Services.AddControllers().AddJsonOptions(x => // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(options => -{ - options.CustomSchemaIds(type => type.ToString()); -}); +builder.Services.AddSwaggerGen(); builder.Services.AddHostedService(); var app = builder.Build(); @@ -63,7 +60,7 @@ app.MapControllers(); Config.LibraryConfiguration.InitLibrary(); // insert unknown platform and game if not present -gaseous_server.Classes.Metadata.Games.GetGame(0, false, true); +gaseous_server.Classes.Metadata.Games.GetGame(0, false, false); gaseous_server.Classes.Metadata.Platforms.GetPlatform(0); // organise library diff --git a/gaseous-signature-parser/Parsers/TosecParser.cs b/gaseous-signature-parser/Parsers/TosecParser.cs index d63c4f8..a8e6f4d 100644 --- a/gaseous-signature-parser/Parsers/TosecParser.cs +++ b/gaseous-signature-parser/Parsers/TosecParser.cs @@ -365,6 +365,7 @@ namespace gaseous_signature_parser.parsers romObject.Crc = xmlGameDetail.Attributes["crc"]?.Value; romObject.Md5 = xmlGameDetail.Attributes["md5"]?.Value; romObject.Sha1 = xmlGameDetail.Attributes["sha1"]?.Value; + romObject.SignatureSource = RomSignatureObject.Game.Rom.SignatureSourceType.TOSEC; // parse name string[] romNameTokens = romDescription.Split("("); diff --git a/gaseous-tools/Database/MySQL/gaseous-1000.sql b/gaseous-tools/Database/MySQL/gaseous-1000.sql index 457cd2d..75a7db3 100644 --- a/gaseous-tools/Database/MySQL/gaseous-1000.sql +++ b/gaseous-tools/Database/MySQL/gaseous-1000.sql @@ -300,9 +300,10 @@ CREATE TABLE `games_roms` ( `romtypemedia` varchar(100) DEFAULT NULL, `medialabel` varchar(100) DEFAULT NULL, `path` longtext, + `metadatasource` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id_UNIQUE` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=398 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -497,7 +498,7 @@ CREATE TABLE `signatures_games` ( KEY `ingest_idx` (`name`,`year`,`publisherid`,`systemid`,`country`,`language`) USING BTREE, CONSTRAINT `publisher` FOREIGN KEY (`publisherid`) REFERENCES `signatures_publishers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `system` FOREIGN KEY (`systemid`) REFERENCES `signatures_platforms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=785672 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -513,7 +514,7 @@ CREATE TABLE `signatures_platforms` ( PRIMARY KEY (`id`), UNIQUE KEY `idsignatures_platforms_UNIQUE` (`id`), KEY `platforms_idx` (`platform`,`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=417 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -529,7 +530,7 @@ CREATE TABLE `signatures_publishers` ( PRIMARY KEY (`id`), UNIQUE KEY `id_UNIQUE` (`id`), KEY `publisher_idx` (`publisher`,`id`) -) ENGINE=InnoDB AUTO_INCREMENT=52259 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -552,6 +553,7 @@ CREATE TABLE `signatures_roms` ( `romtype` int DEFAULT NULL, `romtypemedia` varchar(100) DEFAULT NULL, `medialabel` varchar(100) DEFAULT NULL, + `metadatasource` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id_UNIQUE` (`id`,`gameid`) USING BTREE, KEY `gameid_idx` (`gameid`), @@ -559,7 +561,7 @@ CREATE TABLE `signatures_roms` ( KEY `sha1_idx` (`sha1`) USING BTREE, KEY `flags_idx` ((cast(`flags` as char(255) array))), CONSTRAINT `gameid` FOREIGN KEY (`gameid`) REFERENCES `signatures_games` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=1734101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -586,7 +588,7 @@ CREATE TABLE `signatures_sources` ( UNIQUE KEY `id_UNIQUE` (`id`), KEY `sourcemd5_idx` (`sourcemd5`,`id`) USING BTREE, KEY `sourcesha1_idx` (`sourcesha1`,`id`) USING BTREE -) ENGINE=InnoDB AUTO_INCREMENT=3109 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; --