Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser.Tests/OracleFlavouredSqliteDialect.cs
T
Joseph Doherty 0a5aa9fc43 fix(sql): row-cap key-value and where-pair plans with a TOP/LIMIT N+1 dialect seam (R06:S-1)
Key-value and where-pair plans had no row limit, so a selector pointed at a history
or events table materialised every matching row on every poll. ISqlDialect gains
RowLimitPrefix/RowLimitSuffix(n) (SQL Server TOP (n), Postgres LIMIT n, Oracle FETCH
FIRST n ROWS ONLY); plans carry a RowCap (distinct keys, or 1) and ask for RowCap+1
rows; the reader also stops client-side at RowCap+1. Getting the extra row proves the
selector ambiguous: the group publishes BadConfigurationError with a rate-limited
warning. The reader reports those answered refusals so the driver counts them as
answer evidence, and any refusal-only poll now restores Healthy after a comms fault.

Refs #744
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 15:57:13 -04:00

72 lines
2.8 KiB
C#

using System.Data.Common;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests;
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser.Tests;
/// <summary>
/// <see cref="SqliteDialect"/> in every respect except that it reports <see cref="SqlProvider.Oracle"/>.
/// It exists for exactly one assertion: <b>Oracle's <c>DATE</c> carries a time-of-day component</b>
/// (<c>OracleDialect.MapColumnType</c> says so in as many words), so it is NOT the date-only type the
/// row-selector ranking demotes on the other dialects, and an Oracle table whose <c>DATE</c> column is
/// declared before its <c>TIMESTAMP</c> must still pick the <c>DATE</c> — the ordinary
/// first-in-catalog-order tie-break.
/// <para>Without this control, implementing the ranking provider-blind ("any type family spelled
/// <c>date</c> is date-only") passes every other test in the suite while silently changing the pick on
/// the one backend where the spelling means something else.</para>
/// </summary>
public sealed class OracleFlavouredSqliteDialect : ISqlDialect
{
private readonly SqliteDialect _inner = new();
/// <summary>The whole point of the type: an Oracle-shaped provider over the SQLite catalog.</summary>
public SqlProvider Provider => SqlProvider.Oracle;
/// <inheritdoc/>
public DbProviderFactory Factory => _inner.Factory;
/// <inheritdoc/>
public int MaxBindParameters => _inner.MaxBindParameters;
/// <inheritdoc/>
public string LivenessSql => _inner.LivenessSql;
/// <inheritdoc/>
public string SingleRowLimitPrefix => _inner.SingleRowLimitPrefix;
/// <inheritdoc/>
public string SingleRowLimitSuffix => _inner.SingleRowLimitSuffix;
public string RowLimitPrefix(int rows) => _inner.RowLimitPrefix(rows);
public string RowLimitSuffix(int rows) => _inner.RowLimitSuffix(rows);
/// <inheritdoc/>
public string ListSchemasSql => _inner.ListSchemasSql;
/// <inheritdoc/>
public string DefaultSchemaSql => _inner.DefaultSchemaSql;
/// <inheritdoc/>
public string ListTablesSql => _inner.ListTablesSql;
/// <inheritdoc/>
public string ListColumnsSql => _inner.ListColumnsSql;
/// <inheritdoc/>
public string QuoteIdentifier(string ident) => _inner.QuoteIdentifier(ident);
/// <inheritdoc/>
public string ParameterMarker(string baseName) => _inner.ParameterMarker(baseName);
/// <inheritdoc/>
public string ParameterBindName(string baseName) => _inner.ParameterBindName(baseName);
/// <inheritdoc/>
public void ConfigureCommand(DbCommand command) => _inner.ConfigureCommand(command);
/// <inheritdoc/>
public DriverDataType MapColumnType(string sqlDataType) => _inner.MapColumnType(sqlDataType);
}