using System; using System.Collections.Generic; using System.Data; using Microsoft.Data.Sqlite; using System.IO; using System.Threading.Tasks; namespace DiscordBotCore.Database; public class SqlDatabase { private readonly SqliteConnection _Connection; /// /// Initialize a SQL connection by specifying its private path /// /// The path to the database (it is starting from ./Data/Resources/) public SqlDatabase(string fileName) { var connectionString = $"Data Source={fileName}"; _Connection = new SqliteConnection(connectionString); } /// /// Open the SQL Connection. To close use the Stop() method /// /// public async Task Open() { await _Connection.OpenAsync(); } /// /// /// Insert into a specified table some values /// /// /// The table name /// The values to be inserted (in the correct order and number) /// public async Task InsertAsync(string tableName, params string[] values) { var query = $"INSERT INTO {tableName} VALUES ("; for (var i = 0; i < values.Length; i++) { query += $"'{values[i]}'"; if (i != values.Length - 1) query += ", "; } query += ")"; var command = new SqliteCommand(query, _Connection); await command.ExecuteNonQueryAsync(); } /// /// /// Insert into a specified table some values /// /// /// The table name /// The values to be inserted (in the correct order and number) /// public void Insert(string tableName, params string[] values) { var query = $"INSERT INTO {tableName} VALUES ("; for (var i = 0; i < values.Length; i++) { query += $"'{values[i]}'"; if (i != values.Length - 1) query += ", "; } query += ")"; var command = new SqliteCommand(query, _Connection); command.ExecuteNonQuery(); } /// /// Remove every row in a table that has a certain propery /// /// The table name /// The column name that the search is made by /// The value that is searched in the specified column /// public async Task RemoveKeyAsync(string tableName, string KeyName, string KeyValue) { var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'"; var command = new SqliteCommand(query, _Connection); await command.ExecuteNonQueryAsync(); } /// /// Remove every row in a table that has a certain propery /// /// The table name /// The column name that the search is made by /// The value that is searched in the specified column /// public void RemoveKey(string tableName, string KeyName, string KeyValue) { var query = $"DELETE FROM {tableName} WHERE {KeyName} = '{KeyValue}'"; var command = new SqliteCommand(query, _Connection); command.ExecuteNonQuery(); } /// /// Check if the key exists in the table /// /// The table name /// The column that the search is made by /// The value that is searched in the specified column /// public async Task KeyExistsAsync(string tableName, string keyName, string KeyValue) { var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'"; if (await ReadDataAsync(query) is not null) return true; return false; } /// /// Check if the key exists in the table /// /// The table name /// The column that the search is made by /// The value that is searched in the specified column /// public bool KeyExists(string tableName, string keyName, string KeyValue) { var query = $"SELECT * FROM {tableName} where {keyName} = '{KeyValue}'"; if (ReadData(query) is not null) return true; return false; } /// /// Set value of a column in a table /// /// The table name /// The column that the search is made by /// The value that is searched in the column specified /// The column that has to be modified /// The new value that will replace the old value from the column specified public async Task SetValueAsync( string tableName, string keyName, string KeyValue, string ResultColumnName, string ResultColumnValue) { if (!await TableExistsAsync(tableName)) throw new Exception($"Table {tableName} does not exist"); await ExecuteAsync( $"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'" ); } /// /// Set value of a column in a table /// /// The table name /// The column that the search is made by /// The value that is searched in the column specified /// The column that has to be modified /// The new value that will replace the old value from the column specified public void SetValue( string tableName, string keyName, string KeyValue, string ResultColumnName, string ResultColumnValue) { if (!TableExists(tableName)) throw new Exception($"Table {tableName} does not exist"); Execute($"UPDATE {tableName} SET {ResultColumnName}='{ResultColumnValue}' WHERE {keyName}='{KeyValue}'"); } /// /// Get value from a column in a table /// /// The table name /// The column that the search is made by /// The value that is searched in the specified column /// The column that has the result /// A string that has the requested value (can be null if nothing found) public async Task GetValueAsync( string tableName, string keyName, string KeyValue, string ResultColumnName) { if (!await TableExistsAsync(tableName)) throw new Exception($"Table {tableName} does not exist"); return await ReadDataAsync($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'"); } /// /// Get value from a column in a table /// /// The table name /// The column that the search is made by /// The value that is searched in the specified column /// The column that has the result /// A string that has the requested value (can be null if nothing found) public string? GetValue(string tableName, string keyName, string KeyValue, string ResultColumnName) { if (!TableExists(tableName)) throw new Exception($"Table {tableName} does not exist"); return ReadData($"SELECT {ResultColumnName} FROM {tableName} WHERE {keyName}='{KeyValue}'"); } /// /// Stop the connection to the SQL Database /// /// public async void Stop() { await _Connection.CloseAsync(); } /// /// Change the structure of a table by adding new columns /// /// The table name /// The columns to be added /// The type of the columns (TEXT, INTEGER, FLOAT, etc) /// public async Task AddColumnsToTableAsync(string tableName, string[] columns, string TYPE = "TEXT") { var command = _Connection.CreateCommand(); command.CommandText = $"SELECT * FROM {tableName}"; var reader = await command.ExecuteReaderAsync(); var tableColumns = new List(); for (var i = 0; i < reader.FieldCount; i++) tableColumns.Add(reader.GetName(i)); foreach (var column in columns) if (!tableColumns.Contains(column)) { command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}"; await command.ExecuteNonQueryAsync(); } } /// /// Change the structure of a table by adding new columns /// /// The table name /// The columns to be added /// The type of the columns (TEXT, INTEGER, FLOAT, etc) /// public void AddColumnsToTable(string tableName, string[] columns, string TYPE = "TEXT") { var command = _Connection.CreateCommand(); command.CommandText = $"SELECT * FROM {tableName}"; var reader = command.ExecuteReader(); var tableColumns = new List(); for (var i = 0; i < reader.FieldCount; i++) tableColumns.Add(reader.GetName(i)); foreach (var column in columns) if (!tableColumns.Contains(column)) { command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {column} {TYPE}"; command.ExecuteNonQuery(); } } /// /// Check if a table exists /// /// The table name /// True if the table exists, false if not public async Task TableExistsAsync(string tableName) { var cmd = _Connection.CreateCommand(); cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'"; var result = await cmd.ExecuteScalarAsync(); if (result == null) return false; return true; } /// /// Check if a table exists /// /// The table name /// True if the table exists, false if not public bool TableExists(string tableName) { var cmd = _Connection.CreateCommand(); cmd.CommandText = $"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}'"; var result = cmd.ExecuteScalar(); if (result == null) return false; return true; } /// /// Create a table /// /// The table name /// The columns of the table /// public async Task CreateTableAsync(string tableName, params string[] columns) { var cmd = _Connection.CreateCommand(); cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})"; await cmd.ExecuteNonQueryAsync(); } /// /// Create a table /// /// The table name /// The columns of the table /// public void CreateTable(string tableName, params string[] columns) { var cmd = _Connection.CreateCommand(); cmd.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({string.Join(", ", columns)})"; cmd.ExecuteNonQuery(); } /// /// Execute a custom query /// /// The query /// The number of rows that the query modified public async Task ExecuteAsync(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); var answer = await command.ExecuteNonQueryAsync(); return answer; } /// /// Execute a custom query /// /// The query /// The number of rows that the query modified public int Execute(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) _Connection.Open(); var command = new SqliteCommand(query, _Connection); var r = command.ExecuteNonQuery(); return r; } /// /// Read data from the result table and return the first row /// /// The query /// The result is a string that has all values separated by space character public async Task ReadDataAsync(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); var reader = await command.ExecuteReaderAsync(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return string.Join(" ", values); } return null; } /// /// Read data from the result table and return the first row /// /// The query /// The parameters of the query /// The result is a string that has all values separated by space character public async Task ReadDataAsync(string query, params KeyValuePair[] parameters) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); foreach (var parameter in parameters) { var p = CreateParameter(parameter); if (p is not null) command.Parameters.Add(p); } var reader = await command.ExecuteReaderAsync(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return string.Join(" ", values); } return null; } /// /// Read data from the result table and return the first row /// /// The query /// The result is a string that has all values separated by space character public string? ReadData(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) _Connection.Open(); var command = new SqliteCommand(query, _Connection); var reader = command.ExecuteReader(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return string.Join(" ", values); } return null; } /// /// Read data from the result table and return the first row /// /// The query /// The first row as separated items public async Task ReadDataArrayAsync(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); var reader = await command.ExecuteReaderAsync(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return values; } return null; } public async Task ReadDataArrayAsync(string query, params KeyValuePair[] parameters) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); foreach (var parameter in parameters) { var p = CreateParameter(parameter); if (p is not null) command.Parameters.Add(p); } var reader = await command.ExecuteReaderAsync(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return values; } return null; } /// /// Read data from the result table and return the first row /// /// The query /// The first row as separated items public object[]? ReadDataArray(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) _Connection.Open(); var command = new SqliteCommand(query, _Connection); var reader = command.ExecuteReader(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return values; } return null; } /// /// Read all rows from the result table and return them as a list of string arrays. The string arrays contain the /// values of each row /// /// The query /// A list of string arrays representing the values that the query returns public async Task?> ReadAllRowsAsync(string query) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); var reader = await command.ExecuteReaderAsync(); if (!reader.HasRows) return null; List rows = new(); while (await reader.ReadAsync()) { var values = new string[reader.FieldCount]; reader.GetValues(values); rows.Add(values); } if (rows.Count == 0) return null; return rows; } /// /// Create a parameter for a query /// /// The name of the parameter /// The value of the parameter /// The SQLiteParameter that has the name, value and DBType set according to your inputs private static SqliteParameter? CreateParameter(string name, object value) { var parameter = new SqliteParameter(); parameter.ParameterName = name; parameter.Value = value; if (value is string) parameter.DbType = DbType.String; else if (value is int) parameter.DbType = DbType.Int32; else if (value is long) parameter.DbType = DbType.Int64; else if (value is float) parameter.DbType = DbType.Single; else if (value is double) parameter.DbType = DbType.Double; else if (value is bool) parameter.DbType = DbType.Boolean; else if (value is DateTime) parameter.DbType = DbType.DateTime; else if (value is byte[]) parameter.DbType = DbType.Binary; else if (value is Guid) parameter.DbType = DbType.Guid; else if (value is decimal) parameter.DbType = DbType.Decimal; else if (value is TimeSpan) parameter.DbType = DbType.Time; else if (value is DateTimeOffset) parameter.DbType = DbType.DateTimeOffset; else if (value is ushort) parameter.DbType = DbType.UInt16; else if (value is uint) parameter.DbType = DbType.UInt32; else if (value is ulong) parameter.DbType = DbType.UInt64; else if (value is sbyte) parameter.DbType = DbType.SByte; else if (value is short) parameter.DbType = DbType.Int16; else if (value is byte) parameter.DbType = DbType.Byte; else if (value is char) parameter.DbType = DbType.StringFixedLength; else if (value is char[]) parameter.DbType = DbType.StringFixedLength; else return null; return parameter; } /// /// Create a parameter for a query. The function automatically detects the type of the value. /// /// The parameter raw inputs. The Key is name and the Value is the value of the parameter /// The SQLiteParameter that has the name, value and DBType set according to your inputs private static SqliteParameter? CreateParameter(KeyValuePair parameterValues) { return CreateParameter(parameterValues.Key, parameterValues.Value); } /// /// Execute a query with parameters /// /// The query to execute /// The parameters of the query /// The number of rows that the query modified in the database public async Task ExecuteNonQueryAsync(string query, params KeyValuePair[] parameters) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); foreach (var parameter in parameters) { var p = CreateParameter(parameter); if (p is not null) command.Parameters.Add(p); } return await command.ExecuteNonQueryAsync(); } /// /// Execute a query with parameters that returns a specific type of object. The function will return the first row of the result transformed into the specified type. /// /// The query to execute /// The convertor function that will convert each row of the response into an object of /// The parameters of the query /// The return object type /// An object of type T that represents the output of the convertor function based on the array of objects that the first row of the result has public async Task ReadObjectOfTypeAsync(string query, Func convertor, params KeyValuePair[] parameters) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); foreach (var parameter in parameters) { var p = CreateParameter(parameter); if (p is not null) command.Parameters.Add(p); } var reader = await command.ExecuteReaderAsync(); var values = new object[reader.FieldCount]; if (reader.Read()) { reader.GetValues(values); return convertor(values); } return default; } /// /// Execute a query with parameters that returns a specific type of object. The function will return a list of objects of the specified type. /// /// The query to execute /// The convertor from object[] to T /// The parameters of the query /// The expected object type /// A list of objects of type T that represents each line of the output of the specified query, converted to T public async Task> ReadListOfTypeAsync(string query, Func convertor, params KeyValuePair[] parameters) { if (!_Connection.State.HasFlag(ConnectionState.Open)) await _Connection.OpenAsync(); var command = new SqliteCommand(query, _Connection); foreach (var parameter in parameters) { var p = CreateParameter(parameter); if (p is not null) command.Parameters.Add(p); } var reader = await command.ExecuteReaderAsync(); // if (!reader.HasRows) return null; List rows = new(); while (await reader.ReadAsync()) { var values = new object[reader.FieldCount]; reader.GetValues(values); rows.Add(convertor(values)); } return rows; } }