From 423a33325a62a3c5719c5b10f00cfa33cf4df726 Mon Sep 17 00:00:00 2001 From: high Date: Wed, 3 Aug 2011 18:37:42 -0400 Subject: [PATCH] Finished: -sqlite altering -implemented mysql Todo: -Merge SqlTableCreator into the querybuilders or make it static -Make all the managers use the querybuilder for making tables. (See GroupManager.cs for an example) -Implement more datatypes (see TypesAsStrings in IQueryBuilder.cs) --- TShockAPI/DB/GroupManager.cs | 11 ++--- TShockAPI/DB/IQueryBuilder.cs | 76 ++++++++++++++++++++++++++++++++--- TShockAPI/DB/SqlColumn.cs | 20 ++++++--- TShockAPI/DB/SqlTable.cs | 12 ++++-- TShockAPI/Extensions/DbExt.cs | 2 +- 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index 4fec6cb8..93b093f4 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; +using MySql.Data.MySqlClient; namespace TShockAPI.DB { @@ -18,13 +19,13 @@ namespace TShockAPI.DB string query = ""; - var table = new SqlTable("GroupList", - new SqlColumn("GroupName", "TEXT") { Primary = true }, - new SqlColumn("Commands", "TEXT"), - new SqlColumn("ChatColor", "TEXT") + /*var table = new SqlTable("GroupList", + new SqlColumn("GroupName", MySqlDbType.VarChar, 32) { Primary = true }, + new SqlColumn("Commands", MySqlDbType.Text), + new SqlColumn("ChatColor", MySqlDbType.Text) ); - //new SqlTableCreator(db).EnsureExists(table); + new SqlTableCreator(db, new MysqlQueryCreator()).EnsureExists(table);*/ if (db.GetSqlType() == SqlType.Sqlite) diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs index fa7c28d2..903a18a4 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/DB/IQueryBuilder.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; +using MySql.Data.MySqlClient; using TShockAPI.Extensions; namespace TShockAPI.DB @@ -10,19 +12,33 @@ namespace TShockAPI.DB { string CreateTable(SqlTable table); string AlterTable(SqlTable from, SqlTable to); + string DbTypeToString(MySqlDbType type, int? length); } public class SqliteQueryCreator : IQueryBuilder { public string CreateTable(SqlTable table) { - var columns = table.Columns.Select(c => "'{0}' {1} {2} {3} {4}".SFormat(c.Name, c.Type, c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "", c.Unique ? "UNIQUE" : "")); + var columns = table.Columns.Select(c => "'{0}' {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "", c.Unique ? "UNIQUE" : "")); return "CREATE TABLE '{0}' ({1})".SFormat(table.Name, string.Join(", ", columns)); } static Random rand = new Random(); + /// + /// Alter a table from source to destination + /// + /// Must have name and column names. Column types are not required + /// Must have column names and column types. + /// public string AlterTable(SqlTable from, SqlTable to) { - return ""; + var rstr = rand.NextString(20); + var alter = "ALTER TABLE '{0}' RENAME TO '{1}_{0}'".SFormat(from.Name, rstr); + var create = CreateTable(to); + //combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable. + //exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data? + var insert = "INSERT INTO '{0}' ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name, string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => c.Name)), rstr); + var drop = "DROP TABLE '{0}_{1}'".SFormat(rstr, from.Name); + return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); /* ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans" CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT) @@ -30,18 +46,68 @@ namespace TShockAPI.DB DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans" */ } + + + static readonly Dictionary TypesAsStrings = new Dictionary + { + {MySqlDbType.VarChar, "TEXT"}, + {MySqlDbType.String, "TEXT"}, + {MySqlDbType.Text, "TEXT"}, + {MySqlDbType.TinyText, "TEXT"}, + {MySqlDbType.MediumText, "TEXT"}, + {MySqlDbType.LongText, "TEXT"}, + }; + public string DbTypeToString(MySqlDbType type, int? length) + { + string ret; + if (TypesAsStrings.TryGetValue(type, out ret)) + return ret; + throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); + } } public class MysqlQueryCreator : IQueryBuilder { public string CreateTable(SqlTable table) { - throw new NotImplementedException(); + var columns = table.Columns.Select(c => "{0} {1} {2} {3}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "")); + var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); + return "CREATE TABLE {0} ({1}) {2}".SFormat(table.Name, string.Join(", ", columns), uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); + } + static Random rand = new Random(); + /// + /// Alter a table from source to destination + /// + /// Must have name and column names. Column types are not required + /// Must have column names and column types. + /// + public string AlterTable(SqlTable from, SqlTable to) + { + var rstr = rand.NextString(20); + var alter = "RENAME TABLE {0} TO {1}_{0}".SFormat(from.Name, rstr); + var create = CreateTable(to); + //combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable. + //exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data? + var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name, string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => c.Name)), rstr); + var drop = "DROP TABLE {0}_{1}".SFormat(rstr, from.Name); + return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); } - public string AlterTable(SqlTable from, SqlTable to) + static readonly Dictionary TypesAsStrings = new Dictionary + { + {MySqlDbType.VarChar, "VARCHAR"}, + {MySqlDbType.String, "CHAR"}, + {MySqlDbType.Text, "TEXT"}, + {MySqlDbType.TinyText, "TINYTEXT"}, + {MySqlDbType.MediumText, "MEDIUMTEXT"}, + {MySqlDbType.LongText, "LONGTEXT"}, + }; + public string DbTypeToString(MySqlDbType type, int? length) { - throw new NotImplementedException(); + string ret; + if (TypesAsStrings.TryGetValue(type, out ret)) + return ret + (length != null ? "({0})".SFormat((int)length) : ""); + throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); } } } diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/DB/SqlColumn.cs index ff8472a2..8e86498c 100644 --- a/TShockAPI/DB/SqlColumn.cs +++ b/TShockAPI/DB/SqlColumn.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using MySql.Data.MySqlClient; namespace TShockAPI.DB { @@ -9,7 +6,8 @@ namespace TShockAPI.DB { //Required public string Name { get; set; } - public string Type { get; set; } + public MySqlDbType Type { get; set; } + //Optional public bool Unique { get; set; } @@ -18,10 +16,20 @@ namespace TShockAPI.DB public bool NotNull { get; set; } public string DefaultValue { get; set; } - public SqlColumn(string name, string type) + /// + /// Length of the data type, null = default + /// + public int? Length { get; set; } + + public SqlColumn(string name, MySqlDbType type) + : this(name, type, null) + { + } + public SqlColumn(string name, MySqlDbType type, int? length) { Name = name; Type = type; + Length = length; } } } diff --git a/TShockAPI/DB/SqlTable.cs b/TShockAPI/DB/SqlTable.cs index e2910c9d..3ccca4c4 100644 --- a/TShockAPI/DB/SqlTable.cs +++ b/TShockAPI/DB/SqlTable.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; +using MySql.Data.MySqlClient; namespace TShockAPI.DB { @@ -36,9 +37,10 @@ namespace TShockAPI.DB var columns = GetColumns(table); if (columns.Count > 0) { - if (table.Columns.All(c => columns.Contains(c.Name))) + if (!table.Columns.All(c => columns.Contains(c.Name)) || !columns.All(c => table.Columns.Any(c2 => c2.Name == c))) { - + var from = new SqlTable(table.Name, columns.Select(s => new SqlColumn(s, MySqlDbType.String)).ToList()); + database.Query(creator.AlterTable(from, table)); } } else @@ -61,7 +63,11 @@ namespace TShockAPI.DB } else if (name == SqlType.Mysql) { - throw new NotImplementedException(); + using (var reader = database.QueryReader("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME=@0 AND TABLE_SCHEMA=@1", table.Name, database.Database)) + { + while (reader.Read()) + ret.Add(reader.Get("COLUMN_NAME")); + } } else { diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs index d6541c0c..25429577 100644 --- a/TShockAPI/Extensions/DbExt.cs +++ b/TShockAPI/Extensions/DbExt.cs @@ -71,7 +71,7 @@ namespace TShockAPI.DB var name = conn.GetType().Name; if (name == "SqliteConnection") return SqlType.Sqlite; - if (name == "MysqlConnection") + if (name == "MySqlConnection") return SqlType.Mysql; return SqlType.Unknown; }