This commit is contained in:
JacobTech 2023-06-28 12:44:59 -04:00
parent abff28d1a0
commit de8884aaf4
10 changed files with 1033 additions and 0 deletions

15
.gitignore vendored
View File

@ -8,6 +8,8 @@
.idea/**/usage.statistics.xml .idea/**/usage.statistics.xml
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
.idea/**/.idea/**
.idea/**/.idea/
# AWS User-specific # AWS User-specific
.idea/**/aws.xml .idea/**/aws.xml
@ -77,3 +79,16 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .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/

25
Database.sln Executable file
View File

@ -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

129
Database/Database.cs Executable file
View File

@ -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<ITable> 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<long>($"{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<T>(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!;
}
}

30
Database/Ext.cs Executable file
View File

@ -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, OutputType>(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;
}
}

12
Database/ITable.cs Normal file
View File

@ -0,0 +1,12 @@
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);
}

11
Database/Order.cs Executable file
View File

@ -0,0 +1,11 @@
namespace ServerDatabase;
public class Order
{
internal Order()
{
}
public bool Assending { get; init; } = default!;
public string Type { get; init; } = default!;
}

34
Database/ServerDatabase.csproj Executable file
View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<FileVersion>1.0.0.0</FileVersion>
<Authors>JacobTech</Authors>
<Company>JacobTech, LLC</Company>
<Description>A Database class to handle database interactions</Description>
<IncludeSymbols>False</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageTags>Postgresql;sql</PackageTags>
<Title>Server Database</Title>
<Version>2.8.1</Version>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.6.0" />
<PackageReference Include="Npgsql" Version="7.0.0" />
<PackageReference Include="ServerDatabase.Utils" Version="1.0.0" />
</ItemGroup>
<Target Name="CustomActionsAfterPublish" AfterTargets="Pack">
<Message Text="Actions AfterPublish: $(PackageId).$(PackageVersion).nupkg" Importance="high" />
<Exec Command="nuget push -Source https://nuget.jacobtech.com/v3/index.json bin/Release/$(PackageId).$(PackageVersion).nupkg" />
</Target>
</Project>

463
Database/Table.cs Normal file
View File

@ -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<TRow> : 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<TRow> 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<ITableColumn> GetMissingColumns(out IReadOnlyList<ITableColumn> UpdatedColumns)
{
List<ITableColumn> coll = new(Colums_);
List<ITableColumn> col = new(Colums_);
foreach (DataRow row in DatabaseHandler.CreateConnection().GetSchema("Columns", new[] { DatabaseHandler.DB, null, Name }).Rows)
{
string name = row.Field<string>("COLUMN_NAME")!,
ty = row.Field<string>("DATA_TYPE")!;
IEnumerable<ITableColumn> 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<ITableColumn> m = GetMissingColumns(out IReadOnlyList<ITableColumn> n);
if (m.Any())
{
if (DatabaseHandler.Read<long>(
$"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<TRow> DropColumn<TType>(TableColumn<TType> Column) where TType : notnull
{
Colums_.Remove(Column);
return this;
}
private Dictionary<string, string> colnamesraw = new();
private List<ITableColumn> Colums_ { get; set; } = new();
public IReadOnlyList<ITableColumn> 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<ITableColumn> l = new List<ITableColumn>(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<T>(TableColumn<T> 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<T>(TableColumn<T> column, [NotNullWhen(true)][MaybeNullWhen(false)]out T result, params Parameter[] Parameters) where T : notnull
{
return TryRead(column, null!, out result, Parameters);
}
public void Update<Ttype>(TableColumn<Ttype> condiction_column, Ttype condiction_value, params Parameter[] Parameters) where Ttype : notnull
{
Update(condiction_column, condiction_value, "=", Parameters);
}
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");
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<TRow> 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<T>(TableColumn<T> column, params Parameter[] Parameters) where T : notnull
{
if (Parameters is null) throw new ArgumentNullException(nameof(Parameters));
return DatabaseHandler.Read<T>($"SELECT {column.Name} FROM {Name} WHERE", (Order?)null, Parameters);
}
public T Read<T>(TableColumn<T> column, Order order, params Parameter[] Parameters) where T : notnull
{
if (Parameters is null) throw new ArgumentNullException(nameof(Parameters));
return DatabaseHandler.Read<T>($"SELECT {column.Name} FROM {Name} WHERE", order, Parameters);
}
}

299
Database/TableColumn.cs Normal file
View File

@ -0,0 +1,299 @@
using System.Data;
using System.Diagnostics.CodeAnalysis;
using Npgsql;
using NpgsqlTypes;
using ServerDatabase.Utils;
namespace ServerDatabase;
public class TableColumn<Ttype> : 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<DbType, NpgsqlDbType>? 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<Ttype, Enum>(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<Type, Tuple<DbType, NpgsqlDbType>?> 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<Type, string> 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[]" }
};
}

15
Database/TableVersions.cs Normal file
View File

@ -0,0 +1,15 @@
namespace ServerDatabase;
public class TableVersions : Table<TableVersions.blankrow>
{
internal TableVersions(Database db) : base(db)
{
ID = new TableColumn<short>("id", true);
WithColumn(ID, new("ID"));
}
public class blankrow
{
}
public TableColumn<short> ID { get; }
}