using System.Data.Common;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests;
///
/// A test-only dialect that spells parameters the ODP.NET way — :name in the SQL text and a
/// barename as the — over the SQLite fixture. It is
/// the falsifiability control for dialect-owned parameter naming: every marker in this repo was hardcoded
/// "@" before, and a planner or loader that still hardcodes it emits @k0 against this
/// dialect and fails loudly.
/// Why the two halves must differ. A dialect whose marker and bind name are the same string
/// (SQL Server, Npgsql, SQLite) cannot tell a call site that copies the plan's bind name apart
/// from one that reuses the marker. Oracle is the provider where they genuinely diverge, so this fake
/// reproduces that divergence rather than merely changing the prefix.
/// It really executes. Everything but the parameter naming (and the catalog SQL's markers)
/// delegates to , and Microsoft.Data.Sqlite accepts both a :-prefixed
/// marker and a prefix-less parameter name — so a poll or a catalog load driven through this dialect runs
/// against the real fixture database rather than being asserted on as text.
/// A decorator rather than a subclass: is sealed, and
/// new-hiding a member would leave interface dispatch calling the original — the substitution
/// would silently not happen and the control would pass for the wrong reason.
///
public sealed class ColonMarkerDialect : ISqlDialect
{
private static readonly SqliteDialect Inner = new();
/// Every command handed to , kept so its binds can be read back.
private readonly List _commands = [];
/// How many times has been called on this instance.
///
/// Oracle's BindByName is set here, and a command created without it mis-binds by position
/// rather than failing — so "was every command configured" has to be observable to be testable.
///
public int ConfiguredCommands
{
get { lock (_commands) return _commands.Count; }
}
///
/// The of every parameter bound on every configured command,
/// read after the fact.
/// Why this and not the query's own success. Microsoft.Data.Sqlite resolves a parameter
/// whether it is named :k0 or bare k0, so a poll that returns the right values proves
/// the SQL parsed — not that the caller bound the dialect's bind name. Only the name actually put on
/// the parameter distinguishes the two, and on ODP.NET the wrong one is a hard bind failure.
///
public IReadOnlyList BoundParameterNames
{
get
{
lock (_commands)
return _commands
.SelectMany(c => c.Parameters.Cast())
.Select(p => p.ParameterName)
.ToList();
}
}
///
public SqlProvider Provider => Inner.Provider;
///
public DbProviderFactory Factory => Inner.Factory;
///
public int MaxBindParameters => Inner.MaxBindParameters;
///
public string LivenessSql => Inner.LivenessSql;
///
public string SingleRowLimitPrefix => Inner.SingleRowLimitPrefix;
///
public string SingleRowLimitSuffix => Inner.SingleRowLimitSuffix;
///
public string ListSchemasSql => Colonize(Inner.ListSchemasSql);
///
public string DefaultSchemaSql => Colonize(Inner.DefaultSchemaSql);
///
public string ListTablesSql => Colonize(Inner.ListTablesSql);
///
public string ListColumnsSql => Colonize(Inner.ListColumnsSql);
///
public string ParameterMarker(string baseName) => ":" + baseName;
/// The bare name, with no prefix — ODP.NET's convention, and deliberately NOT the marker.
/// The planner's/loader's base name.
/// The bind name.
public string ParameterBindName(string baseName) => baseName;
///
public void ConfigureCommand(DbCommand command)
{
ArgumentNullException.ThrowIfNull(command);
lock (_commands) _commands.Add(command);
}
/// Every value handed to , in call order.
public List