diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs index 7b202aaf..614b38bc 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/DB/IQueryBuilder.cs @@ -19,42 +19,110 @@ along with this program. If not, see . using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Text; using MySql.Data.MySqlClient; using TShockAPI.Extensions; namespace TShockAPI.DB { + /// + /// Interface for various SQL related utilities. + /// public interface IQueryBuilder { + /// + /// Creates a table from a SqlTable object. + /// + /// The SqlTable to create the table from + /// The sql query for the table creation. string CreateTable(SqlTable table); + /// + /// 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. + /// The SQL Query string AlterTable(SqlTable from, SqlTable to); + /// + /// Converts the MySqlDbType enum to it's string representation. + /// + /// The MySqlDbType type + /// The length of the datatype + /// The string representation string DbTypeToString(MySqlDbType type, int? length); + /// + /// A UPDATE Query + /// + /// The table to update + /// The values to change + /// + /// The SQL query string UpdateValue(string table, List values, List wheres); + /// + /// A INSERT query + /// + /// The table to insert to + /// + /// The SQL Query string InsertValues(string table, List values); + /// + /// A SELECT query to get all columns + /// + /// The table to select from + /// + /// The SQL query string ReadColumn(string table, List wheres); + /// + /// Deletes row(s). + /// + /// The table to delete the row from + /// + /// The SQL query string DeleteRow(string table, List wheres); + /// + /// Renames the given table. + /// + /// Old name of the table + /// New name of the table + /// The sql query for renaming the table. string RenameTable(string from, string to); } + /// + /// Query Creator for Sqlite + /// public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder { + /// + /// Creates a table from a SqlTable object. + /// + /// The SqlTable to create the table from + /// The sql query for the table creation. public override string CreateTable(SqlTable table) { + SqlColumnErrorCheck(table.Columns); var columns = table.Columns.Select( c => - "'{0}' {1} {2} {3} {4}".SFormat(c.Name, + "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", - c.NotNull ? "NOT NULL" : "")); + c.NotNull ? "NOT NULL" : "", + c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : "")); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns), uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); } + /// + /// Renames the given table. + /// + /// Old name of the table + /// New name of the table + /// The sql query for renaming the table. public override string RenameTable(string from, string to) { return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to); @@ -73,8 +141,15 @@ namespace TShockAPI.DB { MySqlDbType.Int32, "INTEGER" }, { MySqlDbType.Blob, "BLOB" }, { MySqlDbType.Int64, "BIGINT"}, + { MySqlDbType.DateTime, "DATETIME"}, }; + /// + /// Converts the MySqlDbType enum to it's string representation. + /// + /// The MySqlDbType type + /// The length of the datatype + /// The string representation public string DbTypeToString(MySqlDbType type, int? length) { string ret; @@ -83,21 +158,38 @@ namespace TShockAPI.DB throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); } + /// + /// Escapes the table name + /// + /// The name of the table to be escaped + /// protected override string EscapeTableName(string table) { return table.SFormat("'{0}'", table); } } + /// + /// Query Creator for MySQL + /// public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder { + /// + /// Creates a table from a SqlTable object. + /// + /// The SqlTable to create the table from + /// The sql query for the table creation. public override string CreateTable(SqlTable table) { + SqlColumnErrorCheck(table.Columns); 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 ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : "")); + "{0} {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), + c.Primary ? "PRIMARY KEY" : "", + c.AutoIncrement ? "AUTO_INCREMENT" : "", + c.NotNull ? "NOT NULL" : "", + c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : "")); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns), uniques.Count() > 0 @@ -105,6 +197,12 @@ namespace TShockAPI.DB : ""); } + /// + /// Renames the given table. + /// + /// Old name of the table + /// New name of the table + /// The sql query for renaming the table. public override string RenameTable(string from, string to) { return "RENAME TABLE {0} TO {1}".SFormat(from, to); @@ -122,8 +220,15 @@ namespace TShockAPI.DB { MySqlDbType.Double, "DOUBLE" }, { MySqlDbType.Int32, "INT" }, { MySqlDbType.Int64, "BIGINT"}, + { MySqlDbType.DateTime, "DATETIME"}, }; + /// + /// Converts the MySqlDbType enum to it's string representation. + /// + /// The MySqlDbType type + /// The length of the datatype + /// The string representation public string DbTypeToString(MySqlDbType type, int? length) { string ret; @@ -132,17 +237,41 @@ namespace TShockAPI.DB throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); } + /// + /// Escapes the table name + /// + /// The name of the table to be escaped + /// protected override string EscapeTableName(string table) { return table.SFormat("`{0}`", table); } } + /// + /// A Generic Query Creator (abstract) + /// public abstract class GenericQueryCreator { protected static Random rand = new Random(); + /// + /// Escapes the table name + /// + /// The name of the table to be escaped + /// protected abstract string EscapeTableName(string table); + /// + /// Creates a table from a SqlTable object. + /// + /// The SqlTable to create the table from + /// The sql query for the table creation. public abstract string CreateTable(SqlTable table); + /// + /// Renames the given table. + /// + /// Old name of the table + /// New name of the table + /// The sql query for renaming the table. public abstract string RenameTable(string from, string to); /// @@ -150,18 +279,9 @@ namespace TShockAPI.DB /// /// Must have name and column names. Column types are not required /// Must have column names and column types. - /// + /// The SQL Query public string AlterTable(SqlTable from, SqlTable to) { - /* - * Any example outpuf from this looks like:- - ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans" - CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT) - INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans" - DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans" - * - * Twitchy - Oh. I get it! - */ var rstr = rand.NextString(20); var escapedTable = EscapeTableName(from.Name); var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name)); @@ -175,11 +295,41 @@ namespace TShockAPI.DB return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); } + /// + /// Check for errors in the columns. + /// + /// + /// description + public void SqlColumnErrorCheck(List columns) + { + columns.ForEach(x => + { + if(x.DefaultCurrentTimestamp && x.Type != MySqlDbType.DateTime) + { + throw new SqlColumnExcepcion("Can't set to true SqlColumn.DefaultCurrentTimestamp " + + "when the MySqlDbType is not DateTime"); + } + }); + } + + /// + /// Deletes row(s). + /// + /// The table to delete the row from + /// + /// The SQL query public string DeleteRow(string table, List wheres) { return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); } + /// + /// A UPDATE Query + /// + /// The table to update + /// The values to change + /// + /// The SQL query public string UpdateValue(string table, List values, List wheres) { if (0 == values.Count) @@ -188,11 +338,23 @@ namespace TShockAPI.DB return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres)); } + /// + /// A SELECT query to get all columns + /// + /// The table to select from + /// + /// The SQL query public string ReadColumn(string table, List wheres) { return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); } + /// + /// A INSERT query + /// + /// The table to insert to + /// + /// The SQL Query public string InsertValues(string table, List values) { var sbnames = new StringBuilder(); @@ -214,6 +376,11 @@ namespace TShockAPI.DB return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues); } + /// + /// Builds the SQL WHERE clause + /// + /// + /// protected static string BuildWhere(List wheres) { if (0 == wheres.Count) @@ -222,4 +389,28 @@ namespace TShockAPI.DB return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => v.Name + " = " + v.Value))); } } + + /// + /// An excepcion generated by the Column check. + /// + [Serializable] + public class SqlColumnExcepcion : Exception + { + public SqlColumnExcepcion() + { + } + + public SqlColumnExcepcion(string message) + : base(message) + { + } + + public SqlColumnExcepcion(string message, Exception innerException) : base(message, innerException) + { + } + + protected SqlColumnExcepcion(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } } diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/DB/SqlColumn.cs index 8a6c6bdf..cef6b3d8 100644 --- a/TShockAPI/DB/SqlColumn.cs +++ b/TShockAPI/DB/SqlColumn.cs @@ -28,11 +28,30 @@ namespace TShockAPI.DB //Optional + /// + /// Sets/Gets if it's unique + /// public bool Unique { get; set; } + /// + /// Sets/Gets if it's primary key + /// public bool Primary { get; set; } + /// + /// Sets/Gets if it autoincrements + /// public bool AutoIncrement { get; set; } + /// + /// Sets/Gets if it can be or not null + /// public bool NotNull { get; set; } + /// + /// Sets the default value + /// public string DefaultValue { get; set; } + /// + /// Use on DateTime only, if true, sets the default value to the current date when creating the row. + /// + public bool DefaultCurrentTimestamp { get; set; } /// /// Length of the data type, null = default @@ -51,4 +70,4 @@ namespace TShockAPI.DB Length = length; } } -} \ No newline at end of file +} diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs index 3db75ec6..b623cae1 100644 --- a/TShockAPI/Extensions/DbExt.cs +++ b/TShockAPI/Extensions/DbExt.cs @@ -23,6 +23,9 @@ using System.Diagnostics.CodeAnalysis; namespace TShockAPI.DB { + /// + /// Database extensions + /// public static class DbExt { /// @@ -189,6 +192,10 @@ namespace TShockAPI.DB typeof (double?), (s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i) }, + { + typeof (DateTime), + (s, i) => s.IsDBNull(i) ? null : (object)s.GetDateTime(i) + }, { typeof (object), (s, i) => s.GetValue(i) @@ -272,4 +279,4 @@ namespace TShockAPI.DB return Reader.Get(Reader.GetOrdinal(column)); } } -} \ No newline at end of file +}