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
This commit is contained in:
@@ -55,6 +55,13 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
|
||||
/// The bare (unquoted) column names in <see cref="SqlText"/>'s SELECT list, in order and distinct — the
|
||||
/// reader's map from result-set ordinal to source column.
|
||||
/// </param>
|
||||
/// <param name="RowCap">
|
||||
/// The most rows this plan can USE — its distinct key count (key-value) or 1 (a where-pair wide row);
|
||||
/// <see langword="null"/> when the statement already limits itself (topByTimestamp). <see cref="SqlText"/>
|
||||
/// asks for <c>RowCap + 1</c>, and the reader stops there: receiving the extra row proves the selector is
|
||||
/// ambiguous (a history table, a non-unique where column) and the group publishes
|
||||
/// <c>BadConfigurationError</c> instead of materialising every matching row on every poll (R06:S-1).
|
||||
/// </param>
|
||||
public sealed record SqlQueryPlan(
|
||||
SqlTagModel Model,
|
||||
object GroupKey,
|
||||
@@ -63,7 +70,8 @@ public sealed record SqlQueryPlan(
|
||||
IReadOnlyList<string> ParameterBindNames,
|
||||
IReadOnlyList<object?> Parameters,
|
||||
IReadOnlyList<SqlTagDefinition> Members,
|
||||
IReadOnlyList<string> SelectedColumns)
|
||||
IReadOnlyList<string> SelectedColumns,
|
||||
int? RowCap = null)
|
||||
{
|
||||
// A plan is documented as safe to cache and reuse across polls, so it must not stay aliased to the
|
||||
// planner's mutable working lists — an IReadOnlyList<T> holding a List<T> is downcastable, and a
|
||||
|
||||
Reference in New Issue
Block a user