Skip to content

Commit

Permalink
Merge pull request #401 from mk3008/397-experimental-generate-queries…
Browse files Browse the repository at this point in the history
…-from-table-definition-classes

397 generate queries from table definition classes
  • Loading branch information
mk3008 authored May 6, 2024
2 parents 3580e8f + 62bfb85 commit c21aa83
Show file tree
Hide file tree
Showing 34 changed files with 1,455 additions and 23 deletions.
1 change: 0 additions & 1 deletion src/Carbunql.Postgres/FromnExtension.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Carbunql.Building;
using Carbunql.Clauses;
using Carbunql.Postgres.Linq;

namespace Carbunql.Postgres;

Expand Down
6 changes: 6 additions & 0 deletions src/Carbunql.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.Migration", "..\demo\M
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.Parse", "..\demo\Parse\Demo.Parse.csproj", "{42FA31DD-B42A-4F88-BEDE-6BE9BF31BE2D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Carbunql.Annotation.Test", "..\test\Carbunql.Annotation.Test\Carbunql.Annotation.Test.csproj", "{E76AE876-D280-401B-BE02-7500B608E320}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -81,6 +83,10 @@ Global
{42FA31DD-B42A-4F88-BEDE-6BE9BF31BE2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42FA31DD-B42A-4F88-BEDE-6BE9BF31BE2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42FA31DD-B42A-4F88-BEDE-6BE9BF31BE2D}.Release|Any CPU.Build.0 = Release|Any CPU
{E76AE876-D280-401B-BE02-7500B608E320}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E76AE876-D280-401B-BE02-7500B608E320}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E76AE876-D280-401B-BE02-7500B608E320}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E76AE876-D280-401B-BE02-7500B608E320}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
10 changes: 1 addition & 9 deletions src/Carbunql/Analysis/Parser/ConstraintParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ namespace Carbunql.Analysis;

