diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e383eb..0e15fc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,13 +60,13 @@ jobs: - name: Check for vulnerable dependencies run: dotnet list KeelMatrix.QueryWatch.sln package --vulnerable - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: csharp - name: Build for CodeQL - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Analyze with CodeQL - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 perf-bench: if: (github.event_name == 'pull_request' || github.ref == 'refs/heads/main') && github.actor != 'dependabot[bot]' needs: build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9b24f00..2f9cfdc 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -81,7 +81,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -109,6 +109,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{ matrix.language }}" diff --git a/README.md b/README.md index 030bb21..146f06b 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,11 @@ This repo ships three tiny sample apps (EF Core, ADO.NET, Dapper) that **consume ```bash dotnet pack ./src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj -c Release --include-symbols --p:SymbolPackageFormat=snupkg --output ./artifacts/packages dotnet pack ./src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj -c Release --include-symbols --p:SymbolPackageFormat=snupkg --output ./artifacts/packages + dotnet pack ./src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj -c Release --include-symbols --p:SymbolPackageFormat=snupkg --output ./artifacts/packages ``` -2. **Install local packages to samples** (pins to `./artifacts/packages` via `samples/NuGet.config`): - - Windows (PowerShell): `./samples/init.ps1` - - Linux/macOS (bash): `./samples/init.sh` +2. **Install local packages to samples** (pins to `../artifacts/packages` via `samples/NuGet.config`): + - Windows (PowerShell): `pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1` + - Linux/macOS (bash): `bash build/Dev-PackInstallSamples.sh` 3. **Run a sample** (EF example shown): ```bash dotnet run --project ./samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release @@ -77,7 +78,7 @@ var options = new DbContextOptionsBuilder() ```csharp using Dapper; using Microsoft.Data.Sqlite; -using KeelMatrix.QueryWatch.Dapper; +using KeelMatrix.QueryWatch; using KeelMatrix.QueryWatch.Testing; using var q = QueryWatchScope.Start(exportJsonPath: "artifacts/dapper.json", sampleTop: 200); @@ -99,7 +100,7 @@ var rows = await conn.QueryAsync("SELECT 1"); ```csharp using Microsoft.Data.Sqlite; -using KeelMatrix.QueryWatch.Ado; +using KeelMatrix.QueryWatch; using KeelMatrix.QueryWatch.Testing; using var q = QueryWatchScope.Start(exportJsonPath: "artifacts/ado.json", sampleTop: 200); diff --git a/build/Dev-CleanPackInstallSamples.ps1 b/build/Dev-CleanPackInstallSamples.ps1 new file mode 100644 index 0000000..c9ddd24 --- /dev/null +++ b/build/Dev-CleanPackInstallSamples.ps1 @@ -0,0 +1,55 @@ +Param() +$ErrorActionPreference = 'Stop' + +function Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } +function Run { + param([Parameter(Mandatory=$true)][string]$exe, [Parameter(ValueFromRemainingArguments=$true)][string[]]$args) + Write-Host (" " + $exe + " " + ($args -join " ")) -ForegroundColor DarkGray + & $exe @args + if ($LASTEXITCODE -ne 0) { throw "Command failed: $exe $($args -join ' ')" } +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +Set-Location $repoRoot + +try { + Step ".NET SDK info" + Run dotnet --info | Out-Null + + $artifacts = Join-Path $repoRoot "artifacts" + $pkgDir = Join-Path $artifacts "packages" + if (-not (Test-Path $pkgDir)) { New-Item -ItemType Directory -Path $pkgDir | Out-Null } + + # 0) Clean local KeelMatrix.* packages only (leave other artifacts intact) + Step "Clean ./artifacts/packages (KeelMatrix.QueryWatch*)" + Get-ChildItem -Path $pkgDir -Filter "KeelMatrix.QueryWatch*.nupkg" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + Get-ChildItem -Path $pkgDir -Filter "KeelMatrix.QueryWatch*.snupkg" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + + # 1) Restore solution + Step "Restore solution" + Run dotnet restore "KeelMatrix.QueryWatch.sln" + + # 2) Build in dependency-friendly order + Step "Build libraries (Release)" + Run dotnet build "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" -c Release --no-restore + Run dotnet build "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" -c Release --no-restore + Run dotnet build "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" -c Release --no-restore + + # 3) Pack all + Step "Pack libraries -> ./artifacts/packages" + $packArgs = @('--configuration','Release','--no-build','--include-symbols','--p:SymbolPackageFormat=snupkg','--output',$pkgDir) + Run dotnet pack "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" @packArgs + Run dotnet pack "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" @packArgs + Run dotnet pack "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" @packArgs + + # 4) Restore samples against local feed + Step "Restore samples with samples/NuGet.config" + Run dotnet restore "samples/QueryWatch.Samples.sln" --configfile "samples/NuGet.config" + + Step "Done" + Write-Host "Cleaned, packed, and restored samples successfully." -ForegroundColor Green +} +catch { + Write-Error $_ + exit 1 +} diff --git a/build/Dev-CleanPackInstallSamples.sh b/build/Dev-CleanPackInstallSamples.sh new file mode 100644 index 0000000..3bcf74e --- /dev/null +++ b/build/Dev-CleanPackInstallSamples.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +step() { printf "\n==> %s\n" "$1"; } +run() { printf " %s\n" "$*" >&2; "$@"; } + +SCRIPT_DIR="$( cd -- "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$REPO_ROOT" + +step ".NET SDK info" +run dotnet --info >/dev/null + +ARTIFACTS="$REPO_ROOT/artifacts" +PKG_DIR="$ARTIFACTS/packages" +mkdir -p "$PKG_DIR" + +step "Clean ./artifacts/packages (KeelMatrix.QueryWatch*)" +find "$PKG_DIR" -maxdepth 1 -type f \( -name 'KeelMatrix.QueryWatch*.nupkg' -o -name 'KeelMatrix.QueryWatch*.snupkg' \) -print -delete || true + +step "Restore solution" +run dotnet restore "KeelMatrix.QueryWatch.sln" + +step "Build libraries (Release)" +run dotnet build "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" -c Release --no-restore +run dotnet build "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" -c Release --no-restore +run dotnet build "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" -c Release --no-restore + +step "Pack libraries -> ./artifacts/packages" +COMMON_PACK_ARGS=('--configuration' 'Release' '--no-build' '--include-symbols' '--p:SymbolPackageFormat=snupkg' '--output' "$PKG_DIR") +run dotnet pack "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" "${COMMON_PACK_ARGS[@]}" +run dotnet pack "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" "${COMMON_PACK_ARGS[@]}" +run dotnet pack "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" "${COMMON_PACK_ARGS[@]}" + +step "Restore samples with samples/NuGet.config" +run dotnet restore "samples/QueryWatch.Samples.sln" --configfile "samples/NuGet.config" + +step "Done" +echo "Cleaned, packed, and restored samples successfully." diff --git a/build/Dev-PackInstallSamples.ps1 b/build/Dev-PackInstallSamples.ps1 new file mode 100644 index 0000000..042f5db --- /dev/null +++ b/build/Dev-PackInstallSamples.ps1 @@ -0,0 +1,51 @@ +Param() +$ErrorActionPreference = 'Stop' + +function Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } +function Run { + param([Parameter(Mandatory=$true)][string]$exe, [Parameter(ValueFromRemainingArguments=$true)][string[]]$args) + Write-Host (" " + $exe + " " + ($args -join " ")) -ForegroundColor DarkGray + & $exe @args + if ($LASTEXITCODE -ne 0) { throw "Command failed: $exe $($args -join ' ')" } +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +Set-Location $repoRoot + +try { + Step ".NET SDK info" + Run dotnet --info | Out-Null + + $artifacts = Join-Path $repoRoot "artifacts" + $pkgDir = Join-Path $artifacts "packages" + if (-not (Test-Path $pkgDir)) { New-Item -ItemType Directory -Path $pkgDir | Out-Null } + + # 1) Restore solution (ensures props/targets are resolved) + Step "Restore solution" + Run dotnet restore "KeelMatrix.QueryWatch.sln" + + # 2) Build packable libraries (Release) + Step "Build libraries (Release)" + Run dotnet build "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" -c Release --no-restore + Run dotnet build "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" -c Release --no-restore + Run dotnet build "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" -c Release --no-restore + + # 3) Pack to ./artifacts/packages (symbols included) + Step "Pack libraries -> ./artifacts/packages" + $packArgs = @('--configuration','Release','--no-build','--include-symbols','--p:SymbolPackageFormat=snupkg','--output',$pkgDir) + Run dotnet pack "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" @packArgs + Run dotnet pack "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" @packArgs + Run dotnet pack "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" @packArgs + + # 4) Restore samples using their NuGet.config (pins KeelMatrix.QueryWatch* to ../artifacts/packages) + Step "Restore samples with samples/NuGet.config" + Run dotnet restore "samples/QueryWatch.Samples.sln" --configfile "samples/NuGet.config" + + Step "Done" + Write-Host "Packages are in: $pkgDir" -ForegroundColor Green + Write-Host "Samples restored against local packages." -ForegroundColor Green +} +catch { + Write-Error $_ + exit 1 +} diff --git a/build/Dev-PackInstallSamples.sh b/build/Dev-PackInstallSamples.sh new file mode 100644 index 0000000..907ba95 --- /dev/null +++ b/build/Dev-PackInstallSamples.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +step() { printf "\n==> %s\n" "$1"; } +run() { printf " %s\n" "$*" >&2; "$@"; } + +SCRIPT_DIR="$( cd -- "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$REPO_ROOT" + +step ".NET SDK info" +run dotnet --info >/dev/null + +ARTIFACTS="$REPO_ROOT/artifacts" +PKG_DIR="$ARTIFACTS/packages" +mkdir -p "$PKG_DIR" + +step "Restore solution" +run dotnet restore "KeelMatrix.QueryWatch.sln" + +step "Build libraries (Release)" +run dotnet build "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" -c Release --no-restore +run dotnet build "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" -c Release --no-restore +run dotnet build "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" -c Release --no-restore + +step "Pack libraries -> ./artifacts/packages" +COMMON_PACK_ARGS=('--configuration' 'Release' '--no-build' '--include-symbols' '--p:SymbolPackageFormat=snupkg' '--output' "$PKG_DIR") +run dotnet pack "src/KeelMatrix.QueryWatch.Redaction/KeelMatrix.QueryWatch.Redaction.csproj" "${COMMON_PACK_ARGS[@]}" +run dotnet pack "src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj" "${COMMON_PACK_ARGS[@]}" +run dotnet pack "src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj" "${COMMON_PACK_ARGS[@]}" + +step "Restore samples with samples/NuGet.config" +run dotnet restore "samples/QueryWatch.Samples.sln" --configfile "samples/NuGet.config" + +step "Done" +echo "Packages are in: $PKG_DIR" +echo "Samples restored against local packages." diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..6fd2158 --- /dev/null +++ b/build/README.md @@ -0,0 +1,94 @@ +# 🧰 Build Scripts + +Helper scripts for local development and CI. +Run from the **repo root** unless otherwise noted. + +--- + +## 📦 What’s Here + +- **`Dev-PackInstallSamples.ps1` / `.sh`** — Restore, build, and **pack** `KeelMatrix.QueryWatch*` libraries, then restore **samples** against the locally packed feed (`./artifacts/packages`). + → `build/Dev-PackInstallSamples.ps1` • `build/Dev-PackInstallSamples.sh` + +- **`Dev-CleanPackInstallSamples.ps1` / `.sh`** — Same as above, but first **cleans** local `KeelMatrix.QueryWatch*.nupkg` / `.snupkg` before rebuilding & packing. Ideal when iterating locally. + → `build/Dev-CleanPackInstallSamples.ps1` • `build/Dev-CleanPackInstallSamples.sh` + +- **`Update-ReadmeFlags.ps1`** — Builds the CLI and updates the README block between + `` and `` using `--print-flags-md`. + Writes fallback output to `docs/CLI_FLAGS.generated.md` if markers are missing. + → `build/Update-ReadmeFlags.ps1` + +- **`Pack-Sign-Push.ps1`** — End-to-end **pack → (optional) sign → push** workflow. + Stubs for signing/publishing (customize for your environment). + → `build/Pack-Sign-Push.ps1` + +- **`New-DevSecrets.ps1`** — Stub that documents how to configure your **NuGet API key** and import a **code-signing certificate** locally. Safe to customize for your organization. + → `build/New-DevSecrets.ps1` + +> PowerShell (`.ps1`) and Bash (`.sh`) variants are provided to support cross-platform workflows. + +--- + +## ⚡ Quick Tasks + +### Pack libs and restore samples (fast path) + +#### Windows / PowerShell +```powershell +pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1 +``` + +#### Linux / macOS +```bash +bash build/Dev-PackInstallSamples.sh +``` + +--- + +### Clean old local packages, repack, restore samples + +```powershell +pwsh -NoProfile -File build/Dev-CleanPackInstallSamples.ps1 +``` + +```bash +bash build/Dev-CleanPackInstallSamples.sh +``` + +--- + +### Refresh CLI flags in README + +```powershell +./build/Update-ReadmeFlags.ps1 +``` + +--- + +### End-to-end pack → (optional) sign → push + +Customize first, then run: +```powershell +./build/Pack-Sign-Push.ps1 +``` + +--- + +## 🧩 Prerequisites + +- **.NET SDK 8.x+** → check via: + ```bash + dotnet --info + ``` + See: `docs/DEV.md` +- For signing/publish flows: configure your **NuGet API key** and (optional) **code-signing certificate** locally. + See: `build/New-DevSecrets.ps1` + +--- + +## 📁 Conventions + +- Artifacts are written to `./artifacts` (subfolders: `packages/`, `benchmarks/`, etc). + See: `build/Dev-PackInstallSamples.ps1` or `bench/README.md`. + +--- diff --git a/docs/DEV.md b/docs/DEV.md index 2479108..48d7ba4 100644 --- a/docs/DEV.md +++ b/docs/DEV.md @@ -195,8 +195,8 @@ Read more in `bench/BENCHMARKS.md`. dotnet pack ./src/KeelMatrix.QueryWatch/KeelMatrix.QueryWatch.csproj -c Release --no-build --include-symbols --p:SymbolPackageFormat=snupkg -o ./artifacts/packages dotnet pack ./src/KeelMatrix.QueryWatch.EfCore/KeelMatrix.QueryWatch.EfCore.csproj -c Release --no-build --include-symbols --p:SymbolPackageFormat=snupkg -o ./artifacts/packages - # Then run the sample helper - pwsh -NoProfile -File samples/init.ps1 + # Then run the sample setup + pwsh -NoProfile -File build/Dev-PackInstallSamples.ps1 dotnet run --project samples/EFCore.Sqlite/EFCore.Sqlite.csproj -c Release ``` diff --git a/samples/Ado.Sqlite/Program.cs b/samples/Ado.Sqlite/Program.cs index 5d0fe5a..bf814b7 100644 --- a/samples/Ado.Sqlite/Program.cs +++ b/samples/Ado.Sqlite/Program.cs @@ -44,7 +44,7 @@ // Query back using (var select = conn.CreateCommand()) { select.CommandText = Redaction.Apply("SELECT COUNT(*) FROM Users WHERE Name LIKE 'User_%';"); - var count = Convert.ToInt32(await select.ExecuteScalarAsync()); + var count = Convert.ToInt32(await select.ExecuteScalarAsync(), System.Globalization.CultureInfo.InvariantCulture); Console.WriteLine($"Users in DB: {count}"); } diff --git a/samples/Dapper.Sqlite/Program.cs b/samples/Dapper.Sqlite/Program.cs index 31b48ad..fac817d 100644 --- a/samples/Dapper.Sqlite/Program.cs +++ b/samples/Dapper.Sqlite/Program.cs @@ -1,6 +1,6 @@ using Dapper; using DapperSample; -using KeelMatrix.QueryWatch.Dapper; +using KeelMatrix.QueryWatch; using KeelMatrix.QueryWatch.Testing; using Microsoft.Data.Sqlite; @@ -26,7 +26,7 @@ await conn.ExecuteAsync(Redaction.Apply("/* contact: admin@example.com */ CREATE TABLE Users(Id INTEGER PRIMARY KEY, Name TEXT NOT NULL);")); // Insert in a transaction (exercise Transaction wrapper + async APIs) -using (var tx = conn.BeginTransaction()) { +using (var tx = await conn.BeginTransactionAsync()) { for (int i = 0; i < 3; i++) { var email = $"user{i}@example.com"; // will be redacted in CommandText await conn.ExecuteAsync( @@ -34,7 +34,7 @@ await conn.ExecuteAsync( new { name = Redaction.Param("User_" + i) }, transaction: tx); } - tx.Commit(); + await tx.CommitAsync(); } // Query back (async) diff --git a/samples/README.md b/samples/README.md index f25c4e7..8e2eb9b 100644 --- a/samples/README.md +++ b/samples/README.md @@ -12,7 +12,7 @@ Tiny apps that consume the local `KeelMatrix.QueryWatch*` packages so you can se ## Start here Follow the **[Quick Start — Samples (local)](../README.md#quick-start--samples-local)** in the root README. -> After you run `dotnet pack ...` at the repo root, use `./init.ps1` (or `./init.sh`) once to add local packages. No other tweaks are needed. +> After you run `dotnet pack ...` at the repo root, use `pwsh -NoProfile -File ../build/Dev-PackInstallSamples.ps1` (or `bash ../build/Dev-PackInstallSamples.sh`) once to install local packages. No other tweaks are needed. ### Run a sample ```bash diff --git a/samples/init.ps1 b/samples/init.ps1 deleted file mode 100644 index ce7c9d8..0000000 --- a/samples/init.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -Param() -$ErrorActionPreference = 'Stop' -Write-Host "Adding KeelMatrix.QueryWatch packages to samples using local NuGet source (../artifacts/packages)..." -ForegroundColor Cyan -dotnet --info | Out-Null - -dotnet add ./EFCore.Sqlite/EFCore.Sqlite.csproj package KeelMatrix.QueryWatch -dotnet add ./EFCore.Sqlite/EFCore.Sqlite.csproj package KeelMatrix.QueryWatch.EfCore -dotnet add ./Ado.Sqlite/Ado.Sqlite.csproj package KeelMatrix.QueryWatch -dotnet add ./Dapper.Sqlite/Dapper.Sqlite.csproj package KeelMatrix.QueryWatch - -Write-Host "Restore completed. You can now run the samples." -ForegroundColor Green - diff --git a/samples/init.sh b/samples/init.sh deleted file mode 100644 index aa4d388..0000000 --- a/samples/init.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "Adding KeelMatrix.QueryWatch packages to samples using local NuGet source (../artifacts/packages)..." -dotnet --info >/dev/null - -dotnet add ./EFCore.Sqlite/EFCore.Sqlite.csproj package KeelMatrix.QueryWatch -dotnet add ./EFCore.Sqlite/EFCore.Sqlite.csproj package KeelMatrix.QueryWatch.EfCore -dotnet add ./Ado.Sqlite/Ado.Sqlite.csproj package KeelMatrix.QueryWatch -dotnet add ./Dapper.Sqlite/Dapper.Sqlite.csproj package KeelMatrix.QueryWatch - -echo "Restore completed. You can now run the samples." - diff --git a/src/KeelMatrix.QueryWatch/Ado/QueryWatchConnection.cs b/src/KeelMatrix.QueryWatch/Ado/QueryWatchConnection.cs index 251e70a..735d2a0 100644 --- a/src/KeelMatrix.QueryWatch/Ado/QueryWatchConnection.cs +++ b/src/KeelMatrix.QueryWatch/Ado/QueryWatchConnection.cs @@ -71,31 +71,4 @@ protected override void Dispose(bool disposing) { base.Dispose(disposing); } } - - /// - /// Extension helpers for wrapping connections with QueryWatch. - /// - public static class QueryWatchConnectionExtensions { - /// - /// Wrap a to record commands into . - /// - /// Connection to wrap. - /// Session to record into. - /// A wrapper connection that instruments commands. - public static DbConnection WithQueryWatch(this DbConnection connection, QueryWatchSession session) - => new QueryWatchConnection(connection, session); - - /// - /// Wrap an whose underlying type is a . - /// - /// Connection to wrap. - /// Session to record into. - /// A wrapper connection that instruments commands. - /// Thrown when the connection type does not derive from . - public static IDbConnection WithQueryWatch(this IDbConnection connection, QueryWatchSession session) { - if (connection is DbConnection db) return new QueryWatchConnection(db, session); - throw new NotSupportedException("This provider doesn't derive from DbConnection; wrap commands manually."); - } - } } - diff --git a/src/KeelMatrix.QueryWatch/Dapper/DapperQueryWatchExtensions.cs b/src/KeelMatrix.QueryWatch/Dapper/DapperQueryWatchExtensions.cs deleted file mode 100644 index 79b8aa9..0000000 --- a/src/KeelMatrix.QueryWatch/Dapper/DapperQueryWatchExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Data; - -namespace KeelMatrix.QueryWatch.Dapper { - /// - /// Extension helpers for Dapper users: wraps an so commands are recorded into a . - /// - public static class DapperQueryWatchExtensions { - /// - /// Wraps a connection for QueryWatch. Returns the high‑fidelity ADO wrapper when possible; otherwise falls back to Dapper‑specific wrapper. - /// - /// Connection to wrap. - /// Session to record into. - /// A wrapper connection that instruments commands. - /// Thrown when or is null. - public static IDbConnection WithQueryWatch(this IDbConnection connection, QueryWatchSession session) { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (session is null) throw new ArgumentNullException(nameof(session)); - - if (connection is System.Data.Common.DbConnection db) - return new KeelMatrix.QueryWatch.Ado.QueryWatchConnection(db, session); - - return new DapperQueryWatchConnection(connection, session); - } - } -} diff --git a/src/KeelMatrix.QueryWatch/PublicAPI.Shipped.txt b/src/KeelMatrix.QueryWatch/PublicAPI.Shipped.txt index dfc95aa..a64b474 100644 --- a/src/KeelMatrix.QueryWatch/PublicAPI.Shipped.txt +++ b/src/KeelMatrix.QueryWatch/PublicAPI.Shipped.txt @@ -5,7 +5,6 @@ KeelMatrix.QueryWatch.Ado.QueryWatchCommand.QueryWatchCommand(System.Data.Common KeelMatrix.QueryWatch.Ado.QueryWatchConnection KeelMatrix.QueryWatch.Ado.QueryWatchConnection.Inner.get -> System.Data.Common.DbConnection! KeelMatrix.QueryWatch.Ado.QueryWatchConnection.QueryWatchConnection(System.Data.Common.DbConnection! inner, KeelMatrix.QueryWatch.QueryWatchSession! session) -> void -KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions KeelMatrix.QueryWatch.Ado.QueryWatchTransaction KeelMatrix.QueryWatch.Ado.QueryWatchTransaction.Inner.get -> System.Data.Common.DbTransaction! KeelMatrix.QueryWatch.Ado.QueryWatchTransaction.QueryWatchTransaction(System.Data.Common.DbTransaction! inner, KeelMatrix.QueryWatch.Ado.QueryWatchConnection! owner) -> void @@ -47,7 +46,6 @@ KeelMatrix.QueryWatch.Dapper.DapperQueryWatchConnection.Dispose() -> void KeelMatrix.QueryWatch.Dapper.DapperQueryWatchConnection.Inner.get -> System.Data.IDbConnection! KeelMatrix.QueryWatch.Dapper.DapperQueryWatchConnection.Open() -> void KeelMatrix.QueryWatch.Dapper.DapperQueryWatchConnection.State.get -> System.Data.ConnectionState -KeelMatrix.QueryWatch.Dapper.DapperQueryWatchExtensions KeelMatrix.QueryWatch.Dapper.DapperQueryWatchTransaction KeelMatrix.QueryWatch.Dapper.DapperQueryWatchTransaction.Commit() -> void KeelMatrix.QueryWatch.Dapper.DapperQueryWatchTransaction.Connection.get -> System.Data.IDbConnection! @@ -177,9 +175,6 @@ override KeelMatrix.QueryWatch.Ado.QueryWatchConnection.State.get -> System.Data override KeelMatrix.QueryWatch.Ado.QueryWatchTransaction.Commit() -> void override KeelMatrix.QueryWatch.Ado.QueryWatchTransaction.IsolationLevel.get -> System.Data.IsolationLevel override KeelMatrix.QueryWatch.Ado.QueryWatchTransaction.Rollback() -> void -static KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions.WithQueryWatch(this System.Data.Common.DbConnection! connection, KeelMatrix.QueryWatch.QueryWatchSession! session) -> System.Data.Common.DbConnection! -static KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions.WithQueryWatch(this System.Data.IDbConnection! connection, KeelMatrix.QueryWatch.QueryWatchSession! session) -> System.Data.IDbConnection! -static KeelMatrix.QueryWatch.Dapper.DapperQueryWatchExtensions.WithQueryWatch(this System.Data.IDbConnection! connection, KeelMatrix.QueryWatch.QueryWatchSession! session) -> System.Data.IDbConnection! static KeelMatrix.QueryWatch.IO.PathUtils.Combine(params string![]! segments) -> string! static KeelMatrix.QueryWatch.QueryWatcher.Start(KeelMatrix.QueryWatch.QueryWatchOptions? options = null) -> KeelMatrix.QueryWatch.QueryWatchSession! static KeelMatrix.QueryWatch.QueryWatchReport.CreateSnapshot(System.Collections.Generic.IReadOnlyList! events, KeelMatrix.QueryWatch.QueryWatchOptions! options, System.DateTimeOffset startedAt, System.DateTimeOffset stoppedAt) -> KeelMatrix.QueryWatch.QueryWatchReport! @@ -199,3 +194,6 @@ KeelMatrix.QueryWatch.QueryWatchOptions.DisableEfCoreTextCapture.get -> bool KeelMatrix.QueryWatch.QueryWatchOptions.DisableEfCoreTextCapture.set -> void KeelMatrix.QueryWatch.QueryWatchViolationException.QueryWatchViolationException() -> void KeelMatrix.QueryWatch.QueryWatchViolationException.QueryWatchViolationException(string? message, System.Exception? innerException) -> void +KeelMatrix.QueryWatch.QueryWatchExtensions +static KeelMatrix.QueryWatch.QueryWatchExtensions.WithQueryWatch(this System.Data.Common.DbConnection! connection, KeelMatrix.QueryWatch.QueryWatchSession! session) -> System.Data.Common.DbConnection! +static KeelMatrix.QueryWatch.QueryWatchExtensions.WithQueryWatch(this System.Data.IDbConnection! connection, KeelMatrix.QueryWatch.QueryWatchSession! session) -> System.Data.IDbConnection! diff --git a/src/KeelMatrix.QueryWatch/QueryWatchExtensions.cs b/src/KeelMatrix.QueryWatch/QueryWatchExtensions.cs new file mode 100644 index 0000000..bc0492c --- /dev/null +++ b/src/KeelMatrix.QueryWatch/QueryWatchExtensions.cs @@ -0,0 +1,41 @@ +#nullable enable +using System.Data; +using System.Data.Common; + +namespace KeelMatrix.QueryWatch { + /// + /// Canonical entry point for wrapping ADO.NET/Dapper connections so commands + /// are recorded into a . + /// + public static class QueryWatchExtensions { + /// + /// Wrap a so commands are recorded into . + /// + /// Provider connection to wrap. + /// Session to record into. + /// A wrapper connection that instruments commands. + /// Thrown if or is null. + public static DbConnection WithQueryWatch(this DbConnection connection, QueryWatchSession session) { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (session is null) throw new ArgumentNullException(nameof(session)); + return new Ado.QueryWatchConnection(connection, session); + } + + /// + /// Wrap an so commands are recorded into . + /// If the underlying type derives from , the high-fidelity ADO wrapper is used; + /// otherwise falls back to Dapper‑specific wrapper. + /// + /// Provider connection to wrap. + /// Session to record into. + /// A wrapper connection that instruments commands. + /// Thrown if or is null. + public static IDbConnection WithQueryWatch(this IDbConnection connection, QueryWatchSession session) { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (session is null) throw new ArgumentNullException(nameof(session)); + + if (connection is DbConnection db) return new Ado.QueryWatchConnection(db, session); + return new Dapper.DapperQueryWatchConnection(connection, session); + } + } +} diff --git a/tests/KeelMatrix.QueryWatch.Tests/DapperWrapperTests.cs b/tests/KeelMatrix.QueryWatch.Tests/DapperWrapperTests.cs index 25bb8df..6fbbe8d 100644 --- a/tests/KeelMatrix.QueryWatch.Tests/DapperWrapperTests.cs +++ b/tests/KeelMatrix.QueryWatch.Tests/DapperWrapperTests.cs @@ -10,7 +10,7 @@ namespace KeelMatrix.QueryWatch.Tests { public class DapperWrapperTests { [Fact] public void CreateCommand_Wraps_And_Records_On_ExecuteNonQuery() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); var inner = new OnlyIdbConnection(); using var wrapped = new DapperQueryWatchConnection(inner, session); @@ -28,7 +28,7 @@ public void CreateCommand_Wraps_And_Records_On_ExecuteNonQuery() { [Fact] public void ExecuteScalar_And_Reader_Record_Events() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); using var wrapped = new DapperQueryWatchConnection(new OnlyIdbConnection(), session); using (var cmd = wrapped.CreateCommand()) { @@ -47,7 +47,7 @@ public void ExecuteScalar_And_Reader_Record_Events() { [Fact] public void BeginTransaction_Preserves_Wrapper_Connection_And_Unwraps_Inner_On_Command_Setter() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); var inner = new OnlyIdbConnection(); using var wrapped = new DapperQueryWatchConnection(inner, session); @@ -63,7 +63,7 @@ public void BeginTransaction_Preserves_Wrapper_Connection_And_Unwraps_Inner_On_C [Fact] public void Command_Connection_Getter_Is_Wrapper_And_Setter_Unwraps() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); var inner = new OnlyIdbConnection(); using var wrapped = new DapperQueryWatchConnection(inner, session); @@ -78,7 +78,7 @@ public void Command_Connection_Getter_Is_Wrapper_And_Setter_Unwraps() { [Fact] public void Dapper_Extension_WithQueryWatch_Chooses_Highest_Fidelity_Wrapper() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); // 1) If provider derives from DbConnection, we should get ADO wrapper (supports async). var dbDerived = new DbDerivedConnection(); @@ -87,7 +87,7 @@ public void Dapper_Extension_WithQueryWatch_Chooses_Highest_Fidelity_Wrapper() { // 2) If provider does not derive from DbConnection, we should get the Dapper-only wrapper. IDbConnection onlyIdb = new OnlyIdbConnection(); - var wrapped2 = KeelMatrix.QueryWatch.Dapper.DapperQueryWatchExtensions.WithQueryWatch(onlyIdb, session); + var wrapped2 = QueryWatchExtensions.WithQueryWatch(onlyIdb, session); wrapped2.Should().BeOfType(); } diff --git a/tests/KeelMatrix.QueryWatch.Tests/ExtensionHelpersOverloadsTests.cs b/tests/KeelMatrix.QueryWatch.Tests/ExtensionHelpersOverloadsTests.cs index 327b265..c3786cb 100644 --- a/tests/KeelMatrix.QueryWatch.Tests/ExtensionHelpersOverloadsTests.cs +++ b/tests/KeelMatrix.QueryWatch.Tests/ExtensionHelpersOverloadsTests.cs @@ -3,40 +3,49 @@ using System.Diagnostics.CodeAnalysis; using FluentAssertions; using KeelMatrix.QueryWatch.Ado; -using KeelMatrix.QueryWatch.Dapper; using Xunit; namespace KeelMatrix.QueryWatch.Tests { public class ExtensionHelpersOverloadsTests { [Fact] public void Ado_Extensions_WithQueryWatch_Overloads_Work_For_DbConnection_And_IDbConnection() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); var provider = new FakeDbConnection(); // DbConnection overload - var w1 = KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions.WithQueryWatch((DbConnection)provider, session); + var w1 = QueryWatchExtensions.WithQueryWatch((DbConnection)provider, session); w1.Should().BeOfType(); // IDbConnection overload with Db-derived type must also return QueryWatchConnection - var w2 = KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions.WithQueryWatch((IDbConnection)provider, session); + var w2 = QueryWatchExtensions.WithQueryWatch((IDbConnection)provider, session); w2.Should().BeOfType(); } [Fact] - public void Ado_Extension_WithQueryWatch_On_NonDbConnection_Throws_NotSupported() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + public void Ado_Extension_WithQueryWatch_On_NonDbConnection_Uses_DapperWrapper_And_Records() { + using var session = QueryWatcher.Start(); IDbConnection onlyIdb = new OnlyIdbConnection(); + var wrapped = QueryWatchExtensions.WithQueryWatch(onlyIdb, session); - Action act = () => KeelMatrix.QueryWatch.Ado.QueryWatchConnectionExtensions.WithQueryWatch(onlyIdb, session); - act.Should().Throw(); + wrapped.Should().NotBeSameAs(onlyIdb); + wrapped.Should().BeOfType(); + + // Smoke: a command executed through the wrapper should be recorded + using (var cmd = wrapped.CreateCommand()) { + cmd.CommandText = "SELECT 1"; + _ = cmd.ExecuteNonQuery(); + } + + var report = session.Stop(); + report.Events.Count.Should().BeGreaterThan(0, "executing a command via the Dapper wrapper must be recorded"); } [Fact] public void Dapper_Extension_Prefers_Ado_Wrapper_For_DbDerived() { - using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(); + using var session = QueryWatcher.Start(); IDbConnection provider = new FakeDbConnection(); - var wrapped = KeelMatrix.QueryWatch.Dapper.DapperQueryWatchExtensions.WithQueryWatch(provider, session); + var wrapped = QueryWatchExtensions.WithQueryWatch(provider, session); wrapped.Should().BeOfType(); }