-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
322 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
using Azure.Data.Tables; | ||
|
||
using TableStorage.Abstracts.Extensions; | ||
|
||
namespace TableStorage.Abstracts; | ||
|
||
/// <summary> | ||
/// Key generation helper methods | ||
/// </summary> | ||
public static class KeyGenerator | ||
{ | ||
private const string PartitionKeyName = nameof(ITableEntity.PartitionKey); | ||
|
||
/// <summary> | ||
/// Generates the PartitionKey based on the specified <paramref name="eventTime"/> timestamp | ||
/// </summary> | ||
/// <param name="eventTime">The event time.</param> | ||
/// <param name="roundSpan">The round span.</param> | ||
/// <returns> | ||
/// The Generated PartitionKey | ||
/// </returns> | ||
/// <remarks> | ||
/// The partition key based on the Timestamp rounded to the nearest 5 min | ||
/// </remarks> | ||
public static string GeneratePartitionKey(DateTimeOffset eventTime, TimeSpan? roundSpan = null) | ||
{ | ||
var span = roundSpan ?? TimeSpan.FromMinutes(5); | ||
var dateTime = eventTime.ToUniversalTime(); | ||
var roundedEvent = dateTime.Round(span); | ||
|
||
// create a 19 character String for reverse chronological ordering. | ||
return $"{DateTimeOffset.MaxValue.Ticks - roundedEvent.Ticks:D19}"; | ||
} | ||
|
||
/// <summary> | ||
/// Generates the PartitionKey based on the specified <paramref name="eventTime"/> timestamp | ||
/// </summary> | ||
/// <param name="eventTime">The event time.</param> | ||
/// <param name="roundSpan">The round span.</param> | ||
/// <returns> | ||
/// The Generated PartitionKey | ||
/// </returns> | ||
/// <remarks> | ||
/// The partition key based on the Timestamp rounded to the nearest 5 min | ||
/// </remarks> | ||
public static string GeneratePartitionKey(DateTime eventTime, TimeSpan? roundSpan = null) | ||
{ | ||
var dateTime = eventTime.ToUniversalTime(); | ||
var dateTimeOffset = new DateTimeOffset(dateTime, TimeSpan.Zero); | ||
|
||
return GeneratePartitionKey(dateTimeOffset, roundSpan); | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Generates the RowKey using a reverse chronological ordering date, newest logs sorted first | ||
/// </summary> | ||
/// <param name="eventTime">The event time.</param> | ||
/// <returns> | ||
/// The generated RowKey | ||
/// </returns> | ||
public static string GenerateRowKey(DateTimeOffset eventTime) | ||
{ | ||
var dateTime = eventTime.ToUniversalTime(); | ||
|
||
// create a reverse chronological ordering date, newest logs sorted first | ||
var timestamp = dateTime.ToReverseChronological(); | ||
|
||
// use Ulid for speed and efficiency | ||
return Ulid.NewUlid(timestamp).ToString(); | ||
} | ||
|
||
/// <summary> | ||
/// Generates the RowKey using a reverse chronological ordering date, newest logs sorted first | ||
/// </summary> | ||
/// <param name="eventTime">The event time.</param> | ||
/// <returns> | ||
/// The generated RowKey | ||
/// </returns> | ||
public static string GenerateRowKey(DateTime eventTime) | ||
{ | ||
var dateTime = eventTime.ToUniversalTime(); | ||
var dateTimeOffset = new DateTimeOffset(dateTime, TimeSpan.Zero); | ||
|
||
return GenerateRowKey(dateTimeOffset); | ||
} | ||
|
||
|
||
#if NET6_0_OR_GREATER | ||
/// <summary> | ||
/// Generates the partition key query using the specified <paramref name="date"/>. | ||
/// </summary> | ||
/// <param name="date">The date to use for query.</param> | ||
/// <param name="offset">The date's offset from Coordinated Universal Time (UTC).</param> | ||
/// <returns>An Azure Table partiion key query.</returns> | ||
public static string GeneratePartitionKeyQuery(DateOnly date, TimeSpan offset) | ||
{ | ||
// date is assumed to be in local time, will be converted to UTC | ||
var eventTime = new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, offset); | ||
return GeneratePartitionKeyQuery(eventTime); | ||
} | ||
|
||
/// <summary> | ||
/// Generates the partition key query using the specified <paramref name="date"/>. | ||
/// </summary> | ||
/// <param name="date">The date to use for query.</param> | ||
/// <param name="zone">The time zone the date is in.</param> | ||
/// <returns>An Azure Table partiion key query.</returns> | ||
public static string GeneratePartitionKeyQuery(DateOnly date, TimeZoneInfo? zone = null) | ||
{ | ||
// date is assumed to be in local time, will be converted to UTC | ||
zone ??= TimeZoneInfo.Local; | ||
|
||
var dateTime = date.ToDateTime(TimeOnly.MinValue); | ||
var offset = zone.GetUtcOffset(dateTime); | ||
|
||
var eventTime = new DateTimeOffset(dateTime, offset); | ||
return GeneratePartitionKeyQuery(eventTime); | ||
} | ||
#endif | ||
|
||
/// <summary> | ||
/// Generates the partition key query using the specified <paramref name="eventTime"/>. | ||
/// </summary> | ||
/// <param name="eventTime">The date to use for query.</param> | ||
/// <returns>An Azure Table partiion key query.</returns> | ||
public static string GeneratePartitionKeyQuery(DateTime eventTime) | ||
{ | ||
var dateTime = eventTime.ToUniversalTime(); | ||
var dateTimeOffset = new DateTimeOffset(dateTime, TimeSpan.Zero); | ||
|
||
return GeneratePartitionKeyQuery(dateTimeOffset); | ||
} | ||
|
||
/// <summary> | ||
/// Generates the partition key query using the specified <paramref name="eventTime"/>. | ||
/// </summary> | ||
/// <param name="eventTime">The date to use for query.</param> | ||
/// <returns>An Azure Table partiion key query.</returns> | ||
public static string GeneratePartitionKeyQuery(DateTimeOffset eventTime) | ||
{ | ||
var dateTime = eventTime.ToUniversalTime(); | ||
|
||
var upper = dateTime.ToReverseChronological().Ticks.ToString("D19"); | ||
var lower = dateTime.AddDays(1).ToReverseChronological().Ticks.ToString("D19"); | ||
|
||
return $"({PartitionKeyName} ge '{lower}') and ({PartitionKeyName} lt '{upper}')"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using TableStorage.Abstracts.Extensions; | ||
|
||
namespace TableStorage.Abstracts.Tests; | ||
|
||
public class KeyGeneratorTests | ||
{ | ||
private readonly ITestOutputHelper _output; | ||
|
||
public KeyGeneratorTests(ITestOutputHelper output) | ||
{ | ||
_output = output; | ||
} | ||
|
||
|
||
[Fact] | ||
public void GenerateRowKeyDateTimeOffsetNow() | ||
{ | ||
var dateTime = new DateTimeOffset(2024, 4, 1, 23, 0, 0, TimeSpan.FromHours(-5)); | ||
|
||
var rowKey = KeyGenerator.GenerateRowKey(dateTime); | ||
rowKey.Should().NotBeNull(); | ||
|
||
var parsed = Ulid.TryParse(rowKey, out var ulid); | ||
parsed.Should().BeTrue(); | ||
ulid.Should().NotBeNull(); | ||
|
||
var reversed = dateTime.ToUniversalTime().ToReverseChronological(); | ||
var ulidDate = ulid.Time; | ||
|
||
ulidDate.Year.Should().Be(reversed.Year); | ||
ulidDate.Month.Should().Be(reversed.Month); | ||
ulidDate.Day.Should().Be(reversed.Day); | ||
ulidDate.Hour.Should().Be(reversed.Hour); | ||
ulidDate.Minute.Should().Be(reversed.Minute); | ||
} | ||
|
||
|
||
[Fact] | ||
public void GeneratePartitionKeyDateTimeOffsetNow() | ||
{ | ||
var dateTime = new DateTimeOffset(2024, 4, 1, 23, 0, 0, TimeSpan.FromHours(-5)); | ||
|
||
var partitionKey = KeyGenerator.GeneratePartitionKey(dateTime); | ||
partitionKey.Should().NotBeNull(); | ||
partitionKey.Should().Be("2516902703999999999"); | ||
} | ||
|
||
[Fact] | ||
public void GeneratePartitionKeyDateTimeNow() | ||
{ | ||
var dateTime = new DateTimeOffset(2024, 4, 1, 23, 0, 0, TimeSpan.FromHours(-5)); | ||
var eventTime = dateTime.UtcDateTime; | ||
|
||
var partitionKey = KeyGenerator.GeneratePartitionKey(eventTime); | ||
partitionKey.Should().NotBeNull(); | ||
partitionKey.Should().Be("2516902703999999999"); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(GetDateRounding))] | ||
public void GeneratePartitionKeyDateTimeNowRound(DateTimeOffset dateTime, string expected) | ||
{ | ||
var partitionKey = KeyGenerator.GeneratePartitionKey(dateTime); | ||
partitionKey.Should().NotBeNull(); | ||
partitionKey.Should().Be(expected); | ||
} | ||
|
||
public static IEnumerable<object[]> GetDateRounding() | ||
{ | ||
yield return new object[] | ||
{ | ||
new DateTimeOffset(2024, 4, 1, 23, 1, 0, TimeSpan.FromHours(-5)), | ||
"2516902703999999999" | ||
}; | ||
yield return new object[] | ||
{ | ||
new DateTimeOffset(2024, 4, 1, 23, 2, 55, TimeSpan.FromHours(-5)), | ||
"2516902700999999999" | ||
}; | ||
yield return new object[] | ||
{ | ||
new DateTimeOffset(2024, 4, 1, 23, 3, 5, TimeSpan.FromHours(-5)), | ||
"2516902700999999999" | ||
}; | ||
yield return new object[] | ||
{ | ||
new DateTimeOffset(2024, 4, 1, 23, 4, 11, TimeSpan.FromHours(-5)), | ||
"2516902700999999999" | ||
}; | ||
yield return new object[] | ||
{ | ||
new DateTimeOffset(2024, 4, 1, 23, 4, 43, TimeSpan.FromHours(-5)), | ||
"2516902700999999999" | ||
}; | ||
} | ||
|
||
|
||
[Fact] | ||
public void GeneratePartitionKeyQueryDateOnly() | ||
{ | ||
var date = new DateOnly(2024, 4, 1); | ||
|
||
var partitionKeyQuery = KeyGenerator.GeneratePartitionKeyQuery(date, TimeSpan.FromHours(-5)); | ||
partitionKeyQuery.Should().NotBeNull(); | ||
partitionKeyQuery.Should().Be("(PartitionKey ge '2516902667999999999') and (PartitionKey lt '2516903531999999999')"); | ||
} | ||
|
||
[Fact] | ||
public void GeneratePartitionKeyQueryDateTime() | ||
{ | ||
var dateTime = new DateTimeOffset(2024, 4, 1, 0, 0, 0, TimeSpan.FromHours(-5)); | ||
var eventTime = dateTime.UtcDateTime; | ||
|
||
var partitionKeyQuery = KeyGenerator.GeneratePartitionKeyQuery(eventTime); | ||
partitionKeyQuery.Should().NotBeNull(); | ||
partitionKeyQuery.Should().Be("(PartitionKey ge '2516902667999999999') and (PartitionKey lt '2516903531999999999')"); | ||
} | ||
|
||
[Fact] | ||
public void GeneratePartitionKeyQueryDateTimeOffset() | ||
{ | ||
var dateTime = new DateTimeOffset(2024, 4, 1, 0, 0, 0, TimeSpan.FromHours(-5)); | ||
|
||
var partitionKeyQuery = KeyGenerator.GeneratePartitionKeyQuery(dateTime); | ||
partitionKeyQuery.Should().NotBeNull(); | ||
partitionKeyQuery.Should().Be("(PartitionKey ge '2516902667999999999') and (PartitionKey lt '2516903531999999999')"); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
test/TableStorage.Abstracts.Tests/LogEventRepositoryTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
using TableStorage.Abstracts.Tests.Services; | ||
|
||
namespace TableStorage.Abstracts.Tests; | ||
|
||
public class LogEventRepositoryTest : DatabaseTestBase | ||
{ | ||
public LogEventRepositoryTest(ITestOutputHelper output, DatabaseFixture databaseFixture) | ||
: base(output, databaseFixture) | ||
{ | ||
} | ||
|
||
[Fact] | ||
public async void QueryTest() | ||
{ | ||
var repository = Services.GetRequiredService<ILogEventRepository>(); | ||
repository.Should().NotBeNull(); | ||
|
||
var today = DateOnly.FromDateTime(DateTime.Now); | ||
|
||
var result = await repository.Query(today); | ||
result.Should().NotBeNull(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.