using ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common; using System.Collections.Frozen; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer; /// /// for the SQL Server historian. Supports every /// and HistoryUpdate. All seams share ONE connection helper, ONE schema /// bootstrapper and ONE tag catalog, so the schema is ensured once and tag ids are cached once. /// /// /// The shared state is built on the first Create* call, not in the constructor: the constructor does no /// validation and no I/O, so resolving the provider for a backend nobody consumes never fails on its options /// (they are validated, consumer-gated, by the host's ServerHistorianOptionsValidator). The first /// Create* call's service provider supplies the logger factory the bootstrapper logs through. /// public sealed class SqlServerHistorianProvider : IHistorianBackendProvider { private static readonly FrozenSet Kinds = Enum.GetValues().ToFrozenSet(); private readonly SqlServerHistorianOptions _options; private readonly object _initLock = new(); private Shared? _shared; /// Creates the provider over the ServerHistorian:SqlServer options (validated elsewhere). /// The ServerHistorian:SqlServer options. /// The shared ServerHistorian:MaxTimeWeightedRawRows cap (<= 0 = unbounded). public SqlServerHistorianProvider(SqlServerHistorianOptions options, int maxTimeWeightedRawRows = TimeWeightedAggregator.DefaultMaxWindowRows) { _options = options ?? throw new ArgumentNullException(nameof(options)); _maxTimeWeightedRawRows = maxTimeWeightedRawRows; } private readonly int _maxTimeWeightedRawRows; /// public string Name => "SqlServer"; /// public IReadOnlySet SupportedKinds => Kinds; private static ILoggerFactory Loggers(IServiceProvider sp) => (sp.GetService(typeof(ILoggerFactory)) as ILoggerFactory) ?? NullLoggerFactory.Instance; private Shared Get(IServiceProvider sp) { if (Volatile.Read(ref _shared) is { } built) return built; lock (_initLock) { if (_shared is { } raced) return raced; var conn = new SqlHistorianConnection(_options); var bootstrap = new SqlServerSchemaBootstrapper(conn, _options, Loggers(sp).CreateLogger()); var shared = new Shared(conn, bootstrap, new SqlServerTagCatalog(conn, bootstrap)); Volatile.Write(ref _shared, shared); return shared; } } /// public IHistorianDataSource CreateDataSource(IServiceProvider sp) { var s = Get(sp); return new SqlServerHistorianDataSource(s.Conn, s.Bootstrap, s.Catalog, Loggers(sp).CreateLogger(), maxTimeWeightedRawRows: _maxTimeWeightedRawRows); } /// public IHistorianValueWriter CreateValueWriter(IServiceProvider sp) { var s = Get(sp); return new SqlServerHistorianValueWriter(s.Conn, s.Bootstrap, s.Catalog, Loggers(sp).CreateLogger()); } /// public IAlarmHistorianWriter CreateAlarmWriter(IServiceProvider sp) { var s = Get(sp); return new SqlServerAlarmHistorianWriter(s.Conn, s.Bootstrap, Loggers(sp).CreateLogger()); } /// public IHistorianProvisioning CreateProvisioning(IServiceProvider sp) { var s = Get(sp); return new SqlServerTagProvisioner(s.Conn, s.Bootstrap, Loggers(sp).CreateLogger()); } /// public IHistorianDataUpdater? CreateUpdater(IServiceProvider sp) { var s = Get(sp); return new SqlServerHistorianDataUpdater(s.Conn, s.Bootstrap, s.Catalog); } /// public IHistorianRetention? CreateRetention(IServiceProvider sp) { if (_options.RetentionDays is not { } days) return null; var s = Get(sp); return new SqlServerHistorianRetention(s.Conn, s.Bootstrap, _options.PurgeInterval, days, _options.PurgeBatchSize, Loggers(sp).CreateLogger()); } private sealed record Shared(SqlHistorianConnection Conn, SqlServerSchemaBootstrapper Bootstrap, SqlServerTagCatalog Catalog); }