public static class ConstraintParser
{
//public static IConstraint Parse(string text)
//{
// var r = new SqlTokenReader(text);
// var q = Parse(r);
// return q;
//}

public static IConstraint Parse(ITable t, ITokenReader r)
{
var token = r.Read();
Expand All @@ -26,10 +19,9 @@ public static IConstraint Parse(ITable t, ITokenReader r)
if (token.IsEqualNoCase("primary key"))
{
var columns = ArrayParser.Parse(r);
return new PrimaryKeyConstraint(t)
return new PrimaryKeyConstraint(t, columns)
{
ConstraintName = name,
ColumnNames = columns
};
}

Expand Down
55 changes: 55 additions & 0 deletions src/Carbunql/Annotations/AutoNumberDefinitionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Carbunql.Analysis.Parser;
using Carbunql.Clauses;
using System.Diagnostics.CodeAnalysis;

namespace Carbunql.Annotations;

/// <summary>
/// Factory class for creating auto number definitions.
/// </summary>
public static class AutoNumberDefinitionFactory
{
/// <summary>
/// Tries to create an auto number definition for the specified type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of class for which to create the auto number definition.</typeparam>
/// <param name="autoNumberDefinition">When this method returns, contains the auto number definition if successful, or the default value if unsuccessful.</param>
/// <returns>True if the auto number definition is successfully created; otherwise, false.</returns>
public static bool TryCreate<T>([MaybeNullWhen(false)] out ValueBase autoNumberDefinition)
{
return TryCreate(typeof(T), out autoNumberDefinition);
}

/// <summary>
/// Tries to create an auto number definition for the specified type.
/// </summary>
/// <param name="type">The type of class for which to create the auto number definition.</param>
/// <param name="autoNumberDefinition">When this method returns, contains the auto number definition if successful, or the default value if unsuccessful.</param>
/// <returns>True if the auto number definition is successfully created; otherwise, false.</returns>
public static bool TryCreate(Type type, [MaybeNullWhen(false)] out ValueBase autoNumberDefinition)
{
autoNumberDefinition = default;

var atr = (TableAttribute?)Attribute.GetCustomAttribute(type, typeof(TableAttribute));
var def = string.Empty;

if (atr != null)
{
if (atr.HasAutoNumber == false) return false;
def = atr.AutoNumberDefinition;
}

if (string.IsNullOrEmpty(def))
{
def = DbmsConfiguration.GetDefaultAutoNumberDefinitionLogic();
}

if (string.IsNullOrEmpty(def))
{
return false;
}

autoNumberDefinition = ValueParser.Parse(def);
return true;
}
}
74 changes: 74 additions & 0 deletions src/Carbunql/Annotations/ColumnAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
namespace Carbunql.Annotations;

/// <summary>
/// Attribute used to define metadata for a column in a database table.
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class ColumnAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the ColumnAttribute class.
/// </summary>
public ColumnAttribute()
{
}

/// <summary>
/// Initializes a new instance of the ColumnAttribute class with the specified column type.
/// </summary>
/// <param name="columnType">The type of the column.</param>
public ColumnAttribute(string columnType)
{
ColumnType = columnType;
}

/// <summary>
/// Gets or sets the name of the column.
/// </summary>
public string ColumnName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the type of the column.
/// </summary>
public string ColumnType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the type of the related column, if any.
/// </summary>
public string RelationColumnType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a value indicating whether the column is auto-incremented.
/// </summary>
public bool IsAutoNumber { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether the column allows null values.
/// </summary>
public bool IsNullable { get; set; } = false;

/// <summary>
/// Gets or sets the definition for the auto-incremented column.
/// </summary>
public string AutoNumberDefinition { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the default value for the column.
/// </summary>
public string DefaultValue { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the comment associated with the column.
/// </summary>
public string Comment { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the command to retrieve a timestamp value.
/// </summary>
public string TimestampCommand { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the special type of the column, if any.
/// </summary>
public SpecialColumn SpecialColumn { get; set; } = SpecialColumn.None;
}
142 changes: 142 additions & 0 deletions src/Carbunql/Annotations/ColumnDefinitionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using Carbunql.Analysis.Parser;
using Carbunql.Definitions;
using System.Reflection;

namespace Carbunql.Annotations;

/// <summary>
/// Factory class for creating column definitions.
/// </summary>
public static class ColumnDefinitionFactory
{
/// <summary>
/// Creates column definitions for the specified type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of class for which to create the column definitions.</typeparam>
/// <param name="t">The table associated with the column definitions.</param>
/// <returns>A list of generated column definitions.</returns>
public static List<ColumnDefinition> Create<T>(ITable t)
{
var lst = new List<ColumnDefinition>();
lst.AddRange(CreateFromLiteralProperties<T>(t));
lst.AddRange(CreateFromRelationPropeties<T>(t));
return lst;
}

private static List<ColumnDefinition> CreateFromLiteralProperties<T>(ITable t)
{
// Exclude properties with invalid attributes.
// Exclude properties with non-literal types.
var props = PropertySelector.SelectLiteralProperties<T>()
.Select(prop => CreateFromLiteralProperty(typeof(T), t, prop));

return props.ToList();
}

internal static ColumnDefinition CreateFromLiteralProperty(Type type, ITable t, PropertyInfo prop)
{
var attribute = (ColumnAttribute?)Attribute.GetCustomAttribute(prop, typeof(ColumnAttribute));

if (attribute == null)
{
return Create(type, t, prop, string.Empty, string.Empty, string.Empty, null, null, string.Empty);

}
else
{
return Create(type, t, prop, attribute.ColumnName, attribute.ColumnType, attribute.RelationColumnType, attribute.IsAutoNumber, attribute.IsNullable, attribute.DefaultValue);
}
}

/// <summary>
/// Generates database column definitions from properties of related types.
/// </summary>
/// <typeparam name="T">The type to generate column definitions for.</typeparam>
/// <param name="t">The table associated with the column definitions.</param>
/// <returns>A list of generated column definitions.</returns>
public static List<ColumnDefinition> CreateFromRelationPropeties<T>(ITable t)
{
var parentprops = PropertySelector.SelectParentProperties<T>()
.SelectMany(prop => CreateAsParentProperties(t, prop));

return parentprops.ToList();
}

internal static List<ColumnDefinition> CreateAsParentProperties(ITable t, PropertyInfo prop)
{
var parentType = prop.PropertyType;
var parentclause = TableDefinitionClauseFactory.CreateTableDefinitionClause(parentType);
var parentPkeyMaps = PrimaryKeyConstraintFactory.Create(parentType).PrimaryKeyMaps;
if (!parentPkeyMaps.Any()) throw new InvalidProgramException();

var attributes = (IEnumerable<ParentRelationColumnAttribute>)Attribute.GetCustomAttributes(prop, typeof(ParentRelationColumnAttribute));

return parentPkeyMaps.Select(map => Create(parentType, t, map)).ToList();
}

private static ColumnDefinition Create(Type parentType, ITable t, PrimaryKeyMap map)
{
var prop = parentType.GetProperty(map.PropertyName)!;
var parentColumn = CreateFromLiteralProperty(parentType, t, prop);
return new ColumnDefinition(t, map.ColumnName, parentColumn.RelationColumnType);
}

private static ColumnDefinition Create<T>(ITable t, PropertyInfo prop, string columnName, string columnType, string relationColumnType, bool? isAutonumber, bool? isNullable, string defaultValue)
{
return Create(typeof(T), t, prop, columnName, columnType, relationColumnType, isAutonumber, isNullable, defaultValue);
}

private static ColumnDefinition Create(Type type, ITable t, PropertyInfo prop, string columnName, string columnType, string relationColumnType, bool? isAutonumber, bool? isNullable, string defaultValue)
{
if (string.IsNullOrEmpty(columnName))
{
columnName = DbmsConfiguration.ConvertToDefaultColumnNameLogic(prop.Name);
}

if (isAutonumber == null)
{
isAutonumber = prop.IsAutoNumber();
}

if (string.IsNullOrEmpty(relationColumnType))
{
relationColumnType = DbmsConfiguration.ToDbType(prop.PropertyType);
}

if (string.IsNullOrEmpty(columnType))
{
if (isAutonumber.Value)
{
columnType = DbmsConfiguration.ToIdentityDbType(prop.PropertyType);
}
else
{
columnType = DbmsConfiguration.ToDbType(prop.PropertyType);
}
}

if (isNullable == null)
{
isNullable = prop.IsDbNullable();
}

var def = new ColumnDefinition(t, columnName, ValueParser.Parse(columnType))
{
RelationColumnType = ValueParser.Parse(relationColumnType),
IsAutoNumber = isAutonumber.Value,
IsNullable = isNullable.Value,
};

if (def.IsAutoNumber && AutoNumberDefinitionFactory.TryCreate(type, out var autoNumberDefinition))
{
def.AutoNumberDefinition = autoNumberDefinition;
}

if (!string.IsNullOrEmpty(defaultValue))
{
def.DefaultValue = ValueParser.Parse(defaultValue);
}

return def;
}
}
42 changes: 42 additions & 0 deletions src/Carbunql/Annotations/CreateIndexQueryFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Carbunql.Analysis.Parser;
using Carbunql.Clauses;
using Carbunql.Definitions;

namespace Carbunql.Annotations;

/// <summary>
/// Factory class for generating index queries.
/// </summary>
public static class CreateIndexQueryFactory
{
/// <summary>
/// Generates index queries based on the properties of the specified type from the given table information.
/// </summary>
/// <typeparam name="T">Type of the object for which indexes are created.</typeparam>
/// <param name="t">Table information for creating indexes.</param>
/// <returns>List of generated index queries.</returns>
public static List<CreateIndexQuery> Creates<T>(ITable t)
{
var queries = new List<CreateIndexQuery>();

var props = PropertySelector.SelectParentProperties<T>();

foreach (var prop in props)
{
var clause = new IndexOnClause(t);

ColumnDefinitionFactory.CreateAsParentProperties(t, prop)
.Select(x => x.ColumnName).ToList()
.ForEach(x => clause.Add(SortableItemParser.Parse(x)));

var query = new CreateIndexQuery(clause)
{
IndexName = DbmsConfiguration.GetDefaultIndexNameLogic(prop.Name)
};

queries.Add(query);
}

return queries;
}
}
18 changes: 18 additions & 0 deletions src/Carbunql/Annotations/CreateTableQueryFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Carbunql.Annotations;

/// <summary>
/// Factory class for creating "CREATE TABLE" queries.
/// </summary>
public static class CreateTableQueryFactory
{
/// <summary>
/// Creates a "CREATE TABLE" query for the specified type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of class for which to create the table query.</typeparam>
/// <returns>The created "CREATE TABLE" query.</returns>
public static CreateTableQuery Create<T>()
{
var clause = TableDefinitionClauseFactory.Create<T>();
return new CreateTableQuery(clause);
}
}
Loading

0 comments on commit c21aa83

Please sign in to comment.