ForeignKeys & CommandHandler

This commit is contained in:
JacobTech 2024-03-22 12:11:19 -04:00
parent aa1e57f6e3
commit e9ec3638c5
8 changed files with 277 additions and 55 deletions

View File

@ -0,0 +1,91 @@
using Npgsql;
using ServerDatabase.Utils;
namespace ServerDatabase;
public class CommandHandler
{
private IBetterTable _betterTable;
internal CommandHandler(IBetterTable betterTable)
{
this._betterTable = betterTable;
}
private List<Parameter> Parameters = new();
private List<Parameter> Values = new();
private Order? Order = null;
public CommandHandler WithFilter(Parameter p)
{
Parameters.Add(p);
return this;
}
public CommandHandler WithValue(Parameter p)
{
Values.Add(p);
return this;
}
public CommandHandler WithFilter<T>(TableColumn<T> column, T value, string sign = "=") where T : notnull
{
return WithFilter(column.CreateParameter(value, sign));
}
public CommandHandler WithValue<T>(TableColumn<T> column, T value) where T : notnull
{
return WithValue(column.CreateParameter(value));
}
public CommandHandler AscendBy<T>(TableColumn<T> column) where T : notnull
{
Order = column.GetAssendingOrder();
return this;
}
public CommandHandler DescendBy<T>(TableColumn<T> column) where T : notnull
{
Order = column.GetDecendingOrder();
return this;
}
public T Read<T>(TableColumn<T> column) where T : notnull
{
return _betterTable.DatabaseHandler.Read<T>($"SELECT {column.Name} FROM {_betterTable.Name} WHERE", Order, Parameters.ToArray());
}
public void Insert()
{
_betterTable.Insert(Values.ToArray());
}
public void Update()
{
if (string.IsNullOrEmpty(_betterTable.DatabaseHandler.DB) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.IP) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.Uname) || string.IsNullOrEmpty(_betterTable.DatabaseHandler.PW)) throw new Exception("Database connection not fully defined");
using NpgsqlConnection con = new(_betterTable.DatabaseHandler.ConectionString);
con.Open();
using NpgsqlCommand cmd = new();
cmd.Connection = con;
string values = "";
foreach (Parameter param in Values)
{
values += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName}, ";
cmd.Parameters.Add(param.PGParameter);
}
values = values.Remove(values.Length - 2, 2);
string fils = "";
foreach (Parameter param in Parameters)
{
fils += $"{param.PGParameter.ParameterName} {param.Sign} @{param.PGParameter.ParameterName} AND";
cmd.Parameters.Add(param.PGParameter);
}
fils = fils.Remove(fils.Length - 4, 4);
cmd.CommandText = $"UPDATE {_betterTable.Name} SET {values} WHERE {fils};";
cmd.Prepare();
cmd.ExecuteNonQuery();
con.Close();
}
}

View File

@ -1,8 +1,4 @@
using Npgsql; using Npgsql;
using NpgsqlTypes;
using System.Data;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using ServerDatabase.Utils; using ServerDatabase.Utils;
namespace ServerDatabase; namespace ServerDatabase;
@ -17,7 +13,7 @@ public sealed class Database
} }
} }
public List<ITable> Tables { get; } = new() public List<IBetterTable> Tables { get; } = new()
{ {
}; };
@ -38,7 +34,7 @@ public sealed class Database
public void RegisterTables() public void RegisterTables()
{ {
foreach (ITable Table in Tables) foreach (IBetterTable Table in Tables)
{ {
foreach (ITableColumn Column in Table.Colums) foreach (ITableColumn Column in Table.Colums)
{ {
@ -49,7 +45,7 @@ public sealed class Database
} }
} }
VersionsTable.UpdateTable(); VersionsTable.UpdateTable();
foreach (ITable Table in Tables) foreach (IBetterTable Table in Tables)
{ {
Table.UpdateTable(); Table.UpdateTable();
} }

31
Database/ForeignKey.cs Normal file
View File

@ -0,0 +1,31 @@
using ServerDatabase.Utils;
using ServerDatabase.Utils.Enums;
namespace ServerDatabase;
public class ForeignKey : IForeignKey
{
public ConstraintAction OnUpdate { get; init; } = ConstraintAction.None;
public ConstraintAction OnDelete { get; init; } = ConstraintAction.None;
public KeyMatchType MatchType { get; init; } = KeyMatchType.Simple;
public required ITable Table { get; init; }
public required ITableColumn Column { get; init; }
private bool d = false;
public bool Deferrable { get; init; } = false;
public bool Deferred
{
get
{
return d & Deferrable;
}
init
{
d = value;
}
}
public bool Validated { get; init; } = false;
}

8
Database/IBetterTable.cs Normal file
View File

@ -0,0 +1,8 @@
using ServerDatabase.Utils;
namespace ServerDatabase;
public interface IBetterTable : ITable
{
public Database DatabaseHandler { get; set; }
}

View File

@ -1,12 +0,0 @@
using ServerDatabase.Utils;
namespace ServerDatabase;
public interface ITable
{
public void UpdateTable();
public IReadOnlyList<ITableColumn> Colums { get; }
public string Name { get; set; }
public Database DatabaseHandler { get; set; }
public void AddColumn(ITableColumn Column, string name);
}

View File

@ -10,8 +10,8 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat> <SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageTags>Postgresql;sql</PackageTags> <PackageTags>Postgresql;sql</PackageTags>
<Title>Server Database</Title> <Title>Server Database</Title>
<Version>2.8.9</Version> <Version>2.9.9</Version>
<LangVersion>10</LangVersion> <LangVersion>11</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
@ -22,8 +22,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.6.0" />
<PackageReference Include="Npgsql" Version="7.0.0" /> <PackageReference Include="ServerDatabase.Utils" Version="1.0.4" />
<PackageReference Include="ServerDatabase.Utils" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,13 +1,13 @@
using System.Data; using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using Npgsql; using Npgsql;
using ServerDatabase.Utils; using ServerDatabase.Utils;
using ServerDatabase.Utils.Enums;
namespace ServerDatabase; namespace ServerDatabase;
public class Table<TRow> : ITable where TRow : class, new() public class Table<TRow> : IBetterTable where TRow : class, new()
{ {
public Table(string Name, Database DB) public Table(string Name, Database DB)
{ {
@ -22,6 +22,11 @@ public class Table<TRow> : ITable where TRow : class, new()
DatabaseHandler = DB; DatabaseHandler = DB;
} }
public CommandHandler CreateCommand()
{
return new CommandHandler(this);
}
public Table<TRow> WithColumn(ITableColumn Column, string name) public Table<TRow> WithColumn(ITableColumn Column, string name)
{ {
Colums_.Add(Column); Colums_.Add(Column);
@ -34,7 +39,6 @@ public class Table<TRow> : ITable where TRow : class, new()
colnamesraw.Add(Column.Name, name); colnamesraw.Add(Column.Name, name);
Colums_.Add(Column); Colums_.Add(Column);
} }
public IReadOnlyList<ITableColumn> GetMissingColumns(out IReadOnlyList<ITableColumn> UpdatedColumns, out string[] ExtraColumns) public IReadOnlyList<ITableColumn> GetMissingColumns(out IReadOnlyList<ITableColumn> UpdatedColumns, out string[] ExtraColumns)
{ {
@ -222,25 +226,94 @@ public class Table<TRow> : ITable where TRow : class, new()
con.Open(); con.Open();
using NpgsqlCommand cmd = new(); using NpgsqlCommand cmd = new();
cmd.Connection = con; cmd.Connection = con;
string command = $"CREATE TABLE public.{Name}("; string command = $"CREATE TABLE IF NOT EXISTS public.{Name}(";
string key = ""; List<string> Keys = new();
List<Tuple<ITableColumn, IForeignKey>> FKeys = new();
foreach (ITableColumn column in Colums_) foreach (ITableColumn column in Colums_)
{ {
command += $"{column.Name} {column.GetDatabaseTypeStr()}"; command += $"{column.Name} {column.GetDatabaseTypeStr()}";
if (column.IsPrimaryKey) key = $"PRIMARY KEY ({column.Name})"; if (column.IsPrimaryKey) Keys.Add(column.Name);
command += " NOT NULL, "; command += " NOT NULL, ";
if (column.ForeignKeys.Length > 0)
{
foreach (IForeignKey Fkey in column.ForeignKeys)
{
FKeys.Add(new(column, Fkey));
}
}
} }
if (key == "") command = command.Remove(command.Length - 2, 2) + ");";
command += $"{key});"; 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<ITableColumn, IForeignKey> 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.CommandText = command;
cmd.Prepare(); cmd.Prepare();
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
con.Close(); con.Close();
DatabaseHandler.ExecuteNonQuery($"ALTER TABLE IF EXISTS " + DatabaseHandler.ExecuteNonQuery($"ALTER TABLE IF EXISTS public.{Name} OWNER to \"{DatabaseHandler.Uname}\";");
$"public.{Name} OWNER to \"{DatabaseHandler.Uname}\";");
} }
public bool TryRead<T>(TableColumn<T> column, Order order, [NotNullWhen(true)][MaybeNullWhen(false)]out T result, params Parameter[] Parameters) where T : notnull public bool TryRead<T>(TableColumn<T> 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"); 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); using NpgsqlConnection con = new(DatabaseHandler.ConectionString);
@ -361,9 +434,9 @@ public class Table<TRow> : ITable where TRow : class, new()
return r; return r;
} }
public bool TryReadRows(out TRow[] rows, params Parameter[] Parameters) => TryReadRows(null!, out rows, Parameters); public bool TryReadRows([NotNullWhen(true)]out TRow[]? rows, params Parameter[] Parameters) => TryReadRows(null!, out rows, Parameters);
public bool TryReadRows(Order order, out TRow[] rows, params Parameter[] 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"); 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); using NpgsqlConnection con = new(DatabaseHandler.ConectionString);
@ -389,9 +462,9 @@ public class Table<TRow> : ITable where TRow : class, new()
List<TRow> Rows = new(); List<TRow> Rows = new();
while (reader.Read()) while (reader.Read())
{ {
TRow Row = new();
for (int i = 0; i < reader.FieldCount; i++) for (int i = 0; i < reader.FieldCount; i++)
{ {
TRow Row = new();
string colname = reader.GetName(i); string colname = reader.GetName(i);
object? val = reader.GetValue(i); object? val = reader.GetValue(i);
if (colnamesraw.ContainsKey(colname)) if (colnamesraw.ContainsKey(colname))
@ -405,15 +478,15 @@ public class Table<TRow> : ITable where TRow : class, new()
} }
pi.SetValue(Row, val, null); pi.SetValue(Row, val, null);
} }
Rows.Add(Row);
} }
Rows.Add(Row);
} }
rows = Rows.ToArray(); rows = Rows.ToArray();
return Rows.Any(); return Rows.Any();
} }
public bool TryRead<T>(TableColumn<T> column, [NotNullWhen(true)][MaybeNullWhen(false)]out T result, params Parameter[] Parameters) where T : notnull public bool TryRead<T>(TableColumn<T> column, [NotNullWhen(true)]out T? result, params Parameter[] Parameters) where T : notnull
{ {
return TryRead(column, null!, out result, Parameters); return TryRead(column, null!, out result, Parameters);
} }
@ -425,23 +498,12 @@ public class Table<TRow> : ITable where TRow : class, new()
public void Update<Ttype>(TableColumn<Ttype> condiction_column, Ttype condiction_value, string Sign, params Parameter[] Parameters) where Ttype : notnull public void Update<Ttype>(TableColumn<Ttype> 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"); CommandHandler command = CreateCommand().WithFilter(condiction_column, condiction_value, Sign);
using NpgsqlConnection con = new(DatabaseHandler.ConectionString); foreach (var Parameter in Parameters)
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}, "; command.WithValue(Parameter);
cmd.Parameters.Add(param.PGParameter);
} }
vals = vals.Remove(vals.Length - 2, 2); command.Update();
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(params Parameter[] Parameters) => ReadRow(null, Parameters);
@ -490,10 +552,54 @@ public class Table<TRow> : ITable where TRow : class, new()
return Row; return Row;
} }
public TColumn[] ReadColumn<TColumn>(TableColumn<TColumn> column, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, uint.MaxValue, null, Parameters);
public TColumn[] ReadColumn<TColumn>(TableColumn<TColumn> column, uint NumRows, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, NumRows, null, Parameters);
public TColumn[] ReadColumn<TColumn>(TableColumn<TColumn> column, Order? Order, params Parameter[] Parameters) where TColumn : notnull => ReadColumn(column, uint.MaxValue, Order, Parameters);
public TColumn[] ReadColumn<TColumn>(TableColumn<TColumn> 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<TColumn> 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(uint NumRows, params Parameter[] Parameters) => ReadRows(NumRows, null, Parameters);
public TRow[] ReadRows(uint NumRows, Order order, params Parameter[] 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"); 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); using NpgsqlConnection con = new(DatabaseHandler.ConectionString);
@ -510,6 +616,7 @@ public class Table<TRow> : ITable where TRow : class, new()
if (command.EndsWith(";")) command = command.Remove(command.Length - 1, 1); 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)) vals = vals.Remove(vals.Length - 5, 5);
if (!string.IsNullOrWhiteSpace(vals) && command.EndsWith("WHERE")) command += $" {vals}"; 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) command += $" ORDER BY {order.Type} ";
if (order is not null && order.Assending) command += "ASC"; if (order is not null && order.Assending) command += "ASC";
else { if (order is not null) command += "DESC"; } else { if (order is not null) command += "DESC"; }

View File

@ -14,6 +14,8 @@ public class TableColumn<Ttype> : ITableColumn where Ttype : notnull
this.IsPrimaryKey = IsPrimaryKey; this.IsPrimaryKey = IsPrimaryKey;
} }
public IForeignKey[] ForeignKeys { get; init; } = Array.Empty<IForeignKey>();
public long ColumnVersion { get; set; } = 0; public long ColumnVersion { get; set; } = 0;
public Order GetDecendingOrder() => new Order() { Assending = false, Type = Name }; public Order GetDecendingOrder() => new Order() { Assending = false, Type = Name };
public Order GetAssendingOrder() => new Order() { Assending = true, Type = Name }; public Order GetAssendingOrder() => new Order() { Assending = true, Type = Name };