diff options
Diffstat (limited to 'docker')
7 files changed, 214 insertions, 8 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj new file mode 100644 index 0000000..59edf0d --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/CrupestApi.Commons.Tests.csproj @@ -0,0 +1,28 @@ +<Project Sdk="Microsoft.NET.Sdk">
 +
 +  <PropertyGroup>
 +    <TargetFramework>net7.0</TargetFramework>
 +    <ImplicitUsings>enable</ImplicitUsings>
 +    <Nullable>enable</Nullable>
 +
 +    <IsPackable>false</IsPackable>
 +  </PropertyGroup>
 +
 +  <ItemGroup>
 +    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
 +    <PackageReference Include="xunit" Version="2.4.2" />
 +    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
 +      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 +      <PrivateAssets>all</PrivateAssets>
 +    </PackageReference>
 +    <PackageReference Include="coverlet.collector" Version="3.2.0">
 +      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 +      <PrivateAssets>all</PrivateAssets>
 +    </PackageReference>
 +  </ItemGroup>
 +
 +  <ItemGroup>
 +    <ProjectReference Include="..\CrupestApi.Commons\CrupestApi.Commons.csproj" />
 +  </ItemGroup>
 +
 +</Project>
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit;
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs new file mode 100644 index 0000000..8b4607d --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -0,0 +1,69 @@ +using System.Reflection; + +namespace CrupestApi.Commons.Crud; + +public class ColumnInfo +{ +    private Type ExtractRealTypeFromNullable(Type type) +    { +        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) +        { +            return type.GetGenericArguments()[0]; +        } + +        return type; +    } + +    // A column with no property. +    public ColumnInfo(Type entityType, string sqlColumnName, bool isPrimaryKey, bool isAutoIncrement, IColumnTypeInfo typeInfo) +    { +        EntityType = entityType; +        PropertyName = null; +        PropertyType = typeof(int); +        PropertyRealType = typeof(int); +        SqlColumnName = sqlColumnName; +        ColumnTypeInfo = typeInfo; +        Nullable = false; +        IsPrimaryKey = isPrimaryKey; +        IsAutoIncrement = isAutoIncrement; +    } + +    public ColumnInfo(Type entityType, string entityPropertyName) +    { +        EntityType = entityType; +        PropertyName = entityPropertyName; + +        var property = entityType.GetProperty(entityPropertyName); + +        if (property is null) +            throw new Exception("Public property with given name does not exist."); + +        PropertyType = property.PropertyType; +        PropertyRealType = ExtractRealTypeFromNullable(PropertyType); + +        var columnAttribute = property.GetCustomAttribute<ColumnAttribute>(); +        if (columnAttribute is null) +        { +            SqlColumnName = PropertyName; +            Nullable = true; +        } +        else +        { +            SqlColumnName = columnAttribute.DatabaseName ?? PropertyName; +            Nullable = !columnAttribute.NonNullable; +        } +        ColumnTypeInfo = ColumnTypeInfoRegistry.Singleton.GetRequiredByDataType(PropertyRealType); + +    } + +    public Type EntityType { get; } +    // If null, there is no corresponding property. +    public string? PropertyName { get; } +    public Type PropertyType { get; } +    public Type PropertyRealType { get; } +    public string SqlColumnName { get; } +    public IColumnTypeInfo ColumnTypeInfo { get; } +    public bool Nullable { get; } +    public bool IsPrimaryKey { get; } +    public bool IsAutoIncrement { get; } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs new file mode 100644 index 0000000..cbf38f7 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -0,0 +1,21 @@ +namespace CrupestApi.Commons.Crud; + +public interface IColumnMetadata +{ + +} + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class ColumnAttribute : Attribute, IColumnMetadata +{ +    // if null, use the property name. +    public string? DatabaseName { get; set; } + +    // default false. +    public bool NonNullable { get; set; } + +    // default false +    public bool IsPrimaryKey { get; set; } + +    public bool IsAutoIncrement { get; set; } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs index 02848f3..1bdc9c0 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs @@ -1,6 +1,6 @@  using System.Diagnostics; -namespace CrupestApi.Commons; +namespace CrupestApi.Commons.Crud;  public interface IColumnTypeInfo  { @@ -15,7 +15,7 @@ public interface IColumnTypeInfo          IColumnTypeInfo current = this; -        while (!typeof(BuiltinColumnTypeInfo<>).IsInstanceOfType(current)) +        while (current is not IBuiltinColumnTypeInfo)          {              var dataType = GetDataType(); @@ -86,7 +86,12 @@ public class BuiltinColumnTypeInfo<T> : IBuiltinColumnTypeInfo      }  } -public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : IColumnTypeInfo +public interface ICustomColumnTypeInfo : IColumnTypeInfo +{ + +} + +public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : ICustomColumnTypeInfo   where TDataType : notnull where TDatabaseType : notnull  { @@ -105,13 +110,13 @@ public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : IColumnTy      object IColumnTypeInfo.ConvertToDatabase(object data)      { -        Debug.Assert(typeof(TDataType).IsInstanceOfType(data)); +        Debug.Assert(data is TDataType);          return ConvertToDatabase((TDataType)data);      }      object IColumnTypeInfo.ConvertFromDatabase(object databaseData)      { -        Debug.Assert(typeof(TDatabaseType).IsInstanceOfType(databaseData)); +        Debug.Assert(databaseData is TDatabaseType);          return ConvertFromDatabase((TDatabaseType)databaseData);      }  } @@ -155,8 +160,6 @@ public class ColumnTypeInfoRegistry          Singleton.Validate();      } - -      private readonly List<IColumnTypeInfo> _list;      private readonly Dictionary<Type, IColumnTypeInfo> _map;      private bool _dirty = false; @@ -180,6 +183,11 @@ public class ColumnTypeInfoRegistry          return _map.GetValueOrDefault(type);      } +    public IColumnTypeInfo GetRequiredByDataType(Type type) +    { +        return GetByDataType(type) ?? throw new Exception("Unsupported type."); +    } +      public string GetSqlType(Type type)      {          EnsureValidity(); @@ -190,7 +198,7 @@ public class ColumnTypeInfoRegistry              throw new Exception("Unsupported type for sql.");          } -        while (!typeof(IBuiltinColumnTypeInfo).IsInstanceOfType(current)) +        while (current is not IBuiltinColumnTypeInfo)          {              current = GetByDataType(current.GetDatabaseType());              Debug.Assert(current is not null); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs new file mode 100644 index 0000000..fb9e1ad --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -0,0 +1,73 @@ +namespace CrupestApi.Commons.Crud; + +public class TableInfo +{ +    public TableInfo(Type entityType) +    { +        EntityType = entityType; + +        var properties = entityType.GetProperties(); + +        var columnInfos = new List<ColumnInfo>(); + +        bool hasPrimaryKey = false; +        bool hasId = false; + +        foreach (var property in properties) +        { +            var columnInfo = new ColumnInfo(entityType, property.Name); +            columnInfos.Add(columnInfo); +            if (columnInfo.IsPrimaryKey) +                hasPrimaryKey = true; +            if (columnInfo.SqlColumnName.Equals("id", StringComparison.OrdinalIgnoreCase)) +            { +                hasId = true; +            } +        } + +        if (!hasPrimaryKey) +        { +            if (hasId) throw new Exception("A column named id already exists but is not primary key."); +            var columnInfo = new ColumnInfo(entityType, "id", true, true, ColumnTypeInfoRegistry.Singleton.GetRequiredByDataType(typeof(int))); +            columnInfos.Add(columnInfo); +        } + +        ColumnInfos = columnInfos; + +        CheckValidity(); +    } + +    public Type EntityType { get; } +    public IReadOnlyList<ColumnInfo> ColumnInfos { get; } + +    public void CheckValidity() +    { +        // Check if there is only one primary key. +        bool hasPrimaryKey = false; +        foreach (var column in ColumnInfos) +        { +            if (column.IsPrimaryKey) +            { +                if (hasPrimaryKey) throw new Exception("Two columns are primary key."); +                hasPrimaryKey = true; +            } +        } + +        if (!hasPrimaryKey) throw new Exception("No column is primary key."); + +        // Check two columns have the same sql name. +        HashSet<string> sqlNameSet = new HashSet<string>(); + +        foreach (var column in ColumnInfos) +        { +            if (sqlNameSet.Contains(column.SqlColumnName)) +                throw new Exception($"Two columns have the same sql name '{column.SqlColumnName}'."); +            sqlNameSet.Add(column.SqlColumnName); +        } +    } + +    public string GenerateCreateTableSql() +    { +        throw new NotImplementedException(); +    } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.sln b/docker/crupest-api/CrupestApi/CrupestApi.sln index 5f21ff5..ebfd960 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.sln +++ b/docker/crupest-api/CrupestApi/CrupestApi.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Secrets", "Crupe  EndProject
  Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Commons", "CrupestApi.Commons\CrupestApi.Commons.csproj", "{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}"
  EndProject
 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrupestApi.Commons.Tests", "CrupestApi.Commons.Tests\CrupestApi.Commons.Tests.csproj", "{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}"
 +EndProject
  Global
  	GlobalSection(SolutionConfigurationPlatforms) = preSolution
  		Debug|Any CPU = Debug|Any CPU
 @@ -36,5 +38,9 @@ Global  		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Debug|Any CPU.Build.0 = Debug|Any CPU
  		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.ActiveCfg = Release|Any CPU
  		{38083CCA-E56C-4D24-BAB6-EEC30E0F478F}.Release|Any CPU.Build.0 = Release|Any CPU
 +		{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 +		{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Debug|Any CPU.Build.0 = Debug|Any CPU
 +		{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Release|Any CPU.ActiveCfg = Release|Any CPU
 +		{0D0304BF-6A18-444C-BAF4-6ABFF98A0F77}.Release|Any CPU.Build.0 = Release|Any CPU
  	EndGlobalSection
  EndGlobal
  | 
