From 55ab73788851c2885673428fe709a9cedd4a83da Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Wed, 20 May 2026 10:45:21 -0500 Subject: [PATCH 1/5] docs: spec for Strata.SqlTools.Query shared-project extraction Designs the extraction of the byte-identical 19-file ExpressionFactory/Query tree (duplicated across Snowflake + SqlServer) into a new Strata.SqlTools.Query project under a clean-break namespace, and folds in the 3 remaining new-code SonarQube smells (IDE0028 x2 on CalculationFilterGroup, NUnit2045 on CommandVisitorTests). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-20-shared-query-project-design.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-20-shared-query-project-design.md diff --git a/docs/superpowers/specs/2026-05-20-shared-query-project-design.md b/docs/superpowers/specs/2026-05-20-shared-query-project-design.md new file mode 100644 index 0000000..a702c3d --- /dev/null +++ b/docs/superpowers/specs/2026-05-20-shared-query-project-design.md @@ -0,0 +1,81 @@ +# Extract `Strata.SqlTools.Query` shared project + clear 3 new-code smells — Design + +**Date:** 2026-05-20 +**Branch:** `fix/Sonarqube-Tech-Debt` +**Author:** Thom Lamb (with Claude) + +## Goal + +Resolve the SonarQube tech debt introduced by yesterday's sweep: + +1. **3 new-code code smells** (the only issues in SonarQube's new-code period): + - `IDE0028` — `src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilterGroup.cs:27` + - `IDE0028` — `src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs:27` + - `NUnit2045` — `tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs:18` +2. **63 new duplicate lines** flagged on the PR — caused by the `CalculationFilterGroup` rewrite landing in two byte-identical copies (Snowflake + SqlServer). + +## Root cause of the duplication + +Snowflake and SqlServer each carry a **byte-identical 19-file `ExpressionFactory/Query/` tree** (verified: every file diffs to zero non-namespace lines). Project-wide duplication density is 11.1% (3,147 duplicated lines / 99 blocks); this tree is a major contributor. The `CalculationFilterGroup` change merely tipped the new-code duplication over threshold. + +The two flagged files cannot be extracted in isolation: their transitive type closure pulls in 14 of the 19 types (`QueryConfigExtensions → QueryConfig → Value → CalculationFilterGroup → CalculationFilter → Filter → …`). The remaining 4 (`AggregationType`, `ColumnQueryConfig`, `SqlResponse`, `ValueFilter`) are also identical duplicates. Therefore the coherent unit of extraction is the **whole 19-file tree**. + +## Approach + +Create a new shared class library and move the entire identical tree into it under a single new namespace. + +### New project + +- `src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj` + - `net8.0` (matches all existing projects) + - `PackageId = Strata.SqlTools.Query`, `GeneratePackageOnBuild = false` (matches dialect packages) + - No project references (the 19 types depend only on BCL: `System.Text.Json.Serialization`, `System.ComponentModel.DataAnnotations`) +- Added to `Strata.SqlTools.QueryBreakdown.sln`. + +### Namespace (clean break) + +- All 19 types move from `Strata.SqlTools.{Snowflake,SqlServer}.ExpressionFactory.Query` to the single flat namespace **`Strata.SqlTools.Query`**. +- File names are unchanged. One physical copy of each type. +- Rationale for clean break: both dialect packages have `GeneratePackageOnBuild = false` (not currently published), so the breaking namespace change has no live external consumers. If backward compat is later needed, `[TypeForwardedTo]` can be added without touching call sites. + +### Wiring + +- `Strata.SqlTools.Snowflake` and `Strata.SqlTools.SqlServer` both add a `ProjectReference` to `Strata.SqlTools.Query`. +- Delete all 38 dialect copies (19 in Snowflake, 19 in SqlServer). +- EFCore / LinqToSql / Markdown obtain the types transitively (they already reference the dialect projects). + +### Consumer migration + +Consumers reference these types either via a `using` directive or via relative resolution through the dialect's ancestor namespace. After the move they need `using Strata.SqlTools.Query;`. Known explicit-`using` sites to update: + +- `src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs` + +Any remaining relative references break the build; the compiler enumerates them precisely. The fix is mechanical: add the `using` (or a per-project `global using Strata.SqlTools.Query;`). + +**Collision safety:** No single source file references *both* dialect Query namespaces (verified via set intersection), so merging the two type families into one shared type cannot create ambiguous-overload or duplicate-signature errors. + +## Code smell fixes (folded into the same branch) + +1. **IDE0028 ×2 → ×1, then fixed.** After extraction there is one `CalculationFilterGroup.cs`. Change line 27 `Filters?.Where(x => x.IsValid()).ToList() ?? new List()` to use the collection expression `?? []`. One edit resolves both original issues. +2. **NUnit2045.** In `CommandVisitorTests.cs`, wrap the three independent `Assert.That` assertions (lines 18–20) in `Assert.Multiple(() => { … });` so all three report together. (The `Assert.That(method, Is.Not.Null, …)` guard in the helper stays as-is — it gates a subsequent `Invoke`, so it is correctly *not* inside a multiple block.) + +## Out of scope + +- The other 798 project-wide code smells (S3776 cognitive complexity, CA/IDE info-level, etc.). +- Extracting non-identical dialect code (visitors, breakdowns, statement parsers). +- Publishing/packaging changes beyond the new project's csproj. +- Pushing to the Gitea remote (user works locally; merge decision deferred). + +## Verification + +1. `dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release` — clean (errors fail; pre-existing warnings OK). +2. `dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build` — all tests green at baseline (~819, including the Task 4 `CommandVisitorTests`). +3. Optional: `./scan-sonar.ps1` then confirm on the dashboard that the 3 new-code smells are gone and new-code duplicated lines drop to 0. + +## Success criteria + +- New-code period shows **0** code smells and **0** duplicated lines. +- Project-wide `duplicated_lines_density` drops measurably (the identical tree no longer double-counts). +- Build clean, full suite green, no behavioral change to query-config semantics. -- 2.54.0 From 70c9a3b3dcb182370af13770f8cf9cd3645eca9b Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Wed, 20 May 2026 11:05:59 -0500 Subject: [PATCH 2/5] docs: implementation plan for Strata.SqlTools.Query extraction Task-by-task plan to scaffold the shared project, move the 19 identical ExpressionFactory/Query files, rewire dialect references, and clear the 3 remaining new-code SonarQube smells (IDE0028 x2, NUnit2045). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../plans/2026-05-20-shared-query-project.md | 421 ++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-20-shared-query-project.md diff --git a/docs/superpowers/plans/2026-05-20-shared-query-project.md b/docs/superpowers/plans/2026-05-20-shared-query-project.md new file mode 100644 index 0000000..57e4b98 --- /dev/null +++ b/docs/superpowers/plans/2026-05-20-shared-query-project.md @@ -0,0 +1,421 @@ +# Extract `Strata.SqlTools.Query` shared project + clear 3 new-code smells — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Eliminate the Snowflake/SqlServer `ExpressionFactory/Query` duplication by extracting the byte-identical 19-file tree into a new `Strata.SqlTools.Query` project, and resolve the 3 remaining new-code SonarQube smells. + +**Architecture:** A new standalone class library (`Strata.SqlTools.Query`, BCL-only, zero project references) holds one copy of the 19 query-config model types under the flat namespace `Strata.SqlTools.Query`. Both `Strata.SqlTools.Snowflake` and `Strata.SqlTools.SqlServer` reference it; their local copies are deleted. The existing ~819-test suite is the regression safety net. + +**Tech Stack:** C# / .NET 8 / NUnit / SonarAnalyzer.CSharp 10.4.0 (via `Directory.Build.props`). + +Implements `docs/superpowers/specs/2026-05-20-shared-query-project-design.md`. + +**Working directory:** All paths relative to `C:\gitea\sql-utilities`. Branch `fix/Sonarqube-Tech-Debt` is checked out; `HEAD` is the spec commit `55ab737`. + +--- + +## Why this is safe (facts established during planning) + +- The 19 files in `src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/` and `src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/` are **byte-identical** except the `namespace` line. +- Those files reference **only each other and BCL types** (no `using` beyond `System.*`; no `SqlBreakdown`/`Rules` references) → the new project needs no project references. +- The **only** external consumers with an explicit `using …ExpressionFactory.Query;` are 3 files (all reference the SqlServer namespace): + - `src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs` + - `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs` + - `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs` +- Other consumers reference the types **relatively** as `Query.X` (e.g. Snowflake `ExpressionFactory.cs`, whose namespace is `Strata.SqlTools.Snowflake.ExpressionFactory`). After the move these resolve automatically, because `Strata.SqlTools.Query` is reachable as `Query` from any `Strata.SqlTools.*` namespace. +- **No file references both dialect Query namespaces**, so collapsing the two type families into one cannot create ambiguous overloads. + +--- + +## File map + +**Created:** +- `src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj` — new BCL-only class library +- `src/Strata.SqlTools.Query/*.cs` — the 19 moved model files (flat namespace `Strata.SqlTools.Query`) + +**Modified:** +- `Strata.SqlTools.QueryBreakdown.sln` — add the new project +- `src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj` — add `ProjectReference` to `Strata.SqlTools.Query` +- `src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj` — add `ProjectReference` to `Strata.SqlTools.Query` +- `src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs` — update `using` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs` — update `using` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs` — update `using` +- `src/Strata.SqlTools.Query/CalculationFilterGroup.cs` — IDE0028 fix (`?? []`) +- `tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs` — NUnit2045 fix (`Assert.Multiple`) + +**Deleted:** +- `src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/*.cs` (19 files) +- `src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/*.cs` (19 files — moved, not copied) + +--- + +## Task 1: Scaffold the `Strata.SqlTools.Query` project + +**Files:** +- Create: `src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj` +- Modify: `Strata.SqlTools.QueryBreakdown.sln` + +- [ ] **Step 1: Record the baseline test result** + +Run: +```powershell +dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --nologo +``` + +Expected: build succeeds; full suite green. **Write down the total passed count** printed at the end — every later test step must match or exceed it (Task 3 leaves it unchanged; no tests are added or removed by this plan). + +- [ ] **Step 2: Create the project directory and csproj** + +Create `src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj` with this exact content (mirrors the dialect csprojs minus dialect-specific package refs): + +```xml + + + + net8.0 + enable + enable + latest + + + Strata.SqlTools.Query + 1.0.0 + Strata Decision Technology + Strata Decision Technology + Strata SQL Utilities - Query Model + Dialect-agnostic query configuration model (QueryConfig, filters, values, rows) shared by the Strata.SqlTools SQL Server and Snowflake dialect packages. + sql;query-builder;query-config;database + https://github.com/stratadecision/sql-builder + https://github.com/stratadecision/sql-builder + git + MIT + README.md + Initial release: shared query configuration model extracted from the dialect packages. + Copyright © Strata Decision Technology 2024-2026 + + + false + true + symbols.nupkg + true + true + + + true + latest + true + + + + + + + +``` + +- [ ] **Step 3: Add the project to the solution** + +Run: +```powershell +dotnet sln Strata.SqlTools.QueryBreakdown.sln add src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj +``` + +Expected: `Project ... added to the solution.` + +- [ ] **Step 4: Build the (empty) project to confirm it is well-formed** + +Run: +```powershell +dotnet build src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj -c Release --nologo +``` + +Expected: `Build succeeded.` (It compiles to an empty assembly — no .cs files yet.) + +- [ ] **Step 5: Commit** + +```powershell +git add src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj Strata.SqlTools.QueryBreakdown.sln +git commit -m "build(query): scaffold empty Strata.SqlTools.Query shared project + +First step of extracting the duplicated ExpressionFactory/Query model +tree. Adds a BCL-only class library and registers it in the solution; +files are moved in the next commit. + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +--- + +## Task 2: Move the 19 model files into the shared project and rewire references + +This task is atomic: the move, namespace rename, reference wiring, duplicate deletion, and `using` fixes must all land together for the solution to compile. Do **not** split the commit. + +**Files:** +- Move (via `git mv`): `src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/*.cs` → `src/Strata.SqlTools.Query/` +- Delete: `src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/*.cs` +- Modify: both dialect csprojs, the 3 explicit-`using` files, and `src/Strata.SqlTools.Query/CalculationFilterGroup.cs` + +- [ ] **Step 1: Move SqlServer's 19 files into the new project (preserving git history)** + +Run: +```powershell +$src = "src/Strata.SqlTools.SqlServer/ExpressionFactory/Query" +$dst = "src/Strata.SqlTools.Query" +Get-ChildItem "$src/*.cs" | ForEach-Object { git mv $_.FullName "$dst/$($_.Name)" } +Get-ChildItem "$dst/*.cs" | Measure-Object | Select-Object -ExpandProperty Count +``` + +Expected: prints `19`. The SqlServer `ExpressionFactory/Query` folder is now empty on disk (git does not track empty folders). + +- [ ] **Step 2: Rename the namespace in all 19 moved files** + +Run: +```powershell +Get-ChildItem "src/Strata.SqlTools.Query/*.cs" | ForEach-Object { + $p = $_.FullName + (Get-Content $p) -replace 'namespace Strata\.SqlTools\.SqlServer\.ExpressionFactory\.Query;', 'namespace Strata.SqlTools.Query;' | Set-Content $p +} +Select-String -Path "src/Strata.SqlTools.Query/*.cs" -Pattern '^namespace ' | Select-Object -ExpandProperty Line | Sort-Object -Unique +``` + +Expected: the only line printed is `namespace Strata.SqlTools.Query;` (all 19 files now share the flat namespace). + +- [ ] **Step 3: Delete Snowflake's duplicate 19 files** + +Run: +```powershell +git rm src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/*.cs +``` + +Expected: `rm 'src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/...'` ×19. + +- [ ] **Step 4: Add the project reference to SqlServer** + +In `src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj`, locate the `ItemGroup` containing the `SqlBreakdown` project reference: + +```xml + + + +``` + +Replace it with: + +```xml + + + + +``` + +- [ ] **Step 5: Add the project reference to Snowflake** + +In `src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj`, locate: + +```xml + + + +``` + +Replace it with: + +```xml + + + + +``` + +- [ ] **Step 6: Fix the 3 explicit `using` directives** + +In each of these files, change the line +`using Strata.SqlTools.SqlServer.ExpressionFactory.Query;` +to +`using Strata.SqlTools.Query;` + +- `src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs` +- `tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs` + +Verify: +```powershell +Get-ChildItem src,tests -Recurse -Filter *.cs | Select-String -Pattern 'SqlServer\.ExpressionFactory\.Query' +``` + +Expected: **no matches** (no source file references the old namespace anymore). + +- [ ] **Step 7: Fix IDE0028 in the moved `CalculationFilterGroup.cs`** + +In `src/Strata.SqlTools.Query/CalculationFilterGroup.cs`, change the `GetValidFilters` body: + +Before: +```csharp + public IEnumerable GetValidFilters() + { + return Filters?.Where(x => x.IsValid()).ToList() ?? new List(); + } +``` + +After: +```csharp + public IEnumerable GetValidFilters() + { + return Filters?.Where(x => x.IsValid()).ToList() ?? []; + } +``` + +(This single edit resolves both original IDE0028 issues, which were the two now-merged copies.) + +- [ ] **Step 8: Build the full solution** + +Run: +```powershell +dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release --nologo +``` + +Expected: `Build succeeded.` + +**If you see `CS0246: The type or namespace name 'X' could not be found`** in a file not listed above: that file referenced a Query type by bare name without resolving it relatively. Fix it by adding a file-scoped `using Strata.SqlTools.Query;` to that specific file (do **not** add a project-wide global using — generic names like `Filter`/`Value`/`Field`/`Row` could become ambiguous). Re-run the build until clean. + +- [ ] **Step 9: Run the full test suite** + +Run: +```powershell +dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build --nologo +``` + +Expected: all tests pass; total count equals the baseline recorded in Task 1 Step 1 (no tests added or removed). + +- [ ] **Step 10: Commit** + +```powershell +git add -A +git commit -m "refactor(query): extract shared Strata.SqlTools.Query model project + +Moves the byte-identical 19-file ExpressionFactory/Query tree (duplicated +across Snowflake and SqlServer) into the new Strata.SqlTools.Query project +under the flat namespace Strata.SqlTools.Query. Both dialect projects now +reference the shared project; the Snowflake copies are deleted. + +Also folds in the IDE0028 fix on CalculationFilterGroup.GetValidFilters +(collection expression `[]`), which resolves both new-code IDE0028 smells +in one place now that there is a single copy. + +Eliminates the 63 new duplicate lines flagged on the PR and removes the +largest contributor to the project's 11.1% duplication density. No +behavioral change: the moved types are identical to the originals. + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +--- + +## Task 3: Fix NUnit2045 in `CommandVisitorTests` + +**Files:** +- Modify: `tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs:18-20` + +**Background:** SonarQube flags `NUnit2045` because the three independent `Assert.That` calls run sequentially — a failure in the first hides the others. Wrapping them in `Assert.Multiple` reports all three together. The `Assert.That(method, Is.Not.Null, …)` guard in the helper method stays as-is: it gates a subsequent reflection `Invoke`, so it must short-circuit and must **not** be inside a multiple block. + +- [ ] **Step 1: Wrap the three assertions in `Assert.Multiple`** + +In `tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs`, replace lines 18-20: + +Before: +```csharp + 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"); +``` + +After: +```csharp + Assert.Multiple(() => + { + 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"); + }); +``` + +- [ ] **Step 2: Run the affected test** + +Run: +```powershell +dotnet test tests/Strata.SqlTools.PostgreSql.Tests/Strata.SqlTools.PostgreSql.Tests.csproj -c Release --filter "FullyQualifiedName~CommandVisitorTests" --nologo +``` + +Expected: PASS (1 test). Behavior is unchanged — only the assertion grouping changed. + +- [ ] **Step 3: Commit** + +```powershell +git add tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs +git commit -m "test(pgsql): wrap CommandVisitor asserts in Assert.Multiple + +Resolves SonarQube NUnit2045. The three independent parameter-index +assertions now report together instead of short-circuiting on the first +failure. The Is.Not.Null guard in the reflection helper stays outside the +multiple block because it gates the subsequent Invoke. + +Co-Authored-By: Claude Opus 4.7 (1M context) " +``` + +--- + +## Final verification + +- [ ] **Step 1: Clean working tree, full build + test** + +Run: +```powershell +git status --short +dotnet build Strata.SqlTools.QueryBreakdown.sln -c Release --nologo +dotnet test Strata.SqlTools.QueryBreakdown.sln -c Release --no-build --nologo +``` + +Expected: empty working tree; clean build; full suite green at the baseline count from Task 1 Step 1. + +- [ ] **Step 2: Confirm no duplicate model files remain** + +Run: +```powershell +Get-ChildItem -Recurse -Filter CalculationFilterGroup.cs src | Select-Object FullName +``` + +Expected: exactly one path — `src/Strata.SqlTools.Query/CalculationFilterGroup.cs`. (No dialect copies.) + +- [ ] **Step 3: Push a fresh SonarQube analysis (optional but recommended)** + +Requires `$env:SONAR_TOKEN`. Run: +```powershell +./scan-sonar.ps1 +``` + +Then confirm on `https://snrqbe.bermudalamb.synology.me` (project `sql-utilities`): +- New-code period shows **0** code smells (the two IDE0028 and the NUnit2045 are gone). +- New-code **duplicated lines = 0**. +- Project-wide `duplicated_lines_density` has dropped from 11.1%. + +Ad-hoc check (token as Basic-auth username; see project memory): +```powershell +$b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($env:SONARQUBE_TOKEN):")) +$h = @{ Authorization = "Basic $b64" } +Invoke-RestMethod -Uri "$($env:SONARQUBE_URL)/api/issues/search?componentKeys=sql-utilities&inNewCodePeriod=true&resolved=false" -Headers $h | + Select-Object -ExpandProperty total +``` +Expected: `0`. + +- [ ] **Step 4: Merge decision** + +Do **not** push to the Gitea remote automatically (user works locally). Report the commit list (`git log --oneline main..HEAD`) and let the user choose merge / PR / hold. + +--- + +## Out of scope (do not do in this plan) + +- The other ~798 project-wide code smells (S3776 cognitive complexity, CA/IDE info-level, etc.). +- Extracting non-identical dialect code (visitors, breakdowns, statement parsers). +- Backward-compat `[TypeForwardedTo]` shims (packages are unpublished; revisit only if external consumers appear). +- Pushing the branch to remote. -- 2.54.0 From c5a4a4b02b9192a10ef0b16f041345e11165a284 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Wed, 20 May 2026 11:13:44 -0500 Subject: [PATCH 3/5] build(query): scaffold empty Strata.SqlTools.Query shared project First step of extracting the duplicated ExpressionFactory/Query model tree. Adds a BCL-only class library and registers it in the solution; files are moved in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- Strata.SqlTools.QueryBreakdown.sln | 17 +++++++- .../Strata.SqlTools.Query.csproj | 42 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj diff --git a/Strata.SqlTools.QueryBreakdown.sln b/Strata.SqlTools.QueryBreakdown.sln index 0de048c..e468eba 100644 --- a/Strata.SqlTools.QueryBreakdown.sln +++ b/Strata.SqlTools.QueryBreakdown.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11527.330 d18.3 +VisualStudioVersion = 18.3.11527.330 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.Rules", "src\Strata.SqlTools.Rules\Strata.SqlTools.Rules.csproj", "{11FE5F8E-5D66-4B68-87F5-9586CFA13B4C}" EndProject @@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.LinqToSql.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.SqlBreakdown.Tests", "tests\Strata.SqlTools.SqlBreakdown.Tests\Strata.SqlTools.SqlBreakdown.Tests.csproj", "{80DC3F4B-D3C0-A104-39DC-889D65A15FAB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.SqlTools.Query", "src\Strata.SqlTools.Query\Strata.SqlTools.Query.csproj", "{177F3799-6A91-477B-87CC-BCCE05B87E1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -241,6 +243,18 @@ Global {80DC3F4B-D3C0-A104-39DC-889D65A15FAB}.Release|x64.Build.0 = Release|Any CPU {80DC3F4B-D3C0-A104-39DC-889D65A15FAB}.Release|x86.ActiveCfg = Release|Any CPU {80DC3F4B-D3C0-A104-39DC-889D65A15FAB}.Release|x86.Build.0 = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|x64.ActiveCfg = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|x64.Build.0 = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Debug|x86.Build.0 = Debug|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|Any CPU.Build.0 = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|x64.ActiveCfg = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|x64.Build.0 = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|x86.ActiveCfg = Release|Any CPU + {177F3799-6A91-477B-87CC-BCCE05B87E1C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -262,6 +276,7 @@ Global {3AD058F3-DC6D-4894-939F-F12432683981} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {25773B63-302B-401E-AE45-6BBE7947562D} = {0AF8EC2A-1121-47D3-8011-DEFBB0C74490} {80DC3F4B-D3C0-A104-39DC-889D65A15FAB} = {0AF8EC2A-1121-47D3-8011-DEFBB0C74490} + {177F3799-6A91-477B-87CC-BCCE05B87E1C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7775F930-B72A-4FA8-BB45-26D55AD44076} diff --git a/src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj b/src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj new file mode 100644 index 0000000..7a0f109 --- /dev/null +++ b/src/Strata.SqlTools.Query/Strata.SqlTools.Query.csproj @@ -0,0 +1,42 @@ + + + + net8.0 + enable + enable + latest + + + Strata.SqlTools.Query + 1.0.0 + Strata Decision Technology + Strata Decision Technology + Strata SQL Utilities - Query Model + Dialect-agnostic query configuration model (QueryConfig, filters, values, rows) shared by the Strata.SqlTools SQL Server and Snowflake dialect packages. + sql;query-builder;query-config;database + https://github.com/stratadecision/sql-builder + https://github.com/stratadecision/sql-builder + git + MIT + README.md + Initial release: shared query configuration model extracted from the dialect packages. + Copyright © Strata Decision Technology 2024-2026 + + + false + true + symbols.nupkg + true + true + + + true + latest + true + + + + + + + -- 2.54.0 From 5cb32d231195baf4d68de5145e60278d294ed0b6 Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Wed, 20 May 2026 12:04:01 -0500 Subject: [PATCH 4/5] refactor(query): extract shared Strata.SqlTools.Query model project Moves the byte-identical 19-file ExpressionFactory/Query tree (duplicated across Snowflake and SqlServer) into the new Strata.SqlTools.Query project under the flat namespace Strata.SqlTools.Query. Both dialect projects now reference the shared project; the Snowflake copies are deleted. Also folds in the IDE0028 fix on CalculationFilterGroup.GetValidFilters (collection expression []), which resolves both new-code IDE0028 smells in one place now that there is a single copy. Eliminates the 63 new duplicate lines flagged on the PR and removes the largest contributor to the project's 11.1% duplication density. No behavioral change: the moved types are identical to the originals. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/settings.local.json | 12 ++- .../AggregationType.cs | 2 +- .../CalculationFilter.cs | 2 +- .../CalculationFilterGroup.cs | 4 +- .../ColumnQueryConfig.cs | 2 +- .../DatePart.cs | 2 +- .../Query => Strata.SqlTools.Query}/Field.cs | 2 +- .../Query => Strata.SqlTools.Query}/Filter.cs | 2 +- .../FilterCondition.cs | 2 +- .../FilterGroup.cs | 2 +- .../FilterOperator.cs | 2 +- .../FilterType.cs | 2 +- .../LogicalOperator.cs | 2 +- .../QueryConfig.cs | 2 +- .../QueryConfigExtensions.cs | 2 +- .../Query => Strata.SqlTools.Query}/Row.cs | 2 +- .../SortDirection.cs | 2 +- .../SqlResponse.cs | 2 +- .../Query => Strata.SqlTools.Query}/Value.cs | 2 +- .../ValueFilter.cs | 2 +- .../Query/CalculationFilter.cs | 12 --- .../Query/ColumnQueryConfig.cs | 23 ----- .../ExpressionFactory/Query/DatePart.cs | 13 --- .../ExpressionFactory/Query/Field.cs | 8 -- .../ExpressionFactory/Query/FilterGroup.cs | 29 ------ .../ExpressionFactory/Query/FilterOperator.cs | 15 --- .../ExpressionFactory/Query/FilterType.cs | 9 -- .../Query/LogicalOperator.cs | 30 ------ .../ExpressionFactory/Query/SortDirection.cs | 15 --- .../ExpressionFactory/Query/Value.cs | 28 ------ .../Strata.SqlTools.Snowflake.csproj | 1 + .../ExpressionFactory/ExpressionFactory.cs | 2 +- .../Query/AggregationType.cs | 12 --- .../Query/CalculationFilterGroup.cs | 34 ------- .../ExpressionFactory/Query/Filter.cs | 91 ------------------- .../Query/FilterCondition.cs | 17 ---- .../ExpressionFactory/Query/QueryConfig.cs | 33 ------- .../Query/QueryConfigExtensions.cs | 20 ---- .../ExpressionFactory/Query/Row.cs | 11 --- .../ExpressionFactory/Query/SqlResponse.cs | 13 --- .../ExpressionFactory/Query/ValueFilter.cs | 8 -- .../Strata.SqlTools.SqlServer.csproj | 1 + .../ExpressionFactoryFilterTests.cs | 2 +- .../ExpressionTests/ExpressionTestsBase.cs | 2 +- 44 files changed, 36 insertions(+), 445 deletions(-) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/AggregationType.cs (69%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/CalculationFilter.cs (87%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/CalculationFilterGroup.cs (83%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/ColumnQueryConfig.cs (89%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/DatePart.cs (68%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/Field.cs (73%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/Filter.cs (98%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/FilterCondition.cs (87%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/FilterGroup.cs (91%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/FilterOperator.cs (84%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/FilterType.cs (61%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/LogicalOperator.cs (90%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/QueryConfig.cs (93%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/QueryConfigExtensions.cs (93%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/Row.cs (71%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/SortDirection.cs (79%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/SqlResponse.cs (83%) rename src/{Strata.SqlTools.SqlServer/ExpressionFactory/Query => Strata.SqlTools.Query}/Value.cs (94%) rename src/{Strata.SqlTools.Snowflake/ExpressionFactory/Query => Strata.SqlTools.Query}/ValueFilter.cs (65%) delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilter.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ColumnQueryConfig.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/DatePart.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Field.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterGroup.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterOperator.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterType.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/LogicalOperator.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SortDirection.cs delete mode 100644 src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Value.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/AggregationType.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Filter.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterCondition.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfig.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfigExtensions.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Row.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SqlResponse.cs delete mode 100644 src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ValueFilter.cs diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 3970ede..d9b3047 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,17 @@ "PowerShell(git *)", "Bash(dotnet build *)", "Bash(command -v gh)", - "Bash(command -v tea)" + "Bash(command -v tea)", + "PowerShell(if \\($env:SONARQUBE_URL -and $env:SONARQUBE_TOKEN\\) { \"URL set: $\\($env:SONARQUBE_URL\\)\"; \"Token set: $\\($env:SONARQUBE_TOKEN.Substring\\(0,8\\)\\)...\" } else { \"MISSING: URL=$\\($env:SONARQUBE_URL -ne $null\\) TOKEN=$\\($env:SONARQUBE_TOKEN -ne $null\\)\" })", + "PowerShell($u = [Environment]::GetEnvironmentVariable\\(\"SONARQUBE_URL\",\"User\"\\); $t = [Environment]::GetEnvironmentVariable\\(\"SONARQUBE_TOKEN\",\"User\"\\); \"User URL: $u\"; if \\($t\\) { \"User TOKEN: $\\($t.Substring\\(0,8\\)\\)... \\(len $\\($t.Length\\)\\)\" } else { \"User TOKEN: \" })", + "Bash(grep -iE \"/\\(QueryConfig|CalculationFilter|LogicalOperator\\)\\\\.cs$\")", + "PowerShell(dotnet build C:\\\\gitea\\\\sql-utilities --nologo -clp:NoSummary 2>&1 | Select-String -Pattern \"warning S\\\\d+\" | ForEach-Object { $_.Line.Trim\\(\\) } | Sort-Object -Unique)", + "Bash(grep -oE 'S[0-9]{3,4}' build-sonar.log)", + "Bash(dotnet sln *)", + "Bash(Get-ChildItem src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/*.cs)", + "Bash(Measure-Object)", + "Bash(Select-Object -ExpandProperty Count)", + "PowerShell(dotnet build *)" ] } } diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/AggregationType.cs b/src/Strata.SqlTools.Query/AggregationType.cs similarity index 69% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/AggregationType.cs rename to src/Strata.SqlTools.Query/AggregationType.cs index debf2b3..088cf1d 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/AggregationType.cs +++ b/src/Strata.SqlTools.Query/AggregationType.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum AggregationType { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilter.cs b/src/Strata.SqlTools.Query/CalculationFilter.cs similarity index 87% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilter.cs rename to src/Strata.SqlTools.Query/CalculationFilter.cs index 7a2ae1d..c83d340 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilter.cs +++ b/src/Strata.SqlTools.Query/CalculationFilter.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class CalculationFilter : Filter { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilterGroup.cs b/src/Strata.SqlTools.Query/CalculationFilterGroup.cs similarity index 83% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilterGroup.cs rename to src/Strata.SqlTools.Query/CalculationFilterGroup.cs index ce0f011..b31ef36 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilterGroup.cs +++ b/src/Strata.SqlTools.Query/CalculationFilterGroup.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class CalculationFilterGroup { @@ -24,7 +24,7 @@ public class CalculationFilterGroup public IEnumerable GetValidFilters() { - return Filters?.Where(x => x.IsValid()).ToList() ?? new List(); + return Filters?.Where(x => x.IsValid()).ToList() ?? []; } public bool IsValid() diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ColumnQueryConfig.cs b/src/Strata.SqlTools.Query/ColumnQueryConfig.cs similarity index 89% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ColumnQueryConfig.cs rename to src/Strata.SqlTools.Query/ColumnQueryConfig.cs index 99cd89a..9fa045d 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ColumnQueryConfig.cs +++ b/src/Strata.SqlTools.Query/ColumnQueryConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class ColumnQueryConfig { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/DatePart.cs b/src/Strata.SqlTools.Query/DatePart.cs similarity index 68% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/DatePart.cs rename to src/Strata.SqlTools.Query/DatePart.cs index 85f5976..98228ea 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/DatePart.cs +++ b/src/Strata.SqlTools.Query/DatePart.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum DatePart { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Field.cs b/src/Strata.SqlTools.Query/Field.cs similarity index 73% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Field.cs rename to src/Strata.SqlTools.Query/Field.cs index 32b695a..84d5b08 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Field.cs +++ b/src/Strata.SqlTools.Query/Field.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class Field { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Filter.cs b/src/Strata.SqlTools.Query/Filter.cs similarity index 98% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Filter.cs rename to src/Strata.SqlTools.Query/Filter.cs index b55e919..bbaff2d 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Filter.cs +++ b/src/Strata.SqlTools.Query/Filter.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; /// /// Represents a filter criteria for querying data with support for various filter types including lists, date ranges, and timeframes. diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterCondition.cs b/src/Strata.SqlTools.Query/FilterCondition.cs similarity index 87% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterCondition.cs rename to src/Strata.SqlTools.Query/FilterCondition.cs index c8f0dc8..a670099 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterCondition.cs +++ b/src/Strata.SqlTools.Query/FilterCondition.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class FilterCondition { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterGroup.cs b/src/Strata.SqlTools.Query/FilterGroup.cs similarity index 91% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterGroup.cs rename to src/Strata.SqlTools.Query/FilterGroup.cs index ff1e7d5..209d3cd 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterGroup.cs +++ b/src/Strata.SqlTools.Query/FilterGroup.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class FilterGroup { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterOperator.cs b/src/Strata.SqlTools.Query/FilterOperator.cs similarity index 84% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterOperator.cs rename to src/Strata.SqlTools.Query/FilterOperator.cs index 970f181..b92d6a2 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterOperator.cs +++ b/src/Strata.SqlTools.Query/FilterOperator.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum FilterOperator { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterType.cs b/src/Strata.SqlTools.Query/FilterType.cs similarity index 61% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterType.cs rename to src/Strata.SqlTools.Query/FilterType.cs index 712c008..047048f 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterType.cs +++ b/src/Strata.SqlTools.Query/FilterType.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum FilterType { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/LogicalOperator.cs b/src/Strata.SqlTools.Query/LogicalOperator.cs similarity index 90% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/LogicalOperator.cs rename to src/Strata.SqlTools.Query/LogicalOperator.cs index fef184c..f1510e5 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/LogicalOperator.cs +++ b/src/Strata.SqlTools.Query/LogicalOperator.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum LogicalOperator { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfig.cs b/src/Strata.SqlTools.Query/QueryConfig.cs similarity index 93% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfig.cs rename to src/Strata.SqlTools.Query/QueryConfig.cs index 2b9073f..0157dda 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfig.cs +++ b/src/Strata.SqlTools.Query/QueryConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class QueryConfig { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfigExtensions.cs b/src/Strata.SqlTools.Query/QueryConfigExtensions.cs similarity index 93% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfigExtensions.cs rename to src/Strata.SqlTools.Query/QueryConfigExtensions.cs index 7d62951..3297933 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/QueryConfigExtensions.cs +++ b/src/Strata.SqlTools.Query/QueryConfigExtensions.cs @@ -1,5 +1,5 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public static class QueryConfigExtensions { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Row.cs b/src/Strata.SqlTools.Query/Row.cs similarity index 71% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Row.cs rename to src/Strata.SqlTools.Query/Row.cs index 22d4f4f..22d7590 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Row.cs +++ b/src/Strata.SqlTools.Query/Row.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class Row : Field { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SortDirection.cs b/src/Strata.SqlTools.Query/SortDirection.cs similarity index 79% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SortDirection.cs rename to src/Strata.SqlTools.Query/SortDirection.cs index 3ddf421..c5f9f02 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SortDirection.cs +++ b/src/Strata.SqlTools.Query/SortDirection.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public enum SortDirection { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SqlResponse.cs b/src/Strata.SqlTools.Query/SqlResponse.cs similarity index 83% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SqlResponse.cs rename to src/Strata.SqlTools.Query/SqlResponse.cs index 9274b07..4ab1125 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SqlResponse.cs +++ b/src/Strata.SqlTools.Query/SqlResponse.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class SqlResponse { diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Value.cs b/src/Strata.SqlTools.Query/Value.cs similarity index 94% rename from src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Value.cs rename to src/Strata.SqlTools.Query/Value.cs index 56ec66a..514c024 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Value.cs +++ b/src/Strata.SqlTools.Query/Value.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class Value { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ValueFilter.cs b/src/Strata.SqlTools.Query/ValueFilter.cs similarity index 65% rename from src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ValueFilter.cs rename to src/Strata.SqlTools.Query/ValueFilter.cs index 6f741fd..62cfad6 100644 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ValueFilter.cs +++ b/src/Strata.SqlTools.Query/ValueFilter.cs @@ -1,4 +1,4 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; +namespace Strata.SqlTools.Query; public class ValueFilter { diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilter.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilter.cs deleted file mode 100644 index c0278f7..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/CalculationFilter.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public class CalculationFilter : Filter -{ - public IEnumerable AliasedDataColumnIds { get; } - - public CalculationFilter(int dataColumnId, IEnumerable aliasedDataColumnIds, IEnumerable values, IEnumerable conditions) - : base(dataColumnId, FilterType.Conditions, values, conditions, DatePart.Continuous, false, 0, 0) - { - AliasedDataColumnIds = aliasedDataColumnIds; - } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ColumnQueryConfig.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ColumnQueryConfig.cs deleted file mode 100644 index 37f472e..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/ColumnQueryConfig.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public class ColumnQueryConfig -{ - public int DataColumnId { get; set; } - - public DatePart DatePart { get; set; } - - public Filter? Filter { get; set; } - - public int RowLimit { get; set; } - - [JsonConstructor] - public ColumnQueryConfig(int dataColumnId, DatePart datePart, Filter? filter, int rowLimit) - { - DataColumnId = dataColumnId; - DatePart = datePart; - Filter = filter != null && filter.IsValid() ? filter : null; - RowLimit = rowLimit; - } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/DatePart.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/DatePart.cs deleted file mode 100644 index 798ca43..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/DatePart.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public enum DatePart -{ - Continuous = 0, - Year, - Quarter, - Month, - Week, - Day, - FiscalYear, - FiscalQuarter -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Field.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Field.cs deleted file mode 100644 index 2608032..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Field.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public class Field -{ - public string ColumnAlias { get; set; } = string.Empty; - public int DataColumnId { get; set; } - public DatePart DatePart { get; set; } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterGroup.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterGroup.cs deleted file mode 100644 index b5c3e37..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterGroup.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public class FilterGroup -{ - // Hereditary logical operation applied to all Filters - public LogicalOperator LogicalOperator { get; set; } - - public IEnumerable Filters { get; } - - public FilterGroup() - { - LogicalOperator = LogicalOperator.And; - Filters = new List(); - } - - [JsonConstructor] - public FilterGroup(IEnumerable filters, LogicalOperator logicalOperator) - { - Filters = filters.Where(x => x.IsValid()).ToList(); - LogicalOperator = logicalOperator; - } - - public bool IsValid() - { - return Filters != null && Filters.Any(); - } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterOperator.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterOperator.cs deleted file mode 100644 index 2bcef52..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterOperator.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public enum FilterOperator -{ - Equals = 0, - NotEquals = 1, - LessThan = 2, - LessThanOrEqualTo = 3, - GreaterThan = 4, - GreaterThanOrEqualTo = 5, - Between = 6, // this is a function, not a comparison - x BETWEEN a AND b is the same as: x >= a AND x <= z - Contains = 7, - StartsWith = 8, - EndsWith = 9 -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterType.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterType.cs deleted file mode 100644 index 6ee9e9b..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/FilterType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public enum FilterType -{ - List = 0, - Conditions = 1, - Calendar = 2, - Timeframe = 3 -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/LogicalOperator.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/LogicalOperator.cs deleted file mode 100644 index 3e022da..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/LogicalOperator.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public enum LogicalOperator -{ - [Display(Name = "and")] - And, - [Display(Name = "or")] - Or -} - -public static class LogicalOperatorExtensions -{ - public static string ToSql(this LogicalOperator logicalOperator, bool withSpaces = true) - { - var sql = ""; - switch (logicalOperator) - { - case LogicalOperator.And: - sql = "and"; - break; - case LogicalOperator.Or: - sql = "or"; - break; - } - - return withSpaces ? $" {sql} " : sql; - } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SortDirection.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SortDirection.cs deleted file mode 100644 index 69c1a3a..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/SortDirection.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public enum SortDirection -{ - Asc = 0, - Desc = 1 -} - -public static class SortDirectionExtensions -{ - public static string ToSql(this SortDirection sortDirection) - { - return sortDirection.ToString().ToUpper(); - } -} diff --git a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Value.cs b/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Value.cs deleted file mode 100644 index 075c5d8..0000000 --- a/src/Strata.SqlTools.Snowflake/ExpressionFactory/Query/Value.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.Snowflake.ExpressionFactory.Query; - -public class Value -{ - public string ColumnAlias { get; } - public string Calculation { get; } - public IEnumerable CalculationDataColumnIds { get; } - public IEnumerable AliasedIds { get; } - - public IEnumerable FilterGroups { get; } - - public Value() : this(string.Empty, string.Empty, new int[0], new string[0], new CalculationFilterGroup[0]) - { - // FilterGroups = new List(); - } - - [JsonConstructor] - public Value(string columnAlias, string calculation, IEnumerable calculationDataColumnIds, IEnumerable aliasedIds, IEnumerable filterGroups) - { - ColumnAlias = columnAlias; - Calculation = calculation; - CalculationDataColumnIds = calculationDataColumnIds ?? Array.Empty(); - AliasedIds = aliasedIds ?? aliasedIds ?? Array.Empty(); - FilterGroups = filterGroups?.Where(x => x.IsValid()).ToList() ?? new List(); - } -} diff --git a/src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj b/src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj index 15ea2d1..9b2ace2 100644 --- a/src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj +++ b/src/Strata.SqlTools.Snowflake/Strata.SqlTools.Snowflake.csproj @@ -41,6 +41,7 @@ + diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs index ee33eb7..7e9dd1d 100644 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs +++ b/src/Strata.SqlTools.SqlServer/ExpressionFactory/ExpressionFactory.cs @@ -4,7 +4,7 @@ using Strata.SqlTools.SqlBreakdown.Expressions.Conditional; using Strata.SqlTools.SqlBreakdown.Expressions.Conditional.Comparisons; using Strata.SqlTools.SqlBreakdown.Expressions.Functions.Conditional; using Strata.SqlTools.SqlBreakdown.Expressions.Functions.DateTime; -using Strata.SqlTools.SqlServer.ExpressionFactory.Query; +using Strata.SqlTools.Query; namespace Strata.SqlTools.SqlServer.ExpressionFactory; diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/AggregationType.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/AggregationType.cs deleted file mode 100644 index 2cd015a..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/AggregationType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public enum AggregationType -{ - Sum = 0, - Count = 1, - CountDistinct = 2, - Avg = 3, - Median = 4, - Min = 5, - Max = 6 -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs deleted file mode 100644 index ce4f1b1..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/CalculationFilterGroup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class CalculationFilterGroup -{ - // Hereditary logical operation applied to all Filters - public LogicalOperator LogicalOperator { get; set; } - - public IEnumerable Filters { get; set; } - - public CalculationFilterGroup() - { - LogicalOperator = LogicalOperator.And; - Filters = new List(); - } - - [JsonConstructor] - public CalculationFilterGroup(IEnumerable filters, LogicalOperator logicalOperator) - { - Filters = filters; - LogicalOperator = logicalOperator; - } - - public IEnumerable GetValidFilters() - { - return Filters?.Where(x => x.IsValid()).ToList() ?? new List(); - } - - public bool IsValid() - { - return Filters != null && Filters.Any(x => x.IsValid()); - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Filter.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Filter.cs deleted file mode 100644 index 726e6f1..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Filter.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -/// -/// Represents a filter criteria for querying data with support for various filter types including lists, date ranges, and timeframes. -/// Filters can be applied to specific data columns and support different date granularities. -/// -public class Filter -{ - /// - /// Gets the identifier of the data column to which this filter applies. - /// - public int DataColumnId { get; } - - /// - /// Gets the type of filter being applied (e.g., List, Calendar, Timeframe). - /// - public FilterType FilterType { get; } - - /// - /// Gets the collection of values to filter by. The interpretation depends on the . - /// - public IEnumerable Values { get; } - - /// - /// Gets the collection of filter conditions that define complex filtering logic. - /// Only valid conditions are retained. - /// - public IEnumerable Conditions { get; } - - /// - /// Gets the date granularity part for date-based filtering (e.g., Year, Month, Day, FiscalYear). - /// - public DatePart DatePart { get; } - - /// - /// Gets a value indicating whether to use NOT IN instead of IN for list-type filters. - /// Only applies when is List. - /// - public bool ListUseNotIn { get; } - - /// - /// Gets the offset from the current time for timeframe-based filters. - /// Used in conjunction with to define relative time periods. - /// - public int DateTimeFrameOffset { get; } - - /// - /// Gets the zero-based number of time increments from the offset. - /// A value of 0 means current period, -1 means one period backward, and 1 means one period forward. - /// The unit (year, month, day, etc.) is determined by the property. - /// - public int DateTimeFrameCount { get; } - - /// - /// Initializes a new instance of the class with the specified filter criteria. - /// - /// The identifier of the data column to filter. - /// The type of filter to apply. - /// The collection of values for the filter. - /// The collection of filter conditions (invalid conditions are automatically removed). - /// The date granularity for date-based filtering. - /// Whether to use NOT IN for list filters; false to use IN. - /// The offset from current time for timeframe filters. - /// The number of time increments from the offset (0 = current, negative = past, positive = future). - [JsonConstructor] - public Filter(int dataColumnId, FilterType filterType, IEnumerable values, IEnumerable conditions, DatePart datePart, bool listUseNotIn, int dateTimeFrameOffset, int dateTimeFrameCount) - { - DataColumnId = dataColumnId; - FilterType = filterType; - Values = values; - Conditions = conditions.Where(x => x.IsValid()).ToList(); - DatePart = datePart; - ListUseNotIn = listUseNotIn; - DateTimeFrameOffset = dateTimeFrameOffset; - DateTimeFrameCount = dateTimeFrameCount; - } - - /// - /// Determines whether this filter has valid criteria that can be applied. - /// A filter is valid if it has values, conditions, or non-default timeframe settings. - /// - /// - /// true if the filter has values, conditions, or timeframe settings; otherwise, false. - /// - public bool IsValid() - { - return (Values != null && Values.Any()) || (Conditions != null && Conditions.Any()) || (DateTimeFrameCount != default || DateTimeFrameOffset != default); - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterCondition.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterCondition.cs deleted file mode 100644 index 9856236..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/FilterCondition.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class FilterCondition -{ - public FilterOperator Operator { get; set; } - - public IEnumerable? Values { get; set; } - - // This is not hereditary to Values; it is used for combination with the next FilterCondition in the set - // todo: That could be indexed to ensure accuracy - public LogicalOperator LogicalOperator { get; set; } - - public bool IsValid() - { - return Values != null && Values.Any(); - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfig.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfig.cs deleted file mode 100644 index f64ddfc..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class QueryConfig -{ - public IEnumerable Rows { get; set; } - - public IEnumerable Values { get; set; } - - public IEnumerable FilterGroups { get; } - - public bool WithTotals { get; set; } - - public int RowLimit { get; set; } - - public QueryConfig() - { - Rows = new List(); - Values = new List(); - FilterGroups = new List(); - } - - [JsonConstructor] - public QueryConfig(IEnumerable filterGroups, IEnumerable rows, IEnumerable values, bool withTotals, int rowLimit) - { - FilterGroups = filterGroups.Where(x => x.IsValid()).ToList(); - Rows = rows; - Values = values; - WithTotals = withTotals; - RowLimit = rowLimit; - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfigExtensions.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfigExtensions.cs deleted file mode 100644 index 33f623d..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/QueryConfigExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ - -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public static class QueryConfigExtensions -{ - /// - /// Gets all column ids referenced by this - /// - /// - /// - public static int[] GetAllColumnIds(this QueryConfig queryConfig) - { - return queryConfig.Values.SelectMany(value => value.CalculationDataColumnIds) - .Union(queryConfig.Values.SelectMany(x => x.FilterGroups.SelectMany(y => y.GetValidFilters().Select(f => f.DataColumnId)))) - .Union(queryConfig.Rows.Select(row => row.DataColumnId)) - // FilterGroup.Filters is pre-filtered at construction (see FilterGroup.cs JsonConstructor); no GetValidFilters() equivalent is needed here. - .Union(queryConfig.FilterGroups.SelectMany(filterGroup => filterGroup.Filters.Select(filter => filter.DataColumnId))) - .ToArray(); - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Row.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Row.cs deleted file mode 100644 index b602d65..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/Row.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class Row : Field -{ - public SortDirection sortDirection { get; set; } - - public Row() - { - sortDirection = SortDirection.Asc; - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SqlResponse.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SqlResponse.cs deleted file mode 100644 index e802cac..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/SqlResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class SqlResponse -{ - public string SqlQuery { get; } - public IEnumerable> Parameters { get; } - - public SqlResponse(string sqlQuery, IEnumerable> parameters) - { - SqlQuery = sqlQuery; - Parameters = parameters; - } -} diff --git a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ValueFilter.cs b/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ValueFilter.cs deleted file mode 100644 index 0df4cb6..0000000 --- a/src/Strata.SqlTools.SqlServer/ExpressionFactory/Query/ValueFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Strata.SqlTools.SqlServer.ExpressionFactory.Query; - -public class ValueFilter -{ - public int DataColumnId { get; set; } - - public object? FilterValue { get; set; } -} diff --git a/src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj b/src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj index 814ad45..4f4d830 100644 --- a/src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj +++ b/src/Strata.SqlTools.SqlServer/Strata.SqlTools.SqlServer.csproj @@ -41,6 +41,7 @@ + diff --git a/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs b/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs index 48f271f..eb5d708 100644 --- a/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs +++ b/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionFactoryFilterTests.cs @@ -1,4 +1,4 @@ -using Strata.SqlTools.SqlServer.ExpressionFactory.Query; +using Strata.SqlTools.Query; namespace Strata.SqlTools.SqlBreakdown.Tests.ExpressionTests; diff --git a/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs b/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs index 0551063..fa1efae 100644 --- a/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs +++ b/tests/Strata.SqlTools.SqlBreakdown.Tests/ExpressionTests/ExpressionTestsBase.cs @@ -1,7 +1,7 @@ using Strata.SqlTools.SqlBreakdown.Classes; using Strata.SqlTools.SqlBreakdown.Expressions; using Strata.SqlTools.SqlBreakdown.Expressions.Conditional; -using Strata.SqlTools.SqlServer.ExpressionFactory.Query; +using Strata.SqlTools.Query; using Strata.SqlTools.SqlBreakdown.Tests.RegisteredTables; using Strata.SqlTools.Visitors.Snowflake; using ExpressionFactoryBase = Strata.SqlTools.SqlServer.ExpressionFactory.ExpressionFactory; -- 2.54.0 From 2fae7738e31bf7049d720d75560d29b754052ccd Mon Sep 17 00:00:00 2001 From: Thom Lamb Date: Wed, 20 May 2026 13:02:28 -0500 Subject: [PATCH 5/5] test(pgsql): wrap CommandVisitor asserts in Assert.Multiple Resolves SonarQube NUnit2045. The three independent parameter-index assertions now report together instead of short-circuiting on the first failure. The Is.Not.Null guard in the reflection helper stays outside the multiple block because it gates the subsequent Invoke. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../PostgreSql/CommandVisitorTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs b/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs index 1b3896a..7745a44 100644 --- a/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs +++ b/tests/Strata.SqlTools.PostgreSql.Tests/PostgreSql/CommandVisitorTests.cs @@ -15,9 +15,12 @@ public class CommandVisitorTests 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"); + Assert.Multiple(() => + { + 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) -- 2.54.0