Add xml comments and DateTime datatype support for SQL

This commit is contained in:
Edgar Luque 2017-12-09 11:35:31 +01:00
parent 27ac4c6318
commit 6a6133f3f7
3 changed files with 233 additions and 16 deletions

View file

@ -19,42 +19,110 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using System.Text; using System.Text;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using TShockAPI.Extensions; using TShockAPI.Extensions;
namespace TShockAPI.DB namespace TShockAPI.DB
{ {
/// <summary>
/// Interface for various SQL related utilities.
/// </summary>
public interface IQueryBuilder public interface IQueryBuilder
{ {
/// <summary>
/// Creates a table from a SqlTable object.
/// </summary>
/// <param name="table">The SqlTable to create the table from</param>
/// <returns>The sql query for the table creation.</returns>
string CreateTable(SqlTable table); string CreateTable(SqlTable table);
/// <summary>
/// Alter a table from source to destination
/// </summary>
/// <param name="from">Must have name and column names. Column types are not required</param>
/// <param name="to">Must have column names and column types.</param>
/// <returns>The SQL Query</returns>
string AlterTable(SqlTable from, SqlTable to); string AlterTable(SqlTable from, SqlTable to);
/// <summary>
/// Converts the MySqlDbType enum to it's string representation.
/// </summary>
/// <param name="type">The MySqlDbType type</param>
/// <param name="length">The length of the datatype</param>
/// <returns>The string representation</returns>
string DbTypeToString(MySqlDbType type, int? length); string DbTypeToString(MySqlDbType type, int? length);
/// <summary>
/// A UPDATE Query
/// </summary>
/// <param name="table">The table to update</param>
/// <param name="values">The values to change</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres); string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres);
/// <summary>
/// A INSERT query
/// </summary>
/// <param name="table">The table to insert to</param>
/// <param name="values"></param>
/// <returns>The SQL Query</returns>
string InsertValues(string table, List<SqlValue> values); string InsertValues(string table, List<SqlValue> values);
/// <summary>
/// A SELECT query to get all columns
/// </summary>
/// <param name="table">The table to select from</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
string ReadColumn(string table, List<SqlValue> wheres); string ReadColumn(string table, List<SqlValue> wheres);
/// <summary>
/// Deletes row(s).
/// </summary>
/// <param name="table">The table to delete the row from</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
string DeleteRow(string table, List<SqlValue> wheres); string DeleteRow(string table, List<SqlValue> wheres);
/// <summary>
/// Renames the given table.
/// </summary>
/// <param name="from">Old name of the table</param>
/// <param name="to">New name of the table</param>
/// <returns>The sql query for renaming the table.</returns>
string RenameTable(string from, string to); string RenameTable(string from, string to);
} }
/// <summary>
/// Query Creator for Sqlite
/// </summary>
public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder
{ {
/// <summary>
/// Creates a table from a SqlTable object.
/// </summary>
/// <param name="table">The SqlTable to create the table from</param>
/// <returns>The sql query for the table creation.</returns>
public override string CreateTable(SqlTable table) public override string CreateTable(SqlTable table)
{ {
SqlColumnErrorCheck(table.Columns);
var columns = var columns =
table.Columns.Select( table.Columns.Select(
c => c =>
"'{0}' {1} {2} {3} {4}".SFormat(c.Name, "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name,
DbTypeToString(c.Type, c.Length), DbTypeToString(c.Type, c.Length),
c.Primary ? "PRIMARY KEY" : "", c.Primary ? "PRIMARY KEY" : "",
c.AutoIncrement ? "AUTOINCREMENT" : "", 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); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name),
string.Join(", ", columns), string.Join(", ", columns),
uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "");
} }
/// <summary>
/// Renames the given table.
/// </summary>
/// <param name="from">Old name of the table</param>
/// <param name="to">New name of the table</param>
/// <returns>The sql query for renaming the table.</returns>
public override string RenameTable(string from, string to) public override string RenameTable(string from, string to)
{ {
return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to); return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
@ -73,8 +141,15 @@ namespace TShockAPI.DB
{ MySqlDbType.Int32, "INTEGER" }, { MySqlDbType.Int32, "INTEGER" },
{ MySqlDbType.Blob, "BLOB" }, { MySqlDbType.Blob, "BLOB" },
{ MySqlDbType.Int64, "BIGINT"}, { MySqlDbType.Int64, "BIGINT"},
{ MySqlDbType.DateTime, "DATETIME"},
}; };
/// <summary>
/// Converts the MySqlDbType enum to it's string representation.
/// </summary>
/// <param name="type">The MySqlDbType type</param>
/// <param name="length">The length of the datatype</param>
/// <returns>The string representation</returns>
public string DbTypeToString(MySqlDbType type, int? length) public string DbTypeToString(MySqlDbType type, int? length)
{ {
string ret; string ret;
@ -83,21 +158,38 @@ namespace TShockAPI.DB
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
} }
/// <summary>
/// Escapes the table name
/// </summary>
/// <param name="table">The name of the table to be escaped</param>
/// <returns></returns>
protected override string EscapeTableName(string table) protected override string EscapeTableName(string table)
{ {
return table.SFormat("'{0}'", table); return table.SFormat("'{0}'", table);
} }
} }
/// <summary>
/// Query Creator for MySQL
/// </summary>
public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder
{ {
/// <summary>
/// Creates a table from a SqlTable object.
/// </summary>
/// <param name="table">The SqlTable to create the table from</param>
/// <returns>The sql query for the table creation.</returns>
public override string CreateTable(SqlTable table) public override string CreateTable(SqlTable table)
{ {
SqlColumnErrorCheck(table.Columns);
var columns = var columns =
table.Columns.Select( table.Columns.Select(
c => c =>
"{0} {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", "{0} {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length),
c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : "")); 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); 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), return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns),
uniques.Count() > 0 uniques.Count() > 0
@ -105,6 +197,12 @@ namespace TShockAPI.DB
: ""); : "");
} }
/// <summary>
/// Renames the given table.
/// </summary>
/// <param name="from">Old name of the table</param>
/// <param name="to">New name of the table</param>
/// <returns>The sql query for renaming the table.</returns>
public override string RenameTable(string from, string to) public override string RenameTable(string from, string to)
{ {
return "RENAME TABLE {0} TO {1}".SFormat(from, to); return "RENAME TABLE {0} TO {1}".SFormat(from, to);
@ -122,8 +220,15 @@ namespace TShockAPI.DB
{ MySqlDbType.Double, "DOUBLE" }, { MySqlDbType.Double, "DOUBLE" },
{ MySqlDbType.Int32, "INT" }, { MySqlDbType.Int32, "INT" },
{ MySqlDbType.Int64, "BIGINT"}, { MySqlDbType.Int64, "BIGINT"},
{ MySqlDbType.DateTime, "DATETIME"},
}; };
/// <summary>
/// Converts the MySqlDbType enum to it's string representation.
/// </summary>
/// <param name="type">The MySqlDbType type</param>
/// <param name="length">The length of the datatype</param>
/// <returns>The string representation</returns>
public string DbTypeToString(MySqlDbType type, int? length) public string DbTypeToString(MySqlDbType type, int? length)
{ {
string ret; string ret;
@ -132,17 +237,41 @@ namespace TShockAPI.DB
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type)); throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
} }
/// <summary>
/// Escapes the table name
/// </summary>
/// <param name="table">The name of the table to be escaped</param>
/// <returns></returns>
protected override string EscapeTableName(string table) protected override string EscapeTableName(string table)
{ {
return table.SFormat("`{0}`", table); return table.SFormat("`{0}`", table);
} }
} }
/// <summary>
/// A Generic Query Creator (abstract)
/// </summary>
public abstract class GenericQueryCreator public abstract class GenericQueryCreator
{ {
protected static Random rand = new Random(); protected static Random rand = new Random();
/// <summary>
/// Escapes the table name
/// </summary>
/// <param name="table">The name of the table to be escaped</param>
/// <returns></returns>
protected abstract string EscapeTableName(string table); protected abstract string EscapeTableName(string table);
/// <summary>
/// Creates a table from a SqlTable object.
/// </summary>
/// <param name="table">The SqlTable to create the table from</param>
/// <returns>The sql query for the table creation.</returns>
public abstract string CreateTable(SqlTable table); public abstract string CreateTable(SqlTable table);
/// <summary>
/// Renames the given table.
/// </summary>
/// <param name="from">Old name of the table</param>
/// <param name="to">New name of the table</param>
/// <returns>The sql query for renaming the table.</returns>
public abstract string RenameTable(string from, string to); public abstract string RenameTable(string from, string to);
/// <summary> /// <summary>
@ -150,18 +279,9 @@ namespace TShockAPI.DB
/// </summary> /// </summary>
/// <param name="from">Must have name and column names. Column types are not required</param> /// <param name="from">Must have name and column names. Column types are not required</param>
/// <param name="to">Must have column names and column types.</param> /// <param name="to">Must have column names and column types.</param>
/// <returns></returns> /// <returns>The SQL Query</returns>
public string AlterTable(SqlTable from, SqlTable to) 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 rstr = rand.NextString(20);
var escapedTable = EscapeTableName(from.Name); var escapedTable = EscapeTableName(from.Name);
var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, 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); return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
} }
/// <summary>
/// Check for errors in the columns.
/// </summary>
/// <param name="columns"></param>
/// <exception cref="SqlColumnExcepcion">description</exception>
public void SqlColumnErrorCheck(List<SqlColumn> 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");
}
});
}
/// <summary>
/// Deletes row(s).
/// </summary>
/// <param name="table">The table to delete the row from</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
public string DeleteRow(string table, List<SqlValue> wheres) public string DeleteRow(string table, List<SqlValue> wheres)
{ {
return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
} }
/// <summary>
/// A UPDATE Query
/// </summary>
/// <param name="table">The table to update</param>
/// <param name="values">The values to change</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres) public string UpdateValue(string table, List<SqlValue> values, List<SqlValue> wheres)
{ {
if (0 == values.Count) 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)); return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres));
} }
/// <summary>
/// A SELECT query to get all columns
/// </summary>
/// <param name="table">The table to select from</param>
/// <param name="wheres"></param>
/// <returns>The SQL query</returns>
public string ReadColumn(string table, List<SqlValue> wheres) public string ReadColumn(string table, List<SqlValue> wheres)
{ {
return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
} }
/// <summary>
/// A INSERT query
/// </summary>
/// <param name="table">The table to insert to</param>
/// <param name="values"></param>
/// <returns>The SQL Query</returns>
public string InsertValues(string table, List<SqlValue> values) public string InsertValues(string table, List<SqlValue> values)
{ {
var sbnames = new StringBuilder(); var sbnames = new StringBuilder();
@ -214,6 +376,11 @@ namespace TShockAPI.DB
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues); return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues);
} }
/// <summary>
/// Builds the SQL WHERE clause
/// </summary>
/// <param name="wheres"></param>
/// <returns></returns>
protected static string BuildWhere(List<SqlValue> wheres) protected static string BuildWhere(List<SqlValue> wheres)
{ {
if (0 == wheres.Count) if (0 == wheres.Count)
@ -222,4 +389,28 @@ namespace TShockAPI.DB
return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => v.Name + " = " + v.Value))); return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => v.Name + " = " + v.Value)));
} }
} }
/// <summary>
/// An excepcion generated by the Column check.
/// </summary>
[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)
{
}
}
} }

