diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad484a17..3c81892a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin
* Update OTAPI to 2.0.0.31, which also updates Newtonsoft.Json to 10.0.3 (@Ryozuki)
* Fixed DumpItems() from trying to dump older versions of certain items (negative item IDs). (@Zaicon)
* Added the `/dump-reference-data` command, which when run, runs Utils.Dump() and outputs Terraria reference data to the server folder. (@hakusaro)
+* Added DateTime datatype support for both MySQL and SQLite. (@Ryozuki)
* Fixed builds to not require a specific version of OTAPI and to not fail when in Release mode (@bartico6)
* Update Assembly Company to Pryaxis (@Ryozuki)
* Removed `/restart` command. (@hakusaro)
diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs
index 7b202aaf..917f634d 100644
--- a/TShockAPI/DB/IQueryBuilder.cs
+++ b/TShockAPI/DB/IQueryBuilder.cs
@@ -16,45 +16,120 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+using MySql.Data.MySqlClient;
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)
{
+ ValidateSqlColumnType(table.Columns);
var columns =
table.Columns.Select(
c =>
- "'{0}' {1} {2} {3} {4}".SFormat(c.Name,
- DbTypeToString(c.Type, c.Length),
+ "'{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.AutoIncrement ? "AUTOINCREMENT" : "",
+ 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),
+ 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);
@@ -72,9 +147,16 @@ namespace TShockAPI.DB
{ MySqlDbType.Double, "REAL" },
{ MySqlDbType.Int32, "INTEGER" },
{ MySqlDbType.Blob, "BLOB" },
- { MySqlDbType.Int64, "BIGINT"},
+ { 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 +165,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)
{
+ ValidateSqlColumnType(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 +204,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);
@@ -121,9 +226,16 @@ namespace TShockAPI.DB
{ MySqlDbType.Float, "FLOAT" },
{ MySqlDbType.Double, "DOUBLE" },
{ MySqlDbType.Int32, "INT" },
- { MySqlDbType.Int64, "BIGINT"},
+ { 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 +244,44 @@ 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 +289,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 +305,41 @@ namespace TShockAPI.DB
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
}
+ ///
+ /// Check for errors in the columns.
+ ///
+ ///
+ ///
+ public void ValidateSqlColumnType(List columns)
+ {
+ columns.ForEach(x =>
+ {
+ if (x.DefaultCurrentTimestamp && x.Type != MySqlDbType.DateTime)
+ {
+ throw new SqlColumnException("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 +348,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 +386,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)
diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/DB/SqlColumn.cs
index 8a6c6bdf..d61501d6 100644
--- a/TShockAPI/DB/SqlColumn.cs
+++ b/TShockAPI/DB/SqlColumn.cs
@@ -17,6 +17,7 @@ along with this program. If not, see .
*/
using MySql.Data.MySqlClient;
+using System;
namespace TShockAPI.DB
{
@@ -28,11 +29,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 +71,19 @@ namespace TShockAPI.DB
Length = length;
}
}
-}
\ No newline at end of file
+
+ ///
+ /// Used when a SqlColumn has validation errors.
+ ///
+ [Serializable]
+ public class SqlColumnException : Exception
+ {
+ ///
+ /// Creates a new SqlColumnException with the given message.
+ ///
+ ///
+ public SqlColumnException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs
index 3db75ec6..fbed21f7 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
{
///
@@ -103,7 +106,7 @@ namespace TShockAPI.DB
public static IDbConnection CloneEx(this IDbConnection conn)
{
- var clone = (IDbConnection) Activator.CreateInstance(conn.GetType());
+ var clone = (IDbConnection)Activator.CreateInstance(conn.GetType());
clone.ConnectionString = conn.ConnectionString;
return clone;
}
@@ -120,80 +123,84 @@ namespace TShockAPI.DB
private static readonly Dictionary> ReadFuncs = new Dictionary
>
- {
- {
- typeof (bool),
- (s, i) => s.GetBoolean(i)
- },
- {
- typeof (bool?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetBoolean(i)
- },
- {
- typeof (byte),
- (s, i) => s.GetByte(i)
- },
- {
- typeof (byte?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetByte(i)
- },
- {
- typeof (Int16),
- (s, i) => s.GetInt16(i)
- },
- {
- typeof (Int16?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt16(i)
- },
- {
- typeof (Int32),
- (s, i) => s.GetInt32(i)
- },
- {
- typeof (Int32?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
- },
- {
- typeof (Int64),
- (s, i) => s.GetInt64(i)
- },
- {
- typeof (Int64?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
- },
- {
- typeof (string),
- (s, i) => s.GetString(i)
- },
- {
- typeof (decimal),
- (s, i) => s.GetDecimal(i)
- },
- {
- typeof (decimal?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetDecimal(i)
- },
- {
- typeof (float),
- (s, i) => s.GetFloat(i)
- },
- {
- typeof (float?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetFloat(i)
- },
- {
- typeof (double),
- (s, i) => s.GetDouble(i)
- },
- {
- typeof (double?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i)
- },
- {
- typeof (object),
- (s, i) => s.GetValue(i)
- },
- };
+ {
+ {
+ typeof (bool),
+ (s, i) => s.GetBoolean(i)
+ },
+ {
+ typeof (bool?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetBoolean(i)
+ },
+ {
+ typeof (byte),
+ (s, i) => s.GetByte(i)
+ },
+ {
+ typeof (byte?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetByte(i)
+ },
+ {
+ typeof (Int16),
+ (s, i) => s.GetInt16(i)
+ },
+ {
+ typeof (Int16?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt16(i)
+ },
+ {
+ typeof (Int32),
+ (s, i) => s.GetInt32(i)
+ },
+ {
+ typeof (Int32?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
+ },
+ {
+ typeof (Int64),
+ (s, i) => s.GetInt64(i)
+ },
+ {
+ typeof (Int64?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
+ },
+ {
+ typeof (string),
+ (s, i) => s.GetString(i)
+ },
+ {
+ typeof (decimal),
+ (s, i) => s.GetDecimal(i)
+ },
+ {
+ typeof (decimal?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetDecimal(i)
+ },
+ {
+ typeof (float),
+ (s, i) => s.GetFloat(i)
+ },
+ {
+ typeof (float?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetFloat(i)
+ },
+ {
+ typeof (double),
+ (s, i) => s.GetDouble(i)
+ },
+ {
+ 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)
+ },
+ };
public static T Get(this IDataReader reader, string column)
{
@@ -205,8 +212,8 @@ namespace TShockAPI.DB
if (reader.IsDBNull(column))
return default(T);
- if (ReadFuncs.ContainsKey(typeof (T)))
- return (T) ReadFuncs[typeof (T)](reader, column);
+ if (ReadFuncs.ContainsKey(typeof(T)))
+ return (T)ReadFuncs[typeof(T)](reader, column);
throw new NotImplementedException();
}
@@ -272,4 +279,4 @@ namespace TShockAPI.DB
return Reader.Get(Reader.GetOrdinal(column));
}
}
-}
\ No newline at end of file
+}