using System.Data; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Npgsql; using ServerDatabase.Utils; using ServerDatabase.Utils.Enums; namespace ServerDatabase; public class Table : IBetterTable 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 CommandHandler CreateCommand() { return new CommandHandler(this); } 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, out string[] ExtraColumns) { List coll = new(Colums_); List col = new(Colums_); List co = new(); 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()); } else { co.Add(name); } } UpdatedColumns = coll; ExtraColumns = co.ToArray(); return col.AsReadOnly(); } public void UpdateTable() { Console.WriteLine("Verifying Table: {0}", Name); bool i = true; IReadOnlyList m = GetMissingColumns(out IReadOnlyList n, out string[] o); if (m.Any()) { if (i) { i = false; Console.WriteLine("Fixing Table: {0}", Name); } 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()) { if (i) { i = false; Console.WriteLine("Fixing Table: {0}", Name); } 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); } } } } if (o.Any()) { if (i) { i = false; Console.WriteLine("Fixing Table: {0}", Name); } foreach (string c in o) { DatabaseHandler.ExecuteNonQuery($"ALTER TABLE IF EXISTS {Name} DROP COLUMN IF EXISTS {c};"); } } } 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 DeleteRows() { 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.CommandText = $"DELETE FROM {Name};"; 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 IF NOT EXISTS public.{Name}("; List Keys = new(); List> FKeys = new(); foreach (ITableColumn column in Colums_) { command += $"{column.Name} {column.GetDatabaseTypeStr()}"; if (column.IsPrimaryKey) Keys.Add(column.Name); command += " NOT NULL, "; if (column.ForeignKeys.Length > 0) { foreach (IForeignKey Fkey in column.ForeignKeys) { FKeys.Add(new(column, Fkey)); } } } if (Keys.Count > 0) { string key = $"CONSTRAINT {Name}_pkey PRIMARY KEY ("; foreach (var Key in Keys) { key += Key + ", "; } key = key.Remove(key.Length - 2, 2); key += "), "; command += key; } if (FKeys.Count > 0) { foreach (Tuple K in FKeys) { string key = $"CONSTRAINT {Name}_{K.Item1.Name}_{K.Item2.Table.Name}_{K.Item2.Column.Name}_fkey FOREIGN KEY ({K.Item1.Name}) REFERENCES public.{K.Item2.Table.Name} ({K.Item2.Column.Name}) MATCH "; if (K.Item2.MatchType == KeyMatchType.Full) key += "FULL ON UPDATE "; else key += "SIMPLE ON UPDATE "; switch (K.Item2.OnUpdate) { case ConstraintAction.None: key += "NO ACTION ON DELETE "; break; case ConstraintAction.Cascade: key += "CASCADE ON DELETE "; break; case ConstraintAction.Restrict: key += "RESTRICT ON DELETE "; break; case ConstraintAction.SetDefault: key += "SET DEFAULT ON DELETE "; break; } switch (K.Item2.OnDelete) { case ConstraintAction.None: key += "NO ACTION "; break; case ConstraintAction.Cascade: key += "CASCADE "; break; case ConstraintAction.Restrict: key += "RESTRICT "; break; case ConstraintAction.SetDefault: key += "SET DEFAULT "; break; } if (K.Item2.Deferrable) key += "DEFERRABLE "; if (K.Item2.Deferred) key += "INITIALLY DEFERRED "; if (!K.Item2.Validated) key += "NOT VALID "; key = key.Remove(key.Length - 1, 1) + ", "; command += key; } } command = command.Remove(command.Length - 2, 2); command += ");"; 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)]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 TryReadRows([NotNullWhen(true)]out TRow[]? rows, params Parameter[] Parameters) => TryReadRows(null!, out rows, Parameters); public bool TryReadRows(Order order, [NotNullWhen(true)]out TRow[]? rows, 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(); List Rows = new(); while (reader.Read()) { TRow Row = new(); 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); } } Rows.Add(Row); } rows = Rows.ToArray(); return Rows.Any(); } public bool TryRead(TableColumn column, [NotNullWhen(true)]out T? result, params Parameter[] Parameters) where T : notnull { return TryRead(column, null!, out result, Parameters); } 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 TColumn[] ReadColumn(TableColumn column, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, uint.MaxValue, null, Parameters); public TColumn[] ReadColumn(TableColumn column, uint NumRows, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, NumRows, null, Parameters); public TColumn[] ReadColumn(TableColumn column, Order? Order, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, uint.MaxValue, Order, Parameters); public TColumn[] ReadColumn(TableColumn column, uint NumRows, Order? order, params Parameter[] Parameters) where TColumn : 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); string command = $"SELECT {column.Name} 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 (command.EndsWith("WHERE")) command = command.Remove(command.Length - 6, 6); 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; Rows.Add((TColumn)reader.GetValue(0)); } return Rows.ToArray(); } public TRow[] ReadRows(params Parameter[] Parameters) => ReadRows(uint.MaxValue, null, Parameters); public TRow[] ReadRows(uint NumRows, params Parameter[] Parameters) => ReadRows(NumRows, null, Parameters); public TRow[] ReadRows(Order? Order, params Parameter[] Parameters) => ReadRows(uint.MaxValue, Order, 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 (command.EndsWith("WHERE")) command = command.Remove(command.Length - 6, 6); 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; TRow Row = new(); 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); } } 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); } }