View file

@ -28,11 +28,30 @@ namespace TShockAPI.DB
//Optional //Optional
/// <summary>
/// Sets/Gets if it's unique
/// </summary>
public bool Unique { get; set; } public bool Unique { get; set; }
/// <summary>
/// Sets/Gets if it's primary key
/// </summary>
public bool Primary { get; set; } public bool Primary { get; set; }
/// <summary>
/// Sets/Gets if it autoincrements
/// </summary>
public bool AutoIncrement { get; set; } public bool AutoIncrement { get; set; }
/// <summary>
/// Sets/Gets if it can be or not null
/// </summary>
public bool NotNull { get; set; } public bool NotNull { get; set; }
/// <summary>
/// Sets the default value
/// </summary>
public string DefaultValue { get; set; } public string DefaultValue { get; set; }
/// <summary>
/// Use on DateTime only, if true, sets the default value to the current date when creating the row.
/// </summary>
public bool DefaultCurrentTimestamp { get; set; }
/// <summary> /// <summary>
/// Length of the data type, null = default /// Length of the data type, null = default

View file

@ -23,6 +23,9 @@ using System.Diagnostics.CodeAnalysis;
namespace TShockAPI.DB namespace TShockAPI.DB
{ {
/// <summary>
/// Database extensions
/// </summary>
public static class DbExt public static class DbExt
{ {
/// <summary> /// <summary>
@ -189,6 +192,10 @@ namespace TShockAPI.DB
typeof (double?), typeof (double?),
(s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i) (s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i)
}, },
{
typeof (DateTime),
(s, i) => s.IsDBNull(i) ? null : (object)s.GetDateTime(i)
},
{ {
typeof (object), typeof (object),
(s, i) => s.GetValue(i) (s, i) => s.GetValue(i)