Simplify database API

This commit is contained in:
UnknownShadow200 2018-07-02 17:03:14 +10:00
parent cabe47f26f
commit 33561ee9d4
14 changed files with 117 additions and 268 deletions

View File

@ -31,13 +31,6 @@ namespace MCGalaxy.Commands.Fun {
get { return new[] { new CommandPerm(LevelPermission.Operator, "can manage CTF") }; }
}
// TODO: avoid code duplication with CTFLevelPicker
static List<string> GetCtfMaps() {
if (!File.Exists("CTF/maps.config")) return new List<string>();
string[] lines = File.ReadAllLines("CTF/maps.config");
return new List<string>(lines);
}
protected override void HandleSetCore(Player p, RoundsGame game, string[] args) {
string prop = args[1];
CTFMapConfig cfg = RetrieveConfig(p);
@ -65,31 +58,6 @@ namespace MCGalaxy.Commands.Fun {
}
}
static void HandleAdd(Player p, Level lvl) {
if (!Directory.Exists("CTF")) Directory.CreateDirectory("CTF");
List<string> maps = GetCtfMaps();
if (maps.CaselessContains(lvl.name)) {
Player.Message(p, "{0} %Sis already in the list of CTF maps", lvl.ColoredName);
} else {
Player.Message(p, "Added {0} %Sto the list of CTF maps", lvl.ColoredName);
maps.Add(p.level.name);
File.WriteAllLines("CTF/maps.config", maps.ToArray());
}
}
static void HandleRemove(Player p, Level lvl) {
if (!Directory.Exists("CTF")) Directory.CreateDirectory("CTF");
List<string> maps = GetCtfMaps();
if (!maps.CaselessRemove(lvl.name)) {
Player.Message(p, "{0} %Swas not in the list of CTF maps", lvl.ColoredName);
} else {
Player.Message(p, "Removed {0} %Sfrom the list of CTF maps", lvl.ColoredName);
File.WriteAllLines("CTF/maps.config", maps.ToArray());
}
}
static bool BlueFlagCallback(Player p, Vec3S32[] marks, object state, BlockID block) {
CTFMapConfig cfg = RetrieveConfig(p);
Vec3U16 P = (Vec3U16)marks[0];

View File

@ -26,40 +26,36 @@ using MySql.Data.MySqlClient;
namespace MCGalaxy.SQL {
public sealed class MySQLBackend : IDatabaseBackend {
public static IDatabaseBackend Instance = new MySQLBackend();
static ParameterisedQuery queryInstance = new MySQLParameterisedQuery();
static string connFormat = "Data Source={0};Port={1};User ID={2};Password={3};Pooling={4};Treat Tiny As Boolean=false;";
public override string ConnectionString {
get { return string.Format(connFormat, ServerConfig.MySQLHost, ServerConfig.MySQLPort,
ServerConfig.MySQLUsername, ServerConfig.MySQLPassword, ServerConfig.DatabasePooling); }
}
public override bool EnforcesTextLength { get { return true; } }
public MySQLBackend() {
CaselessWhereSuffix = " COLLATE utf8_general_ci";
CaselessLikeSuffix = "";
}
public override bool EnforcesTextLength { get { return true; } }
public override bool MultipleSchema { get { return true; } }
internal override IDbConnection CreateConnection() {
const string format = "Data Source={0};Port={1};User ID={2};Password={3};Pooling={4};Treat Tiny As Boolean=false;";
string str = string.Format(format, ServerConfig.MySQLHost, ServerConfig.MySQLPort,
ServerConfig.MySQLUsername, ServerConfig.MySQLPassword, ServerConfig.DatabasePooling);
return new MySqlConnection(str);
}
internal override IDbCommand CreateCommand(string sql, IDbConnection conn) {
return new MySqlCommand(sql, (MySqlConnection)conn);
}
internal override IDbDataParameter CreateParameter() {
return new MySqlParameter();
}
public override void CreateDatabase() {
string sql = "CREATE DATABASE if not exists `" + ServerConfig.MySQLDatabaseName + "`";
Database.Do(sql, true, null, null);
}
public override BulkTransaction CreateBulk() {
return new MySQLBulkTransaction(ConnectionString);
}
public override ParameterisedQuery CreateParameterised() {
return new MySQLParameterisedQuery();
}
protected internal override ParameterisedQuery GetStaticParameterised() {
return queryInstance;
}
public override string RawGetDateTime(IDataRecord record, int col) {
DateTime date = record.GetDateTime(col);
return date.ToString(Database.DateFormat);
@ -165,42 +161,4 @@ namespace MCGalaxy.SQL {
DoInsert("REPLACE INTO", table, columns, args);
}
}
public sealed class MySQLBulkTransaction : BulkTransaction {
public MySQLBulkTransaction(string connString) {
connection = new MySqlConnection(connString);
connection.Open();
connection.ChangeDatabase(ServerConfig.MySQLDatabaseName);
transaction = connection.BeginTransaction();
}
public override IDbCommand CreateCommand(string sql) {
return new MySqlCommand(sql, (MySqlConnection)connection, (MySqlTransaction)transaction);
}
public override IDataParameter CreateParam(string paramName, DbType type) {
MySqlParameter arg = new MySqlParameter(paramName, null);
arg.DbType = type;
return arg;
}
}
public sealed class MySQLParameterisedQuery : ParameterisedQuery {
protected override bool MultipleSchema { get { return true; } }
protected override IDbConnection CreateConnection(string connString) {
return new MySqlConnection(connString);
}
protected override IDbCommand CreateCommand(string sql, IDbConnection conn) {
return new MySqlCommand(sql, (MySqlConnection)conn);
}
protected override IDbDataParameter CreateParameter() {
return new MySqlParameter();
}
}
}

View File

@ -26,36 +26,32 @@ using System.Text;
namespace MCGalaxy.SQL {
public sealed class SQLiteBackend : IDatabaseBackend {
public static IDatabaseBackend Instance = new SQLiteBackend();
static ParameterisedQuery queryInstance = new SQLiteParameterisedQuery();
static string connFormat = "Data Source =" + Utils.FolderPath + "/MCGalaxy.db; Version =3; Pooling ={0}; Max Pool Size =300;";
public override string ConnectionString {
get { return string.Format(connFormat, ServerConfig.DatabasePooling); }
}
public override bool EnforcesTextLength { get { return false; } }
public SQLiteBackend() {
CaselessWhereSuffix = " COLLATE NOCASE";
CaselessLikeSuffix = " COLLATE NOCASE";
}
public override bool EnforcesTextLength { get { return false; } }
public override bool MultipleSchema { get { return false; } }
internal override IDbConnection CreateConnection() {
const string format = "Data Source={0}/MCGalaxy.db;Version=3;Pooling={1};Max Pool Size=300;";
string str = string.Format(format, Utils.FolderPath, ServerConfig.DatabasePooling);
return new SQLiteConnection(str);
}
internal override IDbCommand CreateCommand(string sql, IDbConnection conn) {
return new SQLiteCommand(sql, (SQLiteConnection)conn);
}
internal override IDbDataParameter CreateParameter() {
return new SQLiteParameter();
}
public override void CreateDatabase() { }
public override BulkTransaction CreateBulk() {
return new SQLiteBulkTransaction(ConnectionString);
}
public override ParameterisedQuery CreateParameterised() {
return new SQLiteParameterisedQuery();
}
protected internal override ParameterisedQuery GetStaticParameterised() {
return queryInstance;
}
public override string RawGetDateTime(IDataRecord record, int col) {
return record.GetString(col); // reader.GetDateTime is extremely slow so avoid it
}
@ -104,12 +100,7 @@ namespace MCGalaxy.SQL {
for (int i = 0; i < columns.Length; i++) {
ColumnDesc col = columns[i];
sql.Append(col.Column).Append(' ');
if (col.Type == ColumnType.Bool) {
sql.Append("TINYINT");
} else {
sql.Append(col.FormatType());
}
sql.Append(col.FormatType());
// When the primary key isn't autoincrement, we use the same form as mysql
// Otherwise we have to use sqlite's 'PRIMARY KEY AUTO_INCREMENT' form
@ -151,38 +142,4 @@ namespace MCGalaxy.SQL {
DoInsert("INSERT OR REPLACE INTO", table, columns, args);
}
}
public sealed class SQLiteBulkTransaction : BulkTransaction {
public SQLiteBulkTransaction(string connString) {
connection = new SQLiteConnection(connString);
connection.Open();
transaction = connection.BeginTransaction();
}
public override IDbCommand CreateCommand(string sql) {
return new SQLiteCommand(sql, (SQLiteConnection)connection, (SQLiteTransaction)transaction);
}
public override IDataParameter CreateParam(string paramName, DbType type) {
return new SQLiteParameter(paramName, type);
}
}
public sealed class SQLiteParameterisedQuery : ParameterisedQuery {
protected override bool MultipleSchema { get { return false; } }
protected override IDbConnection CreateConnection(string connString) {
return new SQLiteConnection(connString);
}
protected override IDbCommand CreateCommand(string sql, IDbConnection conn) {
return new SQLiteCommand(sql, (SQLiteConnection)conn);
}
protected override IDbDataParameter CreateParameter() {
return new SQLiteParameter();
}
}
}

View File

@ -53,13 +53,13 @@ namespace MCGalaxy.SQL {
static string[] colTypes = new string[] {
"TINYINT UNSIGNED", "SMALLINT UNSIGNED", "MEDIUMINT UNSIGNED",
"INT UNSIGNED", "BIGINT UNSIGNED", "TINYINT", "SMALLINT",
"MEDIUMINT", "INT", "BIGINT", "INTEGER", "BOOL", "DATETIME",
"MEDIUMINT", "INT", "BIGINT", "INTEGER", "DATETIME",
};
}
public enum ColumnType {
UInt8, UInt16, UInt24, UInt32, UInt64,
Int8, Int16, Int24, Int32, Int64,
Integer, Bool, DateTime, Char, VarChar
Integer, DateTime, Char, VarChar
}
}

View File

@ -76,33 +76,21 @@ namespace MCGalaxy.SQL {
internal static object Do(string sql, bool createDB, object arg,
ReaderCallback callback, params object[] args) {
ParameterisedQuery query;
if (args == null || args.Length == 0) {
query = Backend.GetStaticParameterised();
} else {
query = Backend.CreateParameterised();
}
query.parameters = args;
string connString = Backend.ConnectionString;
Exception e = null;
for (int i = 0; i < 10; i++) {
try {
if (callback != null) {
arg = query.Iterate(sql, connString, arg, callback);
arg = SqlQuery.Iterate(sql, args, arg, callback);
} else {
query.Execute(sql, connString, createDB);
SqlQuery.Execute(sql, args, createDB);
}
query.parameters = null;
return arg;
} catch (Exception ex) {
e = ex; // try yet again
}
}
query.parameters = null;
File.AppendAllText("MySQL_error.log", DateTime.Now + " " + sql + "\r\n");
Logger.LogError(e);
return arg;

View File

@ -26,16 +26,17 @@ namespace MCGalaxy.SQL {
/// <summary> Simple abstraction for a database management system. </summary>
public abstract class IDatabaseBackend {
/// <summary> Describes the arguments for a database connection
/// (such as database name or file location) </summary>
public abstract string ConnectionString { get; }
/// <summary> Whether this backend enforces the character length in VARCHAR columns. </summary>
public abstract bool EnforcesTextLength { get; }
/// <summary> Whether this backend supports multiple database schemas. </summary>
public abstract bool MultipleSchema { get; }
internal abstract IDbConnection CreateConnection();
internal abstract IDbCommand CreateCommand(string sql, IDbConnection conn);
internal abstract IDbDataParameter CreateParameter();
/// <summary> Suffix required after a WHERE clause for caseless string comparison. </summary>
public string CaselessWhereSuffix { get; protected set; }
/// <summary> Suffix required after a LIKE clause for caseless string comparison. </summary>
public string CaselessLikeSuffix { get; protected set; }
@ -43,18 +44,6 @@ namespace MCGalaxy.SQL {
/// <summary> Creates the schema for this database (if required). </summary>
public abstract void CreateDatabase();
/// <summary> Returns a new BulkTransaction instance, which can be used to execute
/// many sql statements as one single transaction. </summary>
public abstract BulkTransaction CreateBulk();
/// <summary> Returns a new ParameterisedQuery instance, which executes sql statements
/// and manages binding of parameters for sql queries. </summary>
public abstract ParameterisedQuery CreateParameterised();
/// <summary> Returns the shared static ParamterisedQuery instance, that is only used
/// for sql queries with no parameters. </summary>
protected internal abstract ParameterisedQuery GetStaticParameterised();
public abstract string RawGetDateTime(IDataRecord record, int col);
protected internal virtual void ParseCreate(ref string cmd) { }
@ -161,7 +150,7 @@ namespace MCGalaxy.SQL {
sql.Append(" `").Append(table).Append("` ");
sql.Append('(').Append(columns).Append(')');
string[] names = ParameterisedQuery.GetNames(args.Length);
string[] names = SqlQuery.GetNames(args.Length);
sql.Append(" VALUES (");
for (int i = 0; i < args.Length; i++) {
sql.Append(names[i]);

View File

@ -20,26 +20,19 @@ using System.Data;
namespace MCGalaxy.SQL {
/// <summary> Represents an SQL command or query, that takes named parameters/arguments. </summary>
public abstract class ParameterisedQuery {
internal object[] parameters;
protected abstract bool MultipleSchema { get; }
protected abstract IDbConnection CreateConnection(string connString);
protected abstract IDbCommand CreateCommand(string sql, IDbConnection conn);
protected abstract IDbDataParameter CreateParameter();
/// <summary> Executes an SQL command or query, that takes named parameters/arguments. </summary>
public static class SqlQuery {
/// <summary> Executes an SQL command that does not return any results. </summary>
public void Execute(string sql, string connString, bool createDB = false) {
using (IDbConnection conn = CreateConnection(connString)) {
public static void Execute(string sql, object[] parameters, bool createDB) {
IDatabaseBackend db = Database.Backend;
using (IDbConnection conn = db.CreateConnection()) {
conn.Open();
if (!createDB && MultipleSchema)
if (!createDB && db.MultipleSchema)
conn.ChangeDatabase(ServerConfig.MySQLDatabaseName);
using (IDbCommand cmd = CreateCommand(sql, conn)) {
FillParams(cmd);
using (IDbCommand cmd = db.CreateCommand(sql, conn)) {
FillParams(cmd, parameters);
cmd.ExecuteNonQuery();
}
conn.Close();
@ -47,14 +40,15 @@ namespace MCGalaxy.SQL {
}
/// <summary> Excecutes an SQL query, invoking a callback on the returned rows one by one. </summary>
public object Iterate(string sql, string connString, object arg, ReaderCallback callback) {
using (IDbConnection conn = CreateConnection(connString)) {
public static object Iterate(string sql, object[] parameters, object arg, ReaderCallback callback) {
IDatabaseBackend db = Database.Backend;
using (IDbConnection conn = db.CreateConnection()) {
conn.Open();
if (MultipleSchema)
if (db.MultipleSchema)
conn.ChangeDatabase(ServerConfig.MySQLDatabaseName);
using (IDbCommand cmd = CreateCommand(sql, conn)) {
FillParams(cmd);
using (IDbCommand cmd = db.CreateCommand(sql, conn)) {
FillParams(cmd, parameters);
using (IDataReader reader = cmd.ExecuteReader()) {
while (reader.Read()) { arg = callback(reader, arg); }
}
@ -65,16 +59,16 @@ namespace MCGalaxy.SQL {
}
void FillParams(IDbCommand cmd) {
object[] args = parameters;
if (args == null || args.Length == 0) return;
internal static void FillParams(IDbCommand cmd, object[] parameters) {
if (parameters == null || parameters.Length == 0) return;
IDatabaseBackend db = Database.Backend;
string[] names = GetNames(args.Length);
for (int i = 0; i < args.Length; i++) {
IDbDataParameter dbParam = CreateParameter();
dbParam.ParameterName = names[i];
dbParam.Value = args[i];
cmd.Parameters.Add(dbParam);
string[] names = GetNames(parameters.Length);
for (int i = 0; i < parameters.Length; i++) {
IDbDataParameter arg = db.CreateParameter();
arg.ParameterName = names[i];
arg.Value = parameters[i];
cmd.Parameters.Add(arg);
}
}

View File

@ -20,12 +20,18 @@ using System.Data;
namespace MCGalaxy.SQL {
public abstract class BulkTransaction : IDisposable {
protected IDbConnection connection;
protected IDbTransaction transaction;
public sealed class SqlTransaction : IDisposable {
internal IDbConnection conn;
internal IDbTransaction transaction;
public abstract IDbCommand CreateCommand(string sql);
public abstract IDataParameter CreateParam(string paramName, DbType type);
public SqlTransaction() {
IDatabaseBackend db = Database.Backend;
conn = db.CreateConnection();
conn.Open();
if (db.MultipleSchema) conn.ChangeDatabase(ServerConfig.MySQLDatabaseName);
transaction = conn.BeginTransaction();
}
public void Commit() {
try {
@ -34,7 +40,7 @@ namespace MCGalaxy.SQL {
Logger.LogError(ex);
Rollback();
} finally {
connection.Close();
conn.Close();
}
}
@ -50,14 +56,17 @@ namespace MCGalaxy.SQL {
public void Dispose() {
transaction.Dispose();
connection.Dispose();
conn.Dispose();
transaction = null;
connection = null;
conn = null;
}
public bool Execute(string sql) {
public bool Execute(string sql, params object[] args) {
IDatabaseBackend db = Database.Backend;
try {
using (IDbCommand cmd = CreateCommand(sql)) {
using (IDbCommand cmd = db.CreateCommand(sql, conn)) {
cmd.Transaction = transaction;
SqlQuery.FillParams(cmd, args);
cmd.ExecuteNonQuery();
return true;
}

View File

@ -408,7 +408,7 @@
<Compile Include="Database\ColumnDesc.cs" />
<Compile Include="Database\IDatabaseBackend.cs" />
<Compile Include="Database\Backends\MySQL.cs" />
<Compile Include="Database\ParameterisedQuery.cs" />
<Compile Include="Database\SqlQuery.cs" />
<Compile Include="Database\PlayerData.cs" />
<Compile Include="Database\Stats\OfflineStat.cs" />
<Compile Include="Database\Stats\OnlineStat.cs" />
@ -612,7 +612,7 @@
<Compile Include="Plugins\PluginManager.cs" />
<Compile Include="Scripting\Scripting.cs" />
<Compile Include="Player\Ban.cs" />
<Compile Include="Database\BulkTransaction.cs" />
<Compile Include="Database\SqlTransaction.cs" />
<Compile Include="Database\Database.cs" />
<Compile Include="Games\CTF\CtfGame.cs" />
<Compile Include="Levels\BlockQueue.cs" />

View File

@ -98,8 +98,7 @@ namespace MCGalaxy {
void SaveEntries(StreamWriter w) {
lock (locker) {
foreach (string l in lines)
w.WriteLine(l);
foreach (string line in lines) w.WriteLine(line);
}
}

View File

@ -31,7 +31,6 @@ namespace MCGalaxy {
readonly object saveLocker = new object();
public PlayerList() { }
public PlayerList(string path) { Path = path; }
/// <summary> Returns a copy of all names in the list. </summary>
public List<string> All() {
@ -82,22 +81,21 @@ namespace MCGalaxy {
void SaveEntries(StreamWriter w) {
lock (locker) {
foreach (string p in names)
w.WriteLine(p);
foreach (string p in names) w.WriteLine(p);
}
}
public static PlayerList Load(string file) {
PlayerList list = new PlayerList(file);
list.Path = file;
public static PlayerList Load(string path) {
PlayerList list = new PlayerList();
list.Path = path;
if (!File.Exists(list.Path)) {
File.Create(list.Path).Close();
Logger.Log(LogType.SystemActivity, "CREATED NEW: " + list.Path);
if (!File.Exists(path)) {
File.Create(path).Close();
Logger.Log(LogType.SystemActivity, "CREATED NEW: " + path);
return list;
}
using (StreamReader r = new StreamReader(list.Path, Encoding.UTF8)) {
using (StreamReader r = new StreamReader(path, Encoding.UTF8)) {
string line = null;
while ((line = r.ReadLine()) != null) {
list.names.Add(line);

View File

@ -78,11 +78,11 @@ namespace MCGalaxy {
}
static void ImportBulk(StreamReader reader) {
BulkTransaction helper = null;
SqlTransaction bulk = null;
List<string> buffer = new List<string>();
try {
helper = Database.Backend.CreateBulk();
bulk = new SqlTransaction();
while (!reader.EndOfStream) {
string cmd = NextStatement(reader, buffer);
if (cmd == null || cmd.Length == 0) continue;
@ -95,17 +95,17 @@ namespace MCGalaxy {
}
//Run the command in the transaction.
if (helper.Execute(cmd)) continue;
if (bulk.Execute(cmd, null)) continue;
// Something went wrong.. commit what we've imported so far.
// We need to recreate connection otherwise every helper.Execute fails
helper.Commit();
helper.Dispose();
helper = Database.Backend.CreateBulk();
bulk.Commit();
bulk.Dispose();
bulk = new SqlTransaction();
}
helper.Commit();
bulk.Commit();
} finally {
if (helper != null) helper.Dispose();
if (bulk != null) bulk.Dispose();
}
}

View File

@ -49,13 +49,12 @@ namespace MCGalaxy {
}
static void LoadPlayerLists(SchedulerTask task) {
agreed = new PlayerList("ranks/agreed.txt");
try {
UpgradeTasks.UpgradeOldAgreed();
agreed = PlayerList.Load("ranks/agreed.txt");
} catch (Exception ex) {
Logger.LogError("Error upgrading agreed list", ex);
}
agreed = PlayerList.Load("ranks/agreed.txt");
bannedIP = PlayerList.Load("ranks/banned-ip.txt");
ircControllers = PlayerList.Load("ranks/IRC_Controllers.txt");

View File

@ -178,7 +178,6 @@ namespace MCGalaxy.Tasks {
static List<long> playerSeconds;
static int playerCount, playerFailed = 0;
static void DumpPlayerTimeSpents() {
playerIds = new List<int>();
playerSeconds = new List<long>();
@ -200,19 +199,10 @@ namespace MCGalaxy.Tasks {
}
static void UpgradePlayerTimeSpents() {
using (BulkTransaction bulk = Database.Backend.CreateBulk()) {
IDataParameter idParam = bulk.CreateParam("@0", DbType.Int32);
IDataParameter secsParam = bulk.CreateParam("@1", DbType.Int64);
using (SqlTransaction bulk = new SqlTransaction()) {
for (int i = 0; i < playerIds.Count; i++) {
idParam.Value = playerIds[i];
secsParam.Value = playerSeconds[i];
using (IDbCommand cmd = bulk.CreateCommand("UPDATE Players SET TimeSpent=@1 WHERE ID=@0")) {
cmd.Parameters.Add(idParam);
cmd.Parameters.Add(secsParam);
cmd.ExecuteNonQuery();
}
bulk.Execute("UPDATE Players SET TimeSpent=@1 WHERE ID=@0",
playerIds[i], playerSeconds[i]);
}
bulk.Commit();
}