Skip to content

Commit

Permalink
[ksqlDB.RestApi.Client]: HasColumnName to Fluent API sample
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasfabian committed May 31, 2024
1 parent 8f67db6 commit 9ebae01
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void Property_IgnoreField()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
}
Expand All @@ -80,7 +80,7 @@ public void Property_HasColumnName()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).ColumnName.Should().Be(columnName);
}
Expand All @@ -101,7 +101,7 @@ public void MultiplePropertiesForSameType()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
entityMetadata.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Amount)).Ignore.Should().BeTrue();
Expand All @@ -123,7 +123,7 @@ public void Property_IgnoreNestedField()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Composite));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Composite));
entityMetadata.Should().NotBeNull();
var memberInfo = GetTitleMemberInfo();
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo == memberInfo).Ignore.Should().BeTrue();
Expand All @@ -139,7 +139,7 @@ public void AddConventionForDecimal()
builder.AddConvention(decimalTypeConvention);

//Assert
builder.Conventions[typeof(decimal)].Should().BeEquivalentTo(decimalTypeConvention);
((IMetadataProvider)builder).Conventions[typeof(decimal)].Should().BeEquivalentTo(decimalTypeConvention);
}

private class PaymentConfiguration : IFromItemTypeConfiguration<Payment>
Expand All @@ -161,7 +161,7 @@ public void FromItemTypeConfiguration()
builder.Apply(configuration);

//Assert
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Description)).Ignore.Should().BeTrue();
}
Expand All @@ -188,7 +188,7 @@ public void Decimal_ConfigurePrecisionAndScale()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
var metadata = (DecimalFieldMetadata)entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Amount));

Expand All @@ -209,7 +209,7 @@ public void Header()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
var metadata = (BytesArrayFieldMetadata)entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Header));

Expand All @@ -228,7 +228,7 @@ public void Headers()

//Assert
fieldTypeBuilder.Should().NotBeNull();
var entityMetadata = builder.GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
var entityMetadata = ((IMetadataProvider)builder).GetEntities().FirstOrDefault(c => c.Type == typeof(Payment));
entityMetadata.Should().NotBeNull();
var metadata = entityMetadata!.FieldsMetadata.First(c => c.MemberInfo.Name == nameof(Payment.Header));

Expand Down
11 changes: 11 additions & 0 deletions ksqlDb.RestApi.Client/FluentAPI/Builders/IMetadataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using ksqlDb.RestApi.Client.FluentAPI.Builders.Configuration;
using ksqlDb.RestApi.Client.Metadata;

namespace ksqlDb.RestApi.Client.FluentAPI.Builders
{
internal interface IMetadataProvider
{
internal IEnumerable<EntityMetadata> GetEntities();
IDictionary<Type, IConventionConfiguration> Conventions { get; }
}
}
16 changes: 12 additions & 4 deletions ksqlDb.RestApi.Client/FluentAPI/Builders/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ namespace ksqlDb.RestApi.Client.FluentAPI.Builders
/// <summary>
/// Represents a builder for configuring the model.
/// </summary>
public class ModelBuilder
public class ModelBuilder : IMetadataProvider
{
private readonly IDictionary<Type, EntityTypeBuilder> builders = new Dictionary<Type, EntityTypeBuilder>();
internal readonly IDictionary<Type, IConventionConfiguration> Conventions = new Dictionary<Type, IConventionConfiguration>();
private readonly IDictionary<Type, IConventionConfiguration> conventions = new Dictionary<Type, IConventionConfiguration>();

internal IEnumerable<EntityMetadata> GetEntities()
IDictionary<Type, IConventionConfiguration> IMetadataProvider.Conventions
{
get
{
return conventions;
}
}

IEnumerable<EntityMetadata> IMetadataProvider.GetEntities()
{
return builders.Values.Select(c => c.Metadata);
}
Expand All @@ -37,7 +45,7 @@ public ModelBuilder Apply<TEntity>(IFromItemTypeConfiguration<TEntity> configura
/// <returns>The current <see cref="ModelBuilder"/> instance.</returns>
public ModelBuilder AddConvention(IConventionConfiguration configuration)
{
Conventions.Add(configuration.Type, configuration);
conventions.Add(configuration.Type, configuration);

return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,16 @@ internal static string ExtractTypeName(this Type type)
return attribute;
}

internal static string GetMemberName(this MemberExpression memberExpression, ModelBuilder? modelBuilder)
internal static string GetMemberName(this MemberExpression memberExpression, IMetadataProvider? metadataProvider)
{
var entityMetadata = modelBuilder?.GetEntities().FirstOrDefault(c => c.Type == memberExpression.Expression?.Type);
var entityMetadata = metadataProvider?.GetEntities().FirstOrDefault(c => c.Type == memberExpression.Expression?.Type);

return memberExpression.Member.GetMemberName(entityMetadata);
}

internal static string GetMemberName(this MemberInfo memberInfo, ModelBuilder? modelBuilder)
internal static string GetMemberName(this MemberInfo memberInfo, IMetadataProvider? metadataProvider)
{
var entityMetadata = modelBuilder?.GetEntities().FirstOrDefault(c => c.Type == memberInfo.DeclaringType);
var entityMetadata = metadataProvider?.GetEntities().FirstOrDefault(c => c.Type == memberInfo.DeclaringType);

return memberInfo.GetMemberName(entityMetadata);
}
Expand Down
1 change: 1 addition & 0 deletions ksqlDb.RestApi.Client/KSql/Query/Context/KSqlDBContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ protected override void OnConfigureServices(IServiceCollection serviceCollection
base.OnConfigureServices(serviceCollection, contextOptions);

serviceCollection.TryAddSingleton(modelBuilder);
serviceCollection.TryAddSingleton<IMetadataProvider>(modelBuilder);
serviceCollection.RegisterEndpointDependencies(contextOptions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

namespace ksqlDB.RestApi.Client.KSql.RestApi.Generators;

internal sealed class TypeGenerator(ModelBuilder modelBuilder) : EntityInfo(modelBuilder)
internal sealed class TypeGenerator(ModelBuilder metadataProvider) : EntityInfo(metadataProvider)
{
private readonly KSqlTypeTranslator typeTranslator = new(modelBuilder);
private readonly KSqlTypeTranslator typeTranslator = new(metadataProvider);

internal string Print<T>(TypeProperties properties)
{
Expand All @@ -36,7 +36,7 @@ private void PrintProperties<T>(StringBuilder stringBuilder, IdentifierEscaping

var ksqlType = typeTranslator.Translate(type, escaping);

var memberName = memberInfo.GetMemberName(modelBuilder);
var memberName = memberInfo.GetMemberName(metadataProvider);
var columnDefinition = $"{EscapeName(memberName, escaping)} {ksqlType}{typeTranslator.ExploreAttributes(typeof(T), memberInfo, type)}";
ksqlProperties.Add(columnDefinition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ internal void JsonPropertyNameModifier(JsonTypeInfo jsonTypeInfo)
JsonPropertyNameModifier(jsonTypeInfo, modelBuilder);
}

internal static void JsonPropertyNameModifier(JsonTypeInfo jsonTypeInfo, ModelBuilder modelBuilder)
internal static void JsonPropertyNameModifier(JsonTypeInfo jsonTypeInfo, IMetadataProvider metadataProvider)
{
var entityMetadata = modelBuilder.GetEntities().FirstOrDefault(c => c.Type == jsonTypeInfo.Type);
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == jsonTypeInfo.Type);

foreach (var typeInfoProperty in jsonTypeInfo.Properties)
{
Expand Down
22 changes: 10 additions & 12 deletions ksqlDb.RestApi.Client/KSql/RestApi/Parsers/IdentifierUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,25 @@ Keywords when IsValid(identifier) && SystemColumns.IsValid(identifier) => identi
};
}

/// <summary>
/// Format the <c>identifier</c>, except when it is a <c>PseudoColumn</c>.
/// </summary>
/// <param name="memberExpression">the memberExpression with the identifier</param>
/// <param name="escaping">the format</param>
/// <param name="modelBuilder">the model builder</param>
/// <returns>the identifier modified based on the provided <c>format</c></returns>
public static string Format(MemberExpression memberExpression, IdentifierEscaping escaping, ModelBuilder? modelBuilder = null)
internal static string Format(MemberExpression memberExpression, IdentifierEscaping escaping, IMetadataProvider? metadataProvider = null)
{
return escaping switch
{
Never => memberExpression.GetMemberName(modelBuilder),
Never => memberExpression.GetMemberName(metadataProvider),
Keywords when memberExpression.Member.GetCustomAttribute<PseudoColumnAttribute>() != null => memberExpression.Member.Name,
Keywords when IsValid(memberExpression.GetMemberName(modelBuilder)) && SystemColumns.IsValid(memberExpression.GetMemberName(modelBuilder)) => memberExpression.GetMemberName(modelBuilder),
Keywords => string.Concat("`", memberExpression.GetMemberName(modelBuilder), "`"),
Keywords when IsValid(memberExpression.GetMemberName(metadataProvider)) && SystemColumns.IsValid(memberExpression.GetMemberName(metadataProvider)) => memberExpression.GetMemberName(metadataProvider),
Keywords => string.Concat("`", memberExpression.GetMemberName(metadataProvider), "`"),
Always when memberExpression.Member.GetCustomAttribute<PseudoColumnAttribute>() != null => memberExpression.Member.Name,
Always => string.Concat("`", memberExpression.GetMemberName(modelBuilder), "`"),
Always => string.Concat("`", memberExpression.GetMemberName(metadataProvider), "`"),
_ => throw new ArgumentOutOfRangeException(nameof(escaping), escaping, "Non-exhaustive match.")
};
}

internal static string Format(MemberInfo memberInfo, IdentifierEscaping escaping, IMetadataProvider? modelBuilder = null)
{
return Format(memberInfo, escaping, modelBuilder as ModelBuilder);
}

/// <summary>
/// Format the <c>identifier</c>, except when it is a <c>PseudoColumn</c>.
/// </summary>
Expand Down
8 changes: 4 additions & 4 deletions ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements;

internal sealed class CreateEntity(ModelBuilder modelBuilder) : EntityInfo(modelBuilder)
internal sealed class CreateEntity(IMetadataProvider metadataProvider) : EntityInfo(metadataProvider)

Check warning on line 12 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateEntity.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IMetadataProvider metadataProvider' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

Check warning on line 12 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateEntity.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IMetadataProvider metadataProvider' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

Check warning on line 12 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateEntity.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'IMetadataProvider metadataProvider' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
{
private readonly StringBuilder stringBuilder = new();

private readonly KSqlTypeTranslator typeTranslator = new(modelBuilder);
private readonly KSqlTypeTranslator typeTranslator = new(metadataProvider);

internal string Print<T>(StatementContext statementContext, EntityCreationMetadata metadata, bool? ifNotExists)
{
Expand Down Expand Up @@ -49,7 +49,7 @@ private void PrintProperties<T>(StatementContext statementContext, EntityCreatio

var ksqlType = typeTranslator.Translate(type, metadata.IdentifierEscaping);

var columnName = IdentifierUtil.Format(memberInfo, metadata.IdentifierEscaping, modelBuilder);
var columnName = IdentifierUtil.Format(memberInfo, metadata.IdentifierEscaping, metadataProvider);
string columnDefinition = $"\t{columnName} {ksqlType}{typeTranslator.ExploreAttributes(typeof(T), memberInfo, type)}";

columnDefinition += TryAttachKey<T>(statementContext.KSqlEntityType, memberInfo);
Expand Down Expand Up @@ -85,7 +85,7 @@ private void PrintCreateOrReplace<T>(StatementContext statementContext, EntityCr

private string TryAttachKey<T>(KSqlEntityType entityType, MemberInfo memberInfo)
{
var entityMetadata = modelBuilder.GetEntities().FirstOrDefault(c => c.Type == typeof(T));
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == typeof(T));

var primaryKey = entityMetadata?.PrimaryKeyMemberInfo;

Expand Down
18 changes: 9 additions & 9 deletions ksqlDb.RestApi.Client/KSql/RestApi/Statements/CreateInsert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements;

internal sealed class CreateInsert : EntityInfo
{
private readonly ModelBuilder modelBuilder;
private readonly ModelBuilder metadataProvider;

public CreateInsert(ModelBuilder modelBuilder)
: base(modelBuilder)
public CreateInsert(ModelBuilder metadataProvider)
: base(metadataProvider)
{
this.modelBuilder = modelBuilder;
this.metadataProvider = metadataProvider;
}

internal string Generate<T>(T entity, InsertProperties? insertProperties = null)
Expand Down Expand Up @@ -51,11 +51,11 @@ internal string Generate<T>(InsertValues<T> insertValues, InsertProperties? inse
valuesStringBuilder.Append(", ");
}

columnsStringBuilder.Append(memberInfo.Format(insertProperties.IdentifierEscaping, modelBuilder));
columnsStringBuilder.Append(memberInfo.Format(insertProperties.IdentifierEscaping, metadataProvider));

var type = GetMemberType(memberInfo);

var value = GetValue(insertValues, insertProperties, memberInfo, type, mi => IdentifierUtil.Format(mi, insertProperties.IdentifierEscaping, modelBuilder));
var value = GetValue(insertValues, insertProperties, memberInfo, type, mi => IdentifierUtil.Format(mi, insertProperties.IdentifierEscaping, metadataProvider));

valuesStringBuilder.Append(value);
}
Expand All @@ -69,14 +69,14 @@ internal string Generate<T>(InsertValues<T> insertValues, InsertProperties? inse
private object GetValue<T>(InsertValues<T> insertValues, InsertProperties insertProperties,
MemberInfo memberInfo, Type type, Func<MemberInfo, string> formatter)
{
var hasValue = insertValues.PropertyValues.ContainsKey(memberInfo.Format(insertProperties.IdentifierEscaping, modelBuilder));
var hasValue = insertValues.PropertyValues.ContainsKey(memberInfo.Format(insertProperties.IdentifierEscaping, metadataProvider));

object value;

if (hasValue)
value = insertValues.PropertyValues[memberInfo.Format(insertProperties.IdentifierEscaping, modelBuilder)];
value = insertValues.PropertyValues[memberInfo.Format(insertProperties.IdentifierEscaping, metadataProvider)];
else
value = new CreateKSqlValue(modelBuilder).ExtractValue(insertValues.Entity, insertProperties, memberInfo, type, formatter);
value = new CreateKSqlValue(metadataProvider).ExtractValue(insertValues.Entity, insertProperties, memberInfo, type, formatter);

return value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements;

#nullable disable
internal sealed class CreateKSqlValue(ModelBuilder modelBuilder) : EntityInfo(modelBuilder)
internal sealed class CreateKSqlValue(ModelBuilder metadataProvider) : EntityInfo(metadataProvider)
{
public object ExtractValue<T>(T inputValue, IValueFormatters valueFormatters, MemberInfo memberInfo, Type type, Func<MemberInfo, string> formatter)
{
Expand Down
4 changes: 2 additions & 2 deletions ksqlDb.RestApi.Client/KSql/RestApi/Statements/EntityInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements;

internal class EntityInfo(ModelBuilder modelBuilder)
internal class EntityInfo(IMetadataProvider metadataProvider)
{
protected static readonly EntityProvider EntityProvider = new();

Expand All @@ -24,7 +24,7 @@ protected IEnumerable<MemberInfo> Members(Type type, bool? includeReadOnly = nul
.OfType<MemberInfo>()
.Concat(fields);

var entityMetadata = modelBuilder.GetEntities().FirstOrDefault(c => c.Type == type);
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == type);

return properties.Where(c =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

namespace ksqlDB.RestApi.Client.KSql.RestApi.Statements
{
internal sealed class KSqlTypeTranslator(ModelBuilder modelBuilder) : EntityInfo(modelBuilder)
internal sealed class KSqlTypeTranslator(IMetadataProvider metadataProvider) : EntityInfo(metadataProvider)
{
private readonly ModelBuilder modelBuilder = modelBuilder;
private readonly DecimalTypeTranslator decimalTypeTranslator = new(modelBuilder);
private readonly IMetadataProvider metadataProvider = metadataProvider;
private readonly DecimalTypeTranslator decimalTypeTranslator = new(metadataProvider);

internal string Translate(Type type, IdentifierEscaping escaping = IdentifierEscaping.Never)
{
Expand Down Expand Up @@ -107,7 +107,7 @@ internal IEnumerable<string> GetProperties(Type type, IdentifierEscaping escapin

var ksqlType = Translate(memberType, escaping);

string columnDefinition = $"{memberInfo.Format(escaping, modelBuilder)} {ksqlType}{ExploreAttributes(type, memberInfo, memberType)}";
string columnDefinition = $"{memberInfo.Format(escaping, metadataProvider as ModelBuilder)} {ksqlType}{ExploreAttributes(type, memberInfo, memberType)}";

Check warning on line 110 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/KSqlTypeTranslator.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'modelBuilder' in 'string MemberInfoExtensions.Format(MemberInfo memberInfo, IdentifierEscaping escaping, ModelBuilder modelBuilder)'.

Check warning on line 110 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/KSqlTypeTranslator.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'modelBuilder' in 'string MemberInfoExtensions.Format(MemberInfo memberInfo, IdentifierEscaping escaping, ModelBuilder modelBuilder)'.

Check warning on line 110 in ksqlDb.RestApi.Client/KSql/RestApi/Statements/KSqlTypeTranslator.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'modelBuilder' in 'string MemberInfoExtensions.Format(MemberInfo memberInfo, IdentifierEscaping escaping, ModelBuilder modelBuilder)'.

ksqlProperties.Add(columnDefinition);
}
Expand All @@ -132,7 +132,7 @@ internal string ExploreAttributes(Type? parentType, MemberInfo memberInfo, Type

private string TryGetHeaderMarker(Type? parentType, MemberInfo memberInfo)
{
var entityMetadata = modelBuilder.GetEntities().FirstOrDefault(c => c.Type == parentType);
var entityMetadata = metadataProvider.GetEntities().FirstOrDefault(c => c.Type == parentType);
var fieldMetadata = entityMetadata?.GetFieldMetadataBy(memberInfo);

if (fieldMetadata?.HasHeaders ?? false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace ksqlDb.RestApi.Client.KSql.RestApi.Statements.Translators
{
internal class DecimalTypeTranslator(ModelBuilder modelBuilder)
internal class DecimalTypeTranslator(IMetadataProvider modelBuilder)
{
internal bool TryGetDecimal(Type? parentType, MemberInfo memberInfo, out string? @decimal)
{
Expand Down

0 comments on commit 9ebae01

Please sign in to comment.