Skip to content

Commit

Permalink
change to use ULID
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Apr 10, 2024
1 parent 8e9c105 commit 1512b57
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 9 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Save or Upsert an item

Azure Table Storage requires both a `RowKey` and `PartitionKey`

The base repository will set the `RowKey` if it hasn't already been set using the `NewRowKey()` method. The default implementation is `Guid.NewGuid().ToString("N")`
The base repository will set the `RowKey` if it hasn't already been set using the `NewRowKey()` method. The default implementation is `Ulid.NewUlid().ToString()`

If `PartitionKey` hasn't been set, `RowKey` will be used.

Expand Down
54 changes: 54 additions & 0 deletions src/TableStorage.Abstracts/Extensions/DateTimeExtentions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace TableStorage.Abstracts.Extensions;

public static class DateTimeExtentions
{
/// <summary>
/// Rounds the date to the specified time span.
/// </summary>
/// <param name="date">The date to round.</param>
/// <param name="span">The time span to round to.</param>
/// <returns>The rounded date</returns>
public static DateTime Round(this DateTime date, TimeSpan span)
{
long ticks = (date.Ticks + (span.Ticks / 2) + 1) / span.Ticks;
return new DateTime(ticks * span.Ticks);
}

/// <summary>
/// Rounds the date to the specified span.
/// </summary>
/// <param name="date">The date to round.</param>
/// <param name="span">The time span to round to.</param>
/// <returns>The rounded date</returns>
public static DateTimeOffset Round(this DateTimeOffset date, TimeSpan span)
{
long ticks = (date.Ticks + (span.Ticks / 2) + 1) / span.Ticks;
return new DateTimeOffset(ticks * span.Ticks, date.Offset);
}

/// <summary>
/// Converts to specified <paramref name="dateTime"/> to its reverse chronological equivalent. DateTime.MaxValue - dateTime
/// </summary>
/// <param name="dateTime">The date time offset.</param>
/// <returns>A <see cref="DateTime"/> chronological reversed.</returns>
public static DateTime ToReverseChronological(this DateTime dateTime)
{
var targetTicks = DateTime.MaxValue.Ticks - dateTime.Ticks;
return new DateTime(targetTicks);
}

/// <summary>
/// Converts to specified <paramref name="dateTimeOffset"/> to its reverse chronological equivalent. DateTimeOffset.MaxValue - dateTimeOffset
/// </summary>
/// <param name="dateTimeOffset">The date time offset.</param>
/// <returns>A <see cref="DateTimeOffset"/> chronological reversed.</returns>
public static DateTimeOffset ToReverseChronological(this DateTimeOffset dateTimeOffset)
{
var targetTicks = DateTimeOffset.MaxValue.Ticks - dateTimeOffset.Ticks;
return new DateTimeOffset(targetTicks, TimeSpan.Zero);
}
}
2 changes: 1 addition & 1 deletion src/TableStorage.Abstracts/TableRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public TableRepository(ILoggerFactory logFactory, TableServiceClient tableServic
protected ILogger Logger { get; }

/// <inheritdoc/>
public virtual string NewRowKey() => Guid.NewGuid().ToString("N");
public virtual string NewRowKey() => Ulid.NewUlid().ToString();

/// <inheritdoc/>
public Task<TableClient> GetClientAsync() => _lazyTableClient.Value;
Expand Down
1 change: 1 addition & 0 deletions src/TableStorage.Abstracts/TableStorage.Abstracts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="ulid" Version="1.3.3" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion test/TableStorage.Abstracts.Tests/CommentRepositoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public CommentRepositoryTest(ITestOutputHelper output, DatabaseFixture databaseF
public async Task FullTest()
{
var generator = new Faker<Comment>()
.RuleFor(p => p.RowKey, _ => Guid.NewGuid().ToString("N"))
.RuleFor(p => p.RowKey, _ => Ulid.NewUlid().ToString())
.RuleFor(p => p.PartitionKey, f => f.PickRandom(Constants.Owners))
.RuleFor(p => p.Name, f => f.Name.FullName())
.RuleFor(p => p.Description, f => f.Lorem.Sentence())
Expand Down
2 changes: 1 addition & 1 deletion test/TableStorage.Abstracts.Tests/ItemRepositoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public async Task LargeResultOne()
private static Faker<Item> CreateGenerator()
{
return new Faker<Item>()
.RuleFor(p => p.RowKey, _ => Guid.NewGuid().ToString("N"))
.RuleFor(p => p.RowKey, _ => Ulid.NewUlid().ToString())
.RuleFor(p => p.PartitionKey, f => f.PickRandom(Constants.Owners))
.RuleFor(p => p.Name, f => f.Name.FullName())
.RuleFor(p => p.Description, f => f.Lorem.Sentence())
Expand Down
14 changes: 14 additions & 0 deletions test/TableStorage.Abstracts.Tests/Models/LogEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace TableStorage.Abstracts.Tests.Models;

public class LogEvent : TableEntityBase
{
public string? Level { get; set; }

public string? MessageTemplate { get; set; }

public string? RenderedMessage { get; set; }

public string? Exception { get; set; }

public string? Data { get; set; }
}
8 changes: 4 additions & 4 deletions test/TableStorage.Abstracts.Tests/RoleRepositoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task CreateRole()
{
var role = new Role
{
RowKey = Guid.NewGuid().ToString("N"),
RowKey = Ulid.NewUlid().ToString(),
Name = "CreateRole",
NormalizedName = "createrole",
Claims = [new Claim { Type = "Test", Value = "testing" }]
Expand All @@ -37,7 +37,7 @@ public async Task SaveRole()
{
var role = new Role
{
RowKey = Guid.NewGuid().ToString("N"),
RowKey = Ulid.NewUlid().ToString(),
Name = "SaveRole",
NormalizedName = "saverole"
};
Expand All @@ -55,7 +55,7 @@ public async Task CreateUpdateRole()
{
var role = new Role
{
RowKey = Guid.NewGuid().ToString("N"),
RowKey = Ulid.NewUlid().ToString(),
Name = "CreateRole",
NormalizedName = "createrole"
};
Expand All @@ -80,7 +80,7 @@ public async Task CreateReadRole()
{
var role = new Role
{
RowKey = Guid.NewGuid().ToString("N"),
RowKey = Ulid.NewUlid().ToString(),
Name = "CreateReadRole",
NormalizedName = "createreadrole"
};
Expand Down
13 changes: 13 additions & 0 deletions test/TableStorage.Abstracts.Tests/Services/ILogEventRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TableStorage.Abstracts.Tests.Models;

namespace TableStorage.Abstracts.Tests.Services;

public interface ILogEventRepository : ITableRepository<LogEvent>
{
Task<PagedResult<LogEvent>> QueryByDate(
DateOnly date,
string? level = null,
string? continuationToken = null,
int? pageSize = 100,
CancellationToken cancellationToken = default);
}
65 changes: 65 additions & 0 deletions test/TableStorage.Abstracts.Tests/Services/LogEventRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Azure.Data.Tables;

using Microsoft.Extensions.Logging;

using TableStorage.Abstracts.Extensions;
using TableStorage.Abstracts.Tests.Models;

namespace TableStorage.Abstracts.Tests.Services;

public class LogEventRepository : TableRepository<LogEvent>, ILogEventRepository
{
public LogEventRepository(ILoggerFactory logFactory, TableServiceClient tableServiceClient)
: base(logFactory, tableServiceClient)
{
}

public async Task<PagedResult<LogEvent>> QueryByDate(
DateOnly date,
string? level = null,
string? continuationToken = null,
int? pageSize = 100,
CancellationToken cancellationToken = default)
{
var baseDate = date.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);

var upperDate = baseDate.ToReverseChronological();
var lowwerDate = baseDate.AddDays(1).ToReverseChronological();

var upper = $"{upperDate.Ticks:D19}";
var lower = $"{lowwerDate.Ticks:D19}";

var filter = $"(PartitionKey ge '{lower}') and (PartitionKey lt '{upper}')";

if (level.HasValue())
filter += $" and (Level eq '{level}')";

return await FindPageAsync(filter, continuationToken, pageSize, cancellationToken);
}

public override string NewRowKey()
{
// store newest log first
var timestamp = DateTimeOffset.UtcNow.ToReverseChronological();
return Ulid.NewUlid(timestamp).ToString();
}

protected override void BeforeSave(LogEvent entity)
{
if (entity.RowKey.IsNullOrWhiteSpace())
entity.RowKey = NewRowKey();

if (entity.PartitionKey.IsNullOrWhiteSpace())
{
var timespan = entity.Timestamp ?? DateTimeOffset.UtcNow;
var roundedDate = timespan
.Round(TimeSpan.FromMinutes(5))
.ToReverseChronological();

// create a 19 character String for reverse chronological ordering.
entity.PartitionKey = $"{roundedDate.Ticks:D19}";
}
}

protected override string GetTableName() => "LogEvent";
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public TemplateRepositoryTest(ITestOutputHelper output, DatabaseFixture database
public async Task FullTest()
{
var generator = new Faker<Template>()
.RuleFor(p => p.RowKey, _ => Guid.NewGuid().ToString("N"))
.RuleFor(p => p.RowKey, _ => Ulid.NewUlid().ToString())
.RuleFor(p => p.PartitionKey, f => f.PickRandom(Constants.Owners))
.RuleFor(p => p.Name, f => f.Name.FullName())
.RuleFor(p => p.Description, f => f.Lorem.Sentence())
Expand Down
33 changes: 33 additions & 0 deletions test/TableStorage.Abstracts.Tests/UlidGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using TableStorage.Abstracts.Extensions;

namespace TableStorage.Abstracts.Tests;

public class UlidGeneratorTests
{
private readonly ITestOutputHelper _output;

public UlidGeneratorTests(ITestOutputHelper output)
{
_output = output;
}

[Fact]
public void UlidTests()
{
var timestamp = DateTimeOffset.UtcNow.ToReverseChronological();

var previousKey = Ulid.NewUlid(timestamp).ToString();

// higher dates should be lower sort
for (int i = 0; i < 100; i++)
{
timestamp = DateTimeOffset.UtcNow.ToReverseChronological();
var key = Ulid.NewUlid(timestamp).ToString();
key.Should().NotBeNull();

_output.WriteLine(key);

string.Compare(key, previousKey).Should().BeLessThan(0);
}
}
}

0 comments on commit 1512b57

Please sign in to comment.