using System.Data.Common;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
///
/// One compiled poll query and the tags it feeds (design §3.6, "group execution shape"). Every tag that
/// shares a reads from the same result set, so a poll pass executes one plan
/// per group rather than one query per tag.
/// Injection boundary. is built from dialect-quoted identifiers only;
/// every authored value lives in and is bound as a
/// named by the positionally-aligned . There is
/// no path by which a tag's keyValue or whereValue reaches the command text.
/// Markers and bind names are carried separately because they are not the same string on
/// every provider: ODP.NET's text says :p_k0 while its is the
/// bare p_k0. Both lists are positionally aligned with , so a binding site
/// copies the pairing the planner minted rather than re-deriving a provider convention it does not own.
/// Produced only by ; consumed by the poll reader, which executes
/// once and slices the rows back to per-member snapshots.
///
///
/// The tag→value mapping model every member shares. Determines how the reader slices the result set:
/// indexes rows by and matches each member's
/// ; takes the single row and
/// reads each member's .
///
///
/// The opaque equality key that decided this grouping — a value tuple, so two plans compare equal exactly
/// when they would have folded together. Exposed for diagnostics and plan caching, never for parsing.
/// Not unique across plans. A key-value group holding more distinct keys than the dialect's
/// is emitted as several plans carrying this same key
/// — the chunking is an execution detail of one group, not a regrouping.
///
/// The single parameterized statement to execute for this group.
///
/// The parameter markers appearing in , in the order they appear, aligned index-for-index
/// with . Carried explicitly so the reader never has to re-derive the naming
/// convention.
///
///
/// The to bind each value under, from
/// , aligned index-for-index with and
/// with . Equal to the marker on every provider but ODP.NET, which wants the
/// bare name — which is exactly why it is a carried list and not a transformation of the marker.
///
///
/// The values to bind, aligned with and .
/// Captured verbatim from the authored tags — a hostile value is inert here because it is data, not text.
///
///
/// The tags this plan feeds, in the planner's input order. Never empty. Duplicates are preserved: two tags
/// may legitimately read the same cell, and both must receive a value.
///
///
/// The bare (unquoted) column names in 's SELECT list, in order and distinct — the
/// reader's map from result-set ordinal to source column.
///
public sealed record SqlQueryPlan(
SqlTagModel Model,
object GroupKey,
string SqlText,
IReadOnlyList ParameterNames,
IReadOnlyList ParameterBindNames,
IReadOnlyList