Replace BLite with Surreal embedded persistence
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m21s
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.CBDDC.Persistence.Surreal;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes Surreal schema objects required by CBDDC persistence stores.
|
||||
/// </summary>
|
||||
public sealed class CBDDCSurrealSchemaInitializer : ICBDDCSurrealSchemaInitializer
|
||||
{
|
||||
private static readonly Regex IdentifierRegex = new("^[A-Za-z_][A-Za-z0-9_]*$", RegexOptions.Compiled);
|
||||
private readonly SemaphoreSlim _initializeGate = new(1, 1);
|
||||
private readonly ICBDDCSurrealEmbeddedClient _surrealClient;
|
||||
private readonly ILogger<CBDDCSurrealSchemaInitializer> _logger;
|
||||
private readonly string _checkpointTable;
|
||||
private readonly string _changefeedRetentionLiteral;
|
||||
private bool _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CBDDCSurrealSchemaInitializer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="surrealClient">Surreal client abstraction.</param>
|
||||
/// <param name="options">Embedded options.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public CBDDCSurrealSchemaInitializer(
|
||||
ICBDDCSurrealEmbeddedClient surrealClient,
|
||||
CBDDCSurrealEmbeddedOptions options,
|
||||
ILogger<CBDDCSurrealSchemaInitializer>? logger = null)
|
||||
{
|
||||
_surrealClient = surrealClient ?? throw new ArgumentNullException(nameof(surrealClient));
|
||||
_logger = logger ?? NullLogger<CBDDCSurrealSchemaInitializer>.Instance;
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (options.Cdc == null) throw new ArgumentException("CDC options are required.", nameof(options));
|
||||
|
||||
_checkpointTable = EnsureValidIdentifier(options.Cdc.CheckpointTable, nameof(options.Cdc.CheckpointTable));
|
||||
_changefeedRetentionLiteral = ToSurrealDurationLiteral(
|
||||
options.Cdc.RetentionDuration,
|
||||
nameof(options.Cdc.RetentionDuration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task EnsureInitializedAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
await _initializeGate.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
string schemaSql = BuildSchemaSql();
|
||||
await _surrealClient.RawQueryAsync(schemaSql, cancellationToken: cancellationToken);
|
||||
|
||||
_initialized = true;
|
||||
_logger.LogInformation(
|
||||
"Surreal schema initialized with checkpoint table '{CheckpointTable}'.",
|
||||
_checkpointTable);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_initializeGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildSchemaSql()
|
||||
{
|
||||
return $"""
|
||||
DEFINE TABLE OVERWRITE {CBDDCSurrealSchemaNames.OplogEntriesTable} SCHEMAFULL CHANGEFEED {_changefeedRetentionLiteral};
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.OplogHashIndex} ON TABLE {CBDDCSurrealSchemaNames.OplogEntriesTable} COLUMNS hash UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.OplogHlcIndex} ON TABLE {CBDDCSurrealSchemaNames.OplogEntriesTable} COLUMNS timestampPhysicalTime, timestampLogicalCounter, timestampNodeId;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.OplogCollectionIndex} ON TABLE {CBDDCSurrealSchemaNames.OplogEntriesTable} COLUMNS collection;
|
||||
|
||||
DEFINE TABLE OVERWRITE {CBDDCSurrealSchemaNames.SnapshotMetadataTable} SCHEMAFULL CHANGEFEED {_changefeedRetentionLiteral};
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.SnapshotNodeIdIndex} ON TABLE {CBDDCSurrealSchemaNames.SnapshotMetadataTable} COLUMNS nodeId UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.SnapshotHlcIndex} ON TABLE {CBDDCSurrealSchemaNames.SnapshotMetadataTable} COLUMNS timestampPhysicalTime, timestampLogicalCounter;
|
||||
|
||||
DEFINE TABLE OVERWRITE {CBDDCSurrealSchemaNames.RemotePeerConfigurationsTable} SCHEMAFULL CHANGEFEED {_changefeedRetentionLiteral};
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.PeerNodeIdIndex} ON TABLE {CBDDCSurrealSchemaNames.RemotePeerConfigurationsTable} COLUMNS nodeId UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.PeerEnabledIndex} ON TABLE {CBDDCSurrealSchemaNames.RemotePeerConfigurationsTable} COLUMNS isEnabled;
|
||||
|
||||
DEFINE TABLE OVERWRITE {CBDDCSurrealSchemaNames.DocumentMetadataTable} SCHEMAFULL CHANGEFEED {_changefeedRetentionLiteral};
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.DocumentMetadataCollectionKeyIndex} ON TABLE {CBDDCSurrealSchemaNames.DocumentMetadataTable} COLUMNS collection, key UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.DocumentMetadataHlcIndex} ON TABLE {CBDDCSurrealSchemaNames.DocumentMetadataTable} COLUMNS hlcPhysicalTime, hlcLogicalCounter, hlcNodeId;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.DocumentMetadataCollectionIndex} ON TABLE {CBDDCSurrealSchemaNames.DocumentMetadataTable} COLUMNS collection;
|
||||
|
||||
DEFINE TABLE OVERWRITE {CBDDCSurrealSchemaNames.PeerOplogConfirmationsTable} SCHEMAFULL CHANGEFEED {_changefeedRetentionLiteral};
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.PeerConfirmationPairIndex} ON TABLE {CBDDCSurrealSchemaNames.PeerOplogConfirmationsTable} COLUMNS peerNodeId, sourceNodeId UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.PeerConfirmationActiveIndex} ON TABLE {CBDDCSurrealSchemaNames.PeerOplogConfirmationsTable} COLUMNS isActive;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.PeerConfirmationSourceHlcIndex} ON TABLE {CBDDCSurrealSchemaNames.PeerOplogConfirmationsTable} COLUMNS sourceNodeId, confirmedWall, confirmedLogic;
|
||||
|
||||
DEFINE TABLE OVERWRITE {_checkpointTable} SCHEMAFULL;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.CdcCheckpointConsumerIndex} ON TABLE {_checkpointTable} COLUMNS consumerId UNIQUE;
|
||||
DEFINE INDEX OVERWRITE {CBDDCSurrealSchemaNames.CdcCheckpointVersionstampIndex} ON TABLE {_checkpointTable} COLUMNS versionstampCursor;
|
||||
""";
|
||||
}
|
||||
|
||||
private static string EnsureValidIdentifier(string? identifier, string argumentName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
throw new ArgumentException("Surreal identifier is required.", argumentName);
|
||||
|
||||
if (!IdentifierRegex.IsMatch(identifier))
|
||||
throw new ArgumentException(
|
||||
$"Invalid Surreal identifier '{identifier}'. Use letters, numbers, and underscores only.",
|
||||
argumentName);
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private static string ToSurrealDurationLiteral(TimeSpan duration, string argumentName)
|
||||
{
|
||||
if (duration <= TimeSpan.Zero)
|
||||
throw new ArgumentOutOfRangeException(argumentName, "Surreal changefeed retention duration must be positive.");
|
||||
|
||||
if (duration.TotalDays >= 1 && duration.TotalDays == Math.Truncate(duration.TotalDays))
|
||||
return $"{(long)duration.TotalDays}d";
|
||||
|
||||
if (duration.TotalHours >= 1 && duration.TotalHours == Math.Truncate(duration.TotalHours))
|
||||
return $"{(long)duration.TotalHours}h";
|
||||
|
||||
if (duration.TotalMinutes >= 1 && duration.TotalMinutes == Math.Truncate(duration.TotalMinutes))
|
||||
return $"{(long)duration.TotalMinutes}m";
|
||||
|
||||
if (duration.TotalSeconds >= 1 && duration.TotalSeconds == Math.Truncate(duration.TotalSeconds))
|
||||
return $"{(long)duration.TotalSeconds}s";
|
||||
|
||||
long totalMs = checked((long)Math.Ceiling(duration.TotalMilliseconds));
|
||||
return $"{totalMs}ms";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user