feat(historian): SQL Server backend skeleton - schema DDL, locked bootstrapper, options, connection/mapper/tag-catalog helpers, status consts, env-gated fixture
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Options for the SQL Server historian backend (the <c>ServerHistorian:SqlServer</c> config block).
|
||||
/// </summary>
|
||||
public sealed partial class SqlServerHistorianOptions
|
||||
{
|
||||
/// <summary>Required. ADO.NET connection string for the historian database (never the ConfigDb).</summary>
|
||||
public string ConnectionString { get; set; } = "";
|
||||
|
||||
/// <summary>SQL schema holding the historian objects. Spliced into DDL/DML, so it is regex-validated.</summary>
|
||||
public string Schema { get; set; } = "hist";
|
||||
|
||||
/// <summary>Per-command timeout. Must be positive.</summary>
|
||||
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> creates the schema and applies missing scripts on first use; <c>false</c> only verifies
|
||||
/// that schema version 1 exists and fails every historian call with a clear error otherwise.
|
||||
/// </summary>
|
||||
public bool AutoCreateSchema { get; set; } = true;
|
||||
|
||||
[GeneratedRegex("^[A-Za-z_][A-Za-z0-9_]{0,127}$")]
|
||||
private static partial Regex IdentifierRegex();
|
||||
|
||||
/// <summary>True when <paramref name="schema"/> is a safe SQL identifier.</summary>
|
||||
/// <param name="schema">Candidate schema name.</param>
|
||||
/// <returns>Whether it matches <c>^[A-Za-z_][A-Za-z0-9_]{0,127}$</c>.</returns>
|
||||
public static bool IsValidSchemaName(string? schema) => schema is not null && IdentifierRegex().IsMatch(schema);
|
||||
|
||||
/// <summary>Validates the options without touching SQL.</summary>
|
||||
/// <returns>One message per problem; empty when valid.</returns>
|
||||
public IReadOnlyList<string> Validate()
|
||||
{
|
||||
var errors = new List<string>();
|
||||
if (string.IsNullOrWhiteSpace(ConnectionString))
|
||||
errors.Add("ServerHistorian:SqlServer:ConnectionString is required.");
|
||||
if (!IsValidSchemaName(Schema))
|
||||
errors.Add("ServerHistorian:SqlServer:Schema must be a SQL identifier matching ^[A-Za-z_][A-Za-z0-9_]{0,127}$.");
|
||||
if (CommandTimeout <= TimeSpan.Zero)
|
||||
errors.Add("ServerHistorian:SqlServer:CommandTimeout must be greater than zero.");
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Thrown when <c>AutoCreateSchema=false</c> and the historian schema is absent or behind.</summary>
|
||||
public sealed class HistorianSchemaMissingException : Exception
|
||||
{
|
||||
/// <summary>Creates the exception.</summary>
|
||||
/// <param name="message">Message naming the missing version.</param>
|
||||
public HistorianSchemaMissingException(string message) : base(message) { }
|
||||
}
|
||||
Reference in New Issue
Block a user