Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identity column for wrapped primitives #189

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 111 additions & 0 deletions source/Nevermore.IntegrationTests/Advanced/CompositeIdentityFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Data;
using System.Data.Common;
using FluentAssertions;
using Nevermore.IntegrationTests.SetUp;
using Nevermore.Mapping;
using NUnit.Framework;
using Microsoft.Data.SqlClient.Server;
using Nevermore.Advanced.TypeHandlers;

namespace Nevermore.IntegrationTests.Advanced
{
[TestFixture]
public class CompositeIdentityFixture : FixtureWithRelationalStore
{
public override void OneTimeSetUp()
{
base.OneTimeSetUp();

ExecuteSql("create table TestSchema.DatabaseModel (Id INT IDENTITY, Name varchar(12))");
Mappings.Register(new DatabaseModelMap());
Configuration.TypeHandlers.Register(new CompositeTypeHandler());
}

class Composite
{
public int Value { get; }

public Composite(int value)
{
Value = value;
}
}

class DatabaseModel
{
public Composite Id { get; private set; }
public string Name { get; private set; }

DatabaseModel()
{
}

public DatabaseModel(string name)
{
Name = name;
}
}

class DatabaseModelMap : DocumentMap<DatabaseModel>
{
public DatabaseModelMap()
{
Id(m => m.Id)
.Identity()
.KeyHandler(new CompositePrimaryKeyHandler());
Column(m => m.Name);
JsonStorageFormat = JsonStorageFormat.NoJson;
}
}
class CompositeTypeHandler : ITypeHandler
{
public bool CanConvert(Type objectType)
{
return objectType == typeof(Composite);
}

public object ReadDatabase(DbDataReader reader, int columnIndex)
{
if (reader.IsDBNull(columnIndex))
return default(Composite);
var value = reader.GetInt32(columnIndex);
return new Composite(value);
}

public void WriteDatabase(DbParameter parameter, object value)
{
parameter.Value = ((Composite) value)?.Value;
}
}
class CompositePrimaryKeyHandler : PrimaryKeyHandler<Composite>
{
public override SqlMetaData GetSqlMetaData(string name) => new SqlMetaData(name, SqlDbType.Int);

public override object GetNextKey(IKeyAllocator keyAllocator, string tableName)
{
return keyAllocator.NextId(tableName);
}
}

[Test]
public void ShouldRoundTrip()
{
var m = new DatabaseModel("Test");
using (var writeTransaction = Store.BeginWriteTransaction())
{
writeTransaction.Insert(m);
writeTransaction.Commit();
}

using (var readTransaction = Store.BeginReadTransaction())
{
readTransaction.Load<DatabaseModel>(m.Id.Value)
.Should()
.NotBeNull();
}
}
}


}
4 changes: 2 additions & 2 deletions source/Nevermore/Advanced/ReadTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,8 @@ protected async Task<TResult[]> ReadResultsAsync<TResult>(PreparedCommand prepar
if (mapping.IdColumn is null)
throw new InvalidOperationException($"Cannot load {mapping.Type.Name} by Id, as no Id column has been mapped.");

if (mapping.IdColumn.Type != typeof(TKey))
throw new ArgumentException($"Provided Id of type '{id?.GetType().FullName}' does not match configured type of '{mapping.IdColumn?.Type.FullName}'.");
// if (mapping.IdColumn.Type != typeof(TKey))
// throw new ArgumentException($"Provided Id of type '{id?.GetType().FullName}' does not match configured type of '{mapping.IdColumn?.Type.FullName}'.");

var columnNames = GetColumnNames(mapping.SchemaName, mapping.TableName);
var tableName = mapping.TableName;
Expand Down
11 changes: 8 additions & 3 deletions source/Nevermore/Advanced/WriteTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ DataModificationOutput[] ExecuteDataModification(PreparedCommand command)
//The results need to be read eagerly so errors are raised while code is still executing within CommandExecutor error handling logic
return ReadResults(command,
reader => DataModificationOutput.Read(reader, command.Mapping,
command.Operation == RetriableOperation.Insert));
command.Operation == RetriableOperation.Insert, configuration));
}

DataModificationOutput ExecuteSingleDataModification(PreparedCommand command)
Expand Down Expand Up @@ -365,7 +365,7 @@ class DataModificationOutput
public byte[] RowVersion { get; private set; }
public object Id { get; private set; }

