From acfe29ee985e43856248773a2c399910635df7b3 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Tue, 19 May 2026 16:21:17 -0500 Subject: [PATCH] fix(pgsql): make CommandVisitor parameter index instance-scoped Resolves SonarQube S2696 in CommandVisitor.cs. The static _parameterIndex field was mutated from an instance method, causing every new CommandVisitor to inherit the previous instance's counter and never reset. Parameter indices now restart at $1 per visitor, which is the intended PostgreSQL behavior. Adds CommandVisitorTests.TwoVisitors_HaveIndependentParameterIndices to lock in the contract via reflection (FormatParameterName is protected). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Visitors/CommandVisitor.cs | 2 +- .../PostgreSql/CommandVisitorTests.cs | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs diff --git a/src/Strata.SqlTools.PostgreSql/Visitors/CommandVisitor.cs b/src/Strata.SqlTools.PostgreSql/Visitors/CommandVisitor.cs index 957bf69..0d3c687 100644 --- a/src/Strata.SqlTools.PostgreSql/Visitors/CommandVisitor.cs +++ b/src/Strata.SqlTools.PostgreSql/Visitors/CommandVisitor.cs @@ -9,7 +9,7 @@ namespace Strata.SqlTools.Visitors.PostgreSql; /// public class CommandVisitor : SqlServerCommandVisitor { - private static int _parameterIndex = 1; + private int _parameterIndex = 1; /// /// Formats an identifier for PostgreSQL using double-quote quoting. diff --git a/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs b/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs new file mode 100644 index 0000000..46c3c5d --- /dev/null +++ b/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs @@ -0,0 +1,34 @@ +using Strata.SqlTools.Visitors.PostgreSql; + +namespace Strata.SqlTools.PostgreSql.Tests.PostgreSql; + +[TestFixture] +public class CommandVisitorTests +{ + [Test] + public void TwoVisitors_HaveIndependentParameterIndices() + { + var visitor1 = new CommandVisitor(); + var visitor2 = new CommandVisitor(); + + var param1a = InvokeFormatParameterName(visitor1, "p"); + var param1b = InvokeFormatParameterName(visitor1, "p"); + var param2a = InvokeFormatParameterName(visitor2, "p"); + + Assert.That(param1a, Is.EqualTo("$1"), "first visitor's first parameter should be $1"); + Assert.That(param1b, Is.EqualTo("$2"), "first visitor's second parameter should be $2"); + Assert.That(param2a, Is.EqualTo("$1"), "second visitor must start at $1, not inherit visitor1's counter"); + } + + private static string InvokeFormatParameterName(CommandVisitor visitor, string name) + { + var method = typeof(CommandVisitor).GetMethod( + "FormatParameterName", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public) + ?? typeof(Strata.SqlTools.Visitors.SqlServer.CommandVisitor).GetMethod( + "FormatParameterName", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + Assert.That(method, Is.Not.Null, "FormatParameterName must exist as a protected method on the visitor hierarchy"); + return (string)method!.Invoke(visitor, new object[] { name })!; + } +}