Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlQueryPlanImmutabilityTests.cs
T
Joseph Doherty 1da770253e refactor(sql): dialect-owned parameter markers, bind names and command configuration
ISqlDialect gains ParameterMarker (the token in the SQL text), ParameterBindName
(the DbParameter.ParameterName) and ConfigureCommand (per-command provider setup,
where Oracle will set BindByName). SQL Server and the test SQLite dialect answer
"@"+name for both spellings and no-op the hook, so SQL Server behaviour is
byte-identical — every existing expected-SQL assertion passes untouched.

SqlGroupPlanner drops its hardcoded @k/@w consts and mints base names (k0..kN, w)
that the dialect spells; SqlQueryPlan carries the emitted markers AND the matching
bind names, so SqlPollReader keeps copying the plan's pairing rather than
re-deriving a provider convention (the two diverge on ODP.NET: ":k0" in the text,
bare "k0" on the parameter). SqlCatalogLoader and SqlBrowseSession bind
schema/table through the dialect, so their bind names cannot drift from the
markers in the dialect-owned catalog SQL. Every parameter-carrying command site
calls ConfigureCommand before its first parameter.

Controlled by a new test-only ColonMarkerDialect (":name" markers, bare bind
names) that really executes against the SQLite fixture. Each leg was falsified
against the pre-change behaviour: the planner emitted "@k0", the reader bound
":k0", and a hardcoded "@schema" in the loader failed catalog load outright.
2026-08-05 06:25:53 -04:00

39 lines
1.3 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;
namespace ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests;
public class SqlQueryPlanImmutabilityTests
{
// A plan is documented as cacheable across polls. If it stayed aliased to the planner's working
// List<T>, a consumer downcast could corrupt every later poll that reused it.
[Fact]
public void Plan_doesNotAliasTheCallersLists()
{
var names = new List<string> { "@k0" };
var bindNames = new List<string> { "@k0" };
var values = new List<object?> { "v" };
var members = new List<SqlTagDefinition>
{
new("raw", SqlTagModel.KeyValue, "t") { KeyColumn = "k", KeyValue = "v", ValueColumn = "c" },
};
var selected = new List<string> { "k", "c" };
var plan = new SqlQueryPlan(
SqlTagModel.KeyValue, "gk", "SELECT 1", names, bindNames, values, members, selected);
names.Add("@k1");
bindNames.Add("@k1");
values.Add("other");
members.Add(members[0]);
selected.Add("extra");
plan.ParameterNames.Count.ShouldBe(1);
plan.ParameterBindNames.Count.ShouldBe(1);
plan.Parameters.Count.ShouldBe(1);
plan.Members.Count.ShouldBe(1);
plan.SelectedColumns.Count.ShouldBe(2);
}
}