From de8884aaf4eb3dd342ade7dcc876e035e48afe82 Mon Sep 17 00:00:00 2001 From: JacobTech Date: Wed, 28 Jun 2023 12:44:59 -0400 Subject: [PATCH] Init --- .gitignore | 15 ++ Database.sln | 25 ++ Database/Database.cs | 129 +++++++++ Database/Ext.cs | 30 +++ Database/ITable.cs | 12 + Database/Order.cs | 11 + Database/ServerDatabase.csproj | 34 +++ Database/Table.cs | 463 +++++++++++++++++++++++++++++++++ Database/TableColumn.cs | 299 +++++++++++++++++++++ Database/TableVersions.cs | 15 ++ 10 files changed, 1033 insertions(+) create mode 100755 Database.sln create mode 100755 Database/Database.cs create mode 100755 Database/Ext.cs create mode 100644 Database/ITable.cs create mode 100755 Database/Order.cs create mode 100755 Database/ServerDatabase.csproj create mode 100644 Database/Table.cs create mode 100644 Database/TableColumn.cs create mode 100644 Database/TableVersions.cs diff --git a/.gitignore b/.gitignore index 57940fd..d4375a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf +.idea/**/.idea/** +.idea/**/.idea/ # AWS User-specific .idea/**/aws.xml @@ -77,3 +79,16 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ diff --git a/Database.sln b/Database.sln new file mode 100755 index 0000000..dcf6e43 --- /dev/null +++ b/Database.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32427.441 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServerDatabase", "Database\ServerDatabase.csproj", "{108C054C-5FA5-4D16-9F48-B6836B1734EB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {108C054C-5FA5-4D16-9F48-B6836B1734EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {108C054C-5FA5-4D16-9F48-B6836B1734EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {108C054C-5FA5-4D16-9F48-B6836B1734EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {108C054C-5FA5-4D16-9F48-B6836B1734EB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D50765B5-8753-440E-A312-24DF78B78E73} + EndGlobalSection +EndGlobal diff --git a/Database/Database.cs b/Database/Database.cs new file mode 100755 index 0000000..6feb755 --- /dev/null +++ b/Database/Database.cs @@ -0,0 +1,129 @@ +using Npgsql; +using NpgsqlTypes; +using System.Data; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using ServerDatabase.Utils; + +namespace ServerDatabase; + +public sealed class Database +{ + internal string ConectionString + { + get + { + return $"SSL Mode=Disable;Persist Security Info=True;Password={PW};Username={Uname};CommandTimeout={CMD};Database={DB};Host={IP}{CName}"; + } + } + + public List Tables { get; } = new() + { + }; + + internal string? IP = null, DB = null, Uname = null, PW = null, CName = ""; + internal int CMD { get; set; } = 20; + + public Database(string IpAddress, string Database, string Username, string Password, string? CustomeName = null, int CommandTimeout = 20) + { + if (!string.IsNullOrWhiteSpace(IpAddress)) IP = IpAddress; + if (!string.IsNullOrWhiteSpace(Database)) DB = Database; + if (!string.IsNullOrWhiteSpace(Username)) Uname = Username; + if (!string.IsNullOrWhiteSpace(Password)) PW = Password; + if (!string.IsNullOrWhiteSpace(CustomeName)) CName = $";ApplicationName={CustomeName}"; + else CName = ""; + VersionsTable = new TableVersions(this); + CMD = CommandTimeout; + } + + public void RegisterTables() + { + foreach (ITable Table in Tables) + { + foreach (ITableColumn Column in Table.Colums) + { + VersionsTable.WithColumn(new TableColumn($"{Table.Name}_{Column.Name}") + { + DefaultValue = Column.ColumnVersion + }, string.Empty); + } + } + VersionsTable.UpdateTable(); + foreach (ITable Table in Tables) + { + Table.UpdateTable(); + } + } + + public TableVersions VersionsTable { get; } = null!; + + public void CreateTable(string table, params ITableColumn[] Parameters) + { + if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string command = $"CREATE TABLE public.{table}("; + string key = ""; + foreach (ITableColumn column in Parameters) + { + command += $"{column.Name} {column.GetDatabaseTypeStr()}"; + if (column.IsPrimaryKey) key = $"PRIMARY KEY ({column.Name})"; + command += " NOT NULL, "; + } + if (key == "") command = command.Remove(command.Length - 2, 2) + ");"; + command += $"{key});"; + cmd.CommandText = command; + cmd.Prepare(); + cmd.ExecuteNonQuery(); + con.Close(); + } + + public NpgsqlConnection CreateConnection() + { + if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined"); + return new NpgsqlConnection(ConectionString); + } + + public void ExecuteNonQuery(string command) + { + if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection connection = new(ConectionString); + connection.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = connection; + cmd.CommandText = command; + cmd.ExecuteNonQuery(); + connection.Close(); + } + + public T Read(string command, Order? order = null, params Parameter[] Parameters) + { + if (string.IsNullOrEmpty(DB) || string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(Uname) || string.IsNullOrEmpty(PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND "; + cmd.Parameters.Add(param.PGParameter); + } + if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); + if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5); + if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; + if (order is not null) command += $" ORDER BY {order.Type} "; + if (order is not null && order.Assending) command += "ASC"; + else { if (order is not null) command += "DESC"; } + command += ";"; + cmd.CommandText = command; + object? temp = cmd.ExecuteScalar(); + con.Close(); + if (temp is DBNull || temp is null) return default!; + if (typeof(T).IsEnum) return (T?)Enum.Parse(typeof(T), temp.ToString()!)!; + if (typeof(T).IsNullableEnum()) return (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, temp.ToString()!)!; + return (T?)temp!; + } +} \ No newline at end of file diff --git a/Database/Ext.cs b/Database/Ext.cs new file mode 100755 index 0000000..bdebf48 --- /dev/null +++ b/Database/Ext.cs @@ -0,0 +1,30 @@ +using System.Collections.Concurrent; + +namespace ServerDatabase; + +internal static class Ext +{ + internal static bool IsNullableEnum(this Type t) + { + Type u = Nullable.GetUnderlyingType(t)!; + return (u != null) && u.IsEnum; + } + + public static OutputType[] Cast(InputType f) + { + Array inputArray_in = (Array)(object)f; + var aRange = Partitioner.Create(0, inputArray_in.Length); + OutputType[] aResult = new OutputType[inputArray_in.Length]; + + Parallel.ForEach(aRange, (r) => + { + for (int i = r.Item1; i < r.Item2; i++) + { + aResult[i] = (OutputType)(inputArray_in.GetValue(i)); + } + }); + + return aResult; + } + +} diff --git a/Database/ITable.cs b/Database/ITable.cs new file mode 100644 index 0000000..e4441a9 --- /dev/null +++ b/Database/ITable.cs @@ -0,0 +1,12 @@ +using ServerDatabase.Utils; + +namespace ServerDatabase; + +public interface ITable +{ + public void UpdateTable(); + public IReadOnlyList Colums { get; } + public string Name { get; set; } + public Database DatabaseHandler { get; set; } + public void AddColumn(ITableColumn Column, string name); +} \ No newline at end of file diff --git a/Database/Order.cs b/Database/Order.cs new file mode 100755 index 0000000..2e5ad17 --- /dev/null +++ b/Database/Order.cs @@ -0,0 +1,11 @@ +namespace ServerDatabase; + +public class Order +{ + internal Order() + { + + } + public bool Assending { get; init; } = default!; + public string Type { get; init; } = default!; +} diff --git a/Database/ServerDatabase.csproj b/Database/ServerDatabase.csproj new file mode 100755 index 0000000..12c26b3 --- /dev/null +++ b/Database/ServerDatabase.csproj @@ -0,0 +1,34 @@ + + + + enable + 1.0.0.0 + JacobTech + JacobTech, LLC + A Database class to handle database interactions + False + snupkg + Postgresql;sql + Server Database + 2.8.1 + 10 + enable + net6.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Database/Table.cs b/Database/Table.cs new file mode 100644 index 0000000..dea5766 --- /dev/null +++ b/Database/Table.cs @@ -0,0 +1,463 @@ +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Npgsql; +using ServerDatabase.Utils; + +namespace ServerDatabase; + +public class Table : ITable where TRow : class, new() +{ + public Table(string Name, Database DB) + { + if (Name == "table_versions") throw new ArgumentException("The table name 'table_versions' is not allowed", nameof(Name)); + this.Name = Name; + DatabaseHandler = DB; + } + + internal Table(Database DB) + { + this.Name = "table_versions"; + DatabaseHandler = DB; + } + + public Table WithColumn(ITableColumn Column, string name) + { + Colums_.Add(Column); + colnamesraw.Add(Column.Name, name); + return this; + } + + public void AddColumn(ITableColumn Column, string name) + { + colnamesraw.Add(Column.Name, name); + Colums_.Add(Column); + } + + + + + public IReadOnlyList GetMissingColumns(out IReadOnlyList UpdatedColumns) + { + List coll = new(Colums_); + List col = new(Colums_); + foreach (DataRow row in DatabaseHandler.CreateConnection().GetSchema("Columns", new[] { DatabaseHandler.DB, null, Name }).Rows) + { + string name = row.Field("COLUMN_NAME")!, + ty = row.Field("DATA_TYPE")!; + IEnumerable t = Colums_.Where(s => s.Name == name); + if (t.Any()) + { + col.Remove(t.First()); + if (t.First()!.GetDatabaseTypeStr() == ty) coll.Remove(t.First()); + } + } + + UpdatedColumns = coll; + return col.AsReadOnly(); + } + + public void UpdateTable() + { + Console.WriteLine("Updating Table: {0}", Name); + IReadOnlyList m = GetMissingColumns(out IReadOnlyList n); + if (m.Any()) + { + if (DatabaseHandler.Read( + $"SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema LIKE 'public' AND table_type LIKE 'BASE TABLE' AND table_name = '{Name}';") == + 0) + { + Create(); + } + else + { + foreach (ITableColumn col in m) + { + try + { + if (col.IsPrimaryKey) throw new Exception($"DELETE THE TABLE '{Name}' TO FIX TABLE GENERATION"); + string sql = $"ALTER TABLE IF EXISTS public.{Name} ADD COLUMN {col.Name} {col.GetDatabaseTypeStr()}"; + sql += " NOT NULL"; + if (col.GetDefaultStr() is not null) sql += $" DEFAULT {col.GetDefaultStr()}"; + sql += ";"; + DatabaseHandler.ExecuteNonQuery(sql); + } + catch (Exception e) + { + Console.WriteLine("Posible fail to set default for public.{0}.{1}", Name, col.Name); + } + } + } + } + + if (n.Any()) + { + foreach (ITableColumn col in n) + { + string sql = $"ALTER TABLE public.{Name} ALTER COLUMN {col.Name} TYPE {col.GetDatabaseTypeStr()};"; + + DatabaseHandler.ExecuteNonQuery(sql); + if (col.GetDefaultStr() is not null) + { + try + { + sql = $"ALTER TABLE IF EXISTS public.{Name} ALTER COLUMN {col.Name} SET DEFAULT {col.GetDefaultStr()};"; + DatabaseHandler.ExecuteNonQuery(sql); + } + catch (Exception e) + { + Console.WriteLine("Failed to set default for public.{0}.{1}\nSQL: {2}", Name, col.Name, sql); + } + } + } + } + } + + public Table DropColumn(TableColumn Column) where TType : notnull + { + Colums_.Remove(Column); + return this; + } + + private Dictionary colnamesraw = new(); + + private List Colums_ { get; set; } = new(); + + public IReadOnlyList Colums => Colums_.AsReadOnly(); + + public string Name { get; set; } = default!; + + public Database DatabaseHandler { get; set; } = default!; + + public void Insert(params Parameter[] Parameters) + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + List l = new List(Colums_); + foreach (Parameter param in Parameters) + { + l.Remove(l.Where(s => s.Name == param.PGParameter.ParameterName).First()); + vals += "@" + param.PGParameter.ParameterName + ", "; + cmd.Parameters.Add(param.PGParameter); + } + + foreach (ITableColumn cop in l) + { + var param = cop.CreateDefaultParameter(); + vals += "@" + param.PGParameter.ParameterName + ", "; + cmd.Parameters.Add(param.PGParameter); + } + vals = vals.Remove(vals.Length - 2, 2); + cmd.CommandText = $"INSERT INTO {Name} ({vals.Replace("@", "")}) VALUES({vals})"; + cmd.Prepare(); + cmd.ExecuteNonQuery(); + con.Close(); + } + + public void DeleteRow(Parameter Parameter) + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + cmd.Parameters.Add(Parameter.PGParameter); + cmd.CommandText = $"DELETE FROM {Name} WHERE {Parameter.PGParameter.ParameterName} IN (@{Parameter.PGParameter.ParameterName});"; + cmd.Prepare(); + cmd.ExecuteNonQuery(); + con.Close(); + } + + public void Create() + { + Console.WriteLine("Making Table: {0}", Name); + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string command = $"CREATE TABLE public.{Name}("; + string key = ""; + foreach (ITableColumn column in Colums_) + { + command += $"{column.Name} {column.GetDatabaseTypeStr()}"; + if (column.IsPrimaryKey) key = $"PRIMARY KEY ({column.Name})"; + command += " NOT NULL, "; + } + if (key == "") command = command.Remove(command.Length - 2, 2) + ");"; + command += $"{key});"; + cmd.CommandText = command; + cmd.Prepare(); + cmd.ExecuteNonQuery(); + con.Close(); + DatabaseHandler.ExecuteNonQuery($"ALTER TABLE IF EXISTS public.{Name} OWNER to {DatabaseHandler.Uname}"); + } + + public bool TryRead(TableColumn column, Order order, [NotNullWhen(true)][MaybeNullWhen(false)]out T result, params Parameter[] Parameters) where T : notnull + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + string command = $"SELECT {column.Name} FROM {Name} WHERE"; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND "; + cmd.Parameters.Add(param.PGParameter); + } + if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); + if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5); + if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; + if (order is not null) command += $" ORDER BY {order.Type} "; + if (order is not null && order.Assending) command += "ASC"; + else { if (order is not null) command += "DESC"; } + command += ";"; + cmd.CommandText = command; + //object? temp = cmd.ExecuteScalar(); + object? temp = null; + try + { + temp = cmd.ExecuteScalar(); + } + catch + { + result = default(T); + return false; + } + con.Close(); + if (temp is DBNull || temp is null) + { + result = default(T); + return false; + } + + if (typeof(T).IsEnum) + { + result = (T)Enum.Parse(typeof(T), temp.ToString()!)!; + return true; + } +/* + if (typeof(T).IsNullableEnum()) + { + result = (T?)Enum.Parse(Nullable.GetUnderlyingType(typeof(T))!, temp.ToString()!)!; + return true; + } +*/ + result = (T)temp!; + return true; + } + + public bool TryReadRow(out TRow row, params Parameter[] Parameters) => TryReadRow(null, out row, Parameters); + + public bool TryReadRow(Order order, out TRow row, params Parameter[] Parameters) + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + string command = $"SELECT * FROM {Name} WHERE"; + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND "; + cmd.Parameters.Add(param.PGParameter); + } + if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); + if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5); + if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; + if (order is not null) command += $" ORDER BY {order.Type} "; + if (order is not null && order.Assending) command += "ASC"; + else { if (order is not null) command += "DESC"; } + command += ";"; + cmd.CommandText = command; + NpgsqlDataReader reader = cmd.ExecuteReader(); + TRow Row = new(); + reader.Read(); + bool r = false; + for (int i = 0; i < reader.FieldCount; i++) + { + r = true; + string colname = reader.GetName(i); + object? val = null; + try + { + val = reader.GetValue(i); + } + catch + { + row = default(TRow); + return false; + } + + if (colnamesraw.ContainsKey(colname)) + { + if (val is DBNull || val is null) + { + row = default(TRow); + return false; + } + PropertyInfo? pi = Row.GetType() + .GetProperty(colnamesraw[colname]); + if (pi is null) + { + Console.WriteLine("Could not find row {0}", colnamesraw[colname]); + continue; + } + pi.SetValue(Row, val, null); + } + } + + row = Row; + return r; + } + + public bool TryRead(TableColumn column, [NotNullWhen(true)][MaybeNullWhen(false)]out T result, params Parameter[] Parameters) where T : notnull + { + return TryRead(column, null!, out result, Parameters); + } + + public void Update(TableColumn condiction_column, Ttype condiction_value, params Parameter[] Parameters) where Ttype : notnull + { + Update(condiction_column, condiction_value, "=", Parameters); + } + + public void Update(TableColumn condiction_column, Ttype condiction_value, string Sign, params Parameter[] Parameters) where Ttype : notnull + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName}, "; + cmd.Parameters.Add(param.PGParameter); + } + vals = vals.Remove(vals.Length - 2, 2); + cmd.Parameters.Add(condiction_column.CreateParameter(condiction_value).PGParameter); + cmd.CommandText = $"UPDATE {Name} SET {vals} WHERE {condiction_column.Name} {Sign} @{condiction_column.Name};"; + cmd.Prepare(); + cmd.ExecuteNonQuery(); + con.Close(); + } + + public TRow ReadRow(params Parameter[] Parameters) => ReadRow(null, Parameters); + + public TRow ReadRow(Order order, params Parameter[] Parameters) + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + string command = $"SELECT * FROM {Name} WHERE"; + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND "; + cmd.Parameters.Add(param.PGParameter); + } + if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); + if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5); + if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; + if (order is not null) command += $" ORDER BY {order.Type} "; + if (order is not null && order.Assending) command += "ASC"; + else { if (order is not null) command += "DESC"; } + command += ";"; + cmd.CommandText = command; + NpgsqlDataReader reader = cmd.ExecuteReader(); + TRow Row = new(); + reader.Read(); + for (int i = 0; i < reader.FieldCount; i++) + { + string colname = reader.GetName(i); + object? val = reader.GetValue(i); + if (colnamesraw.ContainsKey(colname)) + { + PropertyInfo? pi = Row.GetType() + .GetProperty(colnamesraw[colname]); + if (pi is null) + { + Console.WriteLine("Could not find row {0}", colnamesraw[colname]); + continue; + } + pi.SetValue(Row, val, null); + } + } + + return Row; + } + + public TRow[] ReadRows(uint NumRows, params Parameter[] Parameters) => ReadRows(NumRows, null, Parameters); + + public TRow[] ReadRows(uint NumRows, Order order, params Parameter[] Parameters) + { + if (string.IsNullOrEmpty(DatabaseHandler.DB) || string.IsNullOrEmpty(DatabaseHandler.IP) || string.IsNullOrEmpty(DatabaseHandler.Uname) || string.IsNullOrEmpty(DatabaseHandler.PW)) throw new Exception("Database conection not fully defined"); + using NpgsqlConnection con = new(DatabaseHandler.ConectionString); + string command = $"SELECT * FROM {Name} WHERE"; + con.Open(); + using NpgsqlCommand cmd = new(); + cmd.Connection = con; + string vals = ""; + foreach (Parameter param in Parameters) + { + vals += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND "; + cmd.Parameters.Add(param.PGParameter); + } + if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); + if (!string.IsNullOrWhiteSpace(vals)) vals = vals.Remove(vals.Length - 5, 5); + if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; + if (order is not null) command += $" ORDER BY {order.Type} "; + if (order is not null && order.Assending) command += "ASC"; + else { if (order is not null) command += "DESC"; } + command += $" LIMIT {NumRows};"; + cmd.CommandText = command; + NpgsqlDataReader reader = cmd.ExecuteReader(); + List Rows = new(); + for (int j = 0; j < NumRows; j++) + { + if (!reader.Read()) break; + for (int i = 0; i < reader.FieldCount; i++) + { + TRow Row = new(); + string colname = reader.GetName(i); + object? val = reader.GetValue(i); + if (colnamesraw.ContainsKey(colname)) + { + PropertyInfo? pi = Row.GetType() + .GetProperty(colnamesraw[colname]); + if (pi is null) + { + Console.WriteLine("Could not find row {0}", colnamesraw[colname]); + continue; + } + pi.SetValue(Row, val, null); + } + Rows.Add(Row); + } + } + + return Rows.ToArray(); + } + + public T Read(TableColumn column, params Parameter[] Parameters) where T : notnull + { + if (Parameters is null) throw new ArgumentNullException(nameof(Parameters)); + return DatabaseHandler.Read($"SELECT {column.Name} FROM {Name} WHERE", (Order?)null, Parameters); + } + + public T Read(TableColumn column, Order order, params Parameter[] Parameters) where T : notnull + { + if (Parameters is null) throw new ArgumentNullException(nameof(Parameters)); + return DatabaseHandler.Read($"SELECT {column.Name} FROM {Name} WHERE", order, Parameters); + } +} \ No newline at end of file diff --git a/Database/TableColumn.cs b/Database/TableColumn.cs new file mode 100644 index 0000000..14f007f --- /dev/null +++ b/Database/TableColumn.cs @@ -0,0 +1,299 @@ +using System.Data; +using System.Diagnostics.CodeAnalysis; +using Npgsql; +using NpgsqlTypes; +using ServerDatabase.Utils; + +namespace ServerDatabase; + +public class TableColumn : ITableColumn where Ttype : notnull +{ + public TableColumn(string Name, bool IsPrimaryKey = false) + { + this.Name = Name; + this.IsPrimaryKey = IsPrimaryKey; + } + + public long ColumnVersion { get; set; } = 0; + public Order GetDecendingOrder() => new Order() { Assending = false, Type = Name }; + public Order GetAssendingOrder() => new Order() { Assending = true, Type = Name }; + + public Ttype DefaultValue { get; set; } + + public string Name { get; set; } = default!; + public Type Type { get; } = typeof(Ttype); + public bool IsPrimaryKey { get; set; } = false; + public object? GetDefault() => DefaultValue; + + public string? GetDefaultStr() + { + if (DefaultValue is null) return null; + else if (DefaultValue is DateTime) return "CURRENT_DATE"; + else if (Type.FullName == typeof(string).FullName) return $"'{DefaultValue.ToString()}'::text"; + else if (Type.IsArray) + { + Type t = Type.GetElementType(); + if (t.IsEnum) + { + t = t.GetEnumUnderlyingType(); + } + if (t.Name == typeof(long).Name) return $"'{{{string.Join(", ", DefaultValue as long[])}}}'::bigint[]"; + else if (t.Name == typeof(int).Name) return $"'{{{string.Join(", ", DefaultValue as int[])}}}'::integer[]"; + else if (t.Name == typeof(short).Name) + return $"'{{{string.Join(", ", DefaultValue as short[])}}}'::smallint[]"; + else return null; + } + else if (Type.IsEnum) + { + return $"{Convert.ChangeType(DefaultValue, Type.GetEnumUnderlyingType())}"; + } + else + { + return DefaultValue.ToString(); + } + } + + private object? _defalt = null; + + public Type GetDatabaseType() + { + if (Type.IsEnum) + { + return Type.GetEnumUnderlyingType(); + } + + if (Type.IsNullableEnum()) + { + return Nullable.GetUnderlyingType(Type)!.GetEnumUnderlyingType(); + } + + if (Type.IsArray) + { + Type element = Type.GetElementType()!; + if (element.IsEnum) + { + return element.GetEnumUnderlyingType().MakeArrayType(); + } + + if (element.IsNullableEnum()) + { + return Nullable.GetUnderlyingType(element)!.GetEnumUnderlyingType().MakeArrayType(); + } + } + + return Type; + } + + public string GetDatabaseTypeStr() + { + return typestrmap[GetDatabaseType()]; + } + + public Parameter CreateDefaultParameter([DisallowNull] string sign = "=") => + CreateParameter(DefaultValue, sign); + + public Parameter CreateParameter([DisallowNull]Ttype Value, [DisallowNull]string sign = "=") + { + Type t = Value.GetType(); + Tuple? types; + if (t.IsEnum) + { + t = t.GetEnumUnderlyingType(); + types = typemap[t]; + return new() + { + PGParameter = new NpgsqlParameter(Name, Convert.ChangeType(Value, t)) + { + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + } + if (t.IsArray) + { + t = t.GetElementType()!; + + if (t.IsEnum) + { + t = t.GetEnumUnderlyingType(); + types = typemap[t.MakeArrayType()]; + Enum[] tempenum = Ext.Cast(Value); + switch (t.FullName) + { + case "System.Int64": + return new() + { + PGParameter = new NpgsqlParameter(Name, Array.ConvertAll(tempenum, value => Convert.ToInt64(value))) + { + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + case "System.Int32": + return new() + { + PGParameter = new NpgsqlParameter(Name, Array.ConvertAll(tempenum, value => Convert.ToInt32(value))) + { + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + case "System.Int16": + return new() + { + PGParameter = new NpgsqlParameter(Name, Array.ConvertAll(tempenum, value => Convert.ToInt16(value))) + { + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + } + } + + t = t.MakeArrayType(); + types = typemap[t]; + } + /*if (value is null) + { + t = typeof(Ttype); + if (t.IsNullableEnum()) t = Nullable.GetUnderlyingType(t)!.GetEnumUnderlyingType(); + types = typemap[t]; + return new() + { + PGParameter = new(Name, value) + { + IsNullable = true, + Value = DBNull.Value, + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + }*/ + types = typemap[t!]; + if (types is null) + { + return new() + { + PGParameter = new(Name, Value) + }; + } + return new() + { + PGParameter = new NpgsqlParameter(Name, Value) + { + DbType = types.Item1, + NpgsqlDbType = types.Item2 + }, + Sign = sign + }; + } + + public Parameter CreateJsonParameter([DisallowNull]Ttype value, [DisallowNull]string sign = "=") + { + return new() + { + PGParameter = new NpgsqlParameter(Name, value) + { + NpgsqlDbType = NpgsqlDbType.Jsonb + }, + Sign = sign + }; + } + + private readonly Dictionary?> typemap = new() + { + {typeof(byte), new(DbType.Byte, NpgsqlDbType.Unknown)}, + {typeof(sbyte), new(DbType.SByte, NpgsqlDbType.Unknown)}, + {typeof(short), new(DbType.Int16, NpgsqlDbType.Smallint)}, + {typeof(ushort), new(DbType.UInt16, NpgsqlDbType.Smallint)}, + {typeof(int), new(DbType.Int32, NpgsqlDbType.Integer)}, + {typeof(uint), new(DbType.UInt32, NpgsqlDbType.Integer)}, + {typeof(long), new(DbType.Int64, NpgsqlDbType.Bigint)}, + {typeof(ulong), new(DbType.UInt64, NpgsqlDbType.Bigint)}, + {typeof(float), new(DbType.Single, NpgsqlDbType.Real)}, + {typeof(double), new(DbType.Double, NpgsqlDbType.Double)}, + {typeof(decimal), new(DbType.Decimal, NpgsqlDbType.Money)}, + {typeof(bool), new(DbType.Boolean, NpgsqlDbType.Boolean)}, + {typeof(string), new(DbType.String, NpgsqlDbType.Text)}, + {typeof(char), new(DbType.StringFixedLength, NpgsqlDbType.Char)}, + {typeof(Guid), new(DbType.Guid, NpgsqlDbType.Uuid)}, + {typeof(DateTime), new(DbType.DateTime, NpgsqlDbType.Date)}, + {typeof(DateTimeOffset), new(DbType.DateTimeOffset, NpgsqlDbType.TimeTz)}, + {typeof(byte[]), new(DbType.Binary, NpgsqlDbType.Bytea)}, + {typeof(byte[][]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Bytea)}, + {typeof(byte?), new(DbType.Byte, NpgsqlDbType.Unknown)}, + {typeof(sbyte?), new(DbType.SByte, NpgsqlDbType.Unknown)}, + {typeof(short?), new(DbType.Int16, NpgsqlDbType.Smallint)}, + {typeof(ushort?), new(DbType.UInt16, NpgsqlDbType.Smallint)}, + {typeof(int?), new(DbType.Int32, NpgsqlDbType.Integer)}, + {typeof(uint?), new(DbType.UInt32, NpgsqlDbType.Integer)}, + {typeof(long?), new(DbType.Int64, NpgsqlDbType.Bigint)}, + {typeof(ulong?), new(DbType.UInt64, NpgsqlDbType.Bigint)}, + {typeof(float?), new(DbType.Single, NpgsqlDbType.Real)}, + {typeof(double?), new(DbType.Double, NpgsqlDbType.Double)}, + {typeof(decimal?), new(DbType.Decimal, NpgsqlDbType.Money)}, + {typeof(bool?), new(DbType.Boolean, NpgsqlDbType.Boolean)}, + {typeof(char?), new(DbType.StringFixedLength, NpgsqlDbType.Char)}, + {typeof(Guid?), new(DbType.Guid, NpgsqlDbType.Uuid)}, + {typeof(DateTime?), new(DbType.DateTime, NpgsqlDbType.Uuid)}, + {typeof(DateTimeOffset?), new(DbType.DateTimeOffset, NpgsqlDbType.TimeTz)}, + {typeof(string[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Text)}, + {typeof(long[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Bigint)}, + {typeof(long?[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Bigint)}, + {typeof(int[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Integer)}, + {typeof(int?[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Integer)}, + {typeof(short[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Smallint)}, + {typeof(short?[]), new(DbType.Object, NpgsqlDbType.Array | NpgsqlDbType.Smallint)} + }; + + private readonly Dictionary typestrmap = new() + { + {typeof(byte), null! }, + {typeof(sbyte), null! }, + {typeof(short), "smallint" }, + {typeof(ushort), "smallint" }, + {typeof(int), "integer" }, + {typeof(uint), "integer" }, + {typeof(long), "bigint" }, + {typeof(ulong), "bigint" }, + {typeof(float), "real" }, + {typeof(double), "double precision" }, + {typeof(decimal), "money" }, + {typeof(bool), "boolean" }, + {typeof(string), "text" }, + {typeof(char), "character" }, + {typeof(Guid), "uuid" }, + {typeof(DateTime), "date" }, + {typeof(DateTimeOffset), "time with time zone" }, + {typeof(byte[]), "bytea" }, + {typeof(byte[][]), "bytea[]" }, + {typeof(byte?), null! }, + {typeof(sbyte?), null! }, + {typeof(short?), "smallint" }, + {typeof(ushort?), "smallint" }, + {typeof(int?), "integer" }, + {typeof(uint?), "integer" }, + {typeof(long?), "bigint" }, + {typeof(ulong?), "bigint" }, + {typeof(float?), "real" }, + {typeof(double?), "double precision" }, + {typeof(decimal?), "money" }, + {typeof(bool?), "boolean" }, + {typeof(char?), "character" }, + {typeof(Guid?), "uuid" }, + {typeof(DateTime?), "date" }, + {typeof(DateTimeOffset?), "time with time zone" }, + {typeof(string[]), "text[]" }, + {typeof(long[]), "bigint[]" }, + {typeof(long?[]), "bigint[]" }, + {typeof(int[]), "integer[]" }, + {typeof(int?[]), "integer[]" }, + {typeof(short[]), "smallint[]" }, + {typeof(short?[]), "smallint[]" } + }; +} diff --git a/Database/TableVersions.cs b/Database/TableVersions.cs new file mode 100644 index 0000000..807052c --- /dev/null +++ b/Database/TableVersions.cs @@ -0,0 +1,15 @@ +namespace ServerDatabase; + +public class TableVersions : Table +{ + internal TableVersions(Database db) : base(db) + { + ID = new TableColumn("id", true); + WithColumn(ID, new("ID")); + } + public class blankrow + { + + } + public TableColumn ID { get; } +} \ No newline at end of file