public static DataModificationOutput Read(DbDataReader reader, DocumentMap map, bool isInsert)
public static DataModificationOutput Read(DbDataReader reader, DocumentMap map, bool isInsert, IRelationalStoreConfiguration configuration)
{
var output = new DataModificationOutput();

Expand All @@ -374,7 +374,12 @@ public static DataModificationOutput Read(DbDataReader reader, DocumentMap map,
reader.GetFieldValue<byte[]>(map.RowVersionColumn!.ColumnName);

if (map.IsIdentityId && isInsert)
output.Id = reader.GetFieldValue<object>(map.IdColumn!.ColumnName);
{
var typeHandler = configuration.TypeHandlers.Resolve(map.IdColumn.Type);
output.Id = typeHandler == null
? reader.GetFieldValue<object>(map.IdColumn!.ColumnName)
: typeHandler.ReadDatabase(reader, 0);
}

return output;
}
Expand Down
37 changes: 16 additions & 21 deletions source/Nevermore/Mapping/IdColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;

namespace Nevermore.Mapping
Expand All @@ -19,19 +20,25 @@ internal IdColumnMapping(IdColumnMappingBuilder idColumn, IPrimaryKeyHandler pri
public bool IsIdentity { get; }

public IPrimaryKeyHandler PrimaryKeyHandler { get; }
}

public class IdColumnMappingBuilder : ColumnMapping, IIdColumnMappingBuilder
{
static readonly HashSet<Type> ValidIdentityTypes = new HashSet<Type>

static readonly HashSet<SqlDbType> ValidIdentityTypes = new ()
{
typeof(short),
typeof(int),
typeof(long)
SqlDbType.SmallInt,
SqlDbType.Int,
SqlDbType.BigInt
};

bool hasCustomPropertyHandler;
void ValidateForIdentityUse()
{
if (!IsIdentity)
return;
if (!ValidIdentityTypes.Contains(PrimaryKeyHandler.GetSqlMetaData(ColumnName).SqlDbType))
throw new InvalidOperationException($"The type {Type.Name} is not supported for Identity columns. Identity columns must be one of 'short', 'int' or 'long'.");
}
}

public class IdColumnMappingBuilder : ColumnMapping, IIdColumnMappingBuilder
{
internal IdColumnMappingBuilder(string columnName, Type type, IPropertyHandler handler, PropertyInfo property) : base(columnName, type, handler, property)
{
}
Expand All @@ -43,8 +50,6 @@ internal IdColumnMappingBuilder(string columnName, Type type, IPropertyHandler h
/// <inheritdoc cref="IIdColumnMappingBuilder"/>
public IIdColumnMappingBuilder Identity()
{
ValidateForIdentityUse();

IsIdentity = true;
Direction = ColumnDirection.FromDatabase;

Expand All @@ -57,21 +62,11 @@ public IIdColumnMappingBuilder KeyHandler(IPrimaryKeyHandler primaryKeyHandler)
return this;
}

void ValidateForIdentityUse()
{
if (!ValidIdentityTypes.Contains(Type))
throw new InvalidOperationException($"The type {Type.Name} is not supported for Identity columns. Identity columns must be one of 'short', 'int' or 'long'.");

if (hasCustomPropertyHandler)
throw new InvalidOperationException("Unable to configure an Identity Id column with a custom PropertyHandler");
}

protected override void SetCustomPropertyHandler(IPropertyHandler propertyHandler)
{
if (Direction == ColumnDirection.FromDatabase)
throw new InvalidOperationException("Unable to configure an Identity Id column with a custom PropertyHandler");

hasCustomPropertyHandler = true;
base.SetCustomPropertyHandler(propertyHandler);
}

Expand Down
2 changes: 1 addition & 1 deletion source/Nevermore/Util/DataModificationQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ void AppendInsertStatement(StringBuilder sb, DocumentMap mapping, string tableNa
outputColumns.Add(mapping.RowVersionColumn.ColumnName, "binary(8)");

if (mapping.IsIdentityId)
outputColumns.Add(mapping.IdColumn.ColumnName, mapping.IdColumn.Type.GetIdentityIdTypeName());
outputColumns.Add(mapping.IdColumn.ColumnName, mapping.IdColumn.PrimaryKeyHandler.GetSqlMetaData(mapping.IdColumn.ColumnName).SqlDbType.ToString());

outputStatement = $"OUTPUT {string.Join(",", outputColumns.Select(kvp => $"inserted.[{kvp.Key}]"))} INTO @InsertedRows";
outputVariable = $"DECLARE @InsertedRows TABLE ({string.Join(", ", outputColumns.Select(kvp => $"[{kvp.Key}] {kvp.Value}"))})";
Expand Down