/* * Copyright (C) 2012-2017 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Collections; using MySql.Data.MySqlClient; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using System.Transactions; namespace Framework.Database { public class MySqlConnectionInfo { public MySqlConnectionInfo(string info, int poolSize = 10) { var lines = new StringArray(info, ';'); Host = lines[0]; Port = lines[1]; Username = lines[2]; Password = lines[3]; Database = lines[4]; Poolsize = poolSize; } public MySqlConnection GetConnection() { return new MySqlConnection(string.Format("Server={0};Port={1};User Id={2};Password={3};Database={4};Allow Zero Datetime=True;Allow User Variables=True;Pooling=true;MaximumPoolSize={5};", Host, Port, Username, Password, Database, Poolsize)); } public MySqlConnection GetConnectionNoDatabase() { return new MySqlConnection(string.Format("Server={0};Port={1};User Id={2};Password={3};Allow Zero Datetime=True;Allow User Variables=True;Pooling=true;MaximumPoolSize={4};", Host, Port, Username, Password, Poolsize)); } public string Host; public string Port; public string Username; public string Password; public string Database; public int Poolsize; } public abstract class MySqlBase { public MySqlErrorCode Initialize(string infoString) { _connectionInfo = new MySqlConnectionInfo(infoString); _updater = new DatabaseUpdater(this); using (var connection = _connectionInfo.GetConnection()) { try { connection.Open(); Log.outInfo(LogFilter.SqlDriver, "Connected to MySQL(ver: {0}) Database: {1}", connection.ServerVersion, _connectionInfo.Database); return MySqlErrorCode.None; } catch (MySqlException ex) { return (MySqlErrorCode)((MySqlException)ex.InnerException).Number; } } } public void Execute(string sql, params object[] args) { Execute(new PreparedStatement(string.Format(sql, args))); } public void Execute(PreparedStatement stmt) { using (var Connection = _connectionInfo.GetConnection()) { Connection.Open(); using (MySqlCommand cmd = Connection.CreateCommand()) { try { cmd.CommandText = stmt.CommandText; foreach (var parameter in stmt.Parameters) cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); cmd.ExecuteNonQuery(); } catch (MySqlException ex) { HandleMySQLException(ex, stmt.CommandText); } } } } public void ExecuteOrAppend(SQLTransaction trans, PreparedStatement stmt) { if (trans == null) Execute(stmt); else trans.Append(stmt); } public SQLResult Query(string sql, params object[] args) { return Query(new PreparedStatement(string.Format(sql, args))); } public SQLResult Query(PreparedStatement stmt) { List rows = new List(); using (var Connection = _connectionInfo.GetConnection()) { Connection.Open(); using (MySqlCommand cmd = Connection.CreateCommand()) { try { cmd.CommandText = stmt.CommandText; foreach (var parameter in stmt.Parameters) cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); using (var reader = cmd.ExecuteReader()) { if (reader.Read() && reader.HasRows) { do { var row = new object[reader.FieldCount]; reader.GetValues(row); rows.Add(row); } while (reader.Read()); } } } catch (MySqlException ex) { HandleMySQLException(ex, stmt.CommandText); } } } return new SQLResult(rows); } public QueryCallback AsyncQuery(string sql, params object[] args) { return AsyncQuery(new PreparedStatement(string.Format(sql, args))); } public QueryCallback AsyncQuery(PreparedStatement stmt) { return new QueryCallback(_AsyncQuery(stmt)); } async Task _AsyncQuery(PreparedStatement stmt) { List rows = new List(); try { using (var Connection = _connectionInfo.GetConnection()) { Connection.Open(); using (MySqlCommand cmd = Connection.CreateCommand()) { cmd.CommandText = stmt.CommandText; foreach (var parameter in stmt.Parameters) cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); using (var reader = await cmd.ExecuteReaderAsync()) { if (reader.Read() && reader.HasRows) { do { var row = new object[reader.FieldCount]; reader.GetValues(row); rows.Add(row); } while (reader.Read()); } } } } } catch (MySqlException ex) { HandleMySQLException(ex, stmt.CommandText); } return new SQLResult(rows); } public async Task> DelayQueryHolder(SQLQueryHolder holder) { return await Task.Run(async () => { string query = ""; try { using (var Connection = _connectionInfo.GetConnection()) { Connection.Open(); foreach (var pair in holder.m_queries) { List rows = new List(); using (MySqlCommand cmd = Connection.CreateCommand()) { cmd.CommandText = pair.Value.stmt.CommandText; foreach (var parameter in pair.Value.stmt.Parameters) cmd.Parameters.AddWithValue("@" + parameter.Key, parameter.Value); query = cmd.CommandText; using (var reader = await cmd.ExecuteReaderAsync()) { if (reader.Read() && reader.HasRows) { do { var row = new object[reader.FieldCount]; reader.GetValues(row); rows.Add(row); } while (reader.Read()); } } } holder.SetResult(pair.Key, new SQLResult(rows)); } } return holder; } catch (MySqlException ex) { HandleMySQLException(ex, query); return holder; } }); } public void LoadPreparedStatements() { PreparedStatements(); } public void PrepareStatement(T statement, string sql) { StringBuilder sb = new StringBuilder(); int index = 0; for (var i = 0; i < sql.Length; i++) { if (sql[i].Equals('?')) sb.Append("@" + index++); else sb.Append(sql[i]); } _queries[statement] = sb.ToString(); } public PreparedStatement GetPreparedStatement(T statement) { return new PreparedStatement(_queries[statement]); } public bool Apply(string sql) { using (var Connection = _connectionInfo.GetConnectionNoDatabase()) { using (MySqlCommand cmd = Connection.CreateCommand()) { try { Connection.Open(); cmd.CommandText = sql; return cmd.ExecuteNonQuery() > 0; } catch (MySqlException ex) { HandleMySQLException(ex, sql); return false; } } } } public bool ApplyFile(string path) { using (var connection = _connectionInfo.GetConnection()) { using (MySqlCommand cmd = connection.CreateCommand()) { try { connection.Open(); cmd.CommandText = File.ReadAllText(path); return cmd.ExecuteNonQuery() > 0; } catch (MySqlException ex) { HandleMySQLException(ex, path); return false; } } } } public void EscapeString(ref string str) { str = MySqlHelper.EscapeString(str); } public void CommitTransaction(SQLTransaction transaction) { using (var Connection = _connectionInfo.GetConnection()) { string query = ""; Connection.Open(); using (MySqlTransaction trans = Connection.BeginTransaction()) { try { using (var scope = new TransactionScope()) { foreach (var cmd in transaction.commands) { cmd.Transaction = trans; cmd.Connection = Connection; cmd.ExecuteNonQuery(); query = cmd.CommandText; } trans.Commit(); scope.Complete(); } } catch (MySqlException ex) //error occurred { HandleMySQLException(ex, query); trans.Rollback(); } } } } void HandleMySQLException(MySqlException ex, string query) { int code = ex.Number; if (ex.InnerException != null) code = ((MySqlException)ex.InnerException).Number; switch ((MySqlErrorCode)code) { case MySqlErrorCode.BadFieldError: case MySqlErrorCode.NoSuchTable: Log.outError(LogFilter.Sql, "Your database structure is not up to date. Please make sure you've executed all queries in the sql/updates folders."); break; case MySqlErrorCode.ParseError: Log.outError(LogFilter.Sql, "Error while parsing SQL. Core fix required."); break; } Log.outError(LogFilter.Sql, "SqlException: {0} SqlQuery: {1}", ex.Message, query); } public DatabaseUpdater GetUpdater() { return _updater; } public bool IsAutoUpdateEnabled(DatabaseTypeFlags updateMask) { switch (GetType().Name) { case "LoginDatabase": return updateMask.HasAnyFlag(DatabaseTypeFlags.Login); case "CharacterDatabase": return updateMask.HasAnyFlag(DatabaseTypeFlags.Character); case "WorldDatabase": return updateMask.HasAnyFlag(DatabaseTypeFlags.World); case "HotfixDatabase": return updateMask.HasAnyFlag(DatabaseTypeFlags.Hotfix); } return false; } public string GetDatabaseName() { return _connectionInfo.Database; } public abstract void PreparedStatements(); Dictionary _queries = new Dictionary(); MySqlConnectionInfo _connectionInfo; DatabaseUpdater _updater; } }