using System; using System.Collections.Generic; using System.Data; using System.Text; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Configuration; using Microsoft.VisualBasic; using NLog; namespace ChaosBot.Database { public static class Controller { static SqliteConnection _conn = new SqliteConnection($"Data Source={System.IO.Directory.GetCurrentDirectory()}/{Program.Cfg.GetValue("Bot:Database")}"); private static Logger _logger = Program._logger; /// /// Run a raw query on the database /// /// /// string query = "TRUNCATE SomeTable"; /// Controller.RawQuery(query); /// /// Raw query to execute /// Whether to read the output and return it as a DataFrame /// Whether to throw any exceptions or to log and shrug. /// public static DataTable RawQuery(string query, bool readOutput = true, bool throwError = false) { DataTable dt = new DataTable(); try { using (_conn) { // Open the SQLite connection _conn.Open(); // Start creating the command and assign the query given SqliteCommand cmd = _conn.CreateCommand(); cmd.CommandText = query; if (readOutput) { // output needs to be read, make a reader and fill the datatable with the data SqliteDataReader executeReader = cmd.ExecuteReader(CommandBehavior.SingleResult); dt.Load(executeReader); } else { // output does not need to be read, just run the query without reading cmd.ExecuteNonQuery(); } // Clean up after ourselves _conn.Close(); } } catch (Exception ex) { if (throwError) { // If we throw any exception, just throw it so other code can handle it throw; } // We handle the exception, log it and be done _logger.Fatal($"Database.Controller.RawQuery: Exception [{ex}] thrown, <[{ex.Message}]>."); } return dt; } /// /// Insert a list of parameters into a table in the database /// /// /// string table = "SomeTable"; /// Dictionary<string, object> parameters = new Dictionary<string, object> /// { /// { "someInt", 123 }, /// { "someString", "asdf" }, /// { "someChar", 'x' } /// } /// /// Controller.InsertQuery(table, parameters); /// /// Table to insert into /// List of parameters to insert to the row. Every type is a key public static void InsertQuery(string table, Dictionary parameters) { try { using (_conn) { // Open the SQLite connection _conn.Open(); // Start the command creation SqliteCommand cmd = _conn.CreateCommand(); // Build the command bit by bit // INSERT INTO {table} (key1, key2, key3) VALUES (@key1,@key2,@key3) StringBuilder commandText = new StringBuilder(); commandText.Append("INSERT INTO "); commandText.Append(table); commandText.Append(" ("); foreach (string key in parameters.Keys) { commandText.Append($"{key}, "); } commandText.Remove(commandText.Length - 2, 2); commandText.Append(") VALUES ("); foreach (string key in parameters.Keys) { // We use @key to indicate the parameter placeholder commandText.Append($"@{key},"); } // Remove the trailing comma commandText.Remove(commandText.Length - 1, 1); commandText.Append(")"); // Assign onto QueryCommand cmd.CommandText = commandText.ToString(); // Replace all the parameters to the query foreach (string key in parameters.Keys) { cmd.Parameters.AddWithValue($"@{key}", parameters.GetValueOrDefault(key)); } // Prepare the parameters cmd.Prepare(); // Execute, do not return anything cmd.ExecuteNonQuery(); _conn.Close(); } } catch (Exception ex) { _logger.Fatal($"Database.Controller.InsertQuery: Exception [{ex}] thrown, <[{ex.Message}]>."); } } /// /// Update rows in the database /// /// /// string table = "SomeTable"; /// Dictionary<string, object> values = new Dictionary<string, object> /// { /// {"key", "newValue"} /// }; /// Dictionary<string, object> parameters = new Dictionary<string, object> /// { /// {"key", "oldValue"} /// }; /// Controller.UpdateQuery(table, values, parameters); /// /// Table to update data in /// List of keys to set. Equivalent of SQL: SET {key} = {value} /// List of filters to apply in the SQL WHERE condition public static void UpdateQuery(string table, Dictionary values, Dictionary parameters) { try { using (_conn) { // Open SQLite connection _conn.Open(); // Create the command SqliteCommand cmd = _conn.CreateCommand(); // Build the commandtext // UPDATE {table} SET {key}=@val_{key} WHERE {key}=@fil_{key} StringBuilder commandText = new StringBuilder(); commandText.Append("UPDATE "); commandText.Append(table); commandText.Append(" SET "); // Build the list of values List updateList = new List(); foreach (string key in values.Keys) { updateList.Add($"{key}=@val_{key} "); cmd.Parameters.AddWithValue($@"val_{key}", values.GetValueOrDefault(key)); } // Append the list of values, comma-separated commandText.Append(string.Join(", ", updateList)); // Build the list of filters List filterList = new List(); foreach (string key in parameters.Keys) { filterList.Add($"{key}=@fil_{key} "); cmd.Parameters.AddWithValue($"@fil_{key}", parameters.GetValueOrDefault(key)); } if (filterList.Count > 0) { // Prepend with WHERE if there are parameters commandText.Append("WHERE "); // Append the list of filters AND separated commandText.Append(string.Join("AND ", filterList)); } // Assign the command cmd.CommandText = commandText.ToString(); // Prepare the parameters cmd.Prepare(); // Execute, do not return cmd.ExecuteNonQuery(); // Close connection _conn.Close(); } } catch (Exception ex) { _logger.Fatal($"Database.Controller.UpdateQuery: Exception [{ex}] thrown, <[{ex.Message}]>."); } } public static int TransactionQuery(List cmds) { SqliteCommand command = _conn.CreateCommand(); SqliteTransaction transaction; _conn.Open(); // Start a local transaction. transaction = _conn.BeginTransaction(); // Must assign both transaction object and connection // to Command object for a pending local transaction command.Connection = _conn; command.Transaction = transaction; try { foreach (var cmd in cmds) { command.CommandText = cmd.CommandText; command.ExecuteNonQuery(); } // Attempt to commit the transaction. transaction.Commit(); _logger.Info($"{cmds.Count} record(s) are written to database."); } catch (Exception ex) { _logger.Warn("Commit Exception Type: {0}", ex.GetType()); _logger.Warn(" Message: {0}", ex.Message); // Attempt to roll back the transaction. try { transaction.Rollback(); } catch (Exception ex2) { // This catch block will handle any errors that may have occurred // on the server that would cause the rollback to fail, such as // a closed connection. _logger.Warn("Rollback Exception Type: {0}", ex2.GetType()); _logger.Warn(" Message: {0}", ex2.Message); } return 0; } _conn.Close(); return cmds.Count; } public static DataTable SelectQuery(string table, string selectColumns = "*", Dictionary filterColumns = null, string orderByKey = null) { DataTable dt = new DataTable(); try { using (_conn) { _conn.Open(); SqliteCommand cmd = _conn.CreateCommand(); string filter = null; if (filterColumns != null) { List filterList = new List(); foreach (string key in filterColumns.Keys) { filterList.Add($"{key} = @{key}"); } filter = $"WHERE {Strings.Join(filterList.ToArray(), " AND ")}"; foreach (string key in filterColumns.Keys) { cmd.Parameters.AddWithValue($@"{key}", filterColumns.GetValueOrDefault(key)); } } string order = null; if (orderByKey != null) { order = $"ORDER BY {orderByKey}"; } string query = $"SELECT {selectColumns} FROM {table} {filter} {order}"; cmd.CommandText = query; cmd.Prepare(); SqliteDataReader executeReader = cmd.ExecuteReader(); dt.Load(executeReader); _conn.Close(); } } catch (Exception ex) { _logger.Fatal($"Database.Controller.SelectQuery: Exception [{ex}] thrown, <[{ex.Message}]>."); } return dt; } public static void DeleteQuery(string table, Dictionary filterColumns = null, string orderByKey = null, int limit = -1, int offset = -1) { try { using (_conn) { _conn.Open(); SqliteCommand cmd = _conn.CreateCommand(); string filter = null; if (filterColumns != null) { List filterList = new List(); foreach (string key in filterColumns.Keys) { filterList.Add($"{key} = @{key}"); } filter = $"WHERE {Strings.Join(filterList.ToArray(), " AND ")}"; foreach (string key in filterColumns.Keys) { cmd.Parameters.AddWithValue($@"{key}", filterColumns.GetValueOrDefault(key)); } } string order = null; if (orderByKey != null) { order = $"ORDER BY {orderByKey}"; } string limitString = limit > 0 ? $"LIMIT {limit}" : null; string offsetString = offset > 0 ? $"OFFSET {offset}" : null; string query = $"DELETE FROM {table} {filter} {order} {limitString} {offsetString}"; cmd.CommandText = query; cmd.Prepare(); cmd.ExecuteNonQuery(); _conn.Close(); } } catch (Exception ex) { _logger.Fatal($"Database.Controller.DeleteQuery: Exception [{ex}] thrown, <[{ex.Message}]>."); } } } }