diff --git a/.env b/.env
index e8c9f7b96..131f98893 100644
--- a/.env
+++ b/.env
@@ -3,8 +3,10 @@ SOURCE_WASM_FILE="WasmRunner/obj/release/net10.0/wasi-wasm/wasm/for-publish/Wasm
SQLITE_CONNECTION_STRING="Data Source=tests.db;Mode=ReadWrite"
MYSQL_CONNECTION_STRING="server=localhost;database=tests;user=root;AllowLoadLocalInfile=true;ConvertZeroDateTime=True"
+MSSQL_CONNECTION_STRING="Server=localhost,1433;Database=tests;User Id=sa;Password=Passw0rd!Test;TrustServerCertificate=True;Encrypt=False"
POSTGRES_CONNECTION_STRING="host=localhost;database=tests;username=postgres;password=pass;IncludeErrorDetail=true"
+MSSQL_SA_PASSWORD="Passw0rd!Test"
POSTGRES_USER="postgres"
POSTGRES_PASSWORD="pass"
TESTS_DB="tests"
\ No newline at end of file
diff --git a/.github/scripts/start-mssql.ps1 b/.github/scripts/start-mssql.ps1
new file mode 100644
index 000000000..cbc76d8e4
--- /dev/null
+++ b/.github/scripts/start-mssql.ps1
@@ -0,0 +1,20 @@
+# Starts SQL Server Express LocalDB on the Windows runner and points the tests at it (GlobalSetup creates the database).
+$ErrorActionPreference = 'Stop'
+
+if (-not (Get-Command sqllocaldb -ErrorAction SilentlyContinue)) {
+ choco install sqllocaldb -y --no-progress
+ if ($LASTEXITCODE -ne 0) { throw 'installing LocalDB failed' }
+ $env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [Environment]::GetEnvironmentVariable('Path', 'User')
+}
+
+sqllocaldb create MSSQLLocalDB -s
+if ($LASTEXITCODE -ne 0) {
+ sqllocaldb start MSSQLLocalDB
+ if ($LASTEXITCODE -ne 0) { throw 'starting LocalDB failed' }
+}
+sqllocaldb info MSSQLLocalDB
+
+# later steps read it; it wins over the .env value (EndToEndCommon does not overwrite existing variables)
+"MSSQL_CONNECTION_STRING=Server=(localdb)\MSSQLLocalDB;Database=$env:TESTS_DB;Integrated Security=true;TrustServerCertificate=True" |
+ Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
+Write-Host 'mssql ready'
diff --git a/.github/workflows/legacy-tests.yml b/.github/workflows/legacy-tests.yml
index fbb774d43..b51d33177 100644
--- a/.github/workflows/legacy-tests.yml
+++ b/.github/workflows/legacy-tests.yml
@@ -54,12 +54,13 @@ jobs:
# PostgreSQL 17 and MySQL 8.0 come preinstalled on the Windows runner image - using them is much faster than
# downloading and installing them (setup-postgres ~3.5 min, choco mysql ~2 min).
- # Both databases are set up in the background while the test projects are restored and built. This has to be
+ # SQL Server (experimental) runs as LocalDB, installed with choco when the image does not have it.
+ # The databases are set up in the background while the test projects are restored and built. This has to be
# a single step: child processes do not outlive the step that started them (the database services do).
- name: Set up databases and build test projects in parallel
shell: pwsh
run: |
- $dbJobs = foreach ($db in 'postgres', 'mysql') {
+ $dbJobs = foreach ($db in 'postgres', 'mysql', 'mssql') {
Start-Process -FilePath pwsh -PassThru -WindowStyle Hidden `
-ArgumentList '-NoProfile', '-File', ".github/scripts/start-$db.ps1" `
-RedirectStandardOutput "$env:RUNNER_TEMP\$db.log" -RedirectStandardError "$env:RUNNER_TEMP\$db.err.log"
@@ -73,9 +74,9 @@ jobs:
msbuild.exe ./end2end/EndToEndTestsLegacy/EndToEndTestsLegacy.csproj -p:Configuration=Release -p:FrameworkVersion=v4.7.2 -m
$buildExit = $LASTEXITCODE
- $dbJobs | Wait-Process -Timeout 180
+ $dbJobs | Wait-Process -Timeout 300
$failed = $false
- foreach ($db in 'postgres', 'mysql') {
+ foreach ($db in 'postgres', 'mysql', 'mssql') {
if (-not (Select-String -Path "$env:RUNNER_TEMP\$db.log" -Pattern "$db ready" -Quiet)) {
Get-ChildItem $env:RUNNER_TEMP -Filter "$db*.log" | ForEach-Object { Write-Host "--- $_"; Get-Content $_ }
$failed = $true
diff --git a/.github/workflows/mssql.yml b/.github/workflows/mssql.yml
new file mode 100644
index 000000000..3bbc3add3
--- /dev/null
+++ b/.github/workflows/mssql.yml
@@ -0,0 +1,140 @@
+name: SQL Server (experimental)
+defaults:
+ run:
+ shell: bash
+
+env:
+ WASI_SDK_VERSION: 25
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "*" ]
+ paths:
+ - "**"
+ - "!**.md"
+ - "!benchmark/BenchmarkDotNet.Artifacts/**"
+
+# The mssql engine is only on sqlc main and needs the patches in scripts/sqlc-patches, so the SQL Server examples
+# are generated by their own config (sqlc.mssql.ci.yaml) with a sqlc built here. Their end-to-end tests run with
+# the other databases in tests.yml / legacy-tests.yml.
+jobs:
+ build-sqlc:
+ name: Build sqlc (main + patches)
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: actions/setup-go@v6
+ with:
+ go-version: stable
+
+ - name: Build sqlc
+ run: ./scripts/sqlc-patches/build_sqlc.sh bin
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: sqlc-mssql
+ path: bin/
+
+ codegen:
+ name: Codegen (SQL Server)
+ runs-on: ubuntu-latest
+ needs: [build-sqlc]
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v6
+ with:
+ global-json-file: global.json
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: sqlc-mssql
+ path: bin
+
+ - run: chmod +x bin/*
+
+ - name: Verify the generated yamls are synced
+ run: |
+ ./scripts/sync_sqlc_options.sh
+ git diff --exit-code sqlc.mssql.local.generated.yaml sqlc.mssql.request.generated.yaml
+
+ - name: Verify pushed generated code and requests are synced (process plugin)
+ run: |
+ dotnet publish LocalRunner -c release --output dist/
+ bin/sqlc -f sqlc.mssql.local.generated.yaml diff
+ bin/sqlc -f sqlc.mssql.request.generated.yaml diff
+
+ - name: Install the WASI workload and SDK
+ run: |
+ dotnet workload install wasi-experimental
+ folder="wasi-sdk-${{ env.WASI_SDK_VERSION }}.0-x86_64-linux"
+ wget -q "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/$folder.tar.gz"
+ tar xf "$folder.tar.gz"
+ echo "WASI_SDK_PATH=$(pwd)/$folder" >> "$GITHUB_ENV"
+
+ - name: Verify pushed generated code is synced (WASM plugin)
+ run: |
+ dotnet publish WasmRunner -c release
+ ./scripts/wasm/copy_plugin_to.sh dist
+ ./scripts/wasm/update_sha.sh sqlc.mssql.ci.yaml
+ bin/sqlc -f sqlc.mssql.ci.yaml diff
+
+ - name: Build the SQL Server examples
+ run: |
+ for project in examples/*Mssql*/*.csproj; do dotnet build "$project"; done
+
+ syntax-matrix:
+ name: T-SQL syntax matrix
+ runs-on: ubuntu-latest
+ needs: [build-sqlc]
+ services:
+ mssql:
+ image: mcr.microsoft.com/mssql/server:2022-latest
+ env:
+ ACCEPT_EULA: "Y"
+ MSSQL_SA_PASSWORD: "Passw0rd!Test"
+ ports:
+ - 1433:1433
+ options: >-
+ --health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Passw0rd!Test' -Q 'SELECT 1'"
+ --health-interval 5s --health-timeout 10s --health-retries 20
+
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v6
+ with:
+ global-json-file: global.json
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: sqlc-mssql
+ path: bin
+
+ - run: chmod +x bin/*
+
+ - name: Run the syntax matrix
+ run: make mssql-syntax-matrix
+
+ - uses: actions/upload-artifact@v7
+ if: always()
+ with:
+ name: mssql-syntax-matrix
+ path: |
+ end2end/MssqlSyntaxMatrix/REPORT.md
+ end2end/MssqlSyntaxMatrix/results.json
+
+ - name: Verify the pushed report is up to date
+ run: git diff --exit-code end2end/MssqlSyntaxMatrix/REPORT.md end2end/MssqlSyntaxMatrix/results.json
diff --git a/AGENTS.md b/AGENTS.md
index 78b44c8c7..75e5abe16 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,7 +2,8 @@
**sqlc-gen-csharp** is a [sqlc](https://github.com/sqlc-dev/sqlc) plugin (written in C#) that turns a sqlc
`GenerateRequest` (protobuf: catalog + queries + options) into C# data access code for
-PostgreSQL (Npgsql), MySQL (MySqlConnector) and SQLite (Microsoft.Data.Sqlite), optionally wrapped with Dapper.
+PostgreSQL (Npgsql), MySQL (MySqlConnector), SQLite (Microsoft.Data.Sqlite) and, experimentally, SQL Server
+(Microsoft.Data.SqlClient, `engine: mssql`), optionally wrapped with Dapper.
More specific instructions live next to the code - read them before working in those areas:
- `Drivers/AGENTS.md` - type mappings, driver hooks, annotation generators, enums, per-DB quirks
@@ -66,7 +67,7 @@ architecture test in the same change and explain why.
PluginRunner.Run read GenerateRequest from stdin
CodeGenerator.Generate
InitGenerators Options(request); debugRequest -> dump request.json/request.message and stop
- InstantiateDriver engine (postgresql|mysql|sqlite) -> NpgsqlDriver | MySqlConnectorDriver | SqliteDriver
+ InstantiateDriver engine (postgresql|mysql|sqlite|mssql) -> NpgsqlDriver | MySqlConnectorDriver | SqliteDriver | MssqlDriver
group queries by query *file name* -> one QueriesGen file per group
ModelsGen (Models.cs), UtilsGen (Utils.cs, only if it has members), CsprojGen (if generateCsproj)
PluginRunner write GenerateResponse to stdout
@@ -97,6 +98,9 @@ public class QuerySql : IDisposable // Npgsql/MySQL: IDisposa
|-----------------------------------------------------------------------|----------------------------------------------------------|--------------------------------------------------|
| `examples/**` (code, `.csproj`, `request.json`, `request.message`) | plugin code + `examples/config/**` + `sqlc.ci.yaml` | `make sqlc-generate` |
| `sqlc.local.generated.yaml`, `sqlc.request.generated.yaml` | `sqlc.ci.yaml` (`.sql` section) | `make sync-sqlc-options` (needs `yq`; part of `make sqlc-generate`) |
+| `sqlc.mssql.local.generated.yaml`, `sqlc.mssql.request.generated.yaml` | `sqlc.mssql.ci.yaml` | `make sync-sqlc-options` |
+| `examples/*Mssql*/**` | plugin code + `examples/config/mssql/**` + `sqlc.mssql.ci.yaml` | `make sqlc-generate-mssql` (needs `bin/sqlc`, see section 3) |
+| `end2end/MssqlSyntaxMatrix/REPORT.md`, `results.json` | `end2end/MssqlSyntaxMatrix/cases/**` + the sqlc builds | `make mssql-syntax-matrix` (SQL Server running; deterministic, CI diffs it) |
| `end2end/EndToEndTests*/**/*.generated.cs` | `end2end/EndToEndScaffold/` templates + `Config.cs` | `make generate-end2end-tests` |
| `unit-tests/CodegenTests/test-requests/*/request.*` | `unit-tests/sqlc.yaml` + sibling `schema.sql`/`query.sql` | `cd unit-tests && sqlc generate` (part of `make unit-tests`) |
| `GeneratedProtobuf/Codegen.cs` | sqlc's `plugin/codegen.proto` | `make generate-protobuf` |
@@ -148,6 +152,16 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif
- `WasmRunner.csproj` forces a `wasip1` core module because sqlc's wazero cannot load the wasip2 component the .NET 10
WASI SDK produces. The override relies on private SDK item names - re-verify after any SDK upgrade.
+**SQL Server (experimental)**
+- sqlc releases have no `mssql` engine; it is on sqlc `main` and this plugin needs it with the patches in
+ `scripts/sqlc-patches` (`SQLC_REF` pins the commit; `build_sqlc.sh bin` builds `bin/sqlc-upstream` and `bin/sqlc`).
+ That is why the SQL Server examples live in `sqlc.mssql.ci.yaml` and are checked by `.github/workflows/mssql.yml`,
+ while `sqlc.ci.yaml` stays on the pinned sqlc release. The mssql e2e tests still run in the normal e2e jobs.
+- sqlc's mssql catalog calls the default schema `public` (and lists it twice); `dbo.x` and `x` are the same table.
+- Anything sqlc cannot type comes through with an empty type name; `MssqlDriver` maps it to `object` instead of failing.
+- `end2end/MssqlSyntaxMatrix` is the evidence for what sqlc must support: add a case there for any T-SQL construct
+ before reporting it upstream.
+
**Repository**
- `.env` is shared by docker-compose, the end-to-end tests and CI; `RepositoryTests` asserts it is consistent with
`benchmark/.env`.
@@ -200,7 +214,7 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif
| Option | Default | Notes |
|-------------------------------|----------------------------------------------------------------|-------|
| `targetFramework` | `net8.0` | `netstandard2.0`, `netstandard2.1`, `net8.0`, `net9.0`, `net10.0` |
-| `overrideDriverVersion` | `2.4.0` MySqlConnector, `8.0.6` Npgsql, `10.0.13` Microsoft.Data.Sqlite | constants in each driver |
+| `overrideDriverVersion` | `2.4.0` MySqlConnector, `8.0.6` Npgsql, `10.0.13` Microsoft.Data.Sqlite, `7.1.1` Microsoft.Data.SqlClient | constants in each driver |
| `generateCsproj` | `true` | |
| `namespaceName` | output directory name | |
| `useDapper` | `false` | |
@@ -221,6 +235,9 @@ CI fails if any of the generated artifacts above is stale (`sqlc diff`, `git dif
| `sqlc.arg`, `sqlc.narg`, `sqlc.embed` | ✅ | ✅ | ✅ | |
| `sqlc.slice` | 🚫 (use arrays) | ✅ | ✅ | runtime `Utils.TransformQueryForSliceArgs` |
+SQL Server (experimental, `MssqlDriver`) supports every annotation and macro above. `:execlastid` relies on a user-written
+`OUTPUT INSERTED.id`, `:copyfrom` uses `SqlBulkCopy`. It needs sqlc `main` with `scripts/sqlc-patches` (section 3).
+
### Target frameworks
- The plugin projects (section 1 graph) and the tooling/test projects target `net10.0`; SDK pinned in `global.json`.
Exceptions: `end2end/EndToEndCommon` (`netstandard2.0`) and `end2end/EndToEndTestsLegacy` (`net472`).
@@ -235,6 +252,9 @@ make generate-end2end-tests
make test-plugin # everything above + build + end-to-end tests (Docker)
make setup-wasm-plugin # WASM plugin -> dist/plugin.wasm (needs wasi-experimental workload + WASI SDK 25 in WASI_SDK_PATH)
sqlc -f sqlc.ci.yaml diff # WASM output must equal the committed examples
+scripts/sqlc-patches/build_sqlc.sh bin # sqlc main (+ patches) for SQL Server
+make sqlc-generate-mssql # regenerate the SQL Server examples with bin/sqlc
+make mssql-syntax-matrix # T-SQL syntax matrix (SQL Server running)
make dotnet-format
```
diff --git a/CodeGenerator/CodeGenerator.cs b/CodeGenerator/CodeGenerator.cs
index b094a63a0..e2a08076a 100644
--- a/CodeGenerator/CodeGenerator.cs
+++ b/CodeGenerator/CodeGenerator.cs
@@ -98,6 +98,7 @@ private DbDriver InstantiateDriver()
DriverName.MySqlConnector => new MySqlConnectorDriver(Options, Catalog, Queries),
DriverName.Npgsql => new NpgsqlDriver(Options, Catalog, Queries),
DriverName.Sqlite => new SqliteDriver(Options, Catalog, Queries),
+ DriverName.Mssql => new MssqlDriver(Options, Catalog, Queries),
_ => throw new ArgumentException($"unknown driver: {Options.DriverName}")
};
}
diff --git a/CodeGenerator/Generators/QueriesGen.cs b/CodeGenerator/Generators/QueriesGen.cs
index 99559061d..451d4d17c 100644
--- a/CodeGenerator/Generators/QueriesGen.cs
+++ b/CodeGenerator/Generators/QueriesGen.cs
@@ -167,7 +167,10 @@ private IEnumerable
CS\d+): (?[^\r\n]*?) \[", RegexOptions.Multiline)]
+ private static partial Regex ErrorRegex();
+}
\ No newline at end of file
diff --git a/end2end/MssqlSyntaxMatrix/Program.cs b/end2end/MssqlSyntaxMatrix/Program.cs
new file mode 100644
index 000000000..a64d94c43
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/Program.cs
@@ -0,0 +1,54 @@
+using dotenv.net;
+using System.Text.Json;
+
+namespace MssqlSyntaxMatrix;
+
+// Runs every T-SQL case under cases/ through sqlc (one or more builds), the C# plugin and SQL Server's own
+// sp_describe_* procedures, and writes a support matrix. See README.md.
+public static class Program
+{
+ public static async Task Main(string[] args)
+ {
+ var root = FindRepoRoot();
+ DotEnv.Load(options: new DotEnvOptions(envFilePaths: [Path.Combine(root, ".env")]));
+
+ var options = MatrixOptions.Parse(args, root);
+ var cases = TestCase.LoadAll(Path.Combine(options.MatrixDir, "cases"));
+ if (options.Filter is not null)
+ cases = [.. cases.Where(c => c.Id.Contains(options.Filter, StringComparison.OrdinalIgnoreCase))];
+ Console.Error.WriteLine($"{cases.Count} cases, sqlc builds: {string.Join(", ", options.SqlcBuilds.Select(b => b.Name))}");
+
+ var oracle = new SqlServerOracle(options.ConnectionString);
+ var workDir = Directory.CreateTempSubdirectory("mssql-matrix-");
+ var results = new List();
+ foreach (var testCase in cases)
+ {
+ Console.Error.WriteLine($"- {testCase.Id}");
+ var caseDir = Path.Combine(workDir.FullName, testCase.Id.Replace('/', '_'));
+ Directory.CreateDirectory(caseDir);
+ var result = new CaseResult(testCase);
+ foreach (var build in options.SqlcBuilds)
+ result.Sqlc[build.Name] = await SqlcRunner.Run(build, testCase, Path.Combine(caseDir, build.Name), options);
+ result.Oracle = await oracle.Describe(testCase);
+ results.Add(result);
+ }
+
+ if (options.Compile)
+ await PluginCompiler.CompileAll(results, options, Path.Combine(workDir.FullName, "compile"));
+
+ var report = MarkdownReport.Render(results, options);
+ await File.WriteAllTextAsync(Path.Combine(options.MatrixDir, "REPORT.md"), report);
+ await File.WriteAllTextAsync(Path.Combine(options.MatrixDir, "results.json"),
+ JsonSerializer.Serialize(results.Select(r => r.ToJson(options)), new JsonSerializerOptions { WriteIndented = true }) + "\n");
+ Console.Error.WriteLine($"wrote {Path.Combine(options.MatrixDir, "REPORT.md")}");
+ return 0;
+ }
+
+ private static string FindRepoRoot()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "sqlc-gen-csharp.sln")))
+ dir = dir.Parent;
+ return dir?.FullName ?? throw new InvalidOperationException("repository root not found");
+ }
+}
\ No newline at end of file
diff --git a/end2end/MssqlSyntaxMatrix/README.md b/end2end/MssqlSyntaxMatrix/README.md
new file mode 100644
index 000000000..15b3d16a3
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/README.md
@@ -0,0 +1,66 @@
+# SQL Server syntax matrix
+
+What does sqlc need to support for a C# developer to use it with SQL Server? This project answers it with
+evidence. Every file in [`cases/`](cases) is a small schema plus one or more sqlc queries, covering one T-SQL
+feature, and every case is run through:
+
+1. **each sqlc build** given on the command line (typically sqlc `main`, and sqlc `main` with the patches in
+ [`scripts/sqlc-patches`](../../scripts/sqlc-patches)), with the `json` codegen, to get the parameters and
+ result columns sqlc infers;
+2. **SQL Server itself**: the schema is created in a fresh database and the query is described with
+ `sp_describe_undeclared_parameters` and `sp_describe_first_result_set`. This is the oracle: what SQL Server
+ says the parameter and column types are;
+3. **this plugin** (driver and Dapper flavours) on the output of the last sqlc build;
+4. optionally, **the C# compiler** on all the generated code (`--compile`).
+
+The result is [REPORT.md](REPORT.md) (and [results.json](results.json) for tooling). [ISSUES.md](ISSUES.md) groups
+what is missing into upstream issue drafts (sqlc vs. the teesql parser).
+
+| Level | Meaning |
+|-------|---------|
+| ✅ | sqlc agrees with SQL Server |
+| 🟡 | minor: sqlc is more conservative, or picks a compatible type of the same family (`int` vs `bigint`) |
+| 🟠 | wrong or missing type, nullability, parameter, name or column count |
+| 🕳️ | the query is silently dropped from sqlc's output |
+| ❌ | sqlc fails |
+| 💥 | sqlc panics |
+
+## Running it
+
+Needs SQL Server (`docker compose up -d mssqldb`), the published process plugin (`make dotnet-publish-process`)
+and one or more sqlc binaries:
+
+```bash
+make mssql-syntax-matrix SQLC_UPSTREAM=/path/to/sqlc-main SQLC_PATCHED=/path/to/sqlc-patched
+# or directly
+dotnet run --project end2end/MssqlSyntaxMatrix -- \
+ --sqlc "upstream=/path/to/sqlc@sqlc main (3ad8b20)" \
+ --sqlc "patched=/path/to/sqlc@sqlc main + patches" \
+ --compile [--filter select/]
+```
+
+`--sqlc name=path[@label]` is repeatable and sets the report columns; the plugin and compile columns use the last one.
+
+## Adding a case
+
+```sql
+-- @description What the case covers (shown in the report)
+-- @schema
+CREATE TABLE things (id INT NOT NULL); -- optional; cases/base.sql (authors, books) is always created first
+-- @queries
+-- name: ListThings :many
+SELECT id FROM things WHERE id > @min_id;
+```
+
+Keep a case to one feature, write it the way a SQL Server developer would, and make sure SQL Server accepts it
+(the report flags a schema SQL Server rejects, and the details show when SQL Server cannot describe a query).
+
+## Known limits of the oracle
+
+- SQL Server reports most expressions (`COUNT(*)`, `CASE`, ...) as nullable, so a NOT NULL expression from sqlc is
+ only 🟡; for table columns (joins, `ROLLUP`) the difference is 🟠.
+- `sp_describe_undeclared_parameters` refuses a parameter used twice; every use is then described separately and
+ the types are merged (`@p IS NULL` makes SQL Server guess `int`, so a concrete type wins over it).
+- Some parameters cannot be typed by SQL Server either (`COALESCE(@p, '%')`, `STRING_SPLIT(@p, ',')`,
+ `OPENJSON(@p)`) and temp tables cannot be described; the details say so.
+- SQL Server 2025 types (`json`, `vector`) are not covered because the CI image is SQL Server 2022.
diff --git a/end2end/MssqlSyntaxMatrix/REPORT.md b/end2end/MssqlSyntaxMatrix/REPORT.md
new file mode 100644
index 000000000..b322b5783
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/REPORT.md
@@ -0,0 +1,1370 @@
+# SQL Server (mssql) syntax matrix
+
+
+Every case in [`cases/`](cases) is run through each sqlc build, the C# plugin, and SQL Server's own
+`sp_describe_undeclared_parameters` / `sp_describe_first_result_set`, which act as the oracle for what
+the parameter and result column types should be. See [README.md](README.md).
+
+- **upstream**: sqlc main (3ad8b20)
+- **patched**: sqlc main (3ad8b20) + scripts/sqlc-patches
+
+| Legend | |
+|---|---|
+| ✅ | matches SQL Server |
+| 🟡 | minor difference (sqlc is more conservative, or a compatible type of the same family) |
+| 🟠 | wrong or missing type, nullability, name or column count |
+| 🕳️ | the query is silently dropped from sqlc's output |
+| ❌ | sqlc fails |
+| 💥 | sqlc panics |
+
+## Summary
+
+| | upstream | patched |
+|---|---|---|
+| ✅ Ok | 98 | 106 |
+| 🟡 Minor | 17 | 18 |
+| 🟠 Mismatch | 38 | 34 |
+| 🕳️ Dropped | 9 | 8 |
+| ❌ Error | 24 | 21 |
+| 💥 Panic | 1 | 0 |
+| **cases** | 187 | 187 |
+
+C# plugin (on **patched** output): 157/158 generate, 157/158 compile.
+
+## aggregate
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [basic_aggregates](cases/aggregate/basic_aggregates.sql) COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types | 🟡 | 🟡 | ✅ | ✅ | column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) |
+| [group_by_having](cases/aggregate/group_by_having.sql) GROUP BY with HAVING on a parameter | 🟡 | 🟡 | ✅ | ✅ | column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) |
+| [rollup_cube_grouping_sets](cases/aggregate/rollup_cube_grouping_sets.sql) ROLLUP, CUBE, GROUPING SETS and GROUPING() | 🟠 | 🟠 | ✅ | ✅ | column is_total has no type |
+| [statistical](cases/aggregate/statistical.sql) STDEV, VAR and APPROX_COUNT_DISTINCT | 🟠 | 🟠 | ✅ | ✅ | column stdev has no type |
+| [string_agg](cases/aggregate/string_agg.sql) STRING_AGG with WITHIN GROUP | 🟠 | 🟠 | ✅ | ✅ | column titles has no type |
+
+## annotations
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [copyfrom_identity_table](cases/annotations/copyfrom_identity_table.sql) `:copyfrom` into a table with an IDENTITY column | 💥 | ✅ | ✅ | ✅ | |
+| [copyfrom_schema_scoped](cases/annotations/copyfrom_schema_scoped.sql) `:copyfrom` into a table in a user-defined schema | ❌ | ❌ | – | – | query.sql:1:1: expected ), got message |
+| [execlastid_output_identity](cases/annotations/execlastid_output_identity.sql) `:execlastid` via OUTPUT INSERTED.id on an IDENTITY column | ✅ | ✅ | ✅ | ✅ | |
+| [execlastid_scope_identity](cases/annotations/execlastid_scope_identity.sql) `:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT | 🟠 | 🟠 | ✅ | ✅ | 0 result columns, SQL Server returns 1 ((unnamed)) |
+| [execrows](cases/annotations/execrows.sql) `:execrows` on UPDATE and DELETE | ✅ | ✅ | ✅ | ✅ | |
+| [one_many_exec](cases/annotations/one_many_exec.sql) `:one`, `:many`, `:exec` basics | ✅ | ✅ | ✅ | ✅ | |
+
+## batch
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [declare_variables](cases/batch/declare_variables.sql) DECLARE / SET local variables before the final SELECT | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [select_into_temp_table](cases/batch/select_into_temp_table.sql) SELECT ... INTO #temp then reading it, in one query | ✅ | ✅ | ✅ | ✅ | |
+| [set_nocount_on](cases/batch/set_nocount_on.sql) SET NOCOUNT ON in front of a query (a very common T-SQL habit) | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [transaction_in_query](cases/batch/transaction_in_query.sql) Explicit BEGIN TRAN / COMMIT inside a query | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [try_catch_throw](cases/batch/try_catch_throw.sql) TRY ... CATCH with THROW | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+
+## cte
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [cte_with_dml](cases/cte/cte_with_dml.sql) CTEs in front of UPDATE and DELETE | 🟡 | 🟡 | ✅ | ✅ | parameter @n: sqlc int, SQL Server bigint |
+| [multiple_ctes](cases/cte/multiple_ctes.sql) Several CTEs referencing each other | 🟠 | 🟠 | ✅ | ✅ | column rnk has no type |
+| [recursive_cte](cases/cte/recursive_cte.sql) Recursive CTE with UNION ALL | ❌ | ❌ | – | – | query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set |
+| [simple_cte](cases/cte/simple_cte.sql) A single common table expression | ✅ | ✅ | ✅ | ✅ | |
+
+## ddl
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [alias_type](cases/ddl/alias_type.sql) User-defined alias type (CREATE TYPE ... FROM) | 🟠 | 🟠 | ❌ | – | column email: sqlc email_address, SQL Server nvarchar(320) |
+| [alter_column_type_and_nullability](cases/ddl/alter_column_type_and_nullability.sql) ALTER TABLE ... ALTER COLUMN changes the type and nullability | 🟡 | 🟡 | ✅ | ✅ | column bio: sqlc nullable, SQL Server NOT NULL |
+| [alter_table_add_column](cases/ddl/alter_table_add_column.sql) ALTER TABLE ... ADD column | ✅ | ✅ | ✅ | ✅ | |
+| [alter_table_drop_column](cases/ddl/alter_table_drop_column.sql) ALTER TABLE ... DROP COLUMN | ✅ | ✅ | ✅ | ✅ | |
+| [bracketed_identifiers](cases/ddl/bracketed_identifiers.sql) Bracket-quoted table and column names with spaces and reserved words | 🟡 | 🟡 | ✅ | ✅ | column 3 is named unit price, SQL Server keeps the case: Unit Price |
+| [case_insensitive_names](cases/ddl/case_insensitive_names.sql) Object names are case-insensitive under the default collation | 🟡 | 🟡 | ✅ | ✅ | column 1 is named id, SQL Server keeps the case: ID |
+| [check_and_unique_constraints](cases/ddl/check_and_unique_constraints.sql) CHECK and UNIQUE constraints, column and table level | ✅ | ✅ | ✅ | ✅ | |
+| [collate_and_sparse](cases/ddl/collate_and_sparse.sql) COLLATE and SPARSE column options | ✅ | ✅ | ✅ | ✅ | |
+| [computed_columns](cases/ddl/computed_columns.sql) Computed columns, plain and PERSISTED | ❌ | ❌ | – | – | schema.sql:1:1: column people.full_name: missing type name |
+| [drop_table_if_exists](cases/ddl/drop_table_if_exists.sql) DROP TABLE IF EXISTS followed by a re-create with a different shape | ✅ | ✅ | ✅ | ✅ | |
+| [foreign_key_on_delete](cases/ddl/foreign_key_on_delete.sql) Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL | ✅ | ✅ | ✅ | ✅ | |
+| [go_batch_separators](cases/ddl/go_batch_separators.sql) Schema split into several batches with GO | ✅ | ✅ | ✅ | ✅ | |
+| [identity_seed_increment](cases/ddl/identity_seed_increment.sql) IDENTITY(seed, increment) column on a non-default seed | ✅ | ✅ | ✅ | ✅ | |
+| [indexes](cases/ddl/indexes.sql) CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored | ✅ | ✅ | ✅ | ✅ | |
+| [named_default_constraints](cases/ddl/named_default_constraints.sql) Named DEFAULT constraints with GETDATE() and NEWID() | ✅ | ✅ | ✅ | ✅ | |
+| [rowversion](cases/ddl/rowversion.sql) ROWVERSION column used for optimistic concurrency | ✅ | ✅ | ✅ | ✅ | |
+| [same_table_name_two_schemas](cases/ddl/same_table_name_two_schemas.sql) Two tables with the same name in different schemas | ✅ | ✅ | ✅ | ✅ | |
+| [schema_scoped_table](cases/ddl/schema_scoped_table.sql) Table in a user-defined schema (CREATE SCHEMA in its own batch) | ✅ | ✅ | ✅ | ✅ | |
+| [sequence_default](cases/ddl/sequence_default.sql) SEQUENCE used as a column default and with NEXT VALUE FOR in a query | 🟠 | 🟠 | ✅ | ✅ | column next_id has no type |
+| [sp_rename_column](cases/ddl/sp_rename_column.sql) Column renamed with sp_rename (how T-SQL renames columns) | ❌ | ❌ | – | – | query.sql:1:1: unknown column "biography" |
+| [synonym](cases/ddl/synonym.sql) CREATE SYNONYM for a table | ❌ | ❌ | – | – | query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set |
+| [table_level_primary_key](cases/ddl/table_level_primary_key.sql) Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL | ✅ | ✅ | ✅ | ✅ | |
+| [temporal_table](cases/ddl/temporal_table.sql) System-versioned temporal table and FOR SYSTEM_TIME AS OF | 🟠 | 🟠 | ✅ | ✅ | parameter @as_of (datetime2(7)) is missing |
+| [view](cases/ddl/view.sql) Querying a VIEW | ❌ | ❌ | – | – | query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set |
+
+## delete
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [from_join](cases/delete/from_join.sql) DELETE ... FROM with a JOIN | ✅ | ✅ | ✅ | ✅ | |
+| [output_deleted](cases/delete/output_deleted.sql) DELETE ... OUTPUT DELETED.* | ✅ | ✅ | ✅ | ✅ | |
+| [simple](cases/delete/simple.sql) DELETE with a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [top](cases/delete/top.sql) DELETE TOP (@n) | 🟠 | 🟠 | ✅ | ✅ | parameter @n (bigint) is missing |
+| [truncate](cases/delete/truncate.sql) TRUNCATE TABLE | 🕳️ | ✅ | ✅ | ✅ | |
+
+## expressions
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [arithmetic](cases/expressions/arithmetic.sql) Arithmetic operators, integer division, modulo and type promotion | 🟠 | 🟠 | ✅ | ✅ | column doubled: sqlc bigint, SQL Server decimal(12,2) |
+| [at_time_zone](cases/expressions/at_time_zone.sql) AT TIME ZONE and SWITCHOFFSET | 🟠 | 🟠 | ✅ | ✅ | parameter @time_zone (nvarchar(4000)) is missing |
+| [bitwise](cases/expressions/bitwise.sql) Bitwise operators and bit columns | 🟡 | 🟡 | ✅ | ✅ | column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) |
+| [cast_convert](cases/expressions/cast_convert.sql) CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE | 🟠 | 🟠 | ✅ | ✅ | parameter @text (nvarchar(4000)) is missing |
+| [coalesce_isnull_nullif](cases/expressions/coalesce_isnull_nullif.sql) COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference | 🟠 | 🟠 | ✅ | ✅ | column bio_or_empty has no type |
+| [concat_format](cases/expressions/concat_format.sql) CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE | 🟠 | 🟠 | ✅ | ✅ | column label has no type |
+| [datetime_functions](cases/expressions/datetime_functions.sql) GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC | ❌ | ❌ | – | – | query.sql:1:1: unknown column "day" |
+| [greatest_least](cases/expressions/greatest_least.sql) GREATEST and LEAST (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | parameter @floor has no type (SQL Server: numeric(38,19)) |
+| [is_distinct_from](cases/expressions/is_distinct_from.sql) IS [NOT] DISTINCT FROM (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | parameter @bio (nvarchar(max)) is missing |
+| [math_functions](cases/expressions/math_functions.sql) ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND | 🟠 | 🟠 | ✅ | ✅ | column abs_price has no type |
+| [sql2022_functions](cases/expressions/sql2022_functions.sql) GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022) | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [string_functions](cases/expressions/string_functions.sql) LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF | 🟠 | 🟠 | ✅ | ✅ | column prefix has no type |
+| [system_functions](cases/expressions/system_functions.sql) NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM | 🟠 | 🟠 | ✅ | ✅ | parameter @payload has no type (SQL Server: nvarchar(4000)) |
+
+## hints
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [query_hints](cases/hints/query_hints.sql) OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN) | ✅ | ✅ | ✅ | ✅ | |
+| [table_hints](cases/hints/table_hints.sql) Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK | ✅ | ✅ | ✅ | ✅ | |
+
+## identifiers
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [at_sign_parameter_names](cases/identifiers/at_sign_parameter_names.sql) Parameter names with digits, underscores and mixed case | ✅ | ✅ | ✅ | ✅ | |
+| [duplicate_result_names](cases/identifiers/duplicate_result_names.sql) Two result columns with the same name | ✅ | ✅ | ✅ | ✅ | |
+| [mixed_case_names](cases/identifiers/mixed_case_names.sql) Mixed-case table and column names map to PascalCase | 🟡 | 🟡 | ✅ | ✅ | column 1 is named customerid, SQL Server keeps the case: CustomerID |
+| [non_identifier_column_names](cases/identifiers/non_identifier_column_names.sql) Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode | 🟡 | 🟡 | ✅ | ✅ | column 4 is named total $, SQL Server keeps the case: Total $ |
+| [reserved_word_columns](cases/identifiers/reserved_word_columns.sql) Columns named after T-SQL and C# keywords | ✅ | ✅ | ✅ | ✅ | |
+| [unnamed_expression_column](cases/identifiers/unnamed_expression_column.sql) A result column without a name | 🟡 | 🟡 | ✅ | ✅ | column 1 has no name in SQL Server (sqlc calls it count) |
+
+## insert
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [default_values](cases/insert/default_values.sql) INSERT ... DEFAULT VALUES and explicit DEFAULT | ✅ | ✅ | ✅ | ✅ | |
+| [identity_insert](cases/insert/identity_insert.sql) Explicit values for an IDENTITY column with SET IDENTITY_INSERT | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [insert_exec](cases/insert/insert_exec.sql) INSERT ... EXEC from a stored procedure | ✅ | ✅ | ✅ | ✅ | |
+| [insert_select](cases/insert/insert_select.sql) INSERT ... SELECT with a parameter | 🟠 | 🟠 | ✅ | ✅ | parameter @target_author_id has no type (SQL Server: bigint) |
+| [multi_row_values](cases/insert/multi_row_values.sql) INSERT with several VALUES rows | ✅ | ✅ | ✅ | ✅ | |
+| [output_inserted](cases/insert/output_inserted.sql) INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows | ✅ | ✅ | ✅ | ✅ | |
+| [output_into_table_variable](cases/insert/output_into_table_variable.sql) OUTPUT ... INTO a table variable inside a multi-statement query | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [schema_scoped](cases/insert/schema_scoped.sql) INSERT into a table in a user-defined schema | ❌ | ❌ | – | – | query.sql:1:1: expected ), got message |
+| [values](cases/insert/values.sql) INSERT ... VALUES with parameters for every column | ✅ | ✅ | ✅ | ✅ | |
+
+## joins
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [cross_apply](cases/joins/cross_apply.sql) CROSS APPLY and OUTER APPLY with a correlated TOP subquery | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [cross_join](cases/joins/cross_join.sql) CROSS JOIN and the comma join | ✅ | ✅ | ✅ | ✅ | |
+| [inner_left_right_full](cases/joins/inner_left_right_full.sql) INNER, LEFT, RIGHT and FULL joins and the nullability they introduce | 🟠 | 🟠 | ✅ | ✅ | column title: sqlc NOT NULL, SQL Server nullable |
+| [join_derived_table](cases/joins/join_derived_table.sql) Join to an aggregated derived table | ✅ | ✅ | ✅ | ✅ | |
+| [schema_qualified_join](cases/joins/schema_qualified_join.sql) Join across dbo-qualified and bracket-quoted names | ✅ | ✅ | ✅ | ✅ | |
+| [self_join](cases/joins/self_join.sql) Self join with aliases | ✅ | ✅ | ✅ | ✅ | |
+
+## json
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [for_json](cases/json/for_json.sql) FOR JSON PATH / AUTO producing a single JSON column | 🟠 | 🟠 | ✅ | ✅ | 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B) |
+| [json_object_array](cases/json/json_object_array.sql) JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022) | 🟠 | 🟠 | ✅ | ✅ | column obj has no type |
+| [json_value_query](cases/json/json_value_query.sql) JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column | 🟠 | 🟠 | ✅ | ✅ | parameter @new_name has no type (SQL Server: float) |
+| [openjson](cases/json/openjson.sql) OPENJSON with an explicit WITH schema, fed by a parameter | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+
+## macros
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [arg_forms](cases/macros/arg_forms.sql) sqlc.arg with a quoted name, a bare identifier and a double-quoted name | ❌ | ✅ | ✅ | ✅ | |
+| [arg_in_cast](cases/macros/arg_in_cast.sql) sqlc.arg inside CAST, which fixes its type | 🟠 | ✅ | ✅ | ✅ | |
+| [args_in_top_offset](cases/macros/args_in_top_offset.sql) sqlc.arg in TOP and in OFFSET / FETCH | 🟠 | 🟡 | ✅ | ✅ | parameter @take: sqlc int, SQL Server bigint |
+| [embed](cases/macros/embed.sql) sqlc.embed of a table, of aliases (self join) and next to a t.* star | ❌ | ✅ | ✅ | ✅ | |
+| [embed_schema_scoped](cases/macros/embed_schema_scoped.sql) sqlc.embed of a table in a user-defined schema | ❌ | ✅ | ✅ | ✅ | |
+| [mixed_with_at_params](cases/macros/mixed_with_at_params.sql) sqlc.arg mixed with plain @params, sharing a name | ✅ | ✅ | ✅ | ✅ | |
+| [narg](cases/macros/narg.sql) sqlc.narg makes a parameter nullable | 🟠 | ✅ | ✅ | ✅ | |
+| [slice](cases/macros/slice.sql) sqlc.slice for integers, strings, NOT IN and several slices in one query | 🟠 | ✅ | ✅ | ✅ | |
+
+## merge
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [merge_delete](cases/merge/merge_delete.sql) MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [merge_upsert](cases/merge/merge_upsert.sql) MERGE as an upsert, with OUTPUT $action | ❌ | ❌ | – | – | query.sql:1:1: expected identifier for schema object name |
+| [upsert_without_merge](cases/merge/upsert_without_merge.sql) The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT | ✅ | ✅ | ✅ | ✅ | |
+
+## programmability
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [exec_procedure](cases/programmability/exec_procedure.sql) Calling a stored procedure with EXEC and named arguments | 🕳️ | 🕳️ | – | – | every statement is silently dropped (sqlc: no queries contained in paths) |
+| [inline_table_function](cases/programmability/inline_table_function.sql) An inline table-valued function in FROM | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [multi_statement_table_function](cases/programmability/multi_statement_table_function.sql) A multi-statement table-valued function in FROM | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [scalar_function](cases/programmability/scalar_function.sql) A user-defined scalar function in the select list and WHERE clause | 🟠 | 🟠 | ✅ | ✅ | parameter @initials has no type (SQL Server: nvarchar(10)) |
+| [string_split](cases/programmability/string_split.sql) STRING_SPLIT as a table-valued function fed by a parameter | ❌ | ❌ | – | – | query.sql:1:1: where: scope: unsupported FROM item *ast.TODO |
+| [table_valued_parameter](cases/programmability/table_valued_parameter.sql) A table-valued parameter (user-defined table type) used in a join | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+
+## select
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [alias_equals_syntax](cases/select/alias_equals_syntax.sql) T-SQL column alias written as alias = expression | ✅ | ✅ | ✅ | ✅ | |
+| [between_params](cases/select/between_params.sql) BETWEEN with two parameters | ✅ | ✅ | ✅ | ✅ | |
+| [case_expression](cases/select/case_expression.sql) Searched and simple CASE expressions | 🟠 | 🟠 | ✅ | ✅ | column band: sqlc varchar, SQL Server nvarchar(9) |
+| [comments](cases/select/comments.sql) Block comments, nested block comments and trailing line comments inside a query | ❌ | ❌ | – | – | query.sql:1:1: unexpected token: * |
+| [derived_table](cases/select/derived_table.sql) Derived table (subquery in FROM) with column aliases | ✅ | ✅ | ✅ | ✅ | |
+| [distinct](cases/select/distinct.sql) SELECT DISTINCT | ✅ | ✅ | ✅ | ✅ | |
+| [exists](cases/select/exists.sql) EXISTS / NOT EXISTS with a parameter inside the subquery | ✅ | ✅ | ✅ | ✅ | |
+| [function_on_parameter](cases/select/function_on_parameter.sql) Parameters wrapped in functions and compared to columns | 🟡 | 🟡 | ✅ | ✅ | parameter @name: sqlc varchar, SQL Server nvarchar(4000) |
+| [iif_choose](cases/select/iif_choose.sql) IIF and CHOOSE | 🟠 | 🟠 | ✅ | ✅ | column second has no type |
+| [in_list_params](cases/select/in_list_params.sql) IN with a list of scalar parameters | ✅ | ✅ | ✅ | ✅ | |
+| [in_subquery_param](cases/select/in_subquery_param.sql) A parameter inside IN (subquery) | ✅ | ✅ | ✅ | ✅ | |
+| [like_escape](cases/select/like_escape.sql) LIKE with a parameter and an ESCAPE clause | ✅ | ✅ | ✅ | ✅ | |
+| [no_semicolon_terminator](cases/select/no_semicolon_terminator.sql) Statements without a terminating semicolon (optional in T-SQL) | ❌ | ❌ | – | – | query.sql:6:0: missing semicolon at end of file |
+| [offset_fetch](cases/select/offset_fetch.sql) OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters | 🟡 | 🟡 | ✅ | ✅ | parameter @skip: sqlc int, SQL Server bigint |
+| [offset_without_fetch](cases/select/offset_without_fetch.sql) OFFSET ... ROWS without FETCH | 🟡 | 🟡 | ✅ | ✅ | parameter @skip: sqlc int, SQL Server bigint |
+| [optional_filter](cases/select/optional_filter.sql) The optional filter idiom: (@p IS NULL OR column = @p) | ✅ | ✅ | ✅ | ✅ | |
+| [order_by_parameter](cases/select/order_by_parameter.sql) Dynamic ORDER BY driven by a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [parameter_in_select_list](cases/select/parameter_in_select_list.sql) A parameter echoed in the select list, with and without CAST | 🟠 | 🟠 | ✅ | ✅ | parameter @label has no type (SQL Server cannot deduce it either) |
+| [quoted_aliases](cases/select/quoted_aliases.sql) Aliases quoted with brackets, double quotes and single quotes | 🟡 | 🟡 | ✅ | ✅ | column 1 is named author id, SQL Server keeps the case: Author Id |
+| [repeated_parameter](cases/select/repeated_parameter.sql) The same parameter used several times, with different case | ✅ | ✅ | ✅ | ✅ | |
+| [scalar_subquery_column](cases/select/scalar_subquery_column.sql) Correlated scalar subquery in the select list | ✅ | ✅ | ✅ | ✅ | |
+| [select_star](cases/select/select_star.sql) SELECT * and qualified t.* expansion | ✅ | ✅ | ✅ | ✅ | |
+| [table_value_constructor](cases/select/table_value_constructor.sql) VALUES as a derived table | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [top_literal_with_ties](cases/select/top_literal_with_ties.sql) TOP n WITH TIES and TOP n PERCENT | ✅ | ✅ | ✅ | ✅ | |
+| [top_parameter](cases/select/top_parameter.sql) TOP with a parenthesized parameter | 🟡 | 🟡 | ✅ | ✅ | parameter @take: sqlc int, SQL Server bigint |
+| [unicode_literals](cases/select/unicode_literals.sql) N'' Unicode literals and string concatenation with + | 🟠 | 🟠 | ✅ | ✅ | column greeting: sqlc varchar, SQL Server nvarchar(105) |
+| [without_from](cases/select/without_from.sql) SELECT without FROM: parameters and functions only | 🟡 | 🟡 | ✅ | ✅ | column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) |
+
+## setops
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [pivot_unpivot](cases/setops/pivot_unpivot.sql) PIVOT and UNPIVOT | ❌ | ❌ | – | – | query.sql:1:1: scope: unsupported FROM item *ast.TODO |
+| [union_except_intersect](cases/setops/union_except_intersect.sql) UNION, UNION ALL, EXCEPT and INTERSECT | ✅ | ✅ | ✅ | ✅ | |
+
+## types
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [bigint](cases/types/bigint.sql) `BIGINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [binary](cases/types/binary.sql) `BINARY(16)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [bit](cases/types/bit.sql) `BIT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [char](cases/types/char.sql) `CHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [date](cases/types/date.sql) `DATE` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [datetime](cases/types/datetime.sql) `DATETIME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [datetime2](cases/types/datetime2.sql) `DATETIME2(7)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [datetime2_0](cases/types/datetime2_0.sql) `DATETIME2(0)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [datetimeoffset](cases/types/datetimeoffset.sql) `DATETIMEOFFSET(7)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [decimal](cases/types/decimal.sql) `DECIMAL(18, 4)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [float](cases/types/float.sql) `FLOAT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [float_24](cases/types/float_24.sql) `FLOAT(24)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [geography](cases/types/geography.sql) `GEOGRAPHY` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+| [geometry](cases/types/geometry.sql) `GEOMETRY` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+| [hierarchyid](cases/types/hierarchyid.sql) `HIERARCHYID` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [image](cases/types/image.sql) `IMAGE` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+| [int](cases/types/int.sql) `INT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [max_lengths_and_precision](cases/types/max_lengths_and_precision.sql) The type expression keeps lengths, MAX, precision and scale | ✅ | ✅ | ✅ | ✅ | |
+| [money](cases/types/money.sql) `MONEY` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [nchar](cases/types/nchar.sql) `NCHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [ntext](cases/types/ntext.sql) `NTEXT` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+| [numeric](cases/types/numeric.sql) `NUMERIC(10, 0)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [nvarchar](cases/types/nvarchar.sql) `NVARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [nvarchar_max](cases/types/nvarchar_max.sql) `NVARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [real](cases/types/real.sql) `REAL` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [rowversion_timestamp](cases/types/rowversion_timestamp.sql) `ROWVERSION` and its legacy synonym `TIMESTAMP` | ✅ | ✅ | ✅ | ✅ | |
+| [smalldatetime](cases/types/smalldatetime.sql) `SMALLDATETIME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [smallint](cases/types/smallint.sql) `SMALLINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [smallmoney](cases/types/smallmoney.sql) `SMALLMONEY` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [sql_variant](cases/types/sql_variant.sql) `SQL_VARIANT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [sysname](cases/types/sysname.sql) `SYSNAME` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [text](cases/types/text.sql) `TEXT` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+| [time](cases/types/time.sql) `TIME(3)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [tinyint](cases/types/tinyint.sql) `TINYINT` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [uniqueidentifier](cases/types/uniqueidentifier.sql) `UNIQUEIDENTIFIER` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [varbinary](cases/types/varbinary.sql) `VARBINARY(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [varbinary_max](cases/types/varbinary_max.sql) `VARBINARY(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [varchar](cases/types/varchar.sql) `VARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [varchar_max](cases/types/varchar_max.sql) `VARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter | ✅ | ✅ | ✅ | ✅ | |
+| [xml](cases/types/xml.sql) `XML` column: nullable and NOT NULL, read, written | ✅ | ✅ | ✅ | ✅ | |
+
+## update
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [compound_assignment](cases/update/compound_assignment.sql) Compound assignment operators += and *= | ✅ | ✅ | ✅ | ✅ | |
+| [from_join](cases/update/from_join.sql) UPDATE ... FROM with a JOIN (T-SQL specific) | ✅ | ✅ | ✅ | ✅ | |
+| [output_deleted_inserted](cases/update/output_deleted_inserted.sql) UPDATE ... OUTPUT DELETED.x, INSERTED.x | ✅ | ✅ | ✅ | ✅ | |
+| [set_default](cases/update/set_default.sql) UPDATE ... SET column = DEFAULT and = NULL | ✅ | ✅ | ✅ | ✅ | |
+| [simple](cases/update/simple.sql) UPDATE with SET parameters and WHERE | ✅ | ✅ | ✅ | ✅ | |
+| [top](cases/update/top.sql) UPDATE TOP (@n) | 🟠 | 🟠 | ✅ | ✅ | parameter @n (bigint) is missing |
+
+## window
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [aggregate_over](cases/window/aggregate_over.sql) Aggregates over windows, including a running total | 🟡 | 🟡 | ✅ | ✅ | column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions) |
+| [lag_lead_first_value](cases/window/lag_lead_first_value.sql) LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame | 🟠 | 🟠 | ✅ | ✅ | column previous_price has no type |
+| [ranking](cases/window/ranking.sql) ROW_NUMBER, RANK, DENSE_RANK and NTILE | 🟠 | 🟠 | ✅ | ✅ | column rn has no type |
+| [window_clause_and_percentiles](cases/window/window_clause_and_percentiles.sql) Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC | 🟠 | 🟠 | ✅ | ✅ | column median has no type |
+
+## xml
+
+| Case | upstream | patched | plugin | compile | Notes |
+|---|---|---|---|---|---|
+| [for_xml](cases/xml/for_xml.sql) FOR XML PATH, including the string-concatenation idiom | 🟠 | 🟠 | ✅ | ✅ | 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B) |
+| [xml_methods](cases/xml/xml_methods.sql) XML column with the value(), query(), exist() and nodes() methods | ❌ | ❌ | – | – | query.sql:1:1: unexpected token: query |
+
+## Details
+
+### aggregate/basic_aggregates
+
+COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types
+
+- **upstream** `BookStats` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `BookStats` 🟡 column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `BookStats` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `BookStats` 🟡 column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### aggregate/group_by_having
+
+GROUP BY with HAVING on a parameter
+
+- **upstream** `ProlificAuthors` 🟡 column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `ProlificAuthors` 🟡 column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### aggregate/rollup_cube_grouping_sets
+
+ROLLUP, CUBE, GROUPING SETS and GROUPING()
+
+- **upstream** `Rollup` 🟠 column is_total has no type
+- **upstream** `Rollup` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **upstream** `Rollup` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **upstream** `Rollup` 🟡 column is_total: sqlc nullable, SQL Server NOT NULL
+- **upstream** `Cube` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **upstream** `Cube` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **upstream** `Cube` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `GroupingSets` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **upstream** `GroupingSets` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **upstream** `GroupingSets` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Rollup` 🟠 column is_total has no type
+- **patched** `Rollup` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **patched** `Rollup` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **patched** `Rollup` 🟡 column is_total: sqlc nullable, SQL Server NOT NULL
+- **patched** `Cube` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **patched** `Cube` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **patched** `Cube` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `GroupingSets` 🟠 column author_id: sqlc NOT NULL, SQL Server nullable
+- **patched** `GroupingSets` 🟠 column is_available: sqlc NOT NULL, SQL Server nullable
+- **patched** `GroupingSets` 🟡 column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### aggregate/statistical
+
+STDEV, VAR and APPROX_COUNT_DISTINCT
+
+- **upstream** `PriceStatistics` 🟠 column stdev has no type
+- **upstream** `PriceStatistics` 🟠 column variance has no type
+- **upstream** `PriceStatistics` 🟠 column approx_authors has no type
+- **patched** `PriceStatistics` 🟠 column stdev has no type
+- **patched** `PriceStatistics` 🟠 column variance has no type
+- **patched** `PriceStatistics` 🟠 column approx_authors has no type
+
+### aggregate/string_agg
+
+STRING_AGG with WITHIN GROUP
+
+- **upstream** `TitlesPerAuthor` 🟠 column titles has no type
+- **patched** `TitlesPerAuthor` 🟠 column titles has no type
+
+### annotations/copyfrom_identity_table
+
+`:copyfrom` into a table with an IDENTITY column
+
+- **upstream** 💥
+ ```
+ panic: runtime error: invalid memory address or nil pointer dereference
+ github.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x?, 0x?})
+ github.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x?, 0x?}, {0x?, 0x?}, {0x?, 0x?})
+ github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x?, 0x?, {0x?, 0x?}, 0x?)
+ github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x?, {0x?, 0x?}, 0x?, {{0x?}})
+ github.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...)
+ ```
+
+### annotations/copyfrom_schema_scoped
+
+`:copyfrom` into a table in a user-defined schema
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: expected ), got message
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: expected ), got message
+ ```
+
+### annotations/execlastid_scope_identity
+
+`:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT
+
+- **upstream** `CreateAuthorReturnId` 🟠 0 result columns, SQL Server returns 1 ((unnamed))
+- **patched** `CreateAuthorReturnId` 🟠 0 result columns, SQL Server returns 1 ((unnamed))
+
+### batch/declare_variables
+
+DECLARE / SET local variables before the final SELECT
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### batch/set_nocount_on
+
+SET NOCOUNT ON in front of a query (a very common T-SQL habit)
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### batch/transaction_in_query
+
+Explicit BEGIN TRAN / COMMIT inside a query
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### batch/try_catch_throw
+
+TRY ... CATCH with THROW
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### cte/cte_with_dml
+
+CTEs in front of UPDATE and DELETE
+
+- **upstream** `DiscountTopBooks` 🟡 parameter @n: sqlc int, SQL Server bigint
+- **patched** `DiscountTopBooks` 🟡 parameter @n: sqlc int, SQL Server bigint
+
+### cte/multiple_ctes
+
+Several CTEs referencing each other
+
+- **upstream** `AuthorTotals` 🟠 column rnk has no type
+- **patched** `AuthorTotals` 🟠 column rnk has no type
+
+### cte/recursive_cte
+
+Recursive CTE with UNION ALL
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: with numbers: relation "public"."numbers": class "numbers": sql: no rows in result set
+ ```
+
+### ddl/alias_type
+
+User-defined alias type (CREATE TYPE ... FROM)
+
+- **upstream** `ListContacts` 🟠 column email: sqlc email_address, SQL Server nvarchar(320)
+- **upstream** `GetContactByEmail` 🟠 parameter @email: sqlc email_address, SQL Server nvarchar(320)
+- **patched** `ListContacts` 🟠 column email: sqlc email_address, SQL Server nvarchar(320)
+- **patched** `GetContactByEmail` 🟠 parameter @email: sqlc email_address, SQL Server nvarchar(320)
+- **plugin** ❌ `error generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver`
+
+### ddl/alter_column_type_and_nullability
+
+ALTER TABLE ... ALTER COLUMN changes the type and nullability
+
+- **upstream** `ListBios` 🟡 column bio: sqlc nullable, SQL Server NOT NULL
+- **patched** `ListBios` 🟡 column bio: sqlc nullable, SQL Server NOT NULL
+
+### ddl/bracketed_identifiers
+
+Bracket-quoted table and column names with spaces and reserved words
+
+- **upstream** `ListOrderDetails` 🟡 column 3 is named unit price, SQL Server keeps the case: Unit Price
+- **patched** `ListOrderDetails` 🟡 column 3 is named unit price, SQL Server keeps the case: Unit Price
+
+### ddl/case_insensitive_names
+
+Object names are case-insensitive under the default collation
+
+- **upstream** `ListAuthorsUpper` 🟡 column 1 is named id, SQL Server keeps the case: ID
+- **upstream** `ListAuthorsUpper` 🟡 column 2 is named name, SQL Server keeps the case: NAME
+- **patched** `ListAuthorsUpper` 🟡 column 1 is named id, SQL Server keeps the case: ID
+- **patched** `ListAuthorsUpper` 🟡 column 2 is named name, SQL Server keeps the case: NAME
+
+### ddl/computed_columns
+
+Computed columns, plain and PERSISTED
+
+- **upstream** ❌
+ ```
+ schema.sql:1:1: column people.full_name: missing type name
+ ```
+- **patched** ❌
+ ```
+ schema.sql:1:1: column people.full_name: missing type name
+ ```
+
+### ddl/sequence_default
+
+SEQUENCE used as a column default and with NEXT VALUE FOR in a query
+
+- **upstream** `NextTicketId` 🟠 column next_id has no type
+- **patched** `NextTicketId` 🟠 column next_id has no type
+
+### ddl/sp_rename_column
+
+Column renamed with sp_rename (how T-SQL renames columns)
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unknown column "biography"
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: unknown column "biography"
+ ```
+
+### ddl/synonym
+
+CREATE SYNONYM for a table
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: relation "public"."writers": class "writers": sql: no rows in result set
+ ```
+
+### ddl/temporal_table
+
+System-versioned temporal table and FOR SYSTEM_TIME AS OF
+
+- **upstream** `ListPricesAsOf` 🟠 parameter @as_of (datetime2(7)) is missing
+- **patched** `ListPricesAsOf` 🟠 parameter @as_of (datetime2(7)) is missing
+
+### ddl/view
+
+Querying a VIEW
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: relation "public"."author_books": class "author_books": sql: no rows in result set
+ ```
+
+### delete/top
+
+DELETE TOP (@n)
+
+- **upstream** `DeleteSomeBooks` 🟠 parameter @n (bigint) is missing
+- **patched** `DeleteSomeBooks` 🟠 parameter @n (bigint) is missing
+
+### delete/truncate
+
+TRUNCATE TABLE
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### expressions/arithmetic
+
+Arithmetic operators, integer division, modulo and type promotion
+
+- **upstream** `Arithmetic` 🟠 column doubled: sqlc bigint, SQL Server decimal(12,2)
+- **upstream** `Arithmetic` 🟡 column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Arithmetic` 🟡 column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Arithmetic` 🟡 column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Arithmetic` 🟠 column doubled: sqlc bigint, SQL Server decimal(12,2)
+- **patched** `Arithmetic` 🟡 column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Arithmetic` 🟡 column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Arithmetic` 🟡 column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### expressions/at_time_zone
+
+AT TIME ZONE and SWITCHOFFSET
+
+- **upstream** `InTimeZone` 🟠 parameter @time_zone (nvarchar(4000)) is missing
+- **upstream** `InTimeZone` 🟠 column local_time has no type
+- **upstream** `InTimeZone` 🟠 column switched has no type
+- **upstream** `InTimeZone` 🟡 column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `InTimeZone` 🟠 parameter @time_zone (nvarchar(4000)) is missing
+- **patched** `InTimeZone` 🟠 column local_time has no type
+- **patched** `InTimeZone` 🟠 column switched has no type
+- **patched** `InTimeZone` 🟡 column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### expressions/bitwise
+
+Bitwise operators and bit columns
+
+- **upstream** `Bitwise` 🟡 column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Bitwise` 🟡 column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### expressions/cast_convert
+
+CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE
+
+- **upstream** `Conversions` 🟠 parameter @text (nvarchar(4000)) is missing
+- **upstream** `Conversions` 🟠 column parsed has no type
+- **upstream** `Conversions` 🟠 column maybe_date has no type
+- **upstream** `Conversions` 🟡 column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Conversions` 🟡 column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Conversions` 🟡 column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Conversions` 🟡 column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Conversions` 🟡 column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Conversions` 🟠 parameter @text (nvarchar(4000)) is missing
+- **patched** `Conversions` 🟠 column parsed has no type
+- **patched** `Conversions` 🟠 column maybe_date has no type
+- **patched** `Conversions` 🟡 column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Conversions` 🟡 column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Conversions` 🟡 column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Conversions` 🟡 column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Conversions` 🟡 column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### expressions/coalesce_isnull_nullif
+
+COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference
+
+- **upstream** `Fallbacks` 🟠 column bio_or_empty has no type
+- **upstream** `Fallbacks` 🟠 column name_or_null has no type
+- **upstream** `Fallbacks` 🟡 column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `Fallbacks` 🟡 column bio_or_empty: sqlc nullable, SQL Server NOT NULL
+- **upstream** `ByPattern` 🟠 parameter @pattern has no type (SQL Server cannot deduce it either)
+- **upstream** `ByPatternIsNull` 🟠 parameter @pattern has no type (SQL Server: nvarchar(4000))
+- **patched** `Fallbacks` 🟠 column bio_or_empty has no type
+- **patched** `Fallbacks` 🟠 column name_or_null has no type
+- **patched** `Fallbacks` 🟡 column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Fallbacks` 🟡 column bio_or_empty: sqlc nullable, SQL Server NOT NULL
+- **patched** `ByPattern` 🟠 parameter @pattern has no type (SQL Server cannot deduce it either)
+- **patched** `ByPatternIsNull` 🟠 parameter @pattern has no type (SQL Server: nvarchar(4000))
+- SQL Server, `ByPattern` parameters: The type for parameter '@pattern' cannot be deduced in this context.
+
+### expressions/concat_format
+
+CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE
+
+- **upstream** `Formatting` 🟠 column label has no type
+- **upstream** `Formatting` 🟠 column joined has no type
+- **upstream** `Formatting` 🟠 column formatted has no type
+- **upstream** `Formatting` 🟠 column quoted has no type
+- **upstream** `Formatting` 🟠 column translated has no type
+- **upstream** `Formatting` 🟡 column label: sqlc nullable, SQL Server NOT NULL
+- **upstream** `Formatting` 🟡 column joined: sqlc nullable, SQL Server NOT NULL
+- **patched** `Formatting` 🟠 column label has no type
+- **patched** `Formatting` 🟠 column joined has no type
+- **patched** `Formatting` 🟠 column formatted has no type
+- **patched** `Formatting` 🟠 column quoted has no type
+- **patched** `Formatting` 🟠 column translated has no type
+- **patched** `Formatting` 🟡 column label: sqlc nullable, SQL Server NOT NULL
+- **patched** `Formatting` 🟡 column joined: sqlc nullable, SQL Server NOT NULL
+
+### expressions/datetime_functions
+
+GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unknown column "day"
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: unknown column "day"
+ ```
+
+### expressions/greatest_least
+
+GREATEST and LEAST (SQL Server 2022)
+
+- **upstream** `Clamp` 🟠 parameter @floor has no type (SQL Server: numeric(38,19))
+- **upstream** `Clamp` 🟠 parameter @ceiling has no type (SQL Server: numeric(38,19))
+- **upstream** `Clamp` 🟠 column at_least has no type
+- **upstream** `Clamp` 🟠 column at_most has no type
+- **patched** `Clamp` 🟠 parameter @floor has no type (SQL Server: numeric(38,19))
+- **patched** `Clamp` 🟠 parameter @ceiling has no type (SQL Server: numeric(38,19))
+- **patched** `Clamp` 🟠 column at_least has no type
+- **patched** `Clamp` 🟠 column at_most has no type
+
+### expressions/is_distinct_from
+
+IS [NOT] DISTINCT FROM (SQL Server 2022)
+
+- **upstream** `ChangedBios` 🟠 parameter @bio (nvarchar(max)) is missing
+- **patched** `ChangedBios` 🟠 parameter @bio (nvarchar(max)) is missing
+
+### expressions/math_functions
+
+ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND
+
+- **upstream** `MathFunctions` 🟠 column abs_price has no type
+- **upstream** `MathFunctions` 🟠 column rounded has no type
+- **upstream** `MathFunctions` 🟠 column ceil has no type
+- **upstream** `MathFunctions` 🟠 column floor_price has no type
+- **upstream** `MathFunctions` 🟠 column squared has no type
+- **upstream** `MathFunctions` 🟠 column root has no type
+- **upstream** `MathFunctions` 🟠 column sign has no type
+- **upstream** `MathFunctions` 🟠 column pi has no type
+- **upstream** `MathFunctions` 🟠 column random has no type
+- **upstream** `MathFunctions` 🟡 column pi: sqlc nullable, SQL Server NOT NULL
+- **patched** `MathFunctions` 🟠 column abs_price has no type
+- **patched** `MathFunctions` 🟠 column rounded has no type
+- **patched** `MathFunctions` 🟠 column ceil has no type
+- **patched** `MathFunctions` 🟠 column floor_price has no type
+- **patched** `MathFunctions` 🟠 column squared has no type
+- **patched** `MathFunctions` 🟠 column root has no type
+- **patched** `MathFunctions` 🟠 column sign has no type
+- **patched** `MathFunctions` 🟠 column pi has no type
+- **patched** `MathFunctions` 🟠 column random has no type
+- **patched** `MathFunctions` 🟡 column pi: sqlc nullable, SQL Server NOT NULL
+
+### expressions/sql2022_functions
+
+GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022)
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:5:1: unknown column "week"
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:5:1: unknown column "week"
+ ```
+- SQL Server, `Series` parameters: Argument data type void type is invalid for argument 2 of generate_series function. Operand type clash: int is incompatible with void type The batch could not be analyzed because of compile errors.
+- SQL Server, `Series` result set: Argument data type void type is invalid for argument 2 of generate_series function. Operand type clash: int is incompatible with void type The batch could not be analyzed because of compile errors.
+
+### expressions/string_functions
+
+LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF
+
+- **upstream** `StringFunctions` 🟠 column prefix has no type
+- **upstream** `StringFunctions` 🟠 column left2 has no type
+- **upstream** `StringFunctions` 🟠 column right2 has no type
+- **upstream** `StringFunctions` 🟠 column replaced has no type
+- **upstream** `StringFunctions` 🟠 column pos has no type
+- **upstream** `StringFunctions` 🟠 column digit_pos has no type
+- **upstream** `StringFunctions` 🟠 column reversed has no type
+- **upstream** `StringFunctions` 🟠 column stars has no type
+- **upstream** `StringFunctions` 🟠 column stuffed has no type
+- **upstream** `StringFunctions` 🟡 column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `StringFunctions` 🟡 column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟠 column prefix has no type
+- **patched** `StringFunctions` 🟠 column left2 has no type
+- **patched** `StringFunctions` 🟠 column right2 has no type
+- **patched** `StringFunctions` 🟠 column replaced has no type
+- **patched** `StringFunctions` 🟠 column pos has no type
+- **patched** `StringFunctions` 🟠 column digit_pos has no type
+- **patched** `StringFunctions` 🟠 column reversed has no type
+- **patched** `StringFunctions` 🟠 column stars has no type
+- **patched** `StringFunctions` 🟠 column stuffed has no type
+- **patched** `StringFunctions` 🟡 column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `StringFunctions` 🟡 column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### expressions/system_functions
+
+NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM
+
+- **upstream** `SystemValues` 🟠 parameter @payload has no type (SQL Server: nvarchar(4000))
+- **upstream** `SystemValues` 🟠 column row_count has no type
+- **upstream** `SystemValues` 🟠 column current_id has no type
+- **upstream** `SystemValues` 🟠 column db has no type
+- **upstream** `SystemValues` 🟠 column login has no type
+- **upstream** `SystemValues` 🟠 column hash has no type
+- **upstream** `SystemValues` 🟠 column checksum has no type
+- **upstream** `SystemValues` 🟡 column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **upstream** `SystemValues` 🟡 column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `SystemValues` 🟠 parameter @payload has no type (SQL Server: nvarchar(4000))
+- **patched** `SystemValues` 🟠 column row_count has no type
+- **patched** `SystemValues` 🟠 column current_id has no type
+- **patched** `SystemValues` 🟠 column db has no type
+- **patched** `SystemValues` 🟠 column login has no type
+- **patched** `SystemValues` 🟠 column hash has no type
+- **patched** `SystemValues` 🟠 column checksum has no type
+- **patched** `SystemValues` 🟡 column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `SystemValues` 🟡 column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### identifiers/mixed_case_names
+
+Mixed-case table and column names map to PascalCase
+
+- **upstream** `ListMixed` 🟡 column 1 is named customerid, SQL Server keeps the case: CustomerID
+- **upstream** `ListMixed` 🟡 column 2 is named firstname, SQL Server keeps the case: firstName
+- **upstream** `ListMixed` 🟡 column 3 is named last_name, SQL Server keeps the case: LAST_NAME
+- **patched** `ListMixed` 🟡 column 1 is named customerid, SQL Server keeps the case: CustomerID
+- **patched** `ListMixed` 🟡 column 2 is named firstname, SQL Server keeps the case: firstName
+- **patched** `ListMixed` 🟡 column 3 is named last_name, SQL Server keeps the case: LAST_NAME
+
+### identifiers/non_identifier_column_names
+
+Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode
+
+- **upstream** `ListOdd` 🟡 column 4 is named total $, SQL Server keeps the case: Total $
+- **patched** `ListOdd` 🟡 column 4 is named total $, SQL Server keeps the case: Total $
+
+### identifiers/unnamed_expression_column
+
+A result column without a name
+
+- **upstream** `Unnamed` 🟡 column 1 has no name in SQL Server (sqlc calls it count)
+- **upstream** `Unnamed` 🟡 column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `Unnamed` 🟡 column 1 has no name in SQL Server (sqlc calls it count)
+- **patched** `Unnamed` 🟡 column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### insert/identity_insert
+
+Explicit values for an IDENTITY column with SET IDENTITY_INSERT
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### insert/insert_select
+
+INSERT ... SELECT with a parameter
+
+- **upstream** `CopyBooksToAuthor` 🟠 parameter @target_author_id has no type (SQL Server: bigint)
+- **patched** `CopyBooksToAuthor` 🟠 parameter @target_author_id has no type (SQL Server: bigint)
+
+### insert/output_into_table_variable
+
+OUTPUT ... INTO a table variable inside a multi-statement query
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### insert/schema_scoped
+
+INSERT into a table in a user-defined schema
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: expected ), got message
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: expected ), got message
+ ```
+
+### joins/cross_apply
+
+CROSS APPLY and OUTER APPLY with a correlated TOP subquery
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:6:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:6:1: scope: unsupported FROM item *ast.TODO
+ ```
+
+### joins/inner_left_right_full
+
+INNER, LEFT, RIGHT and FULL joins and the nullability they introduce
+
+- **upstream** `LeftJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable
+- **upstream** `RightJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable
+- **upstream** `FullJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable
+- **upstream** `FullJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable
+- **patched** `LeftJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable
+- **patched** `RightJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable
+- **patched** `FullJoin` 🟠 column name: sqlc NOT NULL, SQL Server nullable
+- **patched** `FullJoin` 🟠 column title: sqlc NOT NULL, SQL Server nullable
+
+### json/for_json
+
+FOR JSON PATH / AUTO producing a single JSON column
+
+- **upstream** `AuthorsAsJson` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)
+- **upstream** `AuthorAsJsonObject` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)
+- **patched** `AuthorsAsJson` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)
+- **patched** `AuthorAsJsonObject` 🟠 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)
+
+### json/json_object_array
+
+JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022)
+
+- **upstream** `AuthorJson` 🟠 column obj has no type
+- **upstream** `AuthorJson` 🟠 column arr has no type
+- **patched** `AuthorJson` 🟠 column obj has no type
+- **patched** `AuthorJson` 🟠 column arr has no type
+
+### json/json_value_query
+
+JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column
+
+- **upstream** `JsonAccess` 🟠 parameter @new_name has no type (SQL Server: float)
+- **upstream** `JsonAccess` 🟠 parameter @kind has no type (SQL Server: nvarchar(4000))
+- **upstream** `JsonAccess` 🟠 column name has no type
+- **upstream** `JsonAccess` 🟠 column tags has no type
+- **upstream** `JsonAccess` 🟠 column is_json has no type
+- **upstream** `JsonAccess` 🟠 column renamed has no type
+- **patched** `JsonAccess` 🟠 parameter @new_name has no type (SQL Server: float)
+- **patched** `JsonAccess` 🟠 parameter @kind has no type (SQL Server: nvarchar(4000))
+- **patched** `JsonAccess` 🟠 column name has no type
+- **patched** `JsonAccess` 🟠 column tags has no type
+- **patched** `JsonAccess` 🟠 column is_json has no type
+- **patched** `JsonAccess` 🟠 column renamed has no type
+
+### json/openjson
+
+OPENJSON with an explicit WITH schema, fed by a parameter
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:5:1: where: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:5:1: where: scope: unsupported FROM item *ast.TODO
+ ```
+- SQL Server, `ParseItems` parameters: The parameter type for '@items' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'.
+- SQL Server, `AuthorsByJsonIds` parameters: The parameter type for '@ids' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'.
+
+### macros/arg_forms
+
+sqlc.arg with a quoted name, a bare identifier and a double-quoted name
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: where: unknown column "min_id"
+ ```
+
+### macros/arg_in_cast
+
+sqlc.arg inside CAST, which fixes its type
+
+- **upstream** `CastArg` 🟠 parameter @min_price (decimal(10,2)) is missing
+
+### macros/args_in_top_offset
+
+sqlc.arg in TOP and in OFFSET / FETCH
+
+- **upstream** `TopArg` 🟠 parameter @take (bigint) is missing
+- **upstream** `PageArgs` 🟠 parameter @skip (bigint) is missing
+- **upstream** `PageArgs` 🟠 parameter @take (bigint) is missing
+- **patched** `TopArg` 🟡 parameter @take: sqlc int, SQL Server bigint
+- **patched** `PageArgs` 🟡 parameter @skip: sqlc int, SQL Server bigint
+- **patched** `PageArgs` 🟡 parameter @take: sqlc int, SQL Server bigint
+
+### macros/embed
+
+sqlc.embed of a table, of aliases (self join) and next to a t.* star
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unknown column "authors"
+ query.sql:5:1: unknown column "a1"
+ query.sql:8:1: unknown column "authors"
+ ```
+
+### macros/embed_schema_scoped
+
+sqlc.embed of a table in a user-defined schema
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unknown column "orders"
+ ```
+
+### macros/narg
+
+sqlc.narg makes a parameter nullable
+
+- **upstream** `NargFilter` 🟠 parameter @name (nvarchar(100)) is missing
+
+### macros/slice
+
+sqlc.slice for integers, strings, NOT IN and several slices in one query
+
+- **upstream** `ByIds` 🟠 parameter @ids (bigint) is missing
+- **upstream** `ByNames` 🟠 parameter @names (nvarchar(100)) is missing
+- **upstream** `NotByIds` 🟠 parameter @excluded (bigint) is missing
+- **upstream** `ByIdsAndTitles` 🟠 parameter @author_ids (bigint) is missing
+- **upstream** `ByIdsAndTitles` 🟠 parameter @titles (nvarchar(200)) is missing
+
+### merge/merge_delete
+
+MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- SQL Server, `SyncTags` parameters: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors.
+- SQL Server, `SyncTags` result set: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors.
+
+### merge/merge_upsert
+
+MERGE as an upsert, with OUTPUT $action
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: expected identifier for schema object name
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: expected identifier for schema object name
+ ```
+
+### programmability/exec_procedure
+
+Calling a stored procedure with EXEC and named arguments
+
+- **upstream** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+- **patched** 🕳️
+ ```
+ error parsing queries: no queries contained in paths query.sql
+ ```
+
+### programmability/inline_table_function
+
+An inline table-valued function in FROM
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+
+### programmability/multi_statement_table_function
+
+A multi-statement table-valued function in FROM
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+
+### programmability/scalar_function
+
+A user-defined scalar function in the select list and WHERE clause
+
+- **upstream** `ListWithInitials` 🟠 parameter @initials has no type (SQL Server: nvarchar(10))
+- **upstream** `ListWithInitials` 🟠 column initials has no type
+- **patched** `ListWithInitials` 🟠 parameter @initials has no type (SQL Server: nvarchar(10))
+- **patched** `ListWithInitials` 🟠 column initials has no type
+
+### programmability/string_split
+
+STRING_SPLIT as a table-valued function fed by a parameter
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: where: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: where: scope: unsupported FROM item *ast.TODO
+ ```
+- SQL Server, `AuthorsByNameList` parameters: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors.
+- SQL Server, `AuthorsByNameList` result set: Argument data type void type is invalid for argument 1 of string_split function. The batch could not be analyzed because of compile errors.
+
+### programmability/table_valued_parameter
+
+A table-valued parameter (user-defined table type) used in a join
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+- SQL Server, `AuthorsByIdList` parameters: Must declare the table variable "@ids". The batch could not be analyzed because of compile errors.
+- SQL Server, `AuthorsByIdList` result set: Must declare the table variable "@ids". The batch could not be analyzed because of compile errors.
+
+### select/case_expression
+
+Searched and simple CASE expressions
+
+- **upstream** `ListPriceBands` 🟠 column band: sqlc varchar, SQL Server nvarchar(9)
+- **patched** `ListPriceBands` 🟠 column band: sqlc varchar, SQL Server nvarchar(9)
+
+### select/comments
+
+Block comments, nested block comments and trailing line comments inside a query
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unexpected token: *
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: unexpected token: *
+ ```
+
+### select/function_on_parameter
+
+Parameters wrapped in functions and compared to columns
+
+- **upstream** `CaseInsensitiveMatch` 🟡 parameter @name: sqlc varchar, SQL Server nvarchar(4000)
+- **patched** `CaseInsensitiveMatch` 🟡 parameter @name: sqlc varchar, SQL Server nvarchar(4000)
+
+### select/iif_choose
+
+IIF and CHOOSE
+
+- **upstream** `ListFlags` 🟠 column second has no type
+- **patched** `ListFlags` 🟠 column second has no type
+
+### select/no_semicolon_terminator
+
+Statements without a terminating semicolon (optional in T-SQL)
+
+- **upstream** ❌
+ ```
+ query.sql:6:0: missing semicolon at end of file
+ ```
+- **patched** ❌
+ ```
+ query.sql:6:0: missing semicolon at end of file
+ ```
+
+### select/offset_fetch
+
+OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters
+
+- **upstream** `PageAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint
+- **upstream** `PageAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint
+- **patched** `PageAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint
+- **patched** `PageAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint
+
+### select/offset_without_fetch
+
+OFFSET ... ROWS without FETCH
+
+- **upstream** `SkipAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint
+- **patched** `SkipAuthors` 🟡 parameter @skip: sqlc int, SQL Server bigint
+
+### select/parameter_in_select_list
+
+A parameter echoed in the select list, with and without CAST
+
+- **upstream** `EchoParams` 🟠 parameter @label has no type (SQL Server cannot deduce it either)
+- **upstream** `EchoParams` 🟠 column label has no type
+- **patched** `EchoParams` 🟠 parameter @label has no type (SQL Server cannot deduce it either)
+- **patched** `EchoParams` 🟠 column label has no type
+- SQL Server, `EchoParams` parameters: The parameter type for '@label' cannot be uniquely deduced; two possibilities are 'sql_variant' and 'xml'.
+- SQL Server, `EchoParams` result set: The metadata could not be determined because statement 'SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books' uses an undeclared parameter in a context that affects its metadata.
+
+### select/quoted_aliases
+
+Aliases quoted with brackets, double quotes and single quotes
+
+- **upstream** `ListQuotedAliases` 🟡 column 1 is named author id, SQL Server keeps the case: Author Id
+- **upstream** `ListQuotedAliases` 🟡 column 2 is named author name, SQL Server keeps the case: Author Name
+- **upstream** `ListQuotedAliases` 🟡 column 3 is named biography, SQL Server keeps the case: Biography
+- **patched** `ListQuotedAliases` 🟡 column 1 is named author id, SQL Server keeps the case: Author Id
+- **patched** `ListQuotedAliases` 🟡 column 2 is named author name, SQL Server keeps the case: Author Name
+- **patched** `ListQuotedAliases` 🟡 column 3 is named biography, SQL Server keeps the case: Biography
+
+### select/table_value_constructor
+
+VALUES as a derived table
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ ```
+
+### select/top_parameter
+
+TOP with a parenthesized parameter
+
+- **upstream** `TopAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint
+- **patched** `TopAuthors` 🟡 parameter @take: sqlc int, SQL Server bigint
+
+### select/unicode_literals
+
+N'' Unicode literals and string concatenation with +
+
+- **upstream** `Greetings` 🟠 column greeting: sqlc varchar, SQL Server nvarchar(105)
+- **patched** `Greetings` 🟠 column greeting: sqlc varchar, SQL Server nvarchar(105)
+
+### select/without_from
+
+SELECT without FROM: parameters and functions only
+
+- **upstream** `AddOne` 🟡 column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `AddOne` 🟡 column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### setops/pivot_unpivot
+
+PIVOT and UNPIVOT
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:7:1: scope: unsupported FROM item *ast.TODO
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: scope: unsupported FROM item *ast.TODO
+ query.sql:7:1: scope: unsupported FROM item *ast.TODO
+ ```
+
+### update/top
+
+UPDATE TOP (@n)
+
+- **upstream** `MarkSomeUnavailable` 🟠 parameter @n (bigint) is missing
+- **patched** `MarkSomeUnavailable` 🟠 parameter @n (bigint) is missing
+
+### window/aggregate_over
+
+Aggregates over windows, including a running total
+
+- **upstream** `RunningTotals` 🟡 column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+- **patched** `RunningTotals` 🟡 column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)
+
+### window/lag_lead_first_value
+
+LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame
+
+- **upstream** `PriceNeighbours` 🟠 column previous_price has no type
+- **upstream** `PriceNeighbours` 🟠 column next_price has no type
+- **upstream** `PriceNeighbours` 🟠 column first_title has no type
+- **upstream** `PriceNeighbours` 🟠 column last_title has no type
+- **patched** `PriceNeighbours` 🟠 column previous_price has no type
+- **patched** `PriceNeighbours` 🟠 column next_price has no type
+- **patched** `PriceNeighbours` 🟠 column first_title has no type
+- **patched** `PriceNeighbours` 🟠 column last_title has no type
+
+### window/ranking
+
+ROW_NUMBER, RANK, DENSE_RANK and NTILE
+
+- **upstream** `RankedBooks` 🟠 column rn has no type
+- **upstream** `RankedBooks` 🟠 column rnk has no type
+- **upstream** `RankedBooks` 🟠 column dense_rnk has no type
+- **upstream** `RankedBooks` 🟠 column quartile has no type
+- **patched** `RankedBooks` 🟠 column rn has no type
+- **patched** `RankedBooks` 🟠 column rnk has no type
+- **patched** `RankedBooks` 🟠 column dense_rnk has no type
+- **patched** `RankedBooks` 🟠 column quartile has no type
+
+### window/window_clause_and_percentiles
+
+Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC
+
+- **upstream** `Percentiles` 🟠 column median has no type
+- **upstream** `Percentiles` 🟠 column median_disc has no type
+- **patched** `Percentiles` 🟠 column median has no type
+- **patched** `Percentiles` 🟠 column median_disc has no type
+
+### xml/for_xml
+
+FOR XML PATH, including the string-concatenation idiom
+
+- **upstream** `AuthorsAsXml` 🟠 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)
+- **upstream** `ConcatenatedTitles` 🟠 column titles has no type
+- **patched** `AuthorsAsXml` 🟠 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)
+- **patched** `ConcatenatedTitles` 🟠 column titles has no type
+
+### xml/xml_methods
+
+XML column with the value(), query(), exist() and nodes() methods
+
+- **upstream** ❌
+ ```
+ query.sql:1:1: unexpected token: query
+ ```
+- **patched** ❌
+ ```
+ query.sql:1:1: unexpected token: query
+ ```
+- SQL Server, `XmlAccess` parameters: XQuery: Unable to resolve sql:variable('@status'). The variable must be declared as a scalar TSQL variable.
+- SQL Server, `XmlAccess` result set: XQuery: Unable to resolve sql:variable('@status'). The variable must be declared as a scalar TSQL variable.
+
diff --git a/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs b/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs
new file mode 100644
index 000000000..0ac5b653f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/SqlServerOracle.cs
@@ -0,0 +1,165 @@
+using Microsoft.Data.SqlClient;
+using System.Text.RegularExpressions;
+
+namespace MssqlSyntaxMatrix;
+
+public record OracleColumn(string Name, string Type, bool Nullable);
+
+public record OracleParam(string Name, string Type);
+
+public class OracleQuery
+{
+ public List? Params { get; set; }
+ public string? ParamsError { get; set; }
+ public List? Columns { get; set; }
+ public string? ColumnsError { get; set; }
+}
+
+public class OracleResult
+{
+ public string? SchemaError { get; set; }
+ public Dictionary Queries { get; } = [];
+}
+
+// Asks SQL Server itself what a query takes and returns: sp_describe_undeclared_parameters for the parameters and
+// sp_describe_first_result_set for the result columns. Every case gets a fresh database.
+public partial class SqlServerOracle(string connectionString)
+{
+ private const string Database = "sqlc_syntax_matrix";
+
+ public async Task Describe(TestCase testCase)
+ {
+ var result = new OracleResult();
+ var builder = new SqlConnectionStringBuilder(connectionString) { InitialCatalog = "master" };
+ await using var connection = new SqlConnection(builder.ConnectionString);
+ await connection.OpenAsync();
+ await Execute(connection, $"""
+ IF DB_ID('{Database}') IS NOT NULL
+ BEGIN
+ ALTER DATABASE [{Database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
+ DROP DATABASE [{Database}];
+ END
+ CREATE DATABASE [{Database}];
+ """);
+ await connection.ChangeDatabaseAsync(Database);
+
+ try
+ {
+ foreach (var batch in GoRegex().Split(testCase.Schema).Where(b => b.Trim().Length > 0))
+ await Execute(connection, batch);
+ }
+ catch (SqlException e)
+ {
+ result.SchemaError = e.Message;
+ return result;
+ }
+
+ foreach (var query in testCase.Queries)
+ result.Queries[query.Name] = await DescribeQuery(connection, ToNativeSql(query.Text));
+ return result;
+ }
+
+ private static async Task DescribeQuery(SqlConnection connection, string sql)
+ {
+ var result = new OracleQuery();
+ try
+ {
+ result.Params = await DescribeParams(connection, sql);
+ }
+ catch (SqlException e) when (e.Message.Contains("is used more than once"))
+ {
+ // SQL Server refuses a parameter used twice: describe every use under its own name and keep the first type
+ try
+ {
+ var occurrence = 0;
+ var unique = ParamRegex().Replace(sql, m => $"{m.Value}__{occurrence++}");
+ result.Params = [.. (await DescribeParams(connection, unique))
+ .GroupBy(p => p.Name[..p.Name.LastIndexOf("__", StringComparison.Ordinal)], StringComparer.OrdinalIgnoreCase)
+ .Select(g => new OracleParam(g.Key, MergeTypes(g.Select(p => p.Type))))];
+ }
+ catch (SqlException inner)
+ {
+ result.Params = null;
+ result.ParamsError = inner.Message;
+ }
+ }
+ catch (SqlException e)
+ {
+ result.Params = null;
+ result.ParamsError = e.Message;
+ }
+
+ try
+ {
+ result.Columns = [];
+ await using var command = new SqlCommand("sp_describe_first_result_set", connection) { CommandType = System.Data.CommandType.StoredProcedure };
+ command.Parameters.AddWithValue("@tsql", sql);
+ var declarations = result.Params?.Where(p => p.Type != "").Select(p => $"{p.Name} {p.Type}").ToList() ?? [];
+ command.Parameters.AddWithValue("@params", declarations.Count > 0 ? string.Join(", ", declarations) : DBNull.Value);
+ await using var reader = await command.ExecuteReaderAsync();
+ while (await reader.ReadAsync())
+ {
+ if (reader.GetBoolean(reader.GetOrdinal("is_hidden")))
+ continue;
+ result.Columns.Add(new OracleColumn(
+ reader.IsDBNull(reader.GetOrdinal("name")) ? "" : reader.GetString(reader.GetOrdinal("name")),
+ reader.IsDBNull(reader.GetOrdinal("system_type_name")) ? "" : reader.GetString(reader.GetOrdinal("system_type_name")),
+ reader.GetBoolean(reader.GetOrdinal("is_nullable"))));
+ }
+ }
+ catch (SqlException e)
+ {
+ result.Columns = null;
+ result.ColumnsError = e.Message;
+ }
+ return result;
+ }
+
+ // "@p IS NULL" makes SQL Server suggest int, its default, so a use that pins a real type wins over it
+ private static string MergeTypes(IEnumerable types)
+ {
+ var distinct = types.Where(t => t != "").Distinct().ToList();
+ if (distinct.Count > 1)
+ distinct.Remove("int");
+ return distinct.FirstOrDefault() ?? "";
+ }
+
+ private static async Task> DescribeParams(SqlConnection connection, string sql)
+ {
+ var result = new List();
+ await using var command = new SqlCommand("sp_describe_undeclared_parameters", connection) { CommandType = System.Data.CommandType.StoredProcedure };
+ command.Parameters.AddWithValue("@tsql", sql);
+ await using var reader = await command.ExecuteReaderAsync();
+ while (await reader.ReadAsync())
+ result.Add(new OracleParam(
+ reader.GetString(reader.GetOrdinal("name")),
+ reader.IsDBNull(reader.GetOrdinal("suggested_system_type_name")) ? "" : reader.GetString(reader.GetOrdinal("suggested_system_type_name"))));
+ return result;
+ }
+
+ // what sqlc turns its macros into, so SQL Server sees plain T-SQL
+ public static string ToNativeSql(string sql)
+ {
+ sql = EmbedRegex().Replace(sql, m => m.Groups[1].Value + ".*");
+ return MacroRegex().Replace(sql, m => "@" + m.Groups[1].Value);
+ }
+
+ private static async Task Execute(SqlConnection connection, string sql)
+ {
+ await using var command = new SqlCommand(sql, connection);
+ await command.ExecuteNonQueryAsync();
+ }
+
+ // @name, but not @@ROWCOUNT and not inside a string literal such as sql:variable("@status")
+ [GeneratedRegex(@"(? Columns, List Params);
+
+public enum RunStatus { Ok, Error, Panic, Skipped }
+
+public class SqlcResult
+{
+ public RunStatus Status { get; set; }
+ public string Error { get; set; } = "";
+ public Dictionary Queries { get; } = [];
+ public RunStatus PluginStatus { get; set; } = RunStatus.Skipped;
+ public string PluginError { get; set; } = "";
+ public string? PluginOutputDir { get; set; }
+}
+
+public static partial class SqlcRunner
+{
+ public static async Task Run(SqlcBuild build, TestCase testCase, string dir, MatrixOptions options)
+ {
+ Directory.CreateDirectory(dir);
+ await File.WriteAllTextAsync(Path.Combine(dir, "schema.sql"), testCase.Schema);
+ await File.WriteAllTextAsync(Path.Combine(dir, "query.sql"), testCase.QueriesText);
+ await File.WriteAllTextAsync(Path.Combine(dir, "sqlc.json.yaml"), """
+ version: "2"
+ sql:
+ - engine: mssql
+ schema: schema.sql
+ queries: query.sql
+ gen:
+ json:
+ out: json
+ """);
+
+ var result = new SqlcResult();
+ var (exitCode, output) = await Exec(build.Path, "-f sqlc.json.yaml generate", dir);
+ if (exitCode != 0)
+ {
+ result.Status = output.Contains("panic:") ? RunStatus.Panic : RunStatus.Error;
+ result.Error = Summarize(output.Replace(dir + Path.DirectorySeparatorChar, ""));
+ return result;
+ }
+
+ result.Status = RunStatus.Ok;
+ using var request = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(dir, "json", "codegen_request.json")));
+ foreach (var q in request.RootElement.GetProperty("queries").EnumerateArray())
+ {
+ var query = new SqlcQuery(
+ q.GetProperty("name").GetString()!,
+ q.GetProperty("cmd").GetString()!,
+ q.GetProperty("text").GetString()!,
+ [.. q.GetProperty("columns").EnumerateArray().Select(ReadColumn)],
+ [.. q.GetProperty("params").EnumerateArray().Select(p =>
+ {
+ var column = ReadColumn(p.GetProperty("column"));
+ var name = p.TryGetProperty("name", out var n) && n.GetString() is { Length: > 0 } s ? s : column.Name;
+ return column with { Name = name };
+ })]);
+ result.Queries[query.Name] = query;
+ }
+
+ if (build == options.PluginBuild)
+ await RunPlugin(dir, testCase, options, result);
+ return result;
+ }
+
+ private static SqlcColumn ReadColumn(JsonElement c)
+ {
+ var embed = c.TryGetProperty("embed_table", out var e) && e.ValueKind == JsonValueKind.Object
+ ? e.GetProperty("name").GetString()
+ : null;
+ return new SqlcColumn(
+ c.GetProperty("name").GetString() ?? "",
+ c.GetProperty("type").GetProperty("name").GetString() ?? "",
+ c.GetProperty("not_null").GetBoolean(),
+ embed,
+ c.GetProperty("is_array").GetBoolean(),
+ c.TryGetProperty("is_sqlc_slice", out var s) && s.GetBoolean(),
+ c.TryGetProperty("table", out var t) && t.ValueKind == JsonValueKind.Object && t.GetProperty("name").GetString() is { Length: > 0 });
+ }
+
+ // the plugin generates both the driver and the Dapper flavour, as namespaces the compile step can tell apart
+ private static async Task RunPlugin(string dir, TestCase testCase, MatrixOptions options, SqlcResult result)
+ {
+ var ns = CaseNamespace(testCase);
+ await File.WriteAllTextAsync(Path.Combine(dir, "sqlc.plugin.yaml"), $"""
+ version: "2"
+ plugins:
+ - name: csharp
+ process:
+ cmd: {options.PluginPath}
+ sql:
+ - engine: mssql
+ schema: schema.sql
+ queries: query.sql
+ codegen:
+ - plugin: csharp
+ out: csharp/driver
+ options:
+ namespaceName: {ns}.Driver
+ generateCsproj: false
+ targetFramework: net10.0
+ - plugin: csharp
+ out: csharp/dapper
+ options:
+ namespaceName: {ns}.Dapper
+ generateCsproj: false
+ useDapper: true
+ targetFramework: net10.0
+ """);
+ var (exitCode, output) = await Exec(options.PluginBuild.Path, "-f sqlc.plugin.yaml generate", dir);
+ result.PluginStatus = exitCode == 0 ? RunStatus.Ok : RunStatus.Error;
+ result.PluginError = exitCode == 0 ? "" : Summarize(output.Replace(dir + Path.DirectorySeparatorChar, ""));
+ result.PluginOutputDir = exitCode == 0 ? Path.Combine(dir, "csharp") : null;
+ }
+
+ public static string CaseNamespace(TestCase testCase) =>
+ "Case_" + string.Concat(testCase.Id.Select(ch => char.IsLetterOrDigit(ch) ? ch : '_'));
+
+ // keeps the lines that explain a failure and drops stack traces
+ private static string Summarize(string output)
+ {
+ var lines = output.Split('\n')
+ .Select(l => l.TrimEnd())
+ .Where(l => l.Length > 0 && !l.StartsWith('\t') && !l.StartsWith(" at ") && !l.StartsWith("goroutine ")
+ && !l.StartsWith("# package") && !l.StartsWith("[signal"))
+ .Take(6);
+ // goroutine traces carry addresses that change on every run
+ return HexRegex().Replace(string.Join("\n", lines), "0x?");
+ }
+
+ [GeneratedRegex(@"0x[0-9a-fA-F]+\??")]
+ private static partial Regex HexRegex();
+
+ public static async Task<(int ExitCode, string Output)> Exec(string file, string arguments, string workingDir)
+ {
+ var psi = new ProcessStartInfo(file, arguments)
+ {
+ WorkingDirectory = workingDir,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+ using var process = Process.Start(psi)!;
+ var stdout = process.StandardOutput.ReadToEndAsync();
+ var stderr = process.StandardError.ReadToEndAsync();
+ using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
+ await process.WaitForExitAsync(cts.Token);
+ return (process.ExitCode, (await stdout) + (await stderr));
+ }
+}
\ No newline at end of file
diff --git a/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md b/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md
new file mode 100644
index 000000000..8917914d3
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/TEESQL_ISSUES.md
@@ -0,0 +1,130 @@
+
+
+TITLE: MERGE ... USING (VALUES ...) AS alias (columns) fails to parse
+
+**teesql version:** v1.1.0 (`5cd4b20`)
+
+A row constructor (`VALUES`) as the `MERGE` source fails to parse. This is the standard T-SQL upsert pattern, where the incoming row comes from parameters:
+
+```sql
+MERGE INTO authors AS target
+USING (VALUES (N'a', N'b')) AS source (name, bio)
+ON target.name = source.name
+WHEN MATCHED THEN UPDATE SET bio = source.bio
+WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio);
+```
+
+```
+expected identifier for schema object name
+```
+
+The other source forms parse:
+
+| `USING ...` | Result |
+|---|---|
+| `staging AS source` (table) | ✅ `*ast.MergeStatement` |
+| `(SELECT N'a' AS name) AS source` (derived table) | ✅ `*ast.MergeStatement` |
+| `(VALUES (...)) AS source (name, bio)` | ❌ parse error |
+
+**Expected:** a `MergeStatement` whose source is a derived table built from a row constructor (`InlineDerivedTable` in SqlScriptDOM), with the column alias list. SQL Server accepts the statement. `(VALUES ...) AS t (cols)` in a `FROM` clause already parses, so presumably only the `MERGE ... USING` path is missing it.
+
+**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md), which runs T-SQL cases through sqlc (and so teesql) and checks them against SQL Server 2022. The case ([`merge/merge_upsert.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql)):
+
+```sql
+MERGE INTO authors AS target
+USING (VALUES (@name, @bio)) AS source (name, bio)
+ON target.name = source.name
+WHEN MATCHED THEN UPDATE SET bio = source.bio
+WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio)
+OUTPUT $action AS action, INSERTED.id;
+```
+
+---------------------------------------------------------------------------
+
+TITLE: MESSAGE is treated as a reserved word in expressions and column lists
+
+**teesql version:** v1.1.0 (`5cd4b20`)
+
+A column named `message`, which is common in log and event tables, can be declared but not used:
+
+| Statement | Result |
+|---|---|
+| `CREATE TABLE logs (id INT, message NVARCHAR(200));` | ✅ |
+| `SELECT [message] FROM logs;` | ✅ |
+| `SELECT message FROM logs;` | ❌ `unexpected token in expression: message` |
+| `INSERT INTO logs (message) VALUES (N'x');` | ❌ `expected ), got message` |
+
+**Expected:** all four parse. `MESSAGE` is not a reserved keyword in T-SQL. It is not in the [reserved keywords list](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/reserved-keywords-transact-sql); it is only a contextual keyword in Service Broker statements (`CREATE MESSAGE TYPE`, `SEND ON CONVERSATION ... MESSAGE TYPE`). SQL Server runs both failing statements unquoted.
+
+Other contextual keywords may be lexed the same way. The ones I'd check are `TYPE`, `STATUS`, `LEVEL`, `NAME`, `ROLE`, `STATE` and `VALUE`.
+
+**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`insert/schema_scoped.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql)):
+
+```sql
+CREATE SCHEMA logging;
+GO
+CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL);
+
+INSERT INTO logging.entries (message) VALUES (@message);
+```
+
+---------------------------------------------------------------------------
+
+TITLE: Nested block comments fail to parse
+
+**teesql version:** v1.1.0 (`5cd4b20`)
+
+T-SQL block comments nest; a `/* ... */` inside another one does not end the outer comment:
+
+```sql
+SELECT 1 /* outer /* inner */ still outer */ AS one;
+```
+
+```
+unexpected token: outer
+```
+
+Both the flat comment `SELECT 1 /* flat */ AS one;` and the nested one run on SQL Server.
+
+**Expected:** the whole `/* outer /* inner */ still outer */` is one comment. SQL Server [documents this](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/slash-star-comment-transact-sql): "Nested comments are supported". It matters in practice because commenting out a block that already contains a comment is a common habit.
+
+**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`select/comments.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/select/comments.sql)):
+
+```sql
+SELECT id, /* the name */ name -- trailing
+FROM authors /* outer /* nested */ still a comment */
+WHERE id > @min_id;
+```
+
+---------------------------------------------------------------------------
+
+TITLE: The xml .query() method fails to parse
+
+**teesql version:** v1.1.0 (`5cd4b20`)
+
+Of the methods on an `xml` column, only `.query()` fails:
+
+| Expression | Result |
+|---|---|
+| `doc.value('(/order/@id)[1]', 'INT')` | ✅ |
+| `doc.exist('/order')` | ✅ |
+| `o.doc.nodes('/order/item') AS i(n)` in `CROSS APPLY` | ✅ |
+| `doc.query('/order/item')` | ❌ `unexpected token: query` |
+
+```sql
+SELECT doc.query('/order/item') FROM orders;
+```
+
+**Expected:** a function call on the column (`FunctionCall` with a `CallTarget` in SqlScriptDOM), like `.value()`. `QUERY` is probably lexed as a keyword after the `.`. That position is a method name, so any identifier, keywords included, should be accepted there.
+
+**Context:** found by the SQL Server syntax matrix in [sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/README.md). The case ([`xml/xml_methods.sql`](https://github.com/DaredevilOSS/sqlc-gen-csharp/blob/9eac436c0842634df09af3d8699f4ab5884273be/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql)):
+
+```sql
+CREATE TABLE orders_xml (id INT IDENTITY PRIMARY KEY, doc XML NOT NULL);
+
+SELECT id, doc.value('(/order/@id)[1]', 'INT') AS order_id, doc.query('/order/item') AS items
+FROM orders_xml WHERE doc.exist('/order[@status=sql:variable("@status")]') = 1;
+
+SELECT o.id, i.n.value('@sku', 'NVARCHAR(20)') AS sku
+FROM orders_xml o CROSS APPLY o.doc.nodes('/order/item') AS i(n);
+```
diff --git a/end2end/MssqlSyntaxMatrix/TestCase.cs b/end2end/MssqlSyntaxMatrix/TestCase.cs
new file mode 100644
index 000000000..3e866fdc0
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/TestCase.cs
@@ -0,0 +1,82 @@
+using System.Text.RegularExpressions;
+
+namespace MssqlSyntaxMatrix;
+
+public record CaseQuery(string Name, string Cmd, string Text);
+
+// A case file holds a description, an optional schema and one or more sqlc queries:
+// -- @description What the case covers
+// -- @schema
+// CREATE TABLE ...
+// -- @queries
+// -- name: Foo :many
+// SELECT ...;
+// cases/base.sql is prepended to the schema of every case.
+public partial class TestCase
+{
+ public required string Id { get; init; }
+ public required string Category { get; init; }
+ public required string Description { get; init; }
+ public required string Schema { get; init; }
+ public required string QueriesText { get; init; }
+ public required List Queries { get; init; }
+
+ public static List LoadAll(string casesDir)
+ {
+ var baseSchema = File.ReadAllText(Path.Combine(casesDir, "base.sql"));
+ return [.. Directory.GetFiles(casesDir, "*.sql", SearchOption.AllDirectories)
+ .Where(f => Path.GetFileName(f) != "base.sql")
+ .Order(StringComparer.Ordinal)
+ .Select(f => Load(f, casesDir, baseSchema))];
+ }
+
+ private static TestCase Load(string file, string casesDir, string baseSchema)
+ {
+ var relative = Path.GetRelativePath(casesDir, file).Replace('\\', '/');
+ var id = relative[..^".sql".Length];
+ var description = new List();
+ var schema = new List();
+ var queries = new List();
+ List? section = null;
+ foreach (var line in File.ReadAllLines(file))
+ {
+ var trimmed = line.Trim();
+ if (trimmed.StartsWith("-- @description"))
+ description.Add(trimmed["-- @description".Length..].Trim());
+ else if (trimmed == "-- @schema")
+ section = schema;
+ else if (trimmed == "-- @queries")
+ section = queries;
+ else
+ section?.Add(line);
+ }
+
+ var queriesText = string.Join("\n", queries).Trim() + "\n";
+ return new TestCase
+ {
+ Id = id,
+ Category = id.Contains('/') ? id[..id.IndexOf('/')] : "misc",
+ Description = string.Join(" ", description),
+ // the base schema is its own batch, so a case can start with CREATE SCHEMA / VIEW / PROCEDURE
+ Schema = (baseSchema.TrimEnd() + "\nGO\n\n" + string.Join("\n", schema)).Trim() + "\n",
+ QueriesText = queriesText,
+ Queries = SplitQueries(queriesText)
+ };
+ }
+
+ private static List SplitQueries(string text)
+ {
+ var matches = NameRegex().Matches(text);
+ var result = new List();
+ for (var i = 0; i < matches.Count; i++)
+ {
+ var start = matches[i].Index + matches[i].Length;
+ var end = i + 1 < matches.Count ? matches[i + 1].Index : text.Length;
+ result.Add(new CaseQuery(matches[i].Groups[1].Value, matches[i].Groups[2].Value, text[start..end].Trim()));
+ }
+ return result;
+ }
+
+ [GeneratedRegex(@"^--\s*name:\s*(\w+)\s+(:\w+).*$", RegexOptions.Multiline)]
+ private static partial Regex NameRegex();
+}
\ No newline at end of file
diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql
new file mode 100644
index 000000000..1a759f984
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/basic_aggregates.sql
@@ -0,0 +1,6 @@
+-- @description COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types
+-- @queries
+-- name: BookStats :one
+SELECT COUNT(*) AS cnt, COUNT_BIG(*) AS cnt_big, SUM(price) AS total, AVG(price) AS average,
+ MIN(published_at) AS first_published, MAX(title) AS last_title, AVG(id) AS avg_id
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql
new file mode 100644
index 000000000..620d6f534
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/group_by_having.sql
@@ -0,0 +1,4 @@
+-- @description GROUP BY with HAVING on a parameter
+-- @queries
+-- name: ProlificAuthors :many
+SELECT author_id, COUNT(*) AS book_count FROM books GROUP BY author_id HAVING COUNT(*) >= @min_books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql
new file mode 100644
index 000000000..00e5aa347
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/rollup_cube_grouping_sets.sql
@@ -0,0 +1,10 @@
+-- @description ROLLUP, CUBE, GROUPING SETS and GROUPING()
+-- @queries
+-- name: Rollup :many
+SELECT author_id, is_available, SUM(price) AS total, GROUPING(author_id) AS is_total FROM books GROUP BY ROLLUP (author_id, is_available);
+
+-- name: Cube :many
+SELECT author_id, is_available, COUNT(*) AS cnt FROM books GROUP BY CUBE (author_id, is_available);
+
+-- name: GroupingSets :many
+SELECT author_id, is_available, COUNT(*) AS cnt FROM books GROUP BY GROUPING SETS ((author_id), (is_available), ());
diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql
new file mode 100644
index 000000000..96bfb1af5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/statistical.sql
@@ -0,0 +1,4 @@
+-- @description STDEV, VAR and APPROX_COUNT_DISTINCT
+-- @queries
+-- name: PriceStatistics :one
+SELECT STDEV(price) AS stdev, VAR(price) AS variance, APPROX_COUNT_DISTINCT(author_id) AS approx_authors FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql b/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql
new file mode 100644
index 000000000..9bdc1ea6c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/aggregate/string_agg.sql
@@ -0,0 +1,4 @@
+-- @description STRING_AGG with WITHIN GROUP
+-- @queries
+-- name: TitlesPerAuthor :many
+SELECT author_id, STRING_AGG(title, N', ') WITHIN GROUP (ORDER BY title) AS titles FROM books GROUP BY author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql
new file mode 100644
index 000000000..66345408a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_identity_table.sql
@@ -0,0 +1,4 @@
+-- @description `:copyfrom` into a table with an IDENTITY column
+-- @queries
+-- name: CopyBooks :copyfrom
+INSERT INTO books (author_id, title, price, published_at, is_available) VALUES (@author_id, @title, @price, @published_at, @is_available);
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql
new file mode 100644
index 000000000..f98814fad
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/copyfrom_schema_scoped.sql
@@ -0,0 +1,8 @@
+-- @description `:copyfrom` into a table in a user-defined schema
+-- @schema
+CREATE SCHEMA logging;
+GO
+CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL, level TINYINT NOT NULL);
+-- @queries
+-- name: CopyEntries :copyfrom
+INSERT INTO logging.entries (message, level) VALUES (@message, @level);
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql
new file mode 100644
index 000000000..f1ab4313b
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_output_identity.sql
@@ -0,0 +1,4 @@
+-- @description `:execlastid` via OUTPUT INSERTED.id on an IDENTITY column
+-- @queries
+-- name: CreateBookReturnId :execlastid
+INSERT INTO books (author_id, title) OUTPUT INSERTED.id VALUES (@author_id, @title);
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql
new file mode 100644
index 000000000..74b934024
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execlastid_scope_identity.sql
@@ -0,0 +1,5 @@
+-- @description `:execlastid` via SELECT SCOPE_IDENTITY() after the INSERT
+-- @queries
+-- name: CreateAuthorReturnId :execlastid
+INSERT INTO authors (name) VALUES (@name);
+SELECT CAST(SCOPE_IDENTITY() AS BIGINT);
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql
new file mode 100644
index 000000000..556c5a70d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/execrows.sql
@@ -0,0 +1,7 @@
+-- @description `:execrows` on UPDATE and DELETE
+-- @queries
+-- name: DisableAll :execrows
+UPDATE books SET is_available = 0;
+
+-- name: DeleteUnavailable :execrows
+DELETE FROM books WHERE is_available = 0;
diff --git a/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql b/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql
new file mode 100644
index 000000000..17cbdbfbe
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/annotations/one_many_exec.sql
@@ -0,0 +1,10 @@
+-- @description `:one`, `:many`, `:exec` basics
+-- @queries
+-- name: GetAuthor :one
+SELECT id, name, bio FROM authors WHERE id = @id;
+
+-- name: ListAuthors :many
+SELECT id, name, bio FROM authors ORDER BY name;
+
+-- name: DeleteAllAuthors :exec
+DELETE FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/base.sql b/end2end/MssqlSyntaxMatrix/cases/base.sql
new file mode 100644
index 000000000..b5303a5a5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/base.sql
@@ -0,0 +1,16 @@
+-- Shared by every case; a case adds its own objects in its -- @schema section.
+CREATE TABLE authors (
+ id BIGINT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
+ name NVARCHAR(100) NOT NULL,
+ bio NVARCHAR(MAX) NULL,
+ birth_date DATE NULL
+);
+
+CREATE TABLE books (
+ id INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
+ author_id BIGINT NOT NULL REFERENCES authors (id),
+ title NVARCHAR(200) NOT NULL,
+ price DECIMAL(10, 2) NULL,
+ published_at DATETIME2 NULL,
+ is_available BIT NOT NULL DEFAULT 1
+);
diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql b/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql
new file mode 100644
index 000000000..7d433bdda
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/batch/declare_variables.sql
@@ -0,0 +1,5 @@
+-- @description DECLARE / SET local variables before the final SELECT
+-- @queries
+-- name: BooksSinceDaysAgo :many
+DECLARE @since DATETIME2 = DATEADD(DAY, -@days, SYSUTCDATETIME());
+SELECT id, title FROM books WHERE published_at >= @since;
diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql b/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql
new file mode 100644
index 000000000..278481a9f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/batch/select_into_temp_table.sql
@@ -0,0 +1,5 @@
+-- @description SELECT ... INTO #temp then reading it, in one query
+-- @queries
+-- name: ExpensiveBooksViaTemp :many
+SELECT id, title INTO #expensive FROM books WHERE price > @min_price;
+SELECT id, title FROM #expensive;
diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql b/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql
new file mode 100644
index 000000000..70f024539
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/batch/set_nocount_on.sql
@@ -0,0 +1,5 @@
+-- @description SET NOCOUNT ON in front of a query (a very common T-SQL habit)
+-- @queries
+-- name: ListAuthorsNoCount :many
+SET NOCOUNT ON;
+SELECT id, name FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql b/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql
new file mode 100644
index 000000000..550b64592
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/batch/transaction_in_query.sql
@@ -0,0 +1,7 @@
+-- @description Explicit BEGIN TRAN / COMMIT inside a query
+-- @queries
+-- name: MoveBooks :exec
+BEGIN TRANSACTION;
+UPDATE books SET author_id = @to_author_id WHERE author_id = @from_author_id;
+DELETE FROM authors WHERE id = @from_author_id;
+COMMIT TRANSACTION;
diff --git a/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql b/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql
new file mode 100644
index 000000000..ae22741e5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/batch/try_catch_throw.sql
@@ -0,0 +1,9 @@
+-- @description TRY ... CATCH with THROW
+-- @queries
+-- name: SafeDelete :exec
+BEGIN TRY
+ DELETE FROM authors WHERE id = @id;
+END TRY
+BEGIN CATCH
+ THROW;
+END CATCH;
diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql b/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql
new file mode 100644
index 000000000..2f1bb61fc
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/cte/cte_with_dml.sql
@@ -0,0 +1,9 @@
+-- @description CTEs in front of UPDATE and DELETE
+-- @queries
+-- name: DiscountTopBooks :execrows
+WITH top_books AS (SELECT TOP (@n) * FROM books ORDER BY price DESC)
+UPDATE top_books SET price = price * 0.9;
+
+-- name: DeleteDuplicates :execrows
+WITH dups AS (SELECT id, ROW_NUMBER() OVER (PARTITION BY title ORDER BY id) AS rn FROM books)
+DELETE FROM dups WHERE rn > 1;
diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql b/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql
new file mode 100644
index 000000000..bae1bdeee
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/cte/multiple_ctes.sql
@@ -0,0 +1,6 @@
+-- @description Several CTEs referencing each other
+-- @queries
+-- name: AuthorTotals :many
+WITH totals AS (SELECT author_id, SUM(price) AS total FROM books GROUP BY author_id),
+ ranked AS (SELECT author_id, total, RANK() OVER (ORDER BY total DESC) AS rnk FROM totals)
+SELECT a.name, r.total, r.rnk FROM ranked r JOIN authors a ON a.id = r.author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql b/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql
new file mode 100644
index 000000000..8b6e84d4a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/cte/recursive_cte.sql
@@ -0,0 +1,9 @@
+-- @description Recursive CTE with UNION ALL
+-- @queries
+-- name: Numbers :many
+WITH numbers (n) AS (
+ SELECT 1
+ UNION ALL
+ SELECT n + 1 FROM numbers WHERE n < @upto
+)
+SELECT n FROM numbers OPTION (MAXRECURSION 1000);
diff --git a/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql b/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql
new file mode 100644
index 000000000..944b94d76
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/cte/simple_cte.sql
@@ -0,0 +1,5 @@
+-- @description A single common table expression
+-- @queries
+-- name: CheapBooks :many
+WITH cheap AS (SELECT id, title FROM books WHERE price < @max_price)
+SELECT id, title FROM cheap;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql
new file mode 100644
index 000000000..9478a65c5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alias_type.sql
@@ -0,0 +1,14 @@
+-- @description User-defined alias type (CREATE TYPE ... FROM)
+-- @schema
+CREATE TYPE dbo.email_address FROM NVARCHAR(320) NOT NULL;
+GO
+CREATE TABLE contacts (
+ id INT IDENTITY PRIMARY KEY,
+ email dbo.email_address
+);
+-- @queries
+-- name: ListContacts :many
+SELECT id, email FROM contacts;
+
+-- name: GetContactByEmail :one
+SELECT id FROM contacts WHERE email = @email;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql
new file mode 100644
index 000000000..1d4347c9b
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_column_type_and_nullability.sql
@@ -0,0 +1,6 @@
+-- @description ALTER TABLE ... ALTER COLUMN changes the type and nullability
+-- @schema
+ALTER TABLE authors ALTER COLUMN bio NVARCHAR(500) NOT NULL;
+-- @queries
+-- name: ListBios :many
+SELECT id, bio FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql
new file mode 100644
index 000000000..d0b2390ca
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_add_column.sql
@@ -0,0 +1,6 @@
+-- @description ALTER TABLE ... ADD column
+-- @schema
+ALTER TABLE authors ADD nickname NVARCHAR(50) NULL;
+-- @queries
+-- name: ListNicknames :many
+SELECT id, nickname FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql
new file mode 100644
index 000000000..8fa2f84d2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/alter_table_drop_column.sql
@@ -0,0 +1,6 @@
+-- @description ALTER TABLE ... DROP COLUMN
+-- @schema
+ALTER TABLE authors DROP COLUMN birth_date;
+-- @queries
+-- name: ListAuthors :many
+SELECT * FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql
new file mode 100644
index 000000000..b8ca0ddb9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/bracketed_identifiers.sql
@@ -0,0 +1,10 @@
+-- @description Bracket-quoted table and column names with spaces and reserved words
+-- @schema
+CREATE TABLE [order details] (
+ [order id] INT NOT NULL,
+ [select] NVARCHAR(10) NULL,
+ [Unit Price] MONEY NOT NULL
+);
+-- @queries
+-- name: ListOrderDetails :many
+SELECT [order id], [select], [Unit Price] FROM [order details];
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql
new file mode 100644
index 000000000..e0ca94a6a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/case_insensitive_names.sql
@@ -0,0 +1,4 @@
+-- @description Object names are case-insensitive under the default collation
+-- @queries
+-- name: ListAuthorsUpper :many
+SELECT ID, NAME FROM AUTHORS WHERE NAME = @Name;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql
new file mode 100644
index 000000000..6501a859f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/check_and_unique_constraints.sql
@@ -0,0 +1,13 @@
+-- @description CHECK and UNIQUE constraints, column and table level
+-- @schema
+CREATE TABLE products (
+ id INT IDENTITY PRIMARY KEY,
+ sku VARCHAR(20) NOT NULL UNIQUE,
+ price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
+ discount DECIMAL(10, 2) NULL,
+ CONSTRAINT ck_products_discount CHECK (discount IS NULL OR discount <= price),
+ CONSTRAINT uq_products_sku_price UNIQUE (sku, price)
+);
+-- @queries
+-- name: ListProducts :many
+SELECT id, sku, price, discount FROM products;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql
new file mode 100644
index 000000000..7372b3b52
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/collate_and_sparse.sql
@@ -0,0 +1,10 @@
+-- @description COLLATE and SPARSE column options
+-- @schema
+CREATE TABLE tags (
+ id INT IDENTITY PRIMARY KEY,
+ code VARCHAR(20) COLLATE Latin1_General_CS_AS NOT NULL,
+ note NVARCHAR(100) SPARSE NULL
+);
+-- @queries
+-- name: ListTags :many
+SELECT id, code, note FROM tags;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql
new file mode 100644
index 000000000..e152febfa
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/computed_columns.sql
@@ -0,0 +1,12 @@
+-- @description Computed columns, plain and PERSISTED
+-- @schema
+CREATE TABLE people (
+ id INT IDENTITY PRIMARY KEY,
+ first_name NVARCHAR(50) NOT NULL,
+ last_name NVARCHAR(50) NOT NULL,
+ full_name AS (first_name + N' ' + last_name),
+ name_length AS (LEN(first_name) + LEN(last_name)) PERSISTED
+);
+-- @queries
+-- name: ListPeople :many
+SELECT * FROM people;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql
new file mode 100644
index 000000000..43dfa3428
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/drop_table_if_exists.sql
@@ -0,0 +1,8 @@
+-- @description DROP TABLE IF EXISTS followed by a re-create with a different shape
+-- @schema
+CREATE TABLE scratch (id INT NOT NULL);
+DROP TABLE IF EXISTS scratch;
+CREATE TABLE scratch (id BIGINT NOT NULL, label NVARCHAR(10) NOT NULL);
+-- @queries
+-- name: ListScratch :many
+SELECT id, label FROM scratch;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql
new file mode 100644
index 000000000..d3eb90b8d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/foreign_key_on_delete.sql
@@ -0,0 +1,12 @@
+-- @description Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL
+-- @schema
+CREATE TABLE reviews (
+ id INT IDENTITY PRIMARY KEY,
+ book_id INT NOT NULL,
+ reviewer_id BIGINT NULL,
+ CONSTRAINT fk_reviews_book FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE,
+ CONSTRAINT fk_reviews_reviewer FOREIGN KEY (reviewer_id) REFERENCES authors (id) ON DELETE SET NULL
+);
+-- @queries
+-- name: ListReviews :many
+SELECT id, book_id, reviewer_id FROM reviews;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql
new file mode 100644
index 000000000..5f3a6c770
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/go_batch_separators.sql
@@ -0,0 +1,9 @@
+-- @description Schema split into several batches with GO
+-- @schema
+CREATE TABLE countries (id INT PRIMARY KEY, name NVARCHAR(50) NOT NULL);
+GO
+CREATE TABLE regions (id INT PRIMARY KEY, country_id INT NOT NULL REFERENCES countries (id), name NVARCHAR(50) NOT NULL);
+GO
+-- @queries
+-- name: ListRegions :many
+SELECT r.id, r.name, c.name AS country FROM regions r JOIN countries c ON c.id = r.country_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql
new file mode 100644
index 000000000..9df344ccb
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/identity_seed_increment.sql
@@ -0,0 +1,9 @@
+-- @description IDENTITY(seed, increment) column on a non-default seed
+-- @schema
+CREATE TABLE invoices (
+ id INT IDENTITY(1000, 5) NOT NULL PRIMARY KEY,
+ number NVARCHAR(20) NOT NULL
+);
+-- @queries
+-- name: ListInvoices :many
+SELECT id, number FROM invoices;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql
new file mode 100644
index 000000000..b38e22f77
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/indexes.sql
@@ -0,0 +1,8 @@
+-- @description CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored
+-- @schema
+CREATE NONCLUSTERED INDEX ix_books_title ON books (title) INCLUDE (price);
+CREATE UNIQUE NONCLUSTERED INDEX ux_books_author_title ON books (author_id, title);
+CREATE INDEX ix_books_available ON books (published_at) WHERE is_available = 1;
+-- @queries
+-- name: ListAvailableBooks :many
+SELECT id, title FROM books WHERE is_available = 1;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql
new file mode 100644
index 000000000..c7b76a361
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/named_default_constraints.sql
@@ -0,0 +1,10 @@
+-- @description Named DEFAULT constraints with GETDATE() and NEWID()
+-- @schema
+CREATE TABLE events (
+ id INT IDENTITY CONSTRAINT pk_events PRIMARY KEY,
+ created_at DATETIME2 NOT NULL CONSTRAINT df_events_created_at DEFAULT (SYSUTCDATETIME()),
+ correlation_id UNIQUEIDENTIFIER NOT NULL CONSTRAINT df_events_correlation DEFAULT (NEWID())
+);
+-- @queries
+-- name: ListEvents :many
+SELECT id, created_at, correlation_id FROM events;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql
new file mode 100644
index 000000000..56b6dc0a2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/rowversion.sql
@@ -0,0 +1,13 @@
+-- @description ROWVERSION column used for optimistic concurrency
+-- @schema
+CREATE TABLE documents (
+ id INT IDENTITY PRIMARY KEY,
+ body NVARCHAR(MAX) NOT NULL,
+ version ROWVERSION NOT NULL
+);
+-- @queries
+-- name: GetDocument :one
+SELECT id, body, version FROM documents WHERE id = @id;
+
+-- name: UpdateDocument :execrows
+UPDATE documents SET body = @body WHERE id = @id AND version = @version;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql
new file mode 100644
index 000000000..8594fd639
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/same_table_name_two_schemas.sql
@@ -0,0 +1,14 @@
+-- @description Two tables with the same name in different schemas
+-- @schema
+CREATE SCHEMA audit;
+GO
+CREATE TABLE audit.authors (
+ id BIGINT NOT NULL,
+ changed_at DATETIME2 NOT NULL
+);
+-- @queries
+-- name: ListAuditAuthors :many
+SELECT id, changed_at FROM audit.authors;
+
+-- name: ListAuthors :many
+SELECT id, name FROM dbo.authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql
new file mode 100644
index 000000000..dd11a8a63
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/schema_scoped_table.sql
@@ -0,0 +1,15 @@
+-- @description Table in a user-defined schema (CREATE SCHEMA in its own batch)
+-- @schema
+CREATE SCHEMA sales;
+GO
+CREATE TABLE sales.orders (
+ id INT IDENTITY PRIMARY KEY,
+ author_id BIGINT NOT NULL,
+ total MONEY NOT NULL
+);
+-- @queries
+-- name: ListOrders :many
+SELECT id, author_id, total FROM sales.orders;
+
+-- name: ListOrdersWithAuthor :many
+SELECT o.id, a.name FROM sales.orders o JOIN dbo.authors a ON a.id = o.author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql
new file mode 100644
index 000000000..656fc0462
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/sequence_default.sql
@@ -0,0 +1,13 @@
+-- @description SEQUENCE used as a column default and with NEXT VALUE FOR in a query
+-- @schema
+CREATE SEQUENCE ticket_seq AS BIGINT START WITH 1 INCREMENT BY 1;
+CREATE TABLE tickets (
+ id BIGINT NOT NULL PRIMARY KEY DEFAULT (NEXT VALUE FOR ticket_seq),
+ title NVARCHAR(100) NOT NULL
+);
+-- @queries
+-- name: ListTickets :many
+SELECT id, title FROM tickets;
+
+-- name: NextTicketId :one
+SELECT NEXT VALUE FOR ticket_seq AS next_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql
new file mode 100644
index 000000000..a4d814652
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/sp_rename_column.sql
@@ -0,0 +1,6 @@
+-- @description Column renamed with sp_rename (how T-SQL renames columns)
+-- @schema
+EXEC sp_rename 'authors.bio', 'biography', 'COLUMN';
+-- @queries
+-- name: ListBiographies :many
+SELECT id, biography FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql
new file mode 100644
index 000000000..582c7d863
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/synonym.sql
@@ -0,0 +1,6 @@
+-- @description CREATE SYNONYM for a table
+-- @schema
+CREATE SYNONYM writers FOR dbo.authors;
+-- @queries
+-- name: ListWriters :many
+SELECT id, name FROM writers;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql
new file mode 100644
index 000000000..eb8bc60c3
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/table_level_primary_key.sql
@@ -0,0 +1,11 @@
+-- @description Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL
+-- @schema
+CREATE TABLE memberships (
+ group_id INT,
+ user_id INT,
+ role NVARCHAR(20),
+ CONSTRAINT pk_memberships PRIMARY KEY (group_id, user_id)
+);
+-- @queries
+-- name: ListMemberships :many
+SELECT group_id, user_id, role FROM memberships;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql
new file mode 100644
index 000000000..aa16365a9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/temporal_table.sql
@@ -0,0 +1,12 @@
+-- @description System-versioned temporal table and FOR SYSTEM_TIME AS OF
+-- @schema
+CREATE TABLE prices (
+ id INT NOT NULL PRIMARY KEY CLUSTERED,
+ amount DECIMAL(10, 2) NOT NULL,
+ valid_from DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
+ valid_to DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
+ PERIOD FOR SYSTEM_TIME (valid_from, valid_to)
+) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.prices_history));
+-- @queries
+-- name: ListPricesAsOf :many
+SELECT id, amount FROM prices FOR SYSTEM_TIME AS OF @as_of;
diff --git a/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql b/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql
new file mode 100644
index 000000000..5cb24333e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/ddl/view.sql
@@ -0,0 +1,8 @@
+-- @description Querying a VIEW
+-- @schema
+CREATE VIEW author_books AS
+SELECT a.name AS author_name, b.title
+FROM authors a JOIN books b ON b.author_id = a.id;
+-- @queries
+-- name: ListAuthorBooks :many
+SELECT author_name, title FROM author_books WHERE author_name = @author_name;
diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql b/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql
new file mode 100644
index 000000000..a30f42b8b
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/delete/from_join.sql
@@ -0,0 +1,4 @@
+-- @description DELETE ... FROM with a JOIN
+-- @queries
+-- name: DeleteBooksByAuthorName :execrows
+DELETE b FROM books b JOIN authors a ON a.id = b.author_id WHERE a.name = @author_name;
diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql b/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql
new file mode 100644
index 000000000..620bf848d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/delete/output_deleted.sql
@@ -0,0 +1,4 @@
+-- @description DELETE ... OUTPUT DELETED.*
+-- @queries
+-- name: DeleteBooksOfAuthor :many
+DELETE FROM books OUTPUT DELETED.* WHERE author_id = @author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql b/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql
new file mode 100644
index 000000000..876af0f5f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/delete/simple.sql
@@ -0,0 +1,7 @@
+-- @description DELETE with a parameter
+-- @queries
+-- name: DeleteAuthor :exec
+DELETE FROM authors WHERE id = @id;
+
+-- name: DeleteAuthorNoFrom :exec
+DELETE authors WHERE id = @id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/top.sql b/end2end/MssqlSyntaxMatrix/cases/delete/top.sql
new file mode 100644
index 000000000..201cfeef3
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/delete/top.sql
@@ -0,0 +1,4 @@
+-- @description DELETE TOP (@n)
+-- @queries
+-- name: DeleteSomeBooks :execrows
+DELETE TOP (@n) FROM books WHERE is_available = 0;
diff --git a/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql b/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql
new file mode 100644
index 000000000..b3bec23f1
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/delete/truncate.sql
@@ -0,0 +1,6 @@
+-- @description TRUNCATE TABLE
+-- @schema
+CREATE TABLE books_archive (id INT NOT NULL);
+-- @queries
+-- name: TruncateBooksArchive :exec
+TRUNCATE TABLE books_archive;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql
new file mode 100644
index 000000000..9ba3e30b8
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/arithmetic.sql
@@ -0,0 +1,4 @@
+-- @description Arithmetic operators, integer division, modulo and type promotion
+-- @queries
+-- name: Arithmetic :many
+SELECT id, price * 2 AS doubled, id / 2 AS half_id, id % 3 AS remainder, price + @delta AS adjusted, -id AS negated FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql
new file mode 100644
index 000000000..f750f9cb9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/at_time_zone.sql
@@ -0,0 +1,6 @@
+-- @description AT TIME ZONE and SWITCHOFFSET
+-- @queries
+-- name: InTimeZone :many
+SELECT published_at AT TIME ZONE 'UTC' AT TIME ZONE @time_zone AS local_time,
+ SWITCHOFFSET(SYSDATETIMEOFFSET(), '+02:00') AS switched
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql
new file mode 100644
index 000000000..06e9c3561
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/bitwise.sql
@@ -0,0 +1,4 @@
+-- @description Bitwise operators and bit columns
+-- @queries
+-- name: Bitwise :many
+SELECT id, id & 1 AS low_bit, id | @mask AS masked, id ^ 2 AS xored, ~is_available AS not_available FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql
new file mode 100644
index 000000000..8917cc06a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/cast_convert.sql
@@ -0,0 +1,10 @@
+-- @description CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE
+-- @queries
+-- name: Conversions :many
+SELECT CAST(id AS NVARCHAR(20)) AS id_text,
+ CONVERT(VARCHAR(10), published_at, 23) AS iso_date,
+ TRY_CAST(title AS INT) AS title_number,
+ TRY_CONVERT(DECIMAL(10, 2), @amount) AS amount,
+ PARSE(N'2024-01-02' AS DATE USING 'en-US') AS parsed,
+ TRY_PARSE(@text AS DATETIME2) AS maybe_date
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql
new file mode 100644
index 000000000..8076175e4
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/coalesce_isnull_nullif.sql
@@ -0,0 +1,10 @@
+-- @description COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference
+-- @queries
+-- name: Fallbacks :many
+SELECT id, COALESCE(bio, name) AS bio_or_name, ISNULL(bio, N'') AS bio_or_empty, NULLIF(name, N'') AS name_or_null FROM authors;
+
+-- name: ByPattern :many
+SELECT id, name FROM authors WHERE name LIKE COALESCE(@pattern, N'%');
+
+-- name: ByPatternIsNull :many
+SELECT id, name FROM authors WHERE name LIKE ISNULL(@pattern, N'%');
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql
new file mode 100644
index 000000000..32149c7d7
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/concat_format.sql
@@ -0,0 +1,6 @@
+-- @description CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE
+-- @queries
+-- name: Formatting :many
+SELECT CONCAT(name, N' (', id, N')') AS label, CONCAT_WS(N', ', name, bio) AS joined,
+ FORMAT(birth_date, 'yyyy-MM-dd') AS formatted, QUOTENAME(name) AS quoted, TRANSLATE(name, N'abc', N'xyz') AS translated
+FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql
new file mode 100644
index 000000000..df18c1ff9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/datetime_functions.sql
@@ -0,0 +1,9 @@
+-- @description GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC
+-- @queries
+-- name: DateFunctions :many
+SELECT GETDATE() AS now_local, GETUTCDATE() AS now_utc, SYSDATETIME() AS precise, SYSDATETIMEOFFSET() AS with_offset,
+ DATEADD(DAY, @days, published_at) AS shifted, DATEDIFF(DAY, published_at, @until) AS days_between,
+ DATEDIFF_BIG(SECOND, published_at, SYSUTCDATETIME()) AS seconds_since, DATEPART(YEAR, published_at) AS year_part,
+ DATENAME(MONTH, published_at) AS month_name, YEAR(published_at) AS y, EOMONTH(published_at) AS month_end,
+ DATEFROMPARTS(2024, 1, 31) AS fixed_date, DATETRUNC(MONTH, published_at) AS month_start
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql
new file mode 100644
index 000000000..95a79e3c4
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/greatest_least.sql
@@ -0,0 +1,4 @@
+-- @description GREATEST and LEAST (SQL Server 2022)
+-- @queries
+-- name: Clamp :many
+SELECT id, GREATEST(price, @floor) AS at_least, LEAST(price, @ceiling) AS at_most FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql
new file mode 100644
index 000000000..30f45934e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/is_distinct_from.sql
@@ -0,0 +1,4 @@
+-- @description IS [NOT] DISTINCT FROM (SQL Server 2022)
+-- @queries
+-- name: ChangedBios :many
+SELECT id FROM authors WHERE bio IS DISTINCT FROM @bio;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql
new file mode 100644
index 000000000..2b7d19709
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/math_functions.sql
@@ -0,0 +1,6 @@
+-- @description ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND
+-- @queries
+-- name: MathFunctions :many
+SELECT ABS(price) AS abs_price, ROUND(price, 1) AS rounded, CEILING(price) AS ceil, FLOOR(price) AS floor_price,
+ POWER(id, 2) AS squared, SQRT(id) AS root, SIGN(price) AS sign, PI() AS pi, RAND() AS random
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql
new file mode 100644
index 000000000..2f57bc15c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/sql2022_functions.sql
@@ -0,0 +1,7 @@
+-- @description GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022)
+-- @queries
+-- name: Series :many
+SELECT value FROM GENERATE_SERIES(1, @upto);
+
+-- name: NewFunctions :many
+SELECT DATE_BUCKET(WEEK, 1, published_at) AS week_bucket, TRIM(N'x' FROM title) AS trimmed, STRING_ESCAPE(title, 'json') AS escaped FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql
new file mode 100644
index 000000000..f201a85ae
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/string_functions.sql
@@ -0,0 +1,8 @@
+-- @description LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF
+-- @queries
+-- name: StringFunctions :many
+SELECT LEN(name) AS len, DATALENGTH(name) AS bytes, SUBSTRING(name, 1, 3) AS prefix, LEFT(name, 2) AS left2, RIGHT(name, 2) AS right2,
+ UPPER(name) AS upper_name, LOWER(name) AS lower_name, TRIM(name) AS trimmed, LTRIM(RTRIM(name)) AS trimmed2,
+ REPLACE(name, N'a', N'b') AS replaced, CHARINDEX(N'a', name) AS pos, PATINDEX(N'%[0-9]%', name) AS digit_pos,
+ REVERSE(name) AS reversed, REPLICATE(N'*', 3) AS stars, STUFF(name, 1, 1, N'X') AS stuffed
+FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql b/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql
new file mode 100644
index 000000000..215c33a9e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/expressions/system_functions.sql
@@ -0,0 +1,5 @@
+-- @description NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM
+-- @queries
+-- name: SystemValues :one
+SELECT NEWID() AS id, @@ROWCOUNT AS row_count, SCOPE_IDENTITY() AS scope_id, IDENT_CURRENT('authors') AS current_id,
+ DB_NAME() AS db, SUSER_SNAME() AS login, HASHBYTES('SHA2_256', @payload) AS hash, CHECKSUM(@payload) AS checksum;
diff --git a/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql b/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql
new file mode 100644
index 000000000..1a4dfad1c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/hints/query_hints.sql
@@ -0,0 +1,7 @@
+-- @description OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN)
+-- @queries
+-- name: Recompiled :many
+SELECT id FROM books WHERE author_id = @author_id OPTION (RECOMPILE);
+
+-- name: SingleThreaded :many
+SELECT id FROM books WHERE price > @min_price OPTION (MAXDOP 1, OPTIMIZE FOR UNKNOWN);
diff --git a/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql b/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql
new file mode 100644
index 000000000..d64ba706e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/hints/table_hints.sql
@@ -0,0 +1,10 @@
+-- @description Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK
+-- @queries
+-- name: ReadUncommitted :many
+SELECT id FROM authors WITH (NOLOCK);
+
+-- name: LockForUpdate :one
+SELECT id, name FROM authors WITH (UPDLOCK, ROWLOCK) WHERE id = @id;
+
+-- name: DequeueLike :one
+SELECT TOP 1 id FROM books WITH (READPAST, UPDLOCK) WHERE is_available = 1;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql
new file mode 100644
index 000000000..90c2136b2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/at_sign_parameter_names.sql
@@ -0,0 +1,4 @@
+-- @description Parameter names with digits, underscores and mixed case
+-- @queries
+-- name: OddParams :many
+SELECT id FROM authors WHERE id = @Id1 OR name = @first_Name OR bio = @_bio;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql
new file mode 100644
index 000000000..804f99588
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/duplicate_result_names.sql
@@ -0,0 +1,4 @@
+-- @description Two result columns with the same name
+-- @queries
+-- name: DuplicateIds :many
+SELECT a.id, b.id FROM authors a JOIN books b ON b.author_id = a.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql
new file mode 100644
index 000000000..3bb8c99dc
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/mixed_case_names.sql
@@ -0,0 +1,6 @@
+-- @description Mixed-case table and column names map to PascalCase
+-- @schema
+CREATE TABLE CustomerAccounts (CustomerID INT NOT NULL PRIMARY KEY, firstName NVARCHAR(50) NOT NULL, LAST_NAME NVARCHAR(50) NOT NULL);
+-- @queries
+-- name: ListMixed :many
+SELECT CustomerID, firstName, LAST_NAME FROM CustomerAccounts WHERE CustomerID = @CustomerID;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql
new file mode 100644
index 000000000..28eeafb36
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/non_identifier_column_names.sql
@@ -0,0 +1,6 @@
+-- @description Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode
+-- @schema
+CREATE TABLE odd_names ([1st place] INT NULL, [e-mail] NVARCHAR(50) NULL, [naïve] BIT NULL, [Total $] MONEY NULL);
+-- @queries
+-- name: ListOdd :many
+SELECT [1st place], [e-mail], [naïve], [Total $] FROM odd_names;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql
new file mode 100644
index 000000000..df2f31a70
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/reserved_word_columns.sql
@@ -0,0 +1,6 @@
+-- @description Columns named after T-SQL and C# keywords
+-- @schema
+CREATE TABLE keywords ([user] NVARCHAR(20) NOT NULL, [order] INT NOT NULL, [key] INT NULL, [class] INT NULL, [string] NVARCHAR(10) NULL);
+-- @queries
+-- name: ListKeywords :many
+SELECT [user], [order], [key], [class], [string] FROM keywords WHERE [user] = @user;
diff --git a/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql b/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql
new file mode 100644
index 000000000..89f4a7948
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/identifiers/unnamed_expression_column.sql
@@ -0,0 +1,4 @@
+-- @description A result column without a name
+-- @queries
+-- name: Unnamed :one
+SELECT COUNT(*) FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql
new file mode 100644
index 000000000..167bc9d45
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/default_values.sql
@@ -0,0 +1,9 @@
+-- @description INSERT ... DEFAULT VALUES and explicit DEFAULT
+-- @schema
+CREATE TABLE events (id INT IDENTITY PRIMARY KEY, created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME());
+-- @queries
+-- name: CreateEmptyEvent :exec
+INSERT INTO events DEFAULT VALUES;
+
+-- name: CreateEventWithDefault :exec
+INSERT INTO events (created_at) VALUES (DEFAULT);
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql b/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql
new file mode 100644
index 000000000..650441341
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/identity_insert.sql
@@ -0,0 +1,6 @@
+-- @description Explicit values for an IDENTITY column with SET IDENTITY_INSERT
+-- @queries
+-- name: CreateAuthorWithId :exec
+SET IDENTITY_INSERT authors ON;
+INSERT INTO authors (id, name) VALUES (@id, @name);
+SET IDENTITY_INSERT authors OFF;
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql b/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql
new file mode 100644
index 000000000..f61acb495
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/insert_exec.sql
@@ -0,0 +1,9 @@
+-- @description INSERT ... EXEC from a stored procedure
+-- @schema
+CREATE TABLE author_archive (id BIGINT NOT NULL, name NVARCHAR(100) NOT NULL);
+GO
+CREATE PROCEDURE dbo.list_author_names AS SELECT id, name FROM authors;
+GO
+-- @queries
+-- name: ArchiveAuthors :execrows
+INSERT INTO author_archive (id, name) EXEC dbo.list_author_names;
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql b/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql
new file mode 100644
index 000000000..0d3cf7cf2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/insert_select.sql
@@ -0,0 +1,4 @@
+-- @description INSERT ... SELECT with a parameter
+-- @queries
+-- name: CopyBooksToAuthor :execrows
+INSERT INTO books (author_id, title, price) SELECT @target_author_id, title, price FROM books WHERE author_id = @source_author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql
new file mode 100644
index 000000000..d603a0e9a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/multi_row_values.sql
@@ -0,0 +1,4 @@
+-- @description INSERT with several VALUES rows
+-- @queries
+-- name: CreateTwoAuthors :exec
+INSERT INTO authors (name, bio) VALUES (@first_name, @first_bio), (@second_name, @second_bio);
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql b/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql
new file mode 100644
index 000000000..d16498968
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/output_inserted.sql
@@ -0,0 +1,7 @@
+-- @description INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows
+-- @queries
+-- name: CreateAuthorReturningAll :one
+INSERT INTO authors (name, bio) OUTPUT INSERTED.* VALUES (@name, @bio);
+
+-- name: CreateAuthorReturningSome :one
+INSERT INTO authors (name) OUTPUT INSERTED.id, INSERTED.name VALUES (@name);
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql b/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql
new file mode 100644
index 000000000..3abff4c77
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/output_into_table_variable.sql
@@ -0,0 +1,6 @@
+-- @description OUTPUT ... INTO a table variable inside a multi-statement query
+-- @queries
+-- name: CreateAuthorOutputInto :one
+DECLARE @ids TABLE (id BIGINT);
+INSERT INTO authors (name) OUTPUT INSERTED.id INTO @ids VALUES (@name);
+SELECT id FROM @ids;
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql
new file mode 100644
index 000000000..d3750c8f0
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/schema_scoped.sql
@@ -0,0 +1,8 @@
+-- @description INSERT into a table in a user-defined schema
+-- @schema
+CREATE SCHEMA logging;
+GO
+CREATE TABLE logging.entries (id INT IDENTITY PRIMARY KEY, message NVARCHAR(200) NOT NULL);
+-- @queries
+-- name: CreateLog :exec
+INSERT INTO logging.entries (message) VALUES (@message);
diff --git a/end2end/MssqlSyntaxMatrix/cases/insert/values.sql b/end2end/MssqlSyntaxMatrix/cases/insert/values.sql
new file mode 100644
index 000000000..e752b0f73
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/insert/values.sql
@@ -0,0 +1,4 @@
+-- @description INSERT ... VALUES with parameters for every column
+-- @queries
+-- name: CreateAuthor :exec
+INSERT INTO authors (name, bio, birth_date) VALUES (@name, @bio, @birth_date);
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql b/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql
new file mode 100644
index 000000000..3b6156f0c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/cross_apply.sql
@@ -0,0 +1,9 @@
+-- @description CROSS APPLY and OUTER APPLY with a correlated TOP subquery
+-- @queries
+-- name: LatestBookPerAuthor :many
+SELECT a.id, latest.title FROM authors a
+CROSS APPLY (SELECT TOP 1 b.title FROM books b WHERE b.author_id = a.id ORDER BY b.published_at DESC) AS latest;
+
+-- name: LatestBookPerAuthorOuter :many
+SELECT a.id, latest.title FROM authors a
+OUTER APPLY (SELECT TOP 1 b.title FROM books b WHERE b.author_id = a.id ORDER BY b.published_at DESC) AS latest;
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql
new file mode 100644
index 000000000..2c1658755
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/cross_join.sql
@@ -0,0 +1,7 @@
+-- @description CROSS JOIN and the comma join
+-- @queries
+-- name: CrossJoin :many
+SELECT a.id AS author_id, b.id AS book_id FROM authors a CROSS JOIN books b;
+
+-- name: CommaJoin :many
+SELECT a.id AS author_id, b.id AS book_id FROM authors a, books b WHERE a.id = b.author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql b/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql
new file mode 100644
index 000000000..486e7c93d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/inner_left_right_full.sql
@@ -0,0 +1,13 @@
+-- @description INNER, LEFT, RIGHT and FULL joins and the nullability they introduce
+-- @queries
+-- name: InnerJoin :many
+SELECT a.name, b.title FROM authors a INNER JOIN books b ON b.author_id = a.id;
+
+-- name: LeftJoin :many
+SELECT a.name, b.title, b.price FROM authors a LEFT JOIN books b ON b.author_id = a.id;
+
+-- name: RightJoin :many
+SELECT a.name, b.title FROM authors a RIGHT OUTER JOIN books b ON b.author_id = a.id;
+
+-- name: FullJoin :many
+SELECT a.name, b.title FROM authors a FULL OUTER JOIN books b ON b.author_id = a.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql b/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql
new file mode 100644
index 000000000..33b6ee5d6
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/join_derived_table.sql
@@ -0,0 +1,5 @@
+-- @description Join to an aggregated derived table
+-- @queries
+-- name: AuthorsWithTotals :many
+SELECT a.name, t.total FROM authors a
+JOIN (SELECT author_id, SUM(price) AS total FROM books GROUP BY author_id) t ON t.author_id = a.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql
new file mode 100644
index 000000000..257feb9a7
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/schema_qualified_join.sql
@@ -0,0 +1,4 @@
+-- @description Join across dbo-qualified and bracket-quoted names
+-- @queries
+-- name: QualifiedJoin :many
+SELECT [a].[name], [b].[title] FROM [dbo].[authors] AS [a] JOIN dbo.books AS b ON b.author_id = [a].[id];
diff --git a/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql b/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql
new file mode 100644
index 000000000..a1004d0ad
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/joins/self_join.sql
@@ -0,0 +1,4 @@
+-- @description Self join with aliases
+-- @queries
+-- name: SameNameAuthors :many
+SELECT a1.id AS first_id, a2.id AS second_id FROM authors a1 JOIN authors a2 ON a1.name = a2.name AND a1.id < a2.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql b/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql
new file mode 100644
index 000000000..91f28b80a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/json/for_json.sql
@@ -0,0 +1,7 @@
+-- @description FOR JSON PATH / AUTO producing a single JSON column
+-- @queries
+-- name: AuthorsAsJson :one
+SELECT id, name FROM authors FOR JSON PATH;
+
+-- name: AuthorAsJsonObject :one
+SELECT id, name FROM authors WHERE id = @id FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER;
diff --git a/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql b/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql
new file mode 100644
index 000000000..da0b7f2a8
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/json/json_object_array.sql
@@ -0,0 +1,4 @@
+-- @description JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022)
+-- @queries
+-- name: AuthorJson :many
+SELECT JSON_OBJECT('id': id, 'name': name) AS obj, JSON_ARRAY(id, name) AS arr FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql b/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql
new file mode 100644
index 000000000..f51610567
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/json/json_value_query.sql
@@ -0,0 +1,8 @@
+-- @description JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column
+-- @schema
+CREATE TABLE settings (id INT IDENTITY PRIMARY KEY, payload NVARCHAR(MAX) NOT NULL CHECK (ISJSON(payload) = 1));
+-- @queries
+-- name: JsonAccess :many
+SELECT id, JSON_VALUE(payload, '$.name') AS name, JSON_QUERY(payload, '$.tags') AS tags, ISJSON(payload) AS is_json,
+ JSON_MODIFY(payload, '$.name', @new_name) AS renamed
+FROM settings WHERE JSON_VALUE(payload, '$.kind') = @kind;
diff --git a/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql b/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql
new file mode 100644
index 000000000..cbc2cbaf6
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/json/openjson.sql
@@ -0,0 +1,7 @@
+-- @description OPENJSON with an explicit WITH schema, fed by a parameter
+-- @queries
+-- name: ParseItems :many
+SELECT item_id, quantity FROM OPENJSON(@items) WITH (item_id INT '$.id', quantity INT '$.qty');
+
+-- name: AuthorsByJsonIds :many
+SELECT id, name FROM authors WHERE id IN (SELECT CAST(value AS BIGINT) FROM OPENJSON(@ids));
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql b/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql
new file mode 100644
index 000000000..d6b5aaf39
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/arg_forms.sql
@@ -0,0 +1,4 @@
+-- @description sqlc.arg with a quoted name, a bare identifier and a double-quoted name
+-- @queries
+-- name: ArgForms :many
+SELECT id FROM authors WHERE name = sqlc.arg('author_name') AND id > sqlc.arg(min_id) AND bio <> sqlc.arg("bio_text");
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql b/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql
new file mode 100644
index 000000000..3fc85a6e9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/arg_in_cast.sql
@@ -0,0 +1,4 @@
+-- @description sqlc.arg inside CAST, which fixes its type
+-- @queries
+-- name: CastArg :many
+SELECT id FROM books WHERE price > CAST(sqlc.arg('min_price') AS DECIMAL(10, 2));
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql b/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql
new file mode 100644
index 000000000..69d429131
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/args_in_top_offset.sql
@@ -0,0 +1,7 @@
+-- @description sqlc.arg in TOP and in OFFSET / FETCH
+-- @queries
+-- name: TopArg :many
+SELECT TOP (sqlc.arg('take')) id FROM authors ORDER BY id;
+
+-- name: PageArgs :many
+SELECT id FROM authors ORDER BY id OFFSET sqlc.arg('skip') ROWS FETCH NEXT sqlc.arg('take') ROWS ONLY;
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql b/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql
new file mode 100644
index 000000000..5cb172d80
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/embed.sql
@@ -0,0 +1,10 @@
+-- @description sqlc.embed of a table, of aliases (self join) and next to a t.* star
+-- @queries
+-- name: AuthorsWithBooks :many
+SELECT sqlc.embed(authors), sqlc.embed(books) FROM authors JOIN books ON books.author_id = authors.id;
+
+-- name: DuplicateAuthors :many
+SELECT sqlc.embed(a1), sqlc.embed(a2) FROM authors a1 JOIN authors a2 ON a1.name = a2.name AND a1.id < a2.id;
+
+-- name: BooksWithAuthor :many
+SELECT books.*, sqlc.embed(authors) FROM books JOIN authors ON authors.id = books.author_id WHERE books.title = @title;
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql b/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql
new file mode 100644
index 000000000..0f9604fd5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/embed_schema_scoped.sql
@@ -0,0 +1,8 @@
+-- @description sqlc.embed of a table in a user-defined schema
+-- @schema
+CREATE SCHEMA sales;
+GO
+CREATE TABLE sales.orders (id INT IDENTITY PRIMARY KEY, author_id BIGINT NOT NULL, total MONEY NOT NULL);
+-- @queries
+-- name: OrdersWithAuthor :many
+SELECT sqlc.embed(orders), sqlc.embed(authors) FROM sales.orders JOIN authors ON authors.id = orders.author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql b/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql
new file mode 100644
index 000000000..c98bb85bc
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/mixed_with_at_params.sql
@@ -0,0 +1,4 @@
+-- @description sqlc.arg mixed with plain @params, sharing a name
+-- @queries
+-- name: Mixed :many
+SELECT id FROM authors WHERE name = sqlc.arg('name') AND (bio = @bio OR bio = @name);
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql b/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql
new file mode 100644
index 000000000..ea3b8ab77
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/narg.sql
@@ -0,0 +1,4 @@
+-- @description sqlc.narg makes a parameter nullable
+-- @queries
+-- name: NargFilter :many
+SELECT id FROM authors WHERE name = sqlc.narg('name') OR sqlc.narg('name') IS NULL;
diff --git a/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql b/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql
new file mode 100644
index 000000000..afcac218d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/macros/slice.sql
@@ -0,0 +1,13 @@
+-- @description sqlc.slice for integers, strings, NOT IN and several slices in one query
+-- @queries
+-- name: ByIds :many
+SELECT id, name FROM authors WHERE id IN (sqlc.slice('ids'));
+
+-- name: ByNames :many
+SELECT id FROM authors WHERE name IN (sqlc.slice('names'));
+
+-- name: NotByIds :many
+SELECT id FROM authors WHERE id NOT IN (sqlc.slice('excluded'));
+
+-- name: ByIdsAndTitles :many
+SELECT id FROM books WHERE author_id IN (sqlc.slice('author_ids')) AND title IN (sqlc.slice('titles'));
diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql b/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql
new file mode 100644
index 000000000..58b045a53
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/merge/merge_delete.sql
@@ -0,0 +1,9 @@
+-- @description MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE
+-- @schema
+CREATE TABLE tags (name NVARCHAR(50) NOT NULL PRIMARY KEY);
+-- @queries
+-- name: SyncTags :execrows
+MERGE INTO tags AS t
+USING (SELECT value AS name FROM STRING_SPLIT(@names, ',')) AS s ON t.name = s.name
+WHEN NOT MATCHED BY TARGET THEN INSERT (name) VALUES (s.name)
+WHEN NOT MATCHED BY SOURCE THEN DELETE;
diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql b/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql
new file mode 100644
index 000000000..a187fcf6e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/merge/merge_upsert.sql
@@ -0,0 +1,9 @@
+-- @description MERGE as an upsert, with OUTPUT $action
+-- @queries
+-- name: UpsertAuthor :one
+MERGE INTO authors AS target
+USING (VALUES (@name, @bio)) AS source (name, bio)
+ON target.name = source.name
+WHEN MATCHED THEN UPDATE SET bio = source.bio
+WHEN NOT MATCHED THEN INSERT (name, bio) VALUES (source.name, source.bio)
+OUTPUT $action AS action, INSERTED.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql b/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql
new file mode 100644
index 000000000..3893fbc4c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/merge/upsert_without_merge.sql
@@ -0,0 +1,6 @@
+-- @description The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT
+-- @queries
+-- name: UpsertBio :exec
+UPDATE authors SET bio = @bio WHERE name = @name;
+IF @@ROWCOUNT = 0
+ INSERT INTO authors (name, bio) VALUES (@name, @bio);
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql
new file mode 100644
index 000000000..d21e35e95
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/exec_procedure.sql
@@ -0,0 +1,10 @@
+-- @description Calling a stored procedure with EXEC and named arguments
+-- @schema
+CREATE PROCEDURE dbo.get_author @id BIGINT AS
+BEGIN
+ SELECT id, name FROM authors WHERE id = @id;
+END
+GO
+-- @queries
+-- name: GetAuthorViaProc :many
+EXEC dbo.get_author @id = @id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql
new file mode 100644
index 000000000..19cf35243
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/inline_table_function.sql
@@ -0,0 +1,8 @@
+-- @description An inline table-valued function in FROM
+-- @schema
+CREATE FUNCTION dbo.books_of (@author_id BIGINT) RETURNS TABLE AS
+RETURN (SELECT id, title FROM books WHERE author_id = @author_id);
+GO
+-- @queries
+-- name: BooksOf :many
+SELECT id, title FROM dbo.books_of(@author_id);
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql
new file mode 100644
index 000000000..7e9fd687d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/multi_statement_table_function.sql
@@ -0,0 +1,12 @@
+-- @description A multi-statement table-valued function in FROM
+-- @schema
+CREATE FUNCTION dbo.range_of (@upto INT) RETURNS @result TABLE (n INT NOT NULL) AS
+BEGIN
+ DECLARE @i INT = 1;
+ WHILE @i <= @upto BEGIN INSERT INTO @result VALUES (@i); SET @i += 1; END;
+ RETURN;
+END
+GO
+-- @queries
+-- name: Range :many
+SELECT n FROM dbo.range_of(@upto);
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql
new file mode 100644
index 000000000..05f9f216c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/scalar_function.sql
@@ -0,0 +1,10 @@
+-- @description A user-defined scalar function in the select list and WHERE clause
+-- @schema
+CREATE FUNCTION dbo.initials (@name NVARCHAR(100)) RETURNS NVARCHAR(10) AS
+BEGIN
+ RETURN LEFT(@name, 1);
+END
+GO
+-- @queries
+-- name: ListWithInitials :many
+SELECT id, dbo.initials(name) AS initials FROM authors WHERE dbo.initials(name) = @initials;
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql
new file mode 100644
index 000000000..58fa65440
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/string_split.sql
@@ -0,0 +1,4 @@
+-- @description STRING_SPLIT as a table-valued function fed by a parameter
+-- @queries
+-- name: AuthorsByNameList :many
+SELECT id, name FROM authors WHERE name IN (SELECT value FROM STRING_SPLIT(@names, ','));
diff --git a/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql
new file mode 100644
index 000000000..321e878f2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/programmability/table_valued_parameter.sql
@@ -0,0 +1,6 @@
+-- @description A table-valued parameter (user-defined table type) used in a join
+-- @schema
+CREATE TYPE dbo.id_list AS TABLE (id BIGINT NOT NULL PRIMARY KEY);
+-- @queries
+-- name: AuthorsByIdList :many
+SELECT a.id, a.name FROM authors a JOIN @ids i ON i.id = a.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql b/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql
new file mode 100644
index 000000000..5a2d5ee80
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/alias_equals_syntax.sql
@@ -0,0 +1,4 @@
+-- @description T-SQL column alias written as alias = expression
+-- @queries
+-- name: ListAuthorNames :many
+SELECT author_id = id, author_name = name FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql b/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql
new file mode 100644
index 000000000..bb0054a78
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/between_params.sql
@@ -0,0 +1,4 @@
+-- @description BETWEEN with two parameters
+-- @queries
+-- name: ListBooksInPriceRange :many
+SELECT id, price FROM books WHERE price BETWEEN @min_price AND @max_price;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql b/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql
new file mode 100644
index 000000000..d21fc85f8
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/case_expression.sql
@@ -0,0 +1,7 @@
+-- @description Searched and simple CASE expressions
+-- @queries
+-- name: ListPriceBands :many
+SELECT id,
+ CASE WHEN price IS NULL THEN N'unknown' WHEN price < 10 THEN N'cheap' ELSE N'expensive' END AS band,
+ CASE is_available WHEN 1 THEN 'yes' ELSE 'no' END AS available
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/comments.sql b/end2end/MssqlSyntaxMatrix/cases/select/comments.sql
new file mode 100644
index 000000000..6e6735ebd
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/comments.sql
@@ -0,0 +1,6 @@
+-- @description Block comments, nested block comments and trailing line comments inside a query
+-- @queries
+-- name: Commented :many
+SELECT id, /* the name */ name -- trailing
+FROM authors /* outer /* nested */ still a comment */
+WHERE id > @min_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql b/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql
new file mode 100644
index 000000000..ed90e0f86
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/derived_table.sql
@@ -0,0 +1,4 @@
+-- @description Derived table (subquery in FROM) with column aliases
+-- @queries
+-- name: ListExpensive :many
+SELECT t.book_id, t.price FROM (SELECT id AS book_id, price FROM books WHERE price > @min) AS t;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql b/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql
new file mode 100644
index 000000000..721a6b187
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/distinct.sql
@@ -0,0 +1,4 @@
+-- @description SELECT DISTINCT
+-- @queries
+-- name: ListDistinctAuthorIds :many
+SELECT DISTINCT author_id FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/exists.sql b/end2end/MssqlSyntaxMatrix/cases/select/exists.sql
new file mode 100644
index 000000000..eca04be6d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/exists.sql
@@ -0,0 +1,7 @@
+-- @description EXISTS / NOT EXISTS with a parameter inside the subquery
+-- @queries
+-- name: ListAuthorsWithBooksAbove :many
+SELECT id FROM authors a WHERE EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id AND b.price > @min_price);
+
+-- name: ListAuthorsWithoutBooks :many
+SELECT id FROM authors a WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql
new file mode 100644
index 000000000..a9284bcb4
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/function_on_parameter.sql
@@ -0,0 +1,4 @@
+-- @description Parameters wrapped in functions and compared to columns
+-- @queries
+-- name: CaseInsensitiveMatch :many
+SELECT id FROM authors WHERE LOWER(name) = LOWER(@name) AND LEN(@name) > 0;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql b/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql
new file mode 100644
index 000000000..20143288b
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/iif_choose.sql
@@ -0,0 +1,4 @@
+-- @description IIF and CHOOSE
+-- @queries
+-- name: ListFlags :many
+SELECT id, IIF(price > @threshold, 1, 0) AS is_expensive, CHOOSE(2, 'a', 'b', 'c') AS second FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql b/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql
new file mode 100644
index 000000000..02ee91816
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/in_list_params.sql
@@ -0,0 +1,4 @@
+-- @description IN with a list of scalar parameters
+-- @queries
+-- name: ByThreeIds :many
+SELECT id, name FROM authors WHERE id IN (@first_id, @second_id, @third_id);
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql b/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql
new file mode 100644
index 000000000..5a22729b2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/in_subquery_param.sql
@@ -0,0 +1,4 @@
+-- @description A parameter inside IN (subquery)
+-- @queries
+-- name: ListAuthorsOfTitle :many
+SELECT id, name FROM authors WHERE id IN (SELECT author_id FROM books WHERE title = @title);
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql b/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql
new file mode 100644
index 000000000..746de5c42
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/like_escape.sql
@@ -0,0 +1,4 @@
+-- @description LIKE with a parameter and an ESCAPE clause
+-- @queries
+-- name: SearchTitles :many
+SELECT id, title FROM books WHERE title LIKE @pattern ESCAPE '\';
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql b/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql
new file mode 100644
index 000000000..849c54dea
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/no_semicolon_terminator.sql
@@ -0,0 +1,7 @@
+-- @description Statements without a terminating semicolon (optional in T-SQL)
+-- @queries
+-- name: First :many
+SELECT id FROM authors
+
+-- name: Second :many
+SELECT id FROM books
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql b/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql
new file mode 100644
index 000000000..088f9e846
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/offset_fetch.sql
@@ -0,0 +1,4 @@
+-- @description OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters
+-- @queries
+-- name: PageAuthors :many
+SELECT id, name FROM authors ORDER BY name OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql b/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql
new file mode 100644
index 000000000..82b6e6367
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/offset_without_fetch.sql
@@ -0,0 +1,4 @@
+-- @description OFFSET ... ROWS without FETCH
+-- @queries
+-- name: SkipAuthors :many
+SELECT id FROM authors ORDER BY id OFFSET @skip ROWS;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql b/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql
new file mode 100644
index 000000000..fd245e70c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/optional_filter.sql
@@ -0,0 +1,4 @@
+-- @description The optional filter idiom: (@p IS NULL OR column = @p)
+-- @queries
+-- name: FilterBooks :many
+SELECT id FROM books WHERE (@author_id IS NULL OR author_id = @author_id) AND (@title IS NULL OR title = @title);
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql
new file mode 100644
index 000000000..31140ee89
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/order_by_parameter.sql
@@ -0,0 +1,5 @@
+-- @description Dynamic ORDER BY driven by a parameter
+-- @queries
+-- name: SortedAuthors :many
+SELECT id, name FROM authors
+ORDER BY CASE WHEN @sort = 'name' THEN name END, CASE WHEN @sort = 'id' THEN id END DESC;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql b/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql
new file mode 100644
index 000000000..206087a34
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/parameter_in_select_list.sql
@@ -0,0 +1,4 @@
+-- @description A parameter echoed in the select list, with and without CAST
+-- @queries
+-- name: EchoParams :many
+SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql b/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql
new file mode 100644
index 000000000..ab7d5eb51
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/quoted_aliases.sql
@@ -0,0 +1,4 @@
+-- @description Aliases quoted with brackets, double quotes and single quotes
+-- @queries
+-- name: ListQuotedAliases :many
+SELECT id AS [Author Id], name AS "Author Name", bio 'Biography' FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql
new file mode 100644
index 000000000..dce23bef6
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/repeated_parameter.sql
@@ -0,0 +1,4 @@
+-- @description The same parameter used several times, with different case
+-- @queries
+-- name: ByNameTwice :many
+SELECT id FROM authors WHERE name = @name OR bio = @Name OR @NAME IS NULL;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql b/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql
new file mode 100644
index 000000000..d783a9c86
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/scalar_subquery_column.sql
@@ -0,0 +1,4 @@
+-- @description Correlated scalar subquery in the select list
+-- @queries
+-- name: ListAuthorsWithBookCount :many
+SELECT a.id, (SELECT COUNT(*) FROM books b WHERE b.author_id = a.id) AS book_count FROM authors a;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql b/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql
new file mode 100644
index 000000000..4179bfcb1
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/select_star.sql
@@ -0,0 +1,7 @@
+-- @description SELECT * and qualified t.* expansion
+-- @queries
+-- name: ListAuthors :many
+SELECT * FROM authors;
+
+-- name: ListAuthorsWithBookTitle :many
+SELECT a.*, b.title FROM authors a JOIN books b ON b.author_id = a.id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql b/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql
new file mode 100644
index 000000000..ae6cf788f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/table_value_constructor.sql
@@ -0,0 +1,4 @@
+-- @description VALUES as a derived table
+-- @queries
+-- name: ListConstants :many
+SELECT v.code, v.label FROM (VALUES (1, N'one'), (2, N'two')) AS v (code, label);
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql b/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql
new file mode 100644
index 000000000..99c55b3bc
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/top_literal_with_ties.sql
@@ -0,0 +1,7 @@
+-- @description TOP n WITH TIES and TOP n PERCENT
+-- @queries
+-- name: TopPricedWithTies :many
+SELECT TOP 3 WITH TIES id, price FROM books ORDER BY price DESC;
+
+-- name: TopTenPercent :many
+SELECT TOP 10 PERCENT id FROM books ORDER BY id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql b/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql
new file mode 100644
index 000000000..c5351d089
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/top_parameter.sql
@@ -0,0 +1,4 @@
+-- @description TOP with a parenthesized parameter
+-- @queries
+-- name: TopAuthors :many
+SELECT TOP (@take) id, name FROM authors ORDER BY name;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql b/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql
new file mode 100644
index 000000000..b4d65802a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/unicode_literals.sql
@@ -0,0 +1,4 @@
+-- @description N'' Unicode literals and string concatenation with +
+-- @queries
+-- name: Greetings :many
+SELECT N'שלום ' + name AS greeting, 'plain' AS ascii FROM authors;
diff --git a/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql b/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql
new file mode 100644
index 000000000..2f002cde5
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/select/without_from.sql
@@ -0,0 +1,7 @@
+-- @description SELECT without FROM: parameters and functions only
+-- @queries
+-- name: Now :one
+SELECT SYSUTCDATETIME() AS now_utc;
+
+-- name: AddOne :one
+SELECT @value + 1 AS result;
diff --git a/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql b/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql
new file mode 100644
index 000000000..9c8eee65a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/setops/pivot_unpivot.sql
@@ -0,0 +1,13 @@
+-- @description PIVOT and UNPIVOT
+-- @schema
+CREATE TABLE quarterly (region NVARCHAR(10) NOT NULL, q1 INT NOT NULL, q2 INT NOT NULL);
+-- @queries
+-- name: AvailabilityPivot :many
+SELECT author_id, [0] AS unavailable, [1] AS available
+FROM (SELECT author_id, CAST(is_available AS INT) AS is_available, id FROM books) src
+PIVOT (COUNT(id) FOR is_available IN ([0], [1])) AS p;
+
+-- name: QuarterUnpivot :many
+SELECT region, quarter, amount
+FROM (SELECT region, q1, q2 FROM quarterly) src
+UNPIVOT (amount FOR quarter IN (q1, q2)) AS u;
diff --git a/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql b/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql
new file mode 100644
index 000000000..67f21a690
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/setops/union_except_intersect.sql
@@ -0,0 +1,13 @@
+-- @description UNION, UNION ALL, EXCEPT and INTERSECT
+-- @queries
+-- name: UnionNames :many
+SELECT name FROM authors UNION SELECT title FROM books;
+
+-- name: UnionAllIds :many
+SELECT id FROM authors UNION ALL SELECT author_id FROM books;
+
+-- name: AuthorsWithoutBooks :many
+SELECT id FROM authors EXCEPT SELECT author_id FROM books;
+
+-- name: AuthorsWithBooks :many
+SELECT id FROM authors INTERSECT SELECT author_id FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql b/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql
new file mode 100644
index 000000000..6a0e48a22
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/bigint.sql
@@ -0,0 +1,12 @@
+-- @description `BIGINT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_bigint (v BIGINT NULL, nn BIGINT NOT NULL);
+-- @queries
+-- name: SelectBigint :many
+SELECT v, nn FROM t_bigint;
+
+-- name: InsertBigint :exec
+INSERT INTO t_bigint (v, nn) VALUES (@v, @nn);
+
+-- name: FilterBigint :many
+SELECT v FROM t_bigint WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/binary.sql b/end2end/MssqlSyntaxMatrix/cases/types/binary.sql
new file mode 100644
index 000000000..7db3e9775
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/binary.sql
@@ -0,0 +1,12 @@
+-- @description `BINARY(16)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_binary (v BINARY(16) NULL, nn BINARY(16) NOT NULL);
+-- @queries
+-- name: SelectBinary :many
+SELECT v, nn FROM t_binary;
+
+-- name: InsertBinary :exec
+INSERT INTO t_binary (v, nn) VALUES (@v, @nn);
+
+-- name: FilterBinary :many
+SELECT v FROM t_binary WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/bit.sql b/end2end/MssqlSyntaxMatrix/cases/types/bit.sql
new file mode 100644
index 000000000..733f86c17
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/bit.sql
@@ -0,0 +1,12 @@
+-- @description `BIT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_bit (v BIT NULL, nn BIT NOT NULL);
+-- @queries
+-- name: SelectBit :many
+SELECT v, nn FROM t_bit;
+
+-- name: InsertBit :exec
+INSERT INTO t_bit (v, nn) VALUES (@v, @nn);
+
+-- name: FilterBit :many
+SELECT v FROM t_bit WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/char.sql b/end2end/MssqlSyntaxMatrix/cases/types/char.sql
new file mode 100644
index 000000000..d4efa4dac
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/char.sql
@@ -0,0 +1,12 @@
+-- @description `CHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_char (v CHAR(10) NULL, nn CHAR(10) NOT NULL);
+-- @queries
+-- name: SelectChar :many
+SELECT v, nn FROM t_char;
+
+-- name: InsertChar :exec
+INSERT INTO t_char (v, nn) VALUES (@v, @nn);
+
+-- name: FilterChar :many
+SELECT v FROM t_char WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/date.sql b/end2end/MssqlSyntaxMatrix/cases/types/date.sql
new file mode 100644
index 000000000..282beca87
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/date.sql
@@ -0,0 +1,12 @@
+-- @description `DATE` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_date (v DATE NULL, nn DATE NOT NULL);
+-- @queries
+-- name: SelectDate :many
+SELECT v, nn FROM t_date;
+
+-- name: InsertDate :exec
+INSERT INTO t_date (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDate :many
+SELECT v FROM t_date WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql
new file mode 100644
index 000000000..b4c4d85b1
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime.sql
@@ -0,0 +1,12 @@
+-- @description `DATETIME` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_datetime (v DATETIME NULL, nn DATETIME NOT NULL);
+-- @queries
+-- name: SelectDatetime :many
+SELECT v, nn FROM t_datetime;
+
+-- name: InsertDatetime :exec
+INSERT INTO t_datetime (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDatetime :many
+SELECT v FROM t_datetime WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql
new file mode 100644
index 000000000..6df588115
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime2.sql
@@ -0,0 +1,12 @@
+-- @description `DATETIME2(7)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_datetime2 (v DATETIME2(7) NULL, nn DATETIME2(7) NOT NULL);
+-- @queries
+-- name: SelectDatetime2 :many
+SELECT v, nn FROM t_datetime2;
+
+-- name: InsertDatetime2 :exec
+INSERT INTO t_datetime2 (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDatetime2 :many
+SELECT v FROM t_datetime2 WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql
new file mode 100644
index 000000000..bdfb1382a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/datetime2_0.sql
@@ -0,0 +1,12 @@
+-- @description `DATETIME2(0)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_datetime2_0 (v DATETIME2(0) NULL, nn DATETIME2(0) NOT NULL);
+-- @queries
+-- name: SelectDatetime20 :many
+SELECT v, nn FROM t_datetime2_0;
+
+-- name: InsertDatetime20 :exec
+INSERT INTO t_datetime2_0 (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDatetime20 :many
+SELECT v FROM t_datetime2_0 WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql b/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql
new file mode 100644
index 000000000..3a6dc63b1
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/datetimeoffset.sql
@@ -0,0 +1,12 @@
+-- @description `DATETIMEOFFSET(7)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_datetimeoffset (v DATETIMEOFFSET(7) NULL, nn DATETIMEOFFSET(7) NOT NULL);
+-- @queries
+-- name: SelectDatetimeoffset :many
+SELECT v, nn FROM t_datetimeoffset;
+
+-- name: InsertDatetimeoffset :exec
+INSERT INTO t_datetimeoffset (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDatetimeoffset :many
+SELECT v FROM t_datetimeoffset WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql b/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql
new file mode 100644
index 000000000..55d9ff6ff
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/decimal.sql
@@ -0,0 +1,12 @@
+-- @description `DECIMAL(18, 4)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_decimal (v DECIMAL(18, 4) NULL, nn DECIMAL(18, 4) NOT NULL);
+-- @queries
+-- name: SelectDecimal :many
+SELECT v, nn FROM t_decimal;
+
+-- name: InsertDecimal :exec
+INSERT INTO t_decimal (v, nn) VALUES (@v, @nn);
+
+-- name: FilterDecimal :many
+SELECT v FROM t_decimal WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/float.sql b/end2end/MssqlSyntaxMatrix/cases/types/float.sql
new file mode 100644
index 000000000..2244d90c9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/float.sql
@@ -0,0 +1,12 @@
+-- @description `FLOAT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_float (v FLOAT NULL, nn FLOAT NOT NULL);
+-- @queries
+-- name: SelectFloat :many
+SELECT v, nn FROM t_float;
+
+-- name: InsertFloat :exec
+INSERT INTO t_float (v, nn) VALUES (@v, @nn);
+
+-- name: FilterFloat :many
+SELECT v FROM t_float WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql b/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql
new file mode 100644
index 000000000..8288dfe17
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/float_24.sql
@@ -0,0 +1,12 @@
+-- @description `FLOAT(24)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_float_24 (v FLOAT(24) NULL, nn FLOAT(24) NOT NULL);
+-- @queries
+-- name: SelectFloat24 :many
+SELECT v, nn FROM t_float_24;
+
+-- name: InsertFloat24 :exec
+INSERT INTO t_float_24 (v, nn) VALUES (@v, @nn);
+
+-- name: FilterFloat24 :many
+SELECT v FROM t_float_24 WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/geography.sql b/end2end/MssqlSyntaxMatrix/cases/types/geography.sql
new file mode 100644
index 000000000..e3b6fd47c
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/geography.sql
@@ -0,0 +1,9 @@
+-- @description `GEOGRAPHY` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_geography (v GEOGRAPHY NULL, nn GEOGRAPHY NOT NULL);
+-- @queries
+-- name: SelectGeography :many
+SELECT v, nn FROM t_geography;
+
+-- name: InsertGeography :exec
+INSERT INTO t_geography (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql b/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql
new file mode 100644
index 000000000..1a7573865
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/geometry.sql
@@ -0,0 +1,9 @@
+-- @description `GEOMETRY` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_geometry (v GEOMETRY NULL, nn GEOMETRY NOT NULL);
+-- @queries
+-- name: SelectGeometry :many
+SELECT v, nn FROM t_geometry;
+
+-- name: InsertGeometry :exec
+INSERT INTO t_geometry (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql b/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql
new file mode 100644
index 000000000..2218f405e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/hierarchyid.sql
@@ -0,0 +1,12 @@
+-- @description `HIERARCHYID` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_hierarchyid (v HIERARCHYID NULL, nn HIERARCHYID NOT NULL);
+-- @queries
+-- name: SelectHierarchyid :many
+SELECT v, nn FROM t_hierarchyid;
+
+-- name: InsertHierarchyid :exec
+INSERT INTO t_hierarchyid (v, nn) VALUES (@v, @nn);
+
+-- name: FilterHierarchyid :many
+SELECT v FROM t_hierarchyid WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/image.sql b/end2end/MssqlSyntaxMatrix/cases/types/image.sql
new file mode 100644
index 000000000..10406fa45
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/image.sql
@@ -0,0 +1,9 @@
+-- @description `IMAGE` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_image (v IMAGE NULL, nn IMAGE NOT NULL);
+-- @queries
+-- name: SelectImage :many
+SELECT v, nn FROM t_image;
+
+-- name: InsertImage :exec
+INSERT INTO t_image (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/int.sql b/end2end/MssqlSyntaxMatrix/cases/types/int.sql
new file mode 100644
index 000000000..75facb41b
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/int.sql
@@ -0,0 +1,12 @@
+-- @description `INT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_int (v INT NULL, nn INT NOT NULL);
+-- @queries
+-- name: SelectInt :many
+SELECT v, nn FROM t_int;
+
+-- name: InsertInt :exec
+INSERT INTO t_int (v, nn) VALUES (@v, @nn);
+
+-- name: FilterInt :many
+SELECT v FROM t_int WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql b/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql
new file mode 100644
index 000000000..79dc0bf4f
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/max_lengths_and_precision.sql
@@ -0,0 +1,6 @@
+-- @description The type expression keeps lengths, MAX, precision and scale
+-- @schema
+CREATE TABLE t_precise (d DECIMAL(38, 10) NOT NULL, n NVARCHAR(4000) NOT NULL, t TIME(0) NOT NULL, dt DATETIME2(3) NOT NULL);
+-- @queries
+-- name: SelectPrecise :many
+SELECT d, n, t, dt FROM t_precise WHERE d = @d AND n = @n;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/money.sql b/end2end/MssqlSyntaxMatrix/cases/types/money.sql
new file mode 100644
index 000000000..5be0130bb
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/money.sql
@@ -0,0 +1,12 @@
+-- @description `MONEY` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_money (v MONEY NULL, nn MONEY NOT NULL);
+-- @queries
+-- name: SelectMoney :many
+SELECT v, nn FROM t_money;
+
+-- name: InsertMoney :exec
+INSERT INTO t_money (v, nn) VALUES (@v, @nn);
+
+-- name: FilterMoney :many
+SELECT v FROM t_money WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql
new file mode 100644
index 000000000..afd395221
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/nchar.sql
@@ -0,0 +1,12 @@
+-- @description `NCHAR(10)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_nchar (v NCHAR(10) NULL, nn NCHAR(10) NOT NULL);
+-- @queries
+-- name: SelectNchar :many
+SELECT v, nn FROM t_nchar;
+
+-- name: InsertNchar :exec
+INSERT INTO t_nchar (v, nn) VALUES (@v, @nn);
+
+-- name: FilterNchar :many
+SELECT v FROM t_nchar WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql b/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql
new file mode 100644
index 000000000..a2b71b839
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/ntext.sql
@@ -0,0 +1,9 @@
+-- @description `NTEXT` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_ntext (v NTEXT NULL, nn NTEXT NOT NULL);
+-- @queries
+-- name: SelectNtext :many
+SELECT v, nn FROM t_ntext;
+
+-- name: InsertNtext :exec
+INSERT INTO t_ntext (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql b/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql
new file mode 100644
index 000000000..124251acf
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/numeric.sql
@@ -0,0 +1,12 @@
+-- @description `NUMERIC(10, 0)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_numeric (v NUMERIC(10, 0) NULL, nn NUMERIC(10, 0) NOT NULL);
+-- @queries
+-- name: SelectNumeric :many
+SELECT v, nn FROM t_numeric;
+
+-- name: InsertNumeric :exec
+INSERT INTO t_numeric (v, nn) VALUES (@v, @nn);
+
+-- name: FilterNumeric :many
+SELECT v FROM t_numeric WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql
new file mode 100644
index 000000000..9da83b1a6
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar.sql
@@ -0,0 +1,12 @@
+-- @description `NVARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_nvarchar (v NVARCHAR(50) NULL, nn NVARCHAR(50) NOT NULL);
+-- @queries
+-- name: SelectNvarchar :many
+SELECT v, nn FROM t_nvarchar;
+
+-- name: InsertNvarchar :exec
+INSERT INTO t_nvarchar (v, nn) VALUES (@v, @nn);
+
+-- name: FilterNvarchar :many
+SELECT v FROM t_nvarchar WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql
new file mode 100644
index 000000000..8be2b367e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/nvarchar_max.sql
@@ -0,0 +1,12 @@
+-- @description `NVARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_nvarchar_max (v NVARCHAR(MAX) NULL, nn NVARCHAR(MAX) NOT NULL);
+-- @queries
+-- name: SelectNvarcharMax :many
+SELECT v, nn FROM t_nvarchar_max;
+
+-- name: InsertNvarcharMax :exec
+INSERT INTO t_nvarchar_max (v, nn) VALUES (@v, @nn);
+
+-- name: FilterNvarcharMax :many
+SELECT v FROM t_nvarchar_max WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/real.sql b/end2end/MssqlSyntaxMatrix/cases/types/real.sql
new file mode 100644
index 000000000..25ebae346
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/real.sql
@@ -0,0 +1,12 @@
+-- @description `REAL` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_real (v REAL NULL, nn REAL NOT NULL);
+-- @queries
+-- name: SelectReal :many
+SELECT v, nn FROM t_real;
+
+-- name: InsertReal :exec
+INSERT INTO t_real (v, nn) VALUES (@v, @nn);
+
+-- name: FilterReal :many
+SELECT v FROM t_real WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql b/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql
new file mode 100644
index 000000000..06897009d
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/rowversion_timestamp.sql
@@ -0,0 +1,10 @@
+-- @description `ROWVERSION` and its legacy synonym `TIMESTAMP`
+-- @schema
+CREATE TABLE t_rowversion (id INT NOT NULL, rv ROWVERSION);
+CREATE TABLE t_timestamp (id INT NOT NULL, ts TIMESTAMP);
+-- @queries
+-- name: SelectVersions :many
+SELECT id, rv FROM t_rowversion;
+
+-- name: SelectTimestamp :many
+SELECT id, ts FROM t_timestamp;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql b/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql
new file mode 100644
index 000000000..1dd85b473
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/smalldatetime.sql
@@ -0,0 +1,12 @@
+-- @description `SMALLDATETIME` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_smalldatetime (v SMALLDATETIME NULL, nn SMALLDATETIME NOT NULL);
+-- @queries
+-- name: SelectSmalldatetime :many
+SELECT v, nn FROM t_smalldatetime;
+
+-- name: InsertSmalldatetime :exec
+INSERT INTO t_smalldatetime (v, nn) VALUES (@v, @nn);
+
+-- name: FilterSmalldatetime :many
+SELECT v FROM t_smalldatetime WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql b/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql
new file mode 100644
index 000000000..7872c4055
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/smallint.sql
@@ -0,0 +1,12 @@
+-- @description `SMALLINT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_smallint (v SMALLINT NULL, nn SMALLINT NOT NULL);
+-- @queries
+-- name: SelectSmallint :many
+SELECT v, nn FROM t_smallint;
+
+-- name: InsertSmallint :exec
+INSERT INTO t_smallint (v, nn) VALUES (@v, @nn);
+
+-- name: FilterSmallint :many
+SELECT v FROM t_smallint WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql b/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql
new file mode 100644
index 000000000..c984b1fe6
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/smallmoney.sql
@@ -0,0 +1,12 @@
+-- @description `SMALLMONEY` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_smallmoney (v SMALLMONEY NULL, nn SMALLMONEY NOT NULL);
+-- @queries
+-- name: SelectSmallmoney :many
+SELECT v, nn FROM t_smallmoney;
+
+-- name: InsertSmallmoney :exec
+INSERT INTO t_smallmoney (v, nn) VALUES (@v, @nn);
+
+-- name: FilterSmallmoney :many
+SELECT v FROM t_smallmoney WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql b/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql
new file mode 100644
index 000000000..ea79049fe
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/sql_variant.sql
@@ -0,0 +1,12 @@
+-- @description `SQL_VARIANT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_sql_variant (v SQL_VARIANT NULL, nn SQL_VARIANT NOT NULL);
+-- @queries
+-- name: SelectSqlVariant :many
+SELECT v, nn FROM t_sql_variant;
+
+-- name: InsertSqlVariant :exec
+INSERT INTO t_sql_variant (v, nn) VALUES (@v, @nn);
+
+-- name: FilterSqlVariant :many
+SELECT v FROM t_sql_variant WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql b/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql
new file mode 100644
index 000000000..d583857de
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/sysname.sql
@@ -0,0 +1,12 @@
+-- @description `SYSNAME` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_sysname (v SYSNAME NULL, nn SYSNAME NOT NULL);
+-- @queries
+-- name: SelectSysname :many
+SELECT v, nn FROM t_sysname;
+
+-- name: InsertSysname :exec
+INSERT INTO t_sysname (v, nn) VALUES (@v, @nn);
+
+-- name: FilterSysname :many
+SELECT v FROM t_sysname WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/text.sql b/end2end/MssqlSyntaxMatrix/cases/types/text.sql
new file mode 100644
index 000000000..0de8bb34a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/text.sql
@@ -0,0 +1,9 @@
+-- @description `TEXT` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_text (v TEXT NULL, nn TEXT NOT NULL);
+-- @queries
+-- name: SelectText :many
+SELECT v, nn FROM t_text;
+
+-- name: InsertText :exec
+INSERT INTO t_text (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/time.sql b/end2end/MssqlSyntaxMatrix/cases/types/time.sql
new file mode 100644
index 000000000..7983c99fe
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/time.sql
@@ -0,0 +1,12 @@
+-- @description `TIME(3)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_time (v TIME(3) NULL, nn TIME(3) NOT NULL);
+-- @queries
+-- name: SelectTime :many
+SELECT v, nn FROM t_time;
+
+-- name: InsertTime :exec
+INSERT INTO t_time (v, nn) VALUES (@v, @nn);
+
+-- name: FilterTime :many
+SELECT v FROM t_time WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql b/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql
new file mode 100644
index 000000000..aa0e3b400
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/tinyint.sql
@@ -0,0 +1,12 @@
+-- @description `TINYINT` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_tinyint (v TINYINT NULL, nn TINYINT NOT NULL);
+-- @queries
+-- name: SelectTinyint :many
+SELECT v, nn FROM t_tinyint;
+
+-- name: InsertTinyint :exec
+INSERT INTO t_tinyint (v, nn) VALUES (@v, @nn);
+
+-- name: FilterTinyint :many
+SELECT v FROM t_tinyint WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql b/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql
new file mode 100644
index 000000000..77c6f46bc
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/uniqueidentifier.sql
@@ -0,0 +1,12 @@
+-- @description `UNIQUEIDENTIFIER` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_uniqueidentifier (v UNIQUEIDENTIFIER NULL, nn UNIQUEIDENTIFIER NOT NULL);
+-- @queries
+-- name: SelectUniqueidentifier :many
+SELECT v, nn FROM t_uniqueidentifier;
+
+-- name: InsertUniqueidentifier :exec
+INSERT INTO t_uniqueidentifier (v, nn) VALUES (@v, @nn);
+
+-- name: FilterUniqueidentifier :many
+SELECT v FROM t_uniqueidentifier WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql b/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql
new file mode 100644
index 000000000..824a223f9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/varbinary.sql
@@ -0,0 +1,12 @@
+-- @description `VARBINARY(50)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_varbinary (v VARBINARY(50) NULL, nn VARBINARY(50) NOT NULL);
+-- @queries
+-- name: SelectVarbinary :many
+SELECT v, nn FROM t_varbinary;
+
+-- name: InsertVarbinary :exec
+INSERT INTO t_varbinary (v, nn) VALUES (@v, @nn);
+
+-- name: FilterVarbinary :many
+SELECT v FROM t_varbinary WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql
new file mode 100644
index 000000000..2d0077378
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/varbinary_max.sql
@@ -0,0 +1,12 @@
+-- @description `VARBINARY(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_varbinary_max (v VARBINARY(MAX) NULL, nn VARBINARY(MAX) NOT NULL);
+-- @queries
+-- name: SelectVarbinaryMax :many
+SELECT v, nn FROM t_varbinary_max;
+
+-- name: InsertVarbinaryMax :exec
+INSERT INTO t_varbinary_max (v, nn) VALUES (@v, @nn);
+
+-- name: FilterVarbinaryMax :many
+SELECT v FROM t_varbinary_max WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql b/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql
new file mode 100644
index 000000000..173321715
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/varchar.sql
@@ -0,0 +1,12 @@
+-- @description `VARCHAR(50)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_varchar (v VARCHAR(50) NULL, nn VARCHAR(50) NOT NULL);
+-- @queries
+-- name: SelectVarchar :many
+SELECT v, nn FROM t_varchar;
+
+-- name: InsertVarchar :exec
+INSERT INTO t_varchar (v, nn) VALUES (@v, @nn);
+
+-- name: FilterVarchar :many
+SELECT v FROM t_varchar WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql b/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql
new file mode 100644
index 000000000..f512181e2
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/varchar_max.sql
@@ -0,0 +1,12 @@
+-- @description `VARCHAR(MAX)` column: nullable and NOT NULL, read, written and compared to a parameter
+-- @schema
+CREATE TABLE t_varchar_max (v VARCHAR(MAX) NULL, nn VARCHAR(MAX) NOT NULL);
+-- @queries
+-- name: SelectVarcharMax :many
+SELECT v, nn FROM t_varchar_max;
+
+-- name: InsertVarcharMax :exec
+INSERT INTO t_varchar_max (v, nn) VALUES (@v, @nn);
+
+-- name: FilterVarcharMax :many
+SELECT v FROM t_varchar_max WHERE nn = @nn;
diff --git a/end2end/MssqlSyntaxMatrix/cases/types/xml.sql b/end2end/MssqlSyntaxMatrix/cases/types/xml.sql
new file mode 100644
index 000000000..89b362f99
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/types/xml.sql
@@ -0,0 +1,9 @@
+-- @description `XML` column: nullable and NOT NULL, read, written
+-- @schema
+CREATE TABLE t_xml (v XML NULL, nn XML NOT NULL);
+-- @queries
+-- name: SelectXml :many
+SELECT v, nn FROM t_xml;
+
+-- name: InsertXml :exec
+INSERT INTO t_xml (v, nn) VALUES (@v, @nn);
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql b/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql
new file mode 100644
index 000000000..fd8f1fcfa
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/compound_assignment.sql
@@ -0,0 +1,4 @@
+-- @description Compound assignment operators += and *=
+-- @queries
+-- name: AdjustPrice :exec
+UPDATE books SET price += @delta, title += @suffix WHERE author_id = @author_id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql b/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql
new file mode 100644
index 000000000..3dbf81231
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/from_join.sql
@@ -0,0 +1,4 @@
+-- @description UPDATE ... FROM with a JOIN (T-SQL specific)
+-- @queries
+-- name: MarkAuthorsBooksUnavailable :execrows
+UPDATE b SET b.is_available = 0 FROM books b JOIN authors a ON a.id = b.author_id WHERE a.name = @author_name;
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql b/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql
new file mode 100644
index 000000000..9c03d580a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/output_deleted_inserted.sql
@@ -0,0 +1,4 @@
+-- @description UPDATE ... OUTPUT DELETED.x, INSERTED.x
+-- @queries
+-- name: RenameAuthor :one
+UPDATE authors SET name = @name OUTPUT DELETED.name AS old_name, INSERTED.name AS new_name WHERE id = @id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql b/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql
new file mode 100644
index 000000000..3be2e0816
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/set_default.sql
@@ -0,0 +1,4 @@
+-- @description UPDATE ... SET column = DEFAULT and = NULL
+-- @queries
+-- name: ResetBook :exec
+UPDATE books SET is_available = DEFAULT, price = NULL WHERE id = @id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/simple.sql b/end2end/MssqlSyntaxMatrix/cases/update/simple.sql
new file mode 100644
index 000000000..61b637395
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/simple.sql
@@ -0,0 +1,4 @@
+-- @description UPDATE with SET parameters and WHERE
+-- @queries
+-- name: UpdateBio :execrows
+UPDATE authors SET bio = @bio, birth_date = @birth_date WHERE id = @id;
diff --git a/end2end/MssqlSyntaxMatrix/cases/update/top.sql b/end2end/MssqlSyntaxMatrix/cases/update/top.sql
new file mode 100644
index 000000000..896c9401e
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/update/top.sql
@@ -0,0 +1,4 @@
+-- @description UPDATE TOP (@n)
+-- @queries
+-- name: MarkSomeUnavailable :execrows
+UPDATE TOP (@n) books SET is_available = 0 WHERE is_available = 1;
diff --git a/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql b/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql
new file mode 100644
index 000000000..1e3dc3c30
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/window/aggregate_over.sql
@@ -0,0 +1,6 @@
+-- @description Aggregates over windows, including a running total
+-- @queries
+-- name: RunningTotals :many
+SELECT id, SUM(price) OVER (PARTITION BY author_id ORDER BY id ROWS UNBOUNDED PRECEDING) AS running_total,
+ COUNT(*) OVER () AS total_count
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql b/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql
new file mode 100644
index 000000000..76a8c3e02
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/window/lag_lead_first_value.sql
@@ -0,0 +1,9 @@
+-- @description LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame
+-- @queries
+-- name: PriceNeighbours :many
+SELECT id,
+ LAG(price) OVER (ORDER BY published_at) AS previous_price,
+ LEAD(price, 1, 0) OVER (ORDER BY published_at) AS next_price,
+ FIRST_VALUE(title) OVER (PARTITION BY author_id ORDER BY published_at) AS first_title,
+ LAST_VALUE(title) OVER (PARTITION BY author_id ORDER BY published_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_title
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql b/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql
new file mode 100644
index 000000000..0bf2e1fe3
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/window/ranking.sql
@@ -0,0 +1,9 @@
+-- @description ROW_NUMBER, RANK, DENSE_RANK and NTILE
+-- @queries
+-- name: RankedBooks :many
+SELECT id,
+ ROW_NUMBER() OVER (PARTITION BY author_id ORDER BY price DESC) AS rn,
+ RANK() OVER (ORDER BY price DESC) AS rnk,
+ DENSE_RANK() OVER (ORDER BY price DESC) AS dense_rnk,
+ NTILE(4) OVER (ORDER BY price) AS quartile
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql b/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql
new file mode 100644
index 000000000..09932971a
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/window/window_clause_and_percentiles.sql
@@ -0,0 +1,10 @@
+-- @description Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC
+-- @queries
+-- name: NamedWindow :many
+SELECT id, SUM(price) OVER w AS total, AVG(price) OVER w AS average FROM books WINDOW w AS (PARTITION BY author_id);
+
+-- name: Percentiles :many
+SELECT DISTINCT author_id,
+ PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price) OVER (PARTITION BY author_id) AS median,
+ PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY price) OVER (PARTITION BY author_id) AS median_disc
+FROM books;
diff --git a/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql b/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql
new file mode 100644
index 000000000..262ba37e0
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/xml/for_xml.sql
@@ -0,0 +1,7 @@
+-- @description FOR XML PATH, including the string-concatenation idiom
+-- @queries
+-- name: AuthorsAsXml :one
+SELECT id, name FROM authors FOR XML PATH('author'), ROOT('authors');
+
+-- name: ConcatenatedTitles :many
+SELECT a.id, STUFF((SELECT N',' + b.title FROM books b WHERE b.author_id = a.id FOR XML PATH('')), 1, 1, N'') AS titles FROM authors a;
diff --git a/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql b/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql
new file mode 100644
index 000000000..e17ca42e9
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/cases/xml/xml_methods.sql
@@ -0,0 +1,10 @@
+-- @description XML column with the value(), query(), exist() and nodes() methods
+-- @schema
+CREATE TABLE orders_xml (id INT IDENTITY PRIMARY KEY, doc XML NOT NULL);
+-- @queries
+-- name: XmlAccess :many
+SELECT id, doc.value('(/order/@id)[1]', 'INT') AS order_id, doc.query('/order/item') AS items
+FROM orders_xml WHERE doc.exist('/order[@status=sql:variable("@status")]') = 1;
+
+-- name: XmlNodes :many
+SELECT o.id, i.n.value('@sku', 'NVARCHAR(20)') AS sku FROM orders_xml o CROSS APPLY o.doc.nodes('/order/item') AS i(n);
diff --git a/end2end/MssqlSyntaxMatrix/results.json b/end2end/MssqlSyntaxMatrix/results.json
new file mode 100644
index 000000000..ea7aa6807
--- /dev/null
+++ b/end2end/MssqlSyntaxMatrix/results.json
@@ -0,0 +1,11652 @@
+[
+ {
+ "id": "aggregate/basic_aggregates",
+ "description": "COUNT, COUNT_BIG, SUM, AVG, MIN, MAX and their result types",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "BookStats",
+ "level": "Minor",
+ "findings": [
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "BookStats",
+ "level": "Minor",
+ "findings": [
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column cnt_big: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "BookStats": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "cnt int",
+ "cnt_big bigint",
+ "total decimal(38,2)",
+ "average decimal(38,6)",
+ "first_published datetime2(7)",
+ "last_title nvarchar(200)",
+ "avg_id int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "aggregate/group_by_having",
+ "description": "GROUP BY with HAVING on a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ProlificAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ProlificAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: column book_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ProlificAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_books int"
+ ],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "book_count int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "aggregate/rollup_cube_grouping_sets",
+ "description": "ROLLUP, CUBE, GROUPING SETS and GROUPING()",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Rollup",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column is_total has no type",
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column is_total: sqlc nullable, SQL Server NOT NULL"
+ ]
+ },
+ {
+ "name": "Cube",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ },
+ {
+ "name": "GroupingSets",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Rollup",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column is_total has no type",
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column is_total: sqlc nullable, SQL Server NOT NULL"
+ ]
+ },
+ {
+ "name": "Cube",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ },
+ {
+ "name": "GroupingSets",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column author_id: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column is_available: sqlc NOT NULL, SQL Server nullable",
+ "Minor: column cnt: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Rollup": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint",
+ "is_available bit",
+ "total decimal(38,2)",
+ "is_total tinyint NOT NULL"
+ ]
+ },
+ "Cube": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint",
+ "is_available bit",
+ "cnt int"
+ ]
+ },
+ "GroupingSets": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint",
+ "is_available bit",
+ "cnt int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "aggregate/statistical",
+ "description": "STDEV, VAR and APPROX_COUNT_DISTINCT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "PriceStatistics",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column stdev has no type",
+ "Mismatch: column variance has no type",
+ "Mismatch: column approx_authors has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "PriceStatistics",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column stdev has no type",
+ "Mismatch: column variance has no type",
+ "Mismatch: column approx_authors has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "PriceStatistics": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "stdev float",
+ "variance float",
+ "approx_authors bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "aggregate/string_agg",
+ "description": "STRING_AGG with WITHIN GROUP",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "TitlesPerAuthor",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column titles has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "TitlesPerAuthor",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column titles has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "TitlesPerAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "titles nvarchar(4000)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "annotations/copyfrom_identity_table",
+ "description": "\u0060:copyfrom\u0060 into a table with an IDENTITY column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Panic",
+ "level": "Panic",
+ "error": "panic: runtime error: invalid memory address or nil pointer dereference\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.validateCopyfrom({0x?, 0x?})\ngithub.com/sqlc-dev/sqlc/internal/sql/validate.Cmd({0x?, 0x?}, {0x?, 0x?}, {0x?, 0x?})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQueryCore(0x?, 0x?, {0x?, 0x?}, 0x?)\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).parseQuery(0x?, {0x?, 0x?}, 0x?, {{0x?}})\ngithub.com/sqlc-dev/sqlc/internal/compiler.(*Compiler).analyzeStatements.func1(...)",
+ "queries": [
+ {
+ "name": "CopyBooks",
+ "level": "Panic",
+ "findings": [
+ "Panic: sqlc panics: panic: runtime error: invalid memory address or nil pointer dereference"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CopyBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CopyBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint",
+ "@title nvarchar(200)",
+ "@price decimal(10,2)",
+ "@published_at datetime2(7)",
+ "@is_available bit"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "annotations/copyfrom_schema_scoped",
+ "description": "\u0060:copyfrom\u0060 into a table in a user-defined schema",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected ), got message",
+ "queries": [
+ {
+ "name": "CopyEntries",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected ), got message"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected ), got message",
+ "queries": [
+ {
+ "name": "CopyEntries",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected ), got message"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "CopyEntries": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@message nvarchar(200)",
+ "@level tinyint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "annotations/execlastid_output_identity",
+ "description": "\u0060:execlastid\u0060 via OUTPUT INSERTED.id on an IDENTITY column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateBookReturnId",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateBookReturnId",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateBookReturnId": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint",
+ "@title nvarchar(200)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "annotations/execlastid_scope_identity",
+ "description": "\u0060:execlastid\u0060 via SELECT SCOPE_IDENTITY() after the INSERT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthorReturnId",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 0 result columns, SQL Server returns 1 ((unnamed))"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthorReturnId",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 0 result columns, SQL Server returns 1 ((unnamed))"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateAuthorReturnId": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)"
+ ],
+ "columns": [
+ " bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "annotations/execrows",
+ "description": "\u0060:execrows\u0060 on UPDATE and DELETE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DisableAll",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteUnavailable",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DisableAll",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteUnavailable",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DisableAll": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ },
+ "DeleteUnavailable": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "annotations/one_many_exec",
+ "description": "\u0060:one\u0060, \u0060:many\u0060, \u0060:exec\u0060 basics",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "GetAuthor",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteAllAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "GetAuthor",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteAllAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "GetAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)"
+ ]
+ },
+ "ListAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)"
+ ]
+ },
+ "DeleteAllAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "batch/declare_variables",
+ "description": "DECLARE / SET local variables before the final SELECT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "BooksSinceDaysAgo",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "BooksSinceDaysAgo",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "BooksSinceDaysAgo": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@days int"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "batch/select_into_temp_table",
+ "description": "SELECT ... INTO #temp then reading it, in one query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ExpensiveBooksViaTemp",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ExpensiveBooksViaTemp",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ExpensiveBooksViaTemp": {
+ "paramsError": "Invalid object name \u0027#expensive\u0027.\nThe batch could not be analyzed because of compile errors.",
+ "columnsError": "The metadata could not be determined because statement \u0027SELECT id, title FROM #expensive\u0027 uses a temp table. Metadata discovery only supports temp tables when analyzing a single-statement batch.",
+ "params": null,
+ "columns": null
+ }
+ }
+ },
+ {
+ "id": "batch/set_nocount_on",
+ "description": "SET NOCOUNT ON in front of a query (a very common T-SQL habit)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "ListAuthorsNoCount",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "ListAuthorsNoCount",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorsNoCount": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "batch/transaction_in_query",
+ "description": "Explicit BEGIN TRAN / COMMIT inside a query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "MoveBooks",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "MoveBooks",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "MoveBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@to_author_id bigint",
+ "@from_author_id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "batch/try_catch_throw",
+ "description": "TRY ... CATCH with THROW",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "SafeDelete",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "SafeDelete",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "SafeDelete": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "cte/cte_with_dml",
+ "description": "CTEs in front of UPDATE and DELETE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "DiscountTopBooks",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @n: sqlc int, SQL Server bigint"
+ ]
+ },
+ {
+ "name": "DeleteDuplicates",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "DiscountTopBooks",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @n: sqlc int, SQL Server bigint"
+ ]
+ },
+ {
+ "name": "DeleteDuplicates",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DiscountTopBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@n bigint"
+ ],
+ "columns": []
+ },
+ "DeleteDuplicates": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "cte/multiple_ctes",
+ "description": "Several CTEs referencing each other",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorTotals",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column rnk has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorTotals",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column rnk has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorTotals": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100) NOT NULL",
+ "total decimal(38,2)",
+ "rnk bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "cte/recursive_cte",
+ "description": "Recursive CTE with UNION ALL",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "Numbers",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "Numbers",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: with numbers: relation \u0022public\u0022.\u0022numbers\u0022: class \u0022numbers\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "Numbers": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@upto int"
+ ],
+ "columns": [
+ "n int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "cte/simple_cte",
+ "description": "A single common table expression",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CheapBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CheapBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CheapBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@max_price decimal(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/alias_type",
+ "description": "User-defined alias type (CREATE TYPE ... FROM)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListContacts",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column email: sqlc email_address, SQL Server nvarchar(320)"
+ ]
+ },
+ {
+ "name": "GetContactByEmail",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @email: sqlc email_address, SQL Server nvarchar(320)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListContacts",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column email: sqlc email_address, SQL Server nvarchar(320)"
+ ]
+ },
+ {
+ "name": "GetContactByEmail",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @email: sqlc email_address, SQL Server nvarchar(320)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Error",
+ "pluginError": "error generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver\nerror generating code: process: error running command Unhandled exception. System.NotSupportedException: Column email has unsupported column type: email_address in MssqlDriver",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListContacts": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "email nvarchar(320) NOT NULL"
+ ]
+ },
+ "GetContactByEmail": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@email nvarchar(320)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/alter_column_type_and_nullability",
+ "description": "ALTER TABLE ... ALTER COLUMN changes the type and nullability",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListBios",
+ "level": "Minor",
+ "findings": [
+ "Minor: column bio: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListBios",
+ "level": "Minor",
+ "findings": [
+ "Minor: column bio: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListBios": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "bio nvarchar(500) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/alter_table_add_column",
+ "description": "ALTER TABLE ... ADD column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListNicknames",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListNicknames",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListNicknames": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "nickname nvarchar(50)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/alter_table_drop_column",
+ "description": "ALTER TABLE ... DROP COLUMN",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/bracketed_identifiers",
+ "description": "Bracket-quoted table and column names with spaces and reserved words",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOrderDetails",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 3 is named unit price, SQL Server keeps the case: Unit Price"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOrderDetails",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 3 is named unit price, SQL Server keeps the case: Unit Price"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListOrderDetails": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "order id int NOT NULL",
+ "select nvarchar(10)",
+ "Unit Price money NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/case_insensitive_names",
+ "description": "Object names are case-insensitive under the default collation",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsUpper",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named id, SQL Server keeps the case: ID",
+ "Minor: column 2 is named name, SQL Server keeps the case: NAME"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsUpper",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named id, SQL Server keeps the case: ID",
+ "Minor: column 2 is named name, SQL Server keeps the case: NAME"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorsUpper": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@Name nvarchar(100)"
+ ],
+ "columns": [
+ "ID bigint NOT NULL",
+ "NAME nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/check_and_unique_constraints",
+ "description": "CHECK and UNIQUE constraints, column and table level",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListProducts",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListProducts",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListProducts": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "sku varchar(20) NOT NULL",
+ "price decimal(10,2) NOT NULL",
+ "discount decimal(10,2)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/collate_and_sparse",
+ "description": "COLLATE and SPARSE column options",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListTags",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListTags",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListTags": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "code varchar(20) NOT NULL",
+ "note nvarchar(100)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/computed_columns",
+ "description": "Computed columns, plain and PERSISTED",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "schema.sql:1:1: column people.full_name: missing type name",
+ "queries": [
+ {
+ "name": "ListPeople",
+ "level": "Error",
+ "findings": [
+ "Error: schema.sql:1:1: column people.full_name: missing type name"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "schema.sql:1:1: column people.full_name: missing type name",
+ "queries": [
+ {
+ "name": "ListPeople",
+ "level": "Error",
+ "findings": [
+ "Error: schema.sql:1:1: column people.full_name: missing type name"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListPeople": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "first_name nvarchar(50) NOT NULL",
+ "last_name nvarchar(50) NOT NULL",
+ "full_name nvarchar(101) NOT NULL",
+ "name_length int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/drop_table_if_exists",
+ "description": "DROP TABLE IF EXISTS followed by a re-create with a different shape",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListScratch",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListScratch",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListScratch": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "label nvarchar(10) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/foreign_key_on_delete",
+ "description": "Table-level FOREIGN KEY with ON DELETE CASCADE / SET NULL",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListReviews",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListReviews",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListReviews": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "book_id int NOT NULL",
+ "reviewer_id bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/go_batch_separators",
+ "description": "Schema split into several batches with GO",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListRegions",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListRegions",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListRegions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "name nvarchar(50) NOT NULL",
+ "country nvarchar(50) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/identity_seed_increment",
+ "description": "IDENTITY(seed, increment) column on a non-default seed",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListInvoices",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListInvoices",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListInvoices": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "number nvarchar(20) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/indexes",
+ "description": "CREATE INDEX statements (filtered, INCLUDE, unique nonclustered) are accepted and ignored",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAvailableBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAvailableBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAvailableBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/named_default_constraints",
+ "description": "Named DEFAULT constraints with GETDATE() and NEWID()",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListEvents",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListEvents",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListEvents": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "created_at datetime2(7) NOT NULL",
+ "correlation_id uniqueidentifier NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/rowversion",
+ "description": "ROWVERSION column used for optimistic concurrency",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "GetDocument",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "UpdateDocument",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "GetDocument",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "UpdateDocument",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "GetDocument": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id int"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "body nvarchar(max) NOT NULL",
+ "version timestamp NOT NULL"
+ ]
+ },
+ "UpdateDocument": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@body nvarchar(max)",
+ "@id int",
+ "@version timestamp"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "ddl/same_table_name_two_schemas",
+ "description": "Two tables with the same name in different schemas",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuditAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuditAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuditAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "changed_at datetime2(7) NOT NULL"
+ ]
+ },
+ "ListAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/schema_scoped_table",
+ "description": "Table in a user-defined schema (CREATE SCHEMA in its own batch)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOrders",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListOrdersWithAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOrders",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListOrdersWithAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListOrders": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "author_id bigint NOT NULL",
+ "total money NOT NULL"
+ ]
+ },
+ "ListOrdersWithAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/sequence_default",
+ "description": "SEQUENCE used as a column default and with NEXT VALUE FOR in a query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListTickets",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "NextTicketId",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column next_id has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListTickets",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "NextTicketId",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column next_id has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListTickets": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "title nvarchar(100) NOT NULL"
+ ]
+ },
+ "NextTicketId": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "next_id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/sp_rename_column",
+ "description": "Column renamed with sp_rename (how T-SQL renames columns)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022biography\u0022",
+ "queries": [
+ {
+ "name": "ListBiographies",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022biography\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022biography\u0022",
+ "queries": [
+ {
+ "name": "ListBiographies",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022biography\u0022"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListBiographies": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "biography nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/synonym",
+ "description": "CREATE SYNONYM for a table",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "ListWriters",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "ListWriters",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: relation \u0022public\u0022.\u0022writers\u0022: class \u0022writers\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListWriters": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/table_level_primary_key",
+ "description": "Columns in a table-level (composite) PRIMARY KEY are implicitly NOT NULL",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListMemberships",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListMemberships",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListMemberships": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "group_id int NOT NULL",
+ "user_id int NOT NULL",
+ "role nvarchar(20)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/temporal_table",
+ "description": "System-versioned temporal table and FOR SYSTEM_TIME AS OF",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListPricesAsOf",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @as_of (datetime2(7)) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListPricesAsOf",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @as_of (datetime2(7)) is missing"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListPricesAsOf": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@as_of datetime2(7)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "amount decimal(10,2) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "ddl/view",
+ "description": "Querying a VIEW",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "ListAuthorBooks",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set",
+ "queries": [
+ {
+ "name": "ListAuthorBooks",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: relation \u0022public\u0022.\u0022author_books\u0022: class \u0022author_books\u0022: sql: no rows in result set"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_name nvarchar(100)"
+ ],
+ "columns": [
+ "author_name nvarchar(100) NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "delete/from_join",
+ "description": "DELETE ... FROM with a JOIN",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteBooksByAuthorName",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteBooksByAuthorName",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DeleteBooksByAuthorName": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_name nvarchar(100)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "delete/output_deleted",
+ "description": "DELETE ... OUTPUT DELETED.*",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteBooksOfAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteBooksOfAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DeleteBooksOfAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "author_id bigint NOT NULL",
+ "title nvarchar(200) NOT NULL",
+ "price decimal(10,2)",
+ "published_at datetime2(7)",
+ "is_available bit NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "delete/simple",
+ "description": "DELETE with a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteAuthor",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteAuthorNoFrom",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteAuthor",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DeleteAuthorNoFrom",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DeleteAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": []
+ },
+ "DeleteAuthorNoFrom": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "delete/top",
+ "description": "DELETE TOP (@n)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteSomeBooks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @n (bigint) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "DeleteSomeBooks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @n (bigint) is missing"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DeleteSomeBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@n bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "delete/truncate",
+ "description": "TRUNCATE TABLE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "TruncateBooksArchive",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "TruncateBooksArchive",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "TruncateBooksArchive": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "expressions/arithmetic",
+ "description": "Arithmetic operators, integer division, modulo and type promotion",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Arithmetic",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column doubled: sqlc bigint, SQL Server decimal(12,2)",
+ "Minor: column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Arithmetic",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column doubled: sqlc bigint, SQL Server decimal(12,2)",
+ "Minor: column half_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column remainder: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column negated: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Arithmetic": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@delta numeric(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "doubled decimal(12,2)",
+ "half_id int",
+ "remainder int",
+ "adjusted numeric(38,19)",
+ "negated int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/at_time_zone",
+ "description": "AT TIME ZONE and SWITCHOFFSET",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "InTimeZone",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @time_zone (nvarchar(4000)) is missing",
+ "Mismatch: column local_time has no type",
+ "Mismatch: column switched has no type",
+ "Minor: column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "InTimeZone",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @time_zone (nvarchar(4000)) is missing",
+ "Mismatch: column local_time has no type",
+ "Mismatch: column switched has no type",
+ "Minor: column local_time: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "InTimeZone": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@time_zone nvarchar(4000)"
+ ],
+ "columns": [
+ "local_time datetimeoffset(7)",
+ "switched datetimeoffset(7)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/bitwise",
+ "description": "Bitwise operators and bit columns",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Bitwise",
+ "level": "Minor",
+ "findings": [
+ "Minor: column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Bitwise",
+ "level": "Minor",
+ "findings": [
+ "Minor: column masked: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Bitwise": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@mask int"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "low_bit int NOT NULL",
+ "masked int",
+ "xored int NOT NULL",
+ "not_available bit NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/cast_convert",
+ "description": "CAST, CONVERT (with a style), TRY_CAST, TRY_CONVERT, PARSE and TRY_PARSE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Conversions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @text (nvarchar(4000)) is missing",
+ "Mismatch: column parsed has no type",
+ "Mismatch: column maybe_date has no type",
+ "Minor: column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Conversions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @text (nvarchar(4000)) is missing",
+ "Mismatch: column parsed has no type",
+ "Mismatch: column maybe_date has no type",
+ "Minor: column id_text: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column title_number: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column amount: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column parsed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column maybe_date: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Conversions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@amount decimal(10,2)",
+ "@text nvarchar(4000)"
+ ],
+ "columns": [
+ "id_text nvarchar(20)",
+ "iso_date varchar(10)",
+ "title_number int",
+ "amount decimal(10,2)",
+ "parsed date",
+ "maybe_date datetime2(7)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/coalesce_isnull_nullif",
+ "description": "COALESCE, ISNULL and NULLIF: result types, nullability, and parameter inference",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Fallbacks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column bio_or_empty has no type",
+ "Mismatch: column name_or_null has no type",
+ "Minor: column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column bio_or_empty: sqlc nullable, SQL Server NOT NULL"
+ ]
+ },
+ {
+ "name": "ByPattern",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @pattern has no type (SQL Server cannot deduce it either)"
+ ]
+ },
+ {
+ "name": "ByPatternIsNull",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @pattern has no type (SQL Server: nvarchar(4000))"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Fallbacks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column bio_or_empty has no type",
+ "Mismatch: column name_or_null has no type",
+ "Minor: column bio_or_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column bio_or_empty: sqlc nullable, SQL Server NOT NULL"
+ ]
+ },
+ {
+ "name": "ByPattern",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @pattern has no type (SQL Server cannot deduce it either)"
+ ]
+ },
+ {
+ "name": "ByPatternIsNull",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @pattern has no type (SQL Server: nvarchar(4000))"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Fallbacks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "bio_or_name nvarchar(max)",
+ "bio_or_empty nvarchar(max) NOT NULL",
+ "name_or_null nvarchar(100)"
+ ]
+ },
+ "ByPattern": {
+ "paramsError": "The type for parameter \u0027@pattern\u0027 cannot be deduced in this context.",
+ "columnsError": null,
+ "params": null,
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ },
+ "ByPatternIsNull": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@pattern nvarchar(4000)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/concat_format",
+ "description": "CONCAT, CONCAT_WS, FORMAT, QUOTENAME, TRANSLATE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Formatting",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column label has no type",
+ "Mismatch: column joined has no type",
+ "Mismatch: column formatted has no type",
+ "Mismatch: column quoted has no type",
+ "Mismatch: column translated has no type",
+ "Minor: column label: sqlc nullable, SQL Server NOT NULL",
+ "Minor: column joined: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Formatting",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column label has no type",
+ "Mismatch: column joined has no type",
+ "Mismatch: column formatted has no type",
+ "Mismatch: column quoted has no type",
+ "Mismatch: column translated has no type",
+ "Minor: column label: sqlc nullable, SQL Server NOT NULL",
+ "Minor: column joined: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Formatting": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "label nvarchar(127) NOT NULL",
+ "joined nvarchar(max) NOT NULL",
+ "formatted nvarchar(4000)",
+ "quoted nvarchar(258)",
+ "translated nvarchar(4000)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/datetime_functions",
+ "description": "GETDATE, SYSDATETIME, SYSDATETIMEOFFSET, DATEADD, DATEDIFF, DATEDIFF_BIG, DATEPART, DATENAME, EOMONTH, DATEFROMPARTS, DATETRUNC",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022day\u0022",
+ "queries": [
+ {
+ "name": "DateFunctions",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022day\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022day\u0022",
+ "queries": [
+ {
+ "name": "DateFunctions",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022day\u0022"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "DateFunctions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@days int",
+ "@until datetimeoffset(7)"
+ ],
+ "columns": [
+ "now_local datetime NOT NULL",
+ "now_utc datetime NOT NULL",
+ "precise datetime2(7) NOT NULL",
+ "with_offset datetimeoffset(7) NOT NULL",
+ "shifted datetime2(7)",
+ "days_between int",
+ "seconds_since bigint",
+ "year_part int",
+ "month_name nvarchar(30)",
+ "y int",
+ "month_end date",
+ "fixed_date date NOT NULL",
+ "month_start datetime2(7)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/greatest_least",
+ "description": "GREATEST and LEAST (SQL Server 2022)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Clamp",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @floor has no type (SQL Server: numeric(38,19))",
+ "Mismatch: parameter @ceiling has no type (SQL Server: numeric(38,19))",
+ "Mismatch: column at_least has no type",
+ "Mismatch: column at_most has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Clamp",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @floor has no type (SQL Server: numeric(38,19))",
+ "Mismatch: parameter @ceiling has no type (SQL Server: numeric(38,19))",
+ "Mismatch: column at_least has no type",
+ "Mismatch: column at_most has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Clamp": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@floor numeric(38,19)",
+ "@ceiling numeric(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "at_least decimal(38,19)",
+ "at_most decimal(38,19)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/is_distinct_from",
+ "description": "IS [NOT] DISTINCT FROM (SQL Server 2022)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ChangedBios",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @bio (nvarchar(max)) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ChangedBios",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @bio (nvarchar(max)) is missing"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ChangedBios": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@bio nvarchar(max)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/math_functions",
+ "description": "ABS, ROUND, CEILING, FLOOR, POWER, SQRT, SIGN, PI, RAND",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "MathFunctions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column abs_price has no type",
+ "Mismatch: column rounded has no type",
+ "Mismatch: column ceil has no type",
+ "Mismatch: column floor_price has no type",
+ "Mismatch: column squared has no type",
+ "Mismatch: column root has no type",
+ "Mismatch: column sign has no type",
+ "Mismatch: column pi has no type",
+ "Mismatch: column random has no type",
+ "Minor: column pi: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "MathFunctions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column abs_price has no type",
+ "Mismatch: column rounded has no type",
+ "Mismatch: column ceil has no type",
+ "Mismatch: column floor_price has no type",
+ "Mismatch: column squared has no type",
+ "Mismatch: column root has no type",
+ "Mismatch: column sign has no type",
+ "Mismatch: column pi has no type",
+ "Mismatch: column random has no type",
+ "Minor: column pi: sqlc nullable, SQL Server NOT NULL"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "MathFunctions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "abs_price decimal(10,2)",
+ "rounded decimal(10,2)",
+ "ceil decimal(10,0)",
+ "floor_price decimal(10,0)",
+ "squared int",
+ "root float",
+ "sign decimal(10,2)",
+ "pi float NOT NULL",
+ "random float"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/sql2022_functions",
+ "description": "GENERATE_SERIES, DATE_BUCKET, TRIM ... FROM and STRING_ESCAPE (SQL Server 2022)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022",
+ "queries": [
+ {
+ "name": "Series",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022"
+ ]
+ },
+ {
+ "name": "NewFunctions",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022",
+ "queries": [
+ {
+ "name": "Series",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022"
+ ]
+ },
+ {
+ "name": "NewFunctions",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: unknown column \u0022week\u0022"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "Series": {
+ "paramsError": "Argument data type void type is invalid for argument 2 of generate_series function.\nOperand type clash: int is incompatible with void type\nThe batch could not be analyzed because of compile errors.",
+ "columnsError": "Argument data type void type is invalid for argument 2 of generate_series function.\nOperand type clash: int is incompatible with void type\nThe batch could not be analyzed because of compile errors.",
+ "params": null,
+ "columns": null
+ },
+ "NewFunctions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "week_bucket datetime2(7)",
+ "trimmed nvarchar(200)",
+ "escaped nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/string_functions",
+ "description": "LEN, DATALENGTH, SUBSTRING, LEFT, RIGHT, UPPER, LOWER, TRIM, REPLACE, CHARINDEX, PATINDEX, REVERSE, REPLICATE, STUFF",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "StringFunctions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column prefix has no type",
+ "Mismatch: column left2 has no type",
+ "Mismatch: column right2 has no type",
+ "Mismatch: column replaced has no type",
+ "Mismatch: column pos has no type",
+ "Mismatch: column digit_pos has no type",
+ "Mismatch: column reversed has no type",
+ "Mismatch: column stars has no type",
+ "Mismatch: column stuffed has no type",
+ "Minor: column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "StringFunctions",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column prefix has no type",
+ "Mismatch: column left2 has no type",
+ "Mismatch: column right2 has no type",
+ "Mismatch: column replaced has no type",
+ "Mismatch: column pos has no type",
+ "Mismatch: column digit_pos has no type",
+ "Mismatch: column reversed has no type",
+ "Mismatch: column stars has no type",
+ "Mismatch: column stuffed has no type",
+ "Minor: column len: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column bytes: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column left2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column right2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column upper_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column lower_name: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column trimmed: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column trimmed2: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "StringFunctions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "len int",
+ "bytes int",
+ "prefix nvarchar(3)",
+ "left2 nvarchar(2)",
+ "right2 nvarchar(2)",
+ "upper_name nvarchar(100)",
+ "lower_name nvarchar(100)",
+ "trimmed nvarchar(100)",
+ "trimmed2 nvarchar(100)",
+ "replaced nvarchar(4000)",
+ "pos int",
+ "digit_pos int",
+ "reversed nvarchar(100)",
+ "stars nvarchar(3)",
+ "stuffed nvarchar(100)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "expressions/system_functions",
+ "description": "NEWID, @@ROWCOUNT, SCOPE_IDENTITY, IDENT_CURRENT, DB_NAME, SUSER_SNAME, HASHBYTES, CHECKSUM",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "SystemValues",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @payload has no type (SQL Server: nvarchar(4000))",
+ "Mismatch: column row_count has no type",
+ "Mismatch: column current_id has no type",
+ "Mismatch: column db has no type",
+ "Mismatch: column login has no type",
+ "Mismatch: column hash has no type",
+ "Mismatch: column checksum has no type",
+ "Minor: column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "SystemValues",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @payload has no type (SQL Server: nvarchar(4000))",
+ "Mismatch: column row_count has no type",
+ "Mismatch: column current_id has no type",
+ "Mismatch: column db has no type",
+ "Mismatch: column login has no type",
+ "Mismatch: column hash has no type",
+ "Mismatch: column checksum has no type",
+ "Minor: column id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)",
+ "Minor: column scope_id: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SystemValues": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@payload nvarchar(4000)"
+ ],
+ "columns": [
+ "id uniqueidentifier",
+ "row_count int NOT NULL",
+ "scope_id numeric(38,0)",
+ "current_id numeric(38,0)",
+ "db nvarchar(128)",
+ "login nvarchar(128)",
+ "hash varbinary(8000)",
+ "checksum int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "hints/query_hints",
+ "description": "OPTION (RECOMPILE), OPTION (MAXDOP 1), OPTION (OPTIMIZE FOR UNKNOWN)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "Recompiled",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "SingleThreaded",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "Recompiled",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "SingleThreaded",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Recompiled": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ },
+ "SingleThreaded": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_price decimal(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "hints/table_hints",
+ "description": "Table hints: NOLOCK, UPDLOCK, ROWLOCK, READPAST, FORCESEEK",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ReadUncommitted",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "LockForUpdate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DequeueLike",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ReadUncommitted",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "LockForUpdate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DequeueLike",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ReadUncommitted": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "LockForUpdate": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ },
+ "DequeueLike": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/at_sign_parameter_names",
+ "description": "Parameter names with digits, underscores and mixed case",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "OddParams",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "OddParams",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "OddParams": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@Id1 bigint",
+ "@first_Name nvarchar(100)",
+ "@_bio nvarchar(max)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/duplicate_result_names",
+ "description": "Two result columns with the same name",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DuplicateIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "DuplicateIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "DuplicateIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/mixed_case_names",
+ "description": "Mixed-case table and column names map to PascalCase",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListMixed",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named customerid, SQL Server keeps the case: CustomerID",
+ "Minor: column 2 is named firstname, SQL Server keeps the case: firstName",
+ "Minor: column 3 is named last_name, SQL Server keeps the case: LAST_NAME"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListMixed",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named customerid, SQL Server keeps the case: CustomerID",
+ "Minor: column 2 is named firstname, SQL Server keeps the case: firstName",
+ "Minor: column 3 is named last_name, SQL Server keeps the case: LAST_NAME"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListMixed": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@CustomerID int"
+ ],
+ "columns": [
+ "CustomerID int NOT NULL",
+ "firstName nvarchar(50) NOT NULL",
+ "LAST_NAME nvarchar(50) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/non_identifier_column_names",
+ "description": "Column names that are not valid C# identifiers: leading digits, spaces, dashes, Unicode",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOdd",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 4 is named total $, SQL Server keeps the case: Total $"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListOdd",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 4 is named total $, SQL Server keeps the case: Total $"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListOdd": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "1st place int",
+ "e-mail nvarchar(50)",
+ "na\u00EFve bit",
+ "Total $ money"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/reserved_word_columns",
+ "description": "Columns named after T-SQL and C# keywords",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListKeywords",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListKeywords",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListKeywords": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@user nvarchar(20)"
+ ],
+ "columns": [
+ "user nvarchar(20) NOT NULL",
+ "order int NOT NULL",
+ "key int",
+ "class int",
+ "string nvarchar(10)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "identifiers/unnamed_expression_column",
+ "description": "A result column without a name",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Unnamed",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 has no name in SQL Server (sqlc calls it count)",
+ "Minor: column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Unnamed",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 has no name in SQL Server (sqlc calls it count)",
+ "Minor: column count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Unnamed": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ " int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "insert/default_values",
+ "description": "INSERT ... DEFAULT VALUES and explicit DEFAULT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateEmptyEvent",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CreateEventWithDefault",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateEmptyEvent",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CreateEventWithDefault",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateEmptyEvent": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ },
+ "CreateEventWithDefault": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/identity_insert",
+ "description": "Explicit values for an IDENTITY column with SET IDENTITY_INSERT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "CreateAuthorWithId",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "CreateAuthorWithId",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "CreateAuthorWithId": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint",
+ "@name nvarchar(100)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/insert_exec",
+ "description": "INSERT ... EXEC from a stored procedure",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ArchiveAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ArchiveAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ArchiveAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/insert_select",
+ "description": "INSERT ... SELECT with a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "CopyBooksToAuthor",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @target_author_id has no type (SQL Server: bigint)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "CopyBooksToAuthor",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @target_author_id has no type (SQL Server: bigint)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CopyBooksToAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@target_author_id bigint",
+ "@source_author_id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/multi_row_values",
+ "description": "INSERT with several VALUES rows",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateTwoAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateTwoAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateTwoAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@first_name nvarchar(100)",
+ "@first_bio nvarchar(max)",
+ "@second_name nvarchar(100)",
+ "@second_bio nvarchar(max)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/output_inserted",
+ "description": "INSERT ... OUTPUT INSERTED.* / INSERTED.col returning rows",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthorReturningAll",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CreateAuthorReturningSome",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthorReturningAll",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CreateAuthorReturningSome",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateAuthorReturningAll": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)",
+ "@bio nvarchar(max)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date"
+ ]
+ },
+ "CreateAuthorReturningSome": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "insert/output_into_table_variable",
+ "description": "OUTPUT ... INTO a table variable inside a multi-statement query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "CreateAuthorOutputInto",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "CreateAuthorOutputInto",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "CreateAuthorOutputInto": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)"
+ ],
+ "columns": [
+ "id bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "insert/schema_scoped",
+ "description": "INSERT into a table in a user-defined schema",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected ), got message",
+ "queries": [
+ {
+ "name": "CreateLog",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected ), got message"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected ), got message",
+ "queries": [
+ {
+ "name": "CreateLog",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected ), got message"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "CreateLog": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@message nvarchar(200)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "insert/values",
+ "description": "INSERT ... VALUES with parameters for every column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CreateAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CreateAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)",
+ "@bio nvarchar(max)",
+ "@birth_date date"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "joins/cross_apply",
+ "description": "CROSS APPLY and OUTER APPLY with a correlated TOP subquery",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "LatestBookPerAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "LatestBookPerAuthorOuter",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "LatestBookPerAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "LatestBookPerAuthorOuter",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:6:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "LatestBookPerAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ },
+ "LatestBookPerAuthorOuter": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "title nvarchar(200)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "joins/cross_join",
+ "description": "CROSS JOIN and the comma join",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CrossJoin",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CommaJoin",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CrossJoin",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "CommaJoin",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CrossJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "book_id int NOT NULL"
+ ]
+ },
+ "CommaJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "book_id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "joins/inner_left_right_full",
+ "description": "INNER, LEFT, RIGHT and FULL joins and the nullability they introduce",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "InnerJoin",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "LeftJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column title: sqlc NOT NULL, SQL Server nullable"
+ ]
+ },
+ {
+ "name": "RightJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column name: sqlc NOT NULL, SQL Server nullable"
+ ]
+ },
+ {
+ "name": "FullJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column name: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column title: sqlc NOT NULL, SQL Server nullable"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "InnerJoin",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "LeftJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column title: sqlc NOT NULL, SQL Server nullable"
+ ]
+ },
+ {
+ "name": "RightJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column name: sqlc NOT NULL, SQL Server nullable"
+ ]
+ },
+ {
+ "name": "FullJoin",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column name: sqlc NOT NULL, SQL Server nullable",
+ "Mismatch: column title: sqlc NOT NULL, SQL Server nullable"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "InnerJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100) NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ },
+ "LeftJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100) NOT NULL",
+ "title nvarchar(200)",
+ "price decimal(10,2)"
+ ]
+ },
+ "RightJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100)",
+ "title nvarchar(200) NOT NULL"
+ ]
+ },
+ "FullJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100)",
+ "title nvarchar(200)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "joins/join_derived_table",
+ "description": "Join to an aggregated derived table",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsWithTotals",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsWithTotals",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorsWithTotals": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100) NOT NULL",
+ "total decimal(38,2)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "joins/schema_qualified_join",
+ "description": "Join across dbo-qualified and bracket-quoted names",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "QualifiedJoin",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "QualifiedJoin",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "QualifiedJoin": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(100) NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "joins/self_join",
+ "description": "Self join with aliases",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SameNameAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SameNameAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SameNameAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "first_id bigint NOT NULL",
+ "second_id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "json/for_json",
+ "description": "FOR JSON PATH / AUTO producing a single JSON column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsAsJson",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ },
+ {
+ "name": "AuthorAsJsonObject",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsAsJson",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ },
+ {
+ "name": "AuthorAsJsonObject",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (JSON_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorsAsJson": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "JSON_F52E2B61-18A1-11d1-B105-00805F49916B nvarchar(max)"
+ ]
+ },
+ "AuthorAsJsonObject": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": [
+ "JSON_F52E2B61-18A1-11d1-B105-00805F49916B nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "json/json_object_array",
+ "description": "JSON_OBJECT and JSON_ARRAY constructors (SQL Server 2022)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorJson",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column obj has no type",
+ "Mismatch: column arr has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorJson",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column obj has no type",
+ "Mismatch: column arr has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorJson": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "obj nvarchar(max)",
+ "arr nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "json/json_value_query",
+ "description": "JSON_VALUE, JSON_QUERY, ISJSON and JSON_MODIFY on an NVARCHAR(MAX) column",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "JsonAccess",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @new_name has no type (SQL Server: float)",
+ "Mismatch: parameter @kind has no type (SQL Server: nvarchar(4000))",
+ "Mismatch: column name has no type",
+ "Mismatch: column tags has no type",
+ "Mismatch: column is_json has no type",
+ "Mismatch: column renamed has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "JsonAccess",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @new_name has no type (SQL Server: float)",
+ "Mismatch: parameter @kind has no type (SQL Server: nvarchar(4000))",
+ "Mismatch: column name has no type",
+ "Mismatch: column tags has no type",
+ "Mismatch: column is_json has no type",
+ "Mismatch: column renamed has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "JsonAccess": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@new_name float",
+ "@kind nvarchar(4000)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "name nvarchar(4000)",
+ "tags nvarchar(max)",
+ "is_json int",
+ "renamed nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "json/openjson",
+ "description": "OPENJSON with an explicit WITH schema, fed by a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "ParseItems",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "AuthorsByJsonIds",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "ParseItems",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "AuthorsByJsonIds",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:5:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ParseItems": {
+ "paramsError": "The parameter type for \u0027@items\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.",
+ "columnsError": null,
+ "params": null,
+ "columns": [
+ "item_id int",
+ "quantity int"
+ ]
+ },
+ "AuthorsByJsonIds": {
+ "paramsError": "The parameter type for \u0027@ids\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.",
+ "columnsError": null,
+ "params": null,
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/arg_forms",
+ "description": "sqlc.arg with a quoted name, a bare identifier and a double-quoted name",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: where: unknown column \u0022min_id\u0022",
+ "queries": [
+ {
+ "name": "ArgForms",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: where: unknown column \u0022min_id\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ArgForms",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ArgForms": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_name nvarchar(100)",
+ "@min_id bigint",
+ "@bio_text nvarchar(max)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/arg_in_cast",
+ "description": "sqlc.arg inside CAST, which fixes its type",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "CastArg",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @min_price (decimal(10,2)) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "CastArg",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CastArg": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_price decimal(10,2)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/args_in_top_offset",
+ "description": "sqlc.arg in TOP and in OFFSET / FETCH",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopArg",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @take (bigint) is missing"
+ ]
+ },
+ {
+ "name": "PageArgs",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @skip (bigint) is missing",
+ "Mismatch: parameter @take (bigint) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopArg",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ },
+ {
+ "name": "PageArgs",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @skip: sqlc int, SQL Server bigint",
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "TopArg": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@take bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "PageArgs": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@skip bigint",
+ "@take bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/embed",
+ "description": "sqlc.embed of a table, of aliases (self join) and next to a t.* star",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022",
+ "queries": [
+ {
+ "name": "AuthorsWithBooks",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022"
+ ]
+ },
+ {
+ "name": "DuplicateAuthors",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022"
+ ]
+ },
+ {
+ "name": "BooksWithAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022authors\u0022\nquery.sql:5:1: unknown column \u0022a1\u0022\nquery.sql:8:1: unknown column \u0022authors\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsWithBooks",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "DuplicateAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "BooksWithAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorsWithBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date",
+ "id int NOT NULL",
+ "author_id bigint NOT NULL",
+ "title nvarchar(200) NOT NULL",
+ "price decimal(10,2)",
+ "published_at datetime2(7)",
+ "is_available bit NOT NULL"
+ ]
+ },
+ "DuplicateAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date",
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date"
+ ]
+ },
+ "BooksWithAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@title nvarchar(200)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "author_id bigint NOT NULL",
+ "title nvarchar(200) NOT NULL",
+ "price decimal(10,2)",
+ "published_at datetime2(7)",
+ "is_available bit NOT NULL",
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/embed_schema_scoped",
+ "description": "sqlc.embed of a table in a user-defined schema",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unknown column \u0022orders\u0022",
+ "queries": [
+ {
+ "name": "OrdersWithAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unknown column \u0022orders\u0022"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "OrdersWithAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "OrdersWithAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "author_id bigint NOT NULL",
+ "total money NOT NULL",
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/mixed_with_at_params",
+ "description": "sqlc.arg mixed with plain @params, sharing a name",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "Mixed",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "Mixed",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Mixed": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)",
+ "@bio nvarchar(max)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/narg",
+ "description": "sqlc.narg makes a parameter nullable",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "NargFilter",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @name (nvarchar(100)) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "NargFilter",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "NargFilter": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "macros/slice",
+ "description": "sqlc.slice for integers, strings, NOT IN and several slices in one query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByIds",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @ids (bigint) is missing"
+ ]
+ },
+ {
+ "name": "ByNames",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @names (nvarchar(100)) is missing"
+ ]
+ },
+ {
+ "name": "NotByIds",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @excluded (bigint) is missing"
+ ]
+ },
+ {
+ "name": "ByIdsAndTitles",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @author_ids (bigint) is missing",
+ "Mismatch: parameter @titles (nvarchar(200)) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByIds",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ByNames",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "NotByIds",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ByIdsAndTitles",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ByIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@ids bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ },
+ "ByNames": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@names nvarchar(100)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "NotByIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@excluded bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "ByIdsAndTitles": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_ids bigint",
+ "@titles nvarchar(200)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "merge/merge_delete",
+ "description": "MERGE with WHEN NOT MATCHED BY SOURCE THEN DELETE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "SyncTags",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "SyncTags",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "SyncTags": {
+ "paramsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.",
+ "columnsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.",
+ "params": null,
+ "columns": null
+ }
+ }
+ },
+ {
+ "id": "merge/merge_upsert",
+ "description": "MERGE as an upsert, with OUTPUT $action",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected identifier for schema object name",
+ "queries": [
+ {
+ "name": "UpsertAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected identifier for schema object name"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: expected identifier for schema object name",
+ "queries": [
+ {
+ "name": "UpsertAuthor",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: expected identifier for schema object name"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "UpsertAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)",
+ "@bio nvarchar(max)"
+ ],
+ "columns": [
+ "action nvarchar(10) NOT NULL",
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "merge/upsert_without_merge",
+ "description": "The UPDATE-then-INSERT upsert pattern with @@ROWCOUNT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UpsertBio",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UpsertBio",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "UpsertBio": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@bio nvarchar(max)",
+ "@name nvarchar(100)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "programmability/exec_procedure",
+ "description": "Calling a stored procedure with EXEC and named arguments",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "GetAuthorViaProc",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Dropped",
+ "error": "error parsing queries: no queries contained in paths query.sql",
+ "queries": [
+ {
+ "name": "GetAuthorViaProc",
+ "level": "Dropped",
+ "findings": [
+ "Dropped: every statement is silently dropped (sqlc: no queries contained in paths)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "GetAuthorViaProc": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "programmability/inline_table_function",
+ "description": "An inline table-valued function in FROM",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "BooksOf",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "BooksOf",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "BooksOf": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "programmability/multi_statement_table_function",
+ "description": "A multi-statement table-valued function in FROM",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "Range",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "Range",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "Range": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@upto int"
+ ],
+ "columns": [
+ "n int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "programmability/scalar_function",
+ "description": "A user-defined scalar function in the select list and WHERE clause",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListWithInitials",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @initials has no type (SQL Server: nvarchar(10))",
+ "Mismatch: column initials has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListWithInitials",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @initials has no type (SQL Server: nvarchar(10))",
+ "Mismatch: column initials has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListWithInitials": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@initials nvarchar(10)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "initials nvarchar(10)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "programmability/string_split",
+ "description": "STRING_SPLIT as a table-valued function fed by a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: where: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AuthorsByNameList",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: where: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AuthorsByNameList",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: where: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "AuthorsByNameList": {
+ "paramsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.",
+ "columnsError": "Argument data type void type is invalid for argument 1 of string_split function.\nThe batch could not be analyzed because of compile errors.",
+ "params": null,
+ "columns": null
+ }
+ }
+ },
+ {
+ "id": "programmability/table_valued_parameter",
+ "description": "A table-valued parameter (user-defined table type) used in a join",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AuthorsByIdList",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AuthorsByIdList",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "AuthorsByIdList": {
+ "paramsError": "Must declare the table variable \u0022@ids\u0022.\nThe batch could not be analyzed because of compile errors.",
+ "columnsError": "Must declare the table variable \u0022@ids\u0022.\nThe batch could not be analyzed because of compile errors.",
+ "params": null,
+ "columns": null
+ }
+ }
+ },
+ {
+ "id": "select/alias_equals_syntax",
+ "description": "T-SQL column alias written as alias = expression",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorNames",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorNames",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorNames": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "author_name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/between_params",
+ "description": "BETWEEN with two parameters",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListBooksInPriceRange",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListBooksInPriceRange",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListBooksInPriceRange": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_price decimal(38,19)",
+ "@max_price decimal(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "price decimal(10,2)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/case_expression",
+ "description": "Searched and simple CASE expressions",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListPriceBands",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column band: sqlc varchar, SQL Server nvarchar(9)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListPriceBands",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column band: sqlc varchar, SQL Server nvarchar(9)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListPriceBands": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "band nvarchar(9) NOT NULL",
+ "available varchar(3) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/comments",
+ "description": "Block comments, nested block comments and trailing line comments inside a query",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unexpected token: *",
+ "queries": [
+ {
+ "name": "Commented",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: *"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unexpected token: *",
+ "queries": [
+ {
+ "name": "Commented",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: *"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "Commented": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_id bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/derived_table",
+ "description": "Derived table (subquery in FROM) with column aliases",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListExpensive",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListExpensive",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListExpensive": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min decimal(38,19)"
+ ],
+ "columns": [
+ "book_id int NOT NULL",
+ "price decimal(10,2)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/distinct",
+ "description": "SELECT DISTINCT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListDistinctAuthorIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListDistinctAuthorIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListDistinctAuthorIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/exists",
+ "description": "EXISTS / NOT EXISTS with a parameter inside the subquery",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsWithBooksAbove",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthorsWithoutBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsWithBooksAbove",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthorsWithoutBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorsWithBooksAbove": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@min_price decimal(38,19)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "ListAuthorsWithoutBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/function_on_parameter",
+ "description": "Parameters wrapped in functions and compared to columns",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "CaseInsensitiveMatch",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @name: sqlc varchar, SQL Server nvarchar(4000)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "CaseInsensitiveMatch",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @name: sqlc varchar, SQL Server nvarchar(4000)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "CaseInsensitiveMatch": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(4000)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/iif_choose",
+ "description": "IIF and CHOOSE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListFlags",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column second has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListFlags",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column second has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListFlags": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@threshold decimal(38,19)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "is_expensive int NOT NULL",
+ "second varchar(1)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/in_list_params",
+ "description": "IN with a list of scalar parameters",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByThreeIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByThreeIds",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ByThreeIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@first_id bigint",
+ "@second_id bigint",
+ "@third_id bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/in_subquery_param",
+ "description": "A parameter inside IN (subquery)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsOfTitle",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsOfTitle",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorsOfTitle": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@title nvarchar(200)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/like_escape",
+ "description": "LIKE with a parameter and an ESCAPE clause",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SearchTitles",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SearchTitles",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SearchTitles": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@pattern nvarchar(4000)"
+ ],
+ "columns": [
+ "id int NOT NULL",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/no_semicolon_terminator",
+ "description": "Statements without a terminating semicolon (optional in T-SQL)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:6:0: missing semicolon at end of file",
+ "queries": [
+ {
+ "name": "First",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:6:0: missing semicolon at end of file"
+ ]
+ },
+ {
+ "name": "Second",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:6:0: missing semicolon at end of file"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:6:0: missing semicolon at end of file",
+ "queries": [
+ {
+ "name": "First",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:6:0: missing semicolon at end of file"
+ ]
+ },
+ {
+ "name": "Second",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:6:0: missing semicolon at end of file"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "First": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "Second": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/offset_fetch",
+ "description": "OFFSET ... ROWS FETCH NEXT ... ROWS ONLY with parameters",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "PageAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @skip: sqlc int, SQL Server bigint",
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "PageAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @skip: sqlc int, SQL Server bigint",
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "PageAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@skip bigint",
+ "@take bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/offset_without_fetch",
+ "description": "OFFSET ... ROWS without FETCH",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "SkipAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @skip: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "SkipAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @skip: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SkipAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@skip bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/optional_filter",
+ "description": "The optional filter idiom: (@p IS NULL OR column = @p)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "FilterBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "FilterBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "FilterBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_id bigint",
+ "@title nvarchar(200)"
+ ],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/order_by_parameter",
+ "description": "Dynamic ORDER BY driven by a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SortedAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SortedAuthors",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SortedAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@sort varchar(4)"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/parameter_in_select_list",
+ "description": "A parameter echoed in the select list, with and without CAST",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "EchoParams",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @label has no type (SQL Server cannot deduce it either)",
+ "Mismatch: column label has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "EchoParams",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @label has no type (SQL Server cannot deduce it either)",
+ "Mismatch: column label has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "EchoParams": {
+ "paramsError": "The parameter type for \u0027@label\u0027 cannot be uniquely deduced; two possibilities are \u0027sql_variant\u0027 and \u0027xml\u0027.",
+ "columnsError": "The metadata could not be determined because statement \u0027SELECT id, @label AS label, CAST(@factor AS DECIMAL(5, 2)) * price AS scaled FROM books\u0027 uses an undeclared parameter in a context that affects its metadata.",
+ "params": null,
+ "columns": null
+ }
+ }
+ },
+ {
+ "id": "select/quoted_aliases",
+ "description": "Aliases quoted with brackets, double quotes and single quotes",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListQuotedAliases",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named author id, SQL Server keeps the case: Author Id",
+ "Minor: column 2 is named author name, SQL Server keeps the case: Author Name",
+ "Minor: column 3 is named biography, SQL Server keeps the case: Biography"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListQuotedAliases",
+ "level": "Minor",
+ "findings": [
+ "Minor: column 1 is named author id, SQL Server keeps the case: Author Id",
+ "Minor: column 2 is named author name, SQL Server keeps the case: Author Name",
+ "Minor: column 3 is named biography, SQL Server keeps the case: Biography"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListQuotedAliases": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "Author Id bigint NOT NULL",
+ "Author Name nvarchar(100) NOT NULL",
+ "Biography nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/repeated_parameter",
+ "description": "The same parameter used several times, with different case",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByNameTwice",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ByNameTwice",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ByNameTwice": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)"
+ ],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/scalar_subquery_column",
+ "description": "Correlated scalar subquery in the select list",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsWithBookCount",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthorsWithBookCount",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthorsWithBookCount": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "book_count int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/select_star",
+ "description": "SELECT * and qualified t.* expansion",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthorsWithBookTitle",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ListAuthors",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "ListAuthorsWithBookTitle",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ListAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date"
+ ]
+ },
+ "ListAuthorsWithBookTitle": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL",
+ "bio nvarchar(max)",
+ "birth_date date",
+ "title nvarchar(200) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/table_value_constructor",
+ "description": "VALUES as a derived table",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "ListConstants",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "ListConstants",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "ListConstants": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "code int NOT NULL",
+ "label nvarchar(3) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/top_literal_with_ties",
+ "description": "TOP n WITH TIES and TOP n PERCENT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopPricedWithTies",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "TopTenPercent",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopPricedWithTies",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "TopTenPercent",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "TopPricedWithTies": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "price decimal(10,2)"
+ ]
+ },
+ "TopTenPercent": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/top_parameter",
+ "description": "TOP with a parenthesized parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "TopAuthors",
+ "level": "Minor",
+ "findings": [
+ "Minor: parameter @take: sqlc int, SQL Server bigint"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "TopAuthors": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@take bigint"
+ ],
+ "columns": [
+ "id bigint NOT NULL",
+ "name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/unicode_literals",
+ "description": "N\u0027\u0027 Unicode literals and string concatenation with \u002B",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Greetings",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column greeting: sqlc varchar, SQL Server nvarchar(105)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "Greetings",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column greeting: sqlc varchar, SQL Server nvarchar(105)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Greetings": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "greeting nvarchar(105) NOT NULL",
+ "ascii varchar(5) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "select/without_from",
+ "description": "SELECT without FROM: parameters and functions only",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Now",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AddOne",
+ "level": "Minor",
+ "findings": [
+ "Minor: column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "Now",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AddOne",
+ "level": "Minor",
+ "findings": [
+ "Minor: column result: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "Now": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "now_utc datetime2(7) NOT NULL"
+ ]
+ },
+ "AddOne": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@value int"
+ ],
+ "columns": [
+ "result int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "setops/pivot_unpivot",
+ "description": "PIVOT and UNPIVOT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AvailabilityPivot",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "QuarterUnpivot",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO",
+ "queries": [
+ {
+ "name": "AvailabilityPivot",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ },
+ {
+ "name": "QuarterUnpivot",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: scope: unsupported FROM item *ast.TODO\nquery.sql:7:1: scope: unsupported FROM item *ast.TODO"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "AvailabilityPivot": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "unavailable int",
+ "available int"
+ ]
+ },
+ "QuarterUnpivot": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "region nvarchar(10) NOT NULL",
+ "quarter nvarchar(128)",
+ "amount int NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "setops/union_except_intersect",
+ "description": "UNION, UNION ALL, EXCEPT and INTERSECT",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UnionNames",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "UnionAllIds",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AuthorsWithoutBooks",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AuthorsWithBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UnionNames",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "UnionAllIds",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AuthorsWithoutBooks",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "AuthorsWithBooks",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "UnionNames": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "name nvarchar(200) NOT NULL"
+ ]
+ },
+ "UnionAllIds": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "AuthorsWithoutBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ },
+ "AuthorsWithBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/bigint",
+ "description": "\u0060BIGINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBigint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBigint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBigint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBigint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBigint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBigint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectBigint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v bigint",
+ "nn bigint NOT NULL"
+ ]
+ },
+ "InsertBigint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v bigint",
+ "@nn bigint"
+ ],
+ "columns": []
+ },
+ "FilterBigint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn bigint"
+ ],
+ "columns": [
+ "v bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/binary",
+ "description": "\u0060BINARY(16)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBinary",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBinary",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectBinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v binary(16)",
+ "nn binary(16) NOT NULL"
+ ]
+ },
+ "InsertBinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v binary(16)",
+ "@nn binary(16)"
+ ],
+ "columns": []
+ },
+ "FilterBinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn binary(16)"
+ ],
+ "columns": [
+ "v binary(16)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/bit",
+ "description": "\u0060BIT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBit",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBit",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBit",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectBit",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertBit",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterBit",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectBit": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v bit",
+ "nn bit NOT NULL"
+ ]
+ },
+ "InsertBit": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v bit",
+ "@nn bit"
+ ],
+ "columns": []
+ },
+ "FilterBit": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn bit"
+ ],
+ "columns": [
+ "v bit"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/char",
+ "description": "\u0060CHAR(10)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectChar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertChar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterChar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectChar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertChar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterChar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectChar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v char(10)",
+ "nn char(10) NOT NULL"
+ ]
+ },
+ "InsertChar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v char(10)",
+ "@nn char(10)"
+ ],
+ "columns": []
+ },
+ "FilterChar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn char(10)"
+ ],
+ "columns": [
+ "v char(10)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/date",
+ "description": "\u0060DATE\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDate",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDate",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDate",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDate": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v date",
+ "nn date NOT NULL"
+ ]
+ },
+ "InsertDate": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v date",
+ "@nn date"
+ ],
+ "columns": []
+ },
+ "FilterDate": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn date"
+ ],
+ "columns": [
+ "v date"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/datetime",
+ "description": "\u0060DATETIME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v datetime",
+ "nn datetime NOT NULL"
+ ]
+ },
+ "InsertDatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v datetime",
+ "@nn datetime"
+ ],
+ "columns": []
+ },
+ "FilterDatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn datetime"
+ ],
+ "columns": [
+ "v datetime"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/datetime2",
+ "description": "\u0060DATETIME2(7)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime2",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime2",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime2",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime2",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime2",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime2",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDatetime2": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v datetime2(7)",
+ "nn datetime2(7) NOT NULL"
+ ]
+ },
+ "InsertDatetime2": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v datetime2(7)",
+ "@nn datetime2(7)"
+ ],
+ "columns": []
+ },
+ "FilterDatetime2": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn datetime2(7)"
+ ],
+ "columns": [
+ "v datetime2(7)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/datetime2_0",
+ "description": "\u0060DATETIME2(0)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime20",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime20",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime20",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetime20",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetime20",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetime20",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDatetime20": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v datetime2(0)",
+ "nn datetime2(0) NOT NULL"
+ ]
+ },
+ "InsertDatetime20": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v datetime2(0)",
+ "@nn datetime2(0)"
+ ],
+ "columns": []
+ },
+ "FilterDatetime20": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn datetime2(0)"
+ ],
+ "columns": [
+ "v datetime2(0)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/datetimeoffset",
+ "description": "\u0060DATETIMEOFFSET(7)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDatetimeoffset",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDatetimeoffset": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v datetimeoffset(7)",
+ "nn datetimeoffset(7) NOT NULL"
+ ]
+ },
+ "InsertDatetimeoffset": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v datetimeoffset(7)",
+ "@nn datetimeoffset(7)"
+ ],
+ "columns": []
+ },
+ "FilterDatetimeoffset": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn datetimeoffset(7)"
+ ],
+ "columns": [
+ "v datetimeoffset(7)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/decimal",
+ "description": "\u0060DECIMAL(18, 4)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDecimal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDecimal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDecimal",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectDecimal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertDecimal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterDecimal",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectDecimal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v decimal(18,4)",
+ "nn decimal(18,4) NOT NULL"
+ ]
+ },
+ "InsertDecimal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v decimal(18,4)",
+ "@nn decimal(18,4)"
+ ],
+ "columns": []
+ },
+ "FilterDecimal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn decimal(18,4)"
+ ],
+ "columns": [
+ "v decimal(18,4)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/float",
+ "description": "\u0060FLOAT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectFloat",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertFloat",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterFloat",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectFloat",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertFloat",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterFloat",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectFloat": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v float",
+ "nn float NOT NULL"
+ ]
+ },
+ "InsertFloat": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v float",
+ "@nn float"
+ ],
+ "columns": []
+ },
+ "FilterFloat": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn float"
+ ],
+ "columns": [
+ "v float"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/float_24",
+ "description": "\u0060FLOAT(24)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectFloat24",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertFloat24",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterFloat24",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectFloat24",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertFloat24",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterFloat24",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectFloat24": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v real",
+ "nn real NOT NULL"
+ ]
+ },
+ "InsertFloat24": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v real",
+ "@nn real"
+ ],
+ "columns": []
+ },
+ "FilterFloat24": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn real"
+ ],
+ "columns": [
+ "v real"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/geography",
+ "description": "\u0060GEOGRAPHY\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectGeography",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertGeography",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectGeography",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertGeography",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectGeography": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v geography",
+ "nn geography NOT NULL"
+ ]
+ },
+ "InsertGeography": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v geography",
+ "@nn geography"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "types/geometry",
+ "description": "\u0060GEOMETRY\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectGeometry",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertGeometry",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectGeometry",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertGeometry",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectGeometry": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v geometry",
+ "nn geometry NOT NULL"
+ ]
+ },
+ "InsertGeometry": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v geometry",
+ "@nn geometry"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "types/hierarchyid",
+ "description": "\u0060HIERARCHYID\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectHierarchyid",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertHierarchyid",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterHierarchyid",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectHierarchyid",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertHierarchyid",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterHierarchyid",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectHierarchyid": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v hierarchyid",
+ "nn hierarchyid NOT NULL"
+ ]
+ },
+ "InsertHierarchyid": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v hierarchyid",
+ "@nn hierarchyid"
+ ],
+ "columns": []
+ },
+ "FilterHierarchyid": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn hierarchyid"
+ ],
+ "columns": [
+ "v hierarchyid"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/image",
+ "description": "\u0060IMAGE\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectImage",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertImage",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectImage",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertImage",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectImage": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v image",
+ "nn image NOT NULL"
+ ]
+ },
+ "InsertImage": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v image",
+ "@nn image"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "types/int",
+ "description": "\u0060INT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectInt",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertInt",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterInt",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectInt",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertInt",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterInt",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectInt": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v int",
+ "nn int NOT NULL"
+ ]
+ },
+ "InsertInt": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v int",
+ "@nn int"
+ ],
+ "columns": []
+ },
+ "FilterInt": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn int"
+ ],
+ "columns": [
+ "v int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/max_lengths_and_precision",
+ "description": "The type expression keeps lengths, MAX, precision and scale",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectPrecise",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectPrecise",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectPrecise": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@d decimal(38,10)",
+ "@n nvarchar(4000)"
+ ],
+ "columns": [
+ "d decimal(38,10) NOT NULL",
+ "n nvarchar(4000) NOT NULL",
+ "t time(0) NOT NULL",
+ "dt datetime2(3) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/money",
+ "description": "\u0060MONEY\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectMoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertMoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterMoney",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectMoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertMoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterMoney",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectMoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v money",
+ "nn money NOT NULL"
+ ]
+ },
+ "InsertMoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v money",
+ "@nn money"
+ ],
+ "columns": []
+ },
+ "FilterMoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn money"
+ ],
+ "columns": [
+ "v money"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/nchar",
+ "description": "\u0060NCHAR(10)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectNchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v nchar(10)",
+ "nn nchar(10) NOT NULL"
+ ]
+ },
+ "InsertNchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v nchar(10)",
+ "@nn nchar(10)"
+ ],
+ "columns": []
+ },
+ "FilterNchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn nchar(10)"
+ ],
+ "columns": [
+ "v nchar(10)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/ntext",
+ "description": "\u0060NTEXT\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNtext",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNtext",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNtext",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNtext",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectNtext": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v ntext",
+ "nn ntext NOT NULL"
+ ]
+ },
+ "InsertNtext": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v ntext",
+ "@nn ntext"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "types/numeric",
+ "description": "\u0060NUMERIC(10, 0)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNumeric",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNumeric",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNumeric",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNumeric",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNumeric",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNumeric",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectNumeric": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v numeric(10,0)",
+ "nn numeric(10,0) NOT NULL"
+ ]
+ },
+ "InsertNumeric": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v numeric(10,0)",
+ "@nn numeric(10,0)"
+ ],
+ "columns": []
+ },
+ "FilterNumeric": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn numeric(10,0)"
+ ],
+ "columns": [
+ "v numeric(10,0)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/nvarchar",
+ "description": "\u0060NVARCHAR(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNvarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNvarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNvarchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNvarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNvarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNvarchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectNvarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v nvarchar(50)",
+ "nn nvarchar(50) NOT NULL"
+ ]
+ },
+ "InsertNvarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v nvarchar(50)",
+ "@nn nvarchar(50)"
+ ],
+ "columns": []
+ },
+ "FilterNvarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn nvarchar(50)"
+ ],
+ "columns": [
+ "v nvarchar(50)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/nvarchar_max",
+ "description": "\u0060NVARCHAR(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterNvarcharMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectNvarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v nvarchar(max)",
+ "nn nvarchar(max) NOT NULL"
+ ]
+ },
+ "InsertNvarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v nvarchar(max)",
+ "@nn nvarchar(max)"
+ ],
+ "columns": []
+ },
+ "FilterNvarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn nvarchar(max)"
+ ],
+ "columns": [
+ "v nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/real",
+ "description": "\u0060REAL\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectReal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertReal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterReal",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectReal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertReal",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterReal",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectReal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v real",
+ "nn real NOT NULL"
+ ]
+ },
+ "InsertReal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v real",
+ "@nn real"
+ ],
+ "columns": []
+ },
+ "FilterReal": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn real"
+ ],
+ "columns": [
+ "v real"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/rowversion_timestamp",
+ "description": "\u0060ROWVERSION\u0060 and its legacy synonym \u0060TIMESTAMP\u0060",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVersions",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "SelectTimestamp",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVersions",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "SelectTimestamp",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectVersions": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "rv timestamp NOT NULL"
+ ]
+ },
+ "SelectTimestamp": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "ts timestamp NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/smalldatetime",
+ "description": "\u0060SMALLDATETIME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmalldatetime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectSmalldatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v smalldatetime",
+ "nn smalldatetime NOT NULL"
+ ]
+ },
+ "InsertSmalldatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v smalldatetime",
+ "@nn smalldatetime"
+ ],
+ "columns": []
+ },
+ "FilterSmalldatetime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn smalldatetime"
+ ],
+ "columns": [
+ "v smalldatetime"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/smallint",
+ "description": "\u0060SMALLINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmallint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmallint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmallint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmallint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmallint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmallint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectSmallint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v smallint",
+ "nn smallint NOT NULL"
+ ]
+ },
+ "InsertSmallint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v smallint",
+ "@nn smallint"
+ ],
+ "columns": []
+ },
+ "FilterSmallint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn smallint"
+ ],
+ "columns": [
+ "v smallint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/smallmoney",
+ "description": "\u0060SMALLMONEY\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmallmoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmallmoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmallmoney",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSmallmoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSmallmoney",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSmallmoney",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectSmallmoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v smallmoney",
+ "nn smallmoney NOT NULL"
+ ]
+ },
+ "InsertSmallmoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v smallmoney",
+ "@nn smallmoney"
+ ],
+ "columns": []
+ },
+ "FilterSmallmoney": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn smallmoney"
+ ],
+ "columns": [
+ "v smallmoney"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/sql_variant",
+ "description": "\u0060SQL_VARIANT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSqlVariant",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSqlVariant",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSqlVariant",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSqlVariant",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSqlVariant",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSqlVariant",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectSqlVariant": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v sql_variant",
+ "nn sql_variant NOT NULL"
+ ]
+ },
+ "InsertSqlVariant": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v sql_variant",
+ "@nn sql_variant"
+ ],
+ "columns": []
+ },
+ "FilterSqlVariant": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn sql_variant"
+ ],
+ "columns": [
+ "v sql_variant"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/sysname",
+ "description": "\u0060SYSNAME\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSysname",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSysname",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSysname",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectSysname",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertSysname",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterSysname",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectSysname": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v nvarchar(128)",
+ "nn nvarchar(128) NOT NULL"
+ ]
+ },
+ "InsertSysname": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v nvarchar(128)",
+ "@nn nvarchar(128)"
+ ],
+ "columns": []
+ },
+ "FilterSysname": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn nvarchar(128)"
+ ],
+ "columns": [
+ "v nvarchar(128)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/text",
+ "description": "\u0060TEXT\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectText",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertText",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectText",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertText",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectText": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v text",
+ "nn text NOT NULL"
+ ]
+ },
+ "InsertText": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v text",
+ "@nn text"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "types/time",
+ "description": "\u0060TIME(3)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectTime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertTime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterTime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectTime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertTime",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterTime",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectTime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v time(3)",
+ "nn time(3) NOT NULL"
+ ]
+ },
+ "InsertTime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v time(3)",
+ "@nn time(3)"
+ ],
+ "columns": []
+ },
+ "FilterTime": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn time(3)"
+ ],
+ "columns": [
+ "v time(3)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/tinyint",
+ "description": "\u0060TINYINT\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectTinyint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertTinyint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterTinyint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectTinyint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertTinyint",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterTinyint",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectTinyint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v tinyint",
+ "nn tinyint NOT NULL"
+ ]
+ },
+ "InsertTinyint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v tinyint",
+ "@nn tinyint"
+ ],
+ "columns": []
+ },
+ "FilterTinyint": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn tinyint"
+ ],
+ "columns": [
+ "v tinyint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/uniqueidentifier",
+ "description": "\u0060UNIQUEIDENTIFIER\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterUniqueidentifier",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectUniqueidentifier": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v uniqueidentifier",
+ "nn uniqueidentifier NOT NULL"
+ ]
+ },
+ "InsertUniqueidentifier": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v uniqueidentifier",
+ "@nn uniqueidentifier"
+ ],
+ "columns": []
+ },
+ "FilterUniqueidentifier": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn uniqueidentifier"
+ ],
+ "columns": [
+ "v uniqueidentifier"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/varbinary",
+ "description": "\u0060VARBINARY(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarbinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarbinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarbinary",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarbinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarbinary",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarbinary",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectVarbinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v varbinary(50)",
+ "nn varbinary(50) NOT NULL"
+ ]
+ },
+ "InsertVarbinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v varbinary(50)",
+ "@nn varbinary(50)"
+ ],
+ "columns": []
+ },
+ "FilterVarbinary": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn varbinary(50)"
+ ],
+ "columns": [
+ "v varbinary(50)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/varbinary_max",
+ "description": "\u0060VARBINARY(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarbinaryMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectVarbinaryMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v varbinary(max)",
+ "nn varbinary(max) NOT NULL"
+ ]
+ },
+ "InsertVarbinaryMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v varbinary(max)",
+ "@nn varbinary(max)"
+ ],
+ "columns": []
+ },
+ "FilterVarbinaryMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn varbinary(max)"
+ ],
+ "columns": [
+ "v varbinary(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/varchar",
+ "description": "\u0060VARCHAR(50)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarchar",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarchar",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectVarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v varchar(50)",
+ "nn varchar(50) NOT NULL"
+ ]
+ },
+ "InsertVarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v varchar(50)",
+ "@nn varchar(50)"
+ ],
+ "columns": []
+ },
+ "FilterVarchar": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn varchar(50)"
+ ],
+ "columns": [
+ "v varchar(50)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/varchar_max",
+ "description": "\u0060VARCHAR(MAX)\u0060 column: nullable and NOT NULL, read, written and compared to a parameter",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarcharMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectVarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertVarcharMax",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "FilterVarcharMax",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectVarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v varchar(max)",
+ "nn varchar(max) NOT NULL"
+ ]
+ },
+ "InsertVarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v varchar(max)",
+ "@nn varchar(max)"
+ ],
+ "columns": []
+ },
+ "FilterVarcharMax": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@nn varchar(max)"
+ ],
+ "columns": [
+ "v varchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "types/xml",
+ "description": "\u0060XML\u0060 column: nullable and NOT NULL, read, written",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectXml",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertXml",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "SelectXml",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "InsertXml",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "SelectXml": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "v xml",
+ "nn xml NOT NULL"
+ ]
+ },
+ "InsertXml": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@v xml",
+ "@nn xml"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "update/compound_assignment",
+ "description": "Compound assignment operators \u002B= and *=",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "AdjustPrice",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "AdjustPrice",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AdjustPrice": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@delta decimal(38,19)",
+ "@suffix nvarchar(4000)",
+ "@author_id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "update/from_join",
+ "description": "UPDATE ... FROM with a JOIN (T-SQL specific)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "MarkAuthorsBooksUnavailable",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "MarkAuthorsBooksUnavailable",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "MarkAuthorsBooksUnavailable": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@author_name nvarchar(100)"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "update/output_deleted_inserted",
+ "description": "UPDATE ... OUTPUT DELETED.x, INSERTED.x",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "RenameAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "RenameAuthor",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "RenameAuthor": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@name nvarchar(100)",
+ "@id bigint"
+ ],
+ "columns": [
+ "old_name nvarchar(100) NOT NULL",
+ "new_name nvarchar(100) NOT NULL"
+ ]
+ }
+ }
+ },
+ {
+ "id": "update/set_default",
+ "description": "UPDATE ... SET column = DEFAULT and = NULL",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ResetBook",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "ResetBook",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "ResetBook": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@id int"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "update/simple",
+ "description": "UPDATE with SET parameters and WHERE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UpdateBio",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Ok",
+ "error": "",
+ "queries": [
+ {
+ "name": "UpdateBio",
+ "level": "Ok",
+ "findings": []
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "UpdateBio": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@bio nvarchar(max)",
+ "@birth_date date",
+ "@id bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "update/top",
+ "description": "UPDATE TOP (@n)",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "MarkSomeUnavailable",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @n (bigint) is missing"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "MarkSomeUnavailable",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: parameter @n (bigint) is missing"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "MarkSomeUnavailable": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [
+ "@n bigint"
+ ],
+ "columns": []
+ }
+ }
+ },
+ {
+ "id": "window/aggregate_over",
+ "description": "Aggregates over windows, including a running total",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "RunningTotals",
+ "level": "Minor",
+ "findings": [
+ "Minor: column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Minor",
+ "error": "",
+ "queries": [
+ {
+ "name": "RunningTotals",
+ "level": "Minor",
+ "findings": [
+ "Minor: column total_count: sqlc NOT NULL, SQL Server nullable (SQL Server is conservative for expressions)"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "RunningTotals": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "running_total decimal(38,2)",
+ "total_count int"
+ ]
+ }
+ }
+ },
+ {
+ "id": "window/lag_lead_first_value",
+ "description": "LAG, LEAD, FIRST_VALUE and LAST_VALUE with a frame",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "PriceNeighbours",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column previous_price has no type",
+ "Mismatch: column next_price has no type",
+ "Mismatch: column first_title has no type",
+ "Mismatch: column last_title has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "PriceNeighbours",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column previous_price has no type",
+ "Mismatch: column next_price has no type",
+ "Mismatch: column first_title has no type",
+ "Mismatch: column last_title has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "PriceNeighbours": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "previous_price decimal(10,2)",
+ "next_price decimal(10,2)",
+ "first_title nvarchar(200)",
+ "last_title nvarchar(200)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "window/ranking",
+ "description": "ROW_NUMBER, RANK, DENSE_RANK and NTILE",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "RankedBooks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column rn has no type",
+ "Mismatch: column rnk has no type",
+ "Mismatch: column dense_rnk has no type",
+ "Mismatch: column quartile has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "RankedBooks",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column rn has no type",
+ "Mismatch: column rnk has no type",
+ "Mismatch: column dense_rnk has no type",
+ "Mismatch: column quartile has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "RankedBooks": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "rn bigint",
+ "rnk bigint",
+ "dense_rnk bigint",
+ "quartile bigint"
+ ]
+ }
+ }
+ },
+ {
+ "id": "window/window_clause_and_percentiles",
+ "description": "Named WINDOW clause (SQL Server 2022), PERCENTILE_CONT and PERCENTILE_DISC",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "NamedWindow",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "Percentiles",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column median has no type",
+ "Mismatch: column median_disc has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "NamedWindow",
+ "level": "Ok",
+ "findings": []
+ },
+ {
+ "name": "Percentiles",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column median has no type",
+ "Mismatch: column median_disc has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "NamedWindow": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "total decimal(38,2)",
+ "average decimal(38,6)"
+ ]
+ },
+ "Percentiles": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "author_id bigint NOT NULL",
+ "median float",
+ "median_disc decimal(10,2)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "xml/for_xml",
+ "description": "FOR XML PATH, including the string-concatenation idiom",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsAsXml",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ },
+ {
+ "name": "ConcatenatedTitles",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column titles has no type"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Ok",
+ "level": "Mismatch",
+ "error": "",
+ "queries": [
+ {
+ "name": "AuthorsAsXml",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: 2 result columns, SQL Server returns 1 (XML_F52E2B61-18A1-11d1-B105-00805F49916B)"
+ ]
+ },
+ {
+ "name": "ConcatenatedTitles",
+ "level": "Mismatch",
+ "findings": [
+ "Mismatch: column titles has no type"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Ok",
+ "pluginError": "",
+ "compile": "Ok",
+ "compileError": "",
+ "oracle": {
+ "AuthorsAsXml": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "XML_F52E2B61-18A1-11d1-B105-00805F49916B ntext"
+ ]
+ },
+ "ConcatenatedTitles": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id bigint NOT NULL",
+ "titles nvarchar(max)"
+ ]
+ }
+ }
+ },
+ {
+ "id": "xml/xml_methods",
+ "description": "XML column with the value(), query(), exist() and nodes() methods",
+ "schemaError": null,
+ "builds": {
+ "upstream": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unexpected token: query",
+ "queries": [
+ {
+ "name": "XmlAccess",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: query"
+ ]
+ },
+ {
+ "name": "XmlNodes",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: query"
+ ]
+ }
+ ]
+ },
+ "patched": {
+ "status": "Error",
+ "level": "Error",
+ "error": "query.sql:1:1: unexpected token: query",
+ "queries": [
+ {
+ "name": "XmlAccess",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: query"
+ ]
+ },
+ {
+ "name": "XmlNodes",
+ "level": "Error",
+ "findings": [
+ "Error: query.sql:1:1: unexpected token: query"
+ ]
+ }
+ ]
+ }
+ },
+ "plugin": "Skipped",
+ "pluginError": "",
+ "compile": "Skipped",
+ "compileError": "",
+ "oracle": {
+ "XmlAccess": {
+ "paramsError": "XQuery: Unable to resolve sql:variable(\u0027@status\u0027). The variable must be declared as a scalar TSQL variable.",
+ "columnsError": "XQuery: Unable to resolve sql:variable(\u0027@status\u0027). The variable must be declared as a scalar TSQL variable.",
+ "params": null,
+ "columns": null
+ },
+ "XmlNodes": {
+ "paramsError": null,
+ "columnsError": null,
+ "params": [],
+ "columns": [
+ "id int NOT NULL",
+ "sku nvarchar(20)"
+ ]
+ }
+ }
+ }
+]
diff --git a/end2end/scripts/generate_tests.sh b/end2end/scripts/generate_tests.sh
index 862548d49..5716250bb 100755
--- a/end2end/scripts/generate_tests.sh
+++ b/end2end/scripts/generate_tests.sh
@@ -9,6 +9,8 @@ TARGETS=(
"NpgsqlDapperTester"
"SqliteTester"
"SqliteDapperTester"
+ "MssqlTester"
+ "MssqlDapperTester"
)
generate() {
diff --git a/examples/MssqlAsyncEnumerableExample/Models.cs b/examples/MssqlAsyncEnumerableExample/Models.cs
new file mode 100644
index 000000000..8c8880148
--- /dev/null
+++ b/examples/MssqlAsyncEnumerableExample/Models.cs
@@ -0,0 +1,9 @@
+// auto-generated by sqlc - do not edit
+using System;
+using System.Linq;
+
+namespace MssqlAsyncEnumerableExampleGen;
+public readonly record struct Author(long Id, string Name, string? Bio);
+public readonly record struct Book(long Id, string Name, long AuthorId, string? Description);
+public readonly record struct Note(int Id, string Body);
+public readonly record struct ExtendedBio(string AuthorName, string Name, string? BioType);
\ No newline at end of file
diff --git a/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj b/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj
new file mode 100644
index 000000000..bf2f89711
--- /dev/null
+++ b/examples/MssqlAsyncEnumerableExample/MssqlAsyncEnumerableExample.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+ net8.0
+ MssqlAsyncEnumerableExampleGen
+ Library
+ enable
+ False
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/MssqlAsyncEnumerableExample/QuerySql.cs b/examples/MssqlAsyncEnumerableExample/QuerySql.cs
new file mode 100644
index 000000000..aa3ab0fe2
--- /dev/null
+++ b/examples/MssqlAsyncEnumerableExample/QuerySql.cs
@@ -0,0 +1,976 @@
+// auto-generated by sqlc - do not edit
+// ReSharper disable UseObjectOrCollectionInitializer
+// ReSharper disable UseAwaitUsing
+// ReSharper disable ConvertToUsingDeclaration
+// ReSharper disable NotAccessedPositionalProperty.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+using Microsoft.Data.SqlClient;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MssqlAsyncEnumerableExampleGen;
+public class QuerySql
+{
+ public QuerySql()
+ {
+ }
+
+ public QuerySql(string connectionString) : this()
+ {
+ this.ConnectionString = connectionString;
+ }
+
+ private QuerySql(SqlTransaction transaction) : this()
+ {
+ this.Transaction = transaction;
+ }
+
+ public static QuerySql WithTransaction(SqlTransaction transaction)
+ {
+ return new QuerySql(transaction);
+ }
+
+ private SqlTransaction? Transaction { get; }
+ private string? ConnectionString { get; }
+
+ private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors
+ WHERE name = @name;";
+ public readonly record struct GetAuthorRow(long Id, string Name, string? Bio);
+ public readonly record struct GetAuthorArgs(string Name);
+ public async Task GetAuthorAsync(GetAuthorArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetAuthorSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetAuthorRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+ };
+ return null;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetAuthorSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetAuthorRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private const string ListAuthorsSql = @"SELECT id, name, bio
+ FROM authors
+ ORDER BY name
+ OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;";
+ public readonly record struct ListAuthorsRow(long Id, string Name, string? Bio);
+ public readonly record struct ListAuthorsArgs(int Offset, int Limit);
+ public async IAsyncEnumerable ListAuthorsAsync(ListAuthorsArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(ListAuthorsSql, connection))
+ {
+ command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset;
+ command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new ListAuthorsRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = ListAuthorsSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@offset", SqlDbType.Int).Value = args.Offset;
+ command.Parameters.Add("@limit", SqlDbType.Int).Value = args.Limit;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new ListAuthorsRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);";
+ public readonly record struct CreateAuthorArgs(long Id, string Name, string? Bio);
+ public async Task CreateAuthorAsync(CreateAuthorArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(CreateAuthorSql, connection))
+ {
+ command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = CreateAuthorSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);";
+ public readonly record struct CreateAuthorReturnIdRow(long Id);
+ public readonly record struct CreateAuthorReturnIdArgs(string Name, string? Bio);
+ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(CreateAuthorReturnIdSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt64(result);
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = CreateAuthorReturnIdSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt64(result);
+ }
+ }
+
+ private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment
+ WHERE id = @id;";
+ public readonly record struct GetAuthorByIdRow(long Id, string Name, string? Bio);
+ public readonly record struct GetAuthorByIdArgs(long Id);
+ public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetAuthorByIdSql, connection))
+ {
+ command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetAuthorByIdRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+ };
+ return null;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetAuthorByIdSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@id", SqlDbType.BigInt).Value = args.Id;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetAuthorByIdRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors
+ WHERE name LIKE COALESCE(@name_pattern, '%');";
+ public readonly record struct GetAuthorByNamePatternRow(long Id, string Name, string? Bio);
+ public readonly record struct GetAuthorByNamePatternArgs(object? NamePattern);
+ public async IAsyncEnumerable GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetAuthorByNamePatternSql, connection))
+ {
+ command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorByNamePatternRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetAuthorByNamePatternSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.AddWithValue("@name_pattern", args.NamePattern ?? (object)DBNull.Value);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorByNamePatternRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ private const string DeleteAuthorSql = @"DELETE FROM authors
+ WHERE name = @name;";
+ public readonly record struct DeleteAuthorArgs(string Name);
+ public async Task DeleteAuthorAsync(DeleteAuthorArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(DeleteAuthorSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = DeleteAuthorSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string DeleteAllAuthorsSql = "DELETE FROM authors;";
+ public async Task DeleteAllAuthorsAsync(CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(DeleteAllAuthorsSql, connection))
+ {
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = DeleteAllAuthorsSql;
+ command.Transaction = this.Transaction;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string UpdateAuthorsSql = @"UPDATE authors
+ SET bio = @bio
+ WHERE bio IS NOT NULL;";
+ public readonly record struct UpdateAuthorsArgs(string? Bio);
+ public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(UpdateAuthorsSql, connection))
+ {
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ return await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = UpdateAuthorsSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@bio", SqlDbType.NVarChar).Value = args.Bio ?? (object)DBNull.Value;
+ return await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);";
+ public readonly record struct GetAuthorsByIdsRow(long Id, string Name, string? Bio);
+ public readonly record struct GetAuthorsByIdsArgs(long[] Ids);
+ public async IAsyncEnumerable GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var transformedSql = GetAuthorsByIdsSql;
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids");
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(transformedSql, connection))
+ {
+ for (int i = 0; i < args.Ids.Length; i++)
+ command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByIdsRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = transformedSql;
+ command.Transaction = this.Transaction;
+ for (int i = 0; i < args.Ids.Length; i++)
+ command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByIdsRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);";
+ public readonly record struct GetAuthorsByIdsAndNamesRow(long Id, string Name, string? Bio);
+ public readonly record struct GetAuthorsByIdsAndNamesArgs(long[] Ids, string[] Names);
+ public async IAsyncEnumerable GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var transformedSql = GetAuthorsByIdsAndNamesSql;
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids");
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names");
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(transformedSql, connection))
+ {
+ for (int i = 0; i < args.Ids.Length; i++)
+ command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]);
+ for (int i = 0; i < args.Names.Length; i++)
+ command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByIdsAndNamesRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = transformedSql;
+ command.Transaction = this.Transaction;
+ for (int i = 0; i < args.Ids.Length; i++)
+ command.Parameters.AddWithValue($"@idsArg{i}", args.Ids[i]);
+ for (int i = 0; i < args.Names.Length; i++)
+ command.Parameters.AddWithValue($"@namesArg{i}", args.Names[i]);
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByIdsAndNamesRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);";
+ public readonly record struct CreateBookRow(long Id);
+ public readonly record struct CreateBookArgs(string Name, long AuthorId);
+ public async Task CreateBookAsync(CreateBookArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(CreateBookSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt64(result);
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = CreateBookSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@author_id", SqlDbType.BigInt).Value = args.AuthorId;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt64(result);
+ }
+ }
+
+ private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description
+ FROM authors JOIN books ON authors.id = books.author_id
+ ORDER BY authors.name;";
+ public readonly record struct ListAllAuthorsBooksRow(Author? Author, Book? Book);
+ public async IAsyncEnumerable ListAllAuthorsBooksAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection))
+ {
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new ListAllAuthorsBooksRow
+ {
+ Author = new Author
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ },
+ Book = new Book
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ AuthorId = reader.GetInt64(5),
+ Description = reader.IsDBNull(6) ? null : reader.GetString(6)
+ }
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = ListAllAuthorsBooksSql;
+ command.Transaction = this.Transaction;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new ListAllAuthorsBooksRow
+ {
+ Author = new Author
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ },
+ Book = new Book
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ AuthorId = reader.GetInt64(5),
+ Description = reader.IsDBNull(6) ? null : reader.GetString(6)
+ }
+ };
+ }
+ }
+ }
+
+ private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio
+ FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name
+ WHERE authors1.id < authors2.id;";
+ public readonly record struct GetDuplicateAuthorsRow(Author? Author, Author? Author2);
+ public async IAsyncEnumerable GetDuplicateAuthorsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection))
+ {
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetDuplicateAuthorsRow
+ {
+ Author = new Author
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ },
+ Author2 = new Author
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ Bio = reader.IsDBNull(5) ? null : reader.GetString(5)
+ }
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetDuplicateAuthorsSql;
+ command.Transaction = this.Transaction;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetDuplicateAuthorsRow
+ {
+ Author = new Author
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2)
+ },
+ Author2 = new Author
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ Bio = reader.IsDBNull(5) ? null : reader.GetString(5)
+ }
+ };
+ }
+ }
+ }
+
+ private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description
+ FROM authors JOIN books ON authors.id = books.author_id
+ WHERE books.name = @name;";
+ public readonly record struct GetAuthorsByBookNameRow(long Id, string Name, string? Bio, Book? Book);
+ public readonly record struct GetAuthorsByBookNameArgs(string Name);
+ public async IAsyncEnumerable GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByBookNameRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2),
+ Book = new Book
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ AuthorId = reader.GetInt64(5),
+ Description = reader.IsDBNull(6) ? null : reader.GetString(6)
+ }
+ };
+ }
+ }
+ }
+
+ yield break;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetAuthorsByBookNameSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ while (await reader.ReadAsync(cancellationToken))
+ yield return new GetAuthorsByBookNameRow
+ {
+ Id = reader.GetInt64(0),
+ Name = reader.GetString(1),
+ Bio = reader.IsDBNull(2) ? null : reader.GetString(2),
+ Book = new Book
+ {
+ Id = reader.GetInt64(3),
+ Name = reader.GetString(4),
+ AuthorId = reader.GetInt64(5),
+ Description = reader.IsDBNull(6) ? null : reader.GetString(6)
+ }
+ };
+ }
+ }
+ }
+
+ private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);";
+ public readonly record struct CreateNoteReturnIdRow(int Id);
+ public readonly record struct CreateNoteReturnIdArgs(string Body);
+ public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(CreateNoteReturnIdSql, connection))
+ {
+ command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt32(result);
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = CreateNoteReturnIdSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@body", SqlDbType.NVarChar).Value = args.Body;
+ var result = await command.ExecuteScalarAsync(cancellationToken);
+ return Convert.ToInt32(result);
+ }
+ }
+
+ private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;";
+ public readonly record struct GetNoteRow(int Id, string Body);
+ public readonly record struct GetNoteArgs(int Id);
+ public async Task GetNoteAsync(GetNoteArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetNoteSql, connection))
+ {
+ command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetNoteRow
+ {
+ Id = reader.GetInt32(0),
+ Body = reader.GetString(1)
+ };
+ }
+ }
+ }
+ };
+ return null;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetNoteSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@id", SqlDbType.Int).Value = args.Id;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetNoteRow
+ {
+ Id = reader.GetInt32(0),
+ Body = reader.GetString(1)
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private const string TruncateNotesSql = "TRUNCATE TABLE notes;";
+ public async Task TruncateNotesAsync(CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(TruncateNotesSql, connection))
+ {
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = TruncateNotesSql;
+ command.Transaction = this.Transaction;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);";
+ public readonly record struct CreateExtendedBioArgs(string AuthorName, string Name, string? BioType);
+ public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(CreateExtendedBioSql, connection))
+ {
+ command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = CreateExtendedBioSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@author_name", SqlDbType.NVarChar).Value = args.AuthorName;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+
+ private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;";
+ public readonly record struct GetFirstExtendedBioByTypeRow(string AuthorName, string Name, string? BioType);
+ public readonly record struct GetFirstExtendedBioByTypeArgs(string? BioType);
+ public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args, CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(GetFirstExtendedBioByTypeSql, connection))
+ {
+ command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetFirstExtendedBioByTypeRow
+ {
+ AuthorName = reader.GetString(0),
+ Name = reader.GetString(1),
+ BioType = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+ };
+ return null;
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetFirstExtendedBioByTypeSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@bio_type", SqlDbType.NVarChar).Value = args.BioType ?? (object)DBNull.Value;
+ using (var reader = await command.ExecuteReaderAsync(cancellationToken))
+ {
+ if (await reader.ReadAsync(cancellationToken))
+ {
+ return new GetFirstExtendedBioByTypeRow
+ {
+ AuthorName = reader.GetString(0),
+ Name = reader.GetString(1),
+ BioType = reader.IsDBNull(2) ? null : reader.GetString(2)
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;";
+ public async Task TruncateExtendedBiosAsync(CancellationToken cancellationToken = default)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync(cancellationToken);
+ using (var command = new SqlCommand(TruncateExtendedBiosSql, connection))
+ {
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = TruncateExtendedBiosSql;
+ command.Transaction = this.Transaction;
+ await command.ExecuteNonQueryAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/MssqlAsyncEnumerableExample/Utils.cs b/examples/MssqlAsyncEnumerableExample/Utils.cs
new file mode 100644
index 000000000..f3ee62580
--- /dev/null
+++ b/examples/MssqlAsyncEnumerableExample/Utils.cs
@@ -0,0 +1,13 @@
+// auto-generated by sqlc - do not edit
+using System;
+using System.Linq;
+
+namespace MssqlAsyncEnumerableExampleGen;
+public static class Utils
+{
+ public static string TransformQueryForSliceArgs(string originalSql, int sliceSize, string paramName)
+ {
+ var paramArgs = Enumerable.Range(0, sliceSize).Select(i => $"@{paramName}Arg{i}").ToList();
+ return originalSql.Replace($"/*SLICE:{paramName}*/@{paramName}", string.Join(",", paramArgs));
+ }
+}
\ No newline at end of file
diff --git a/examples/MssqlAsyncEnumerableExample/request.json b/examples/MssqlAsyncEnumerableExample/request.json
new file mode 100644
index 000000000..878219571
--- /dev/null
+++ b/examples/MssqlAsyncEnumerableExample/request.json
@@ -0,0 +1,1225 @@
+{
+ "settings": {
+ "version": "2",
+ "engine": "mssql",
+ "schema": [
+ "examples/config/mssql/authors/schema.sql"
+ ],
+ "queries": [
+ "examples/config/mssql/authors/query.sql"
+ ],
+ "codegen": {
+ "out": "examples/MssqlAsyncEnumerableExample",
+ "plugin": "csharp",
+ "options": "eyJkZWJ1Z1JlcXVlc3QiOnRydWUsImdlbmVyYXRlQ3Nwcm9qIjp0cnVlLCJuYW1lc3BhY2VOYW1lIjoiTXNzcWxBc3luY0VudW1lcmFibGVFeGFtcGxlR2VuIiwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OC4wIiwidXNlQXN5bmNFbnVtZXJhYmxlIjp0cnVlLCJ1c2VEYXBwZXIiOmZhbHNlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOnRydWV9",
+ "process": {
+ "cmd": "./dist/LocalRunner"
+ }
+ }
+ },
+ "catalog": {
+ "defaultSchema": "public",
+ "schemas": [
+ {
+ "name": "public"
+ },
+ {
+ "name": "public",
+ "tables": [
+ {
+ "rel": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ }
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ }
+ ]
+ },
+ {
+ "rel": {
+ "schema": "public",
+ "name": "books"
+ },
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "bigint"
+ }
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ },
+ {
+ "name": "author_id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "bigint"
+ }
+ },
+ {
+ "name": "description",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ }
+ ]
+ },
+ {
+ "rel": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "int"
+ }
+ },
+ {
+ "name": "body",
+ "notNull": true,
+ "length": 200,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "sys"
+ },
+ {
+ "name": "information_schema"
+ },
+ {
+ "name": "extended",
+ "tables": [
+ {
+ "rel": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "columns": [
+ {
+ "name": "author_name",
+ "notNull": true,
+ "length": 100,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 100,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ },
+ {
+ "name": "bio_type",
+ "length": 20,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "queries": [
+ {
+ "text": "SELECT TOP 1 id, name, bio FROM authors\nWHERE name = @name;",
+ "name": "GetAuthor",
+ "cmd": ":one",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT id, name, bio\nFROM authors\nORDER BY name\nOFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;",
+ "name": "ListAuthors",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "offset",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "type": {
+ "name": "int"
+ }
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "limit",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "type": {
+ "name": "int"
+ }
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);",
+ "name": "CreateAuthor",
+ "cmd": ":exec",
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ },
+ {
+ "number": 3,
+ "column": {
+ "name": "bio",
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ }
+ ],
+ "filename": "query.sql",
+ "insert_into_table": {
+ "name": "authors"
+ }
+ },
+ {
+ "text": "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);",
+ "name": "CreateAuthorReturnId",
+ "cmd": ":execlastid",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "bio",
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ }
+ ],
+ "filename": "query.sql",
+ "insert_into_table": {
+ "name": "authors"
+ }
+ },
+ {
+ "text": "SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment\nWHERE id = @id;",
+ "name": "GetAuthorById",
+ "cmd": ":one",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT id, name, bio FROM authors\nWHERE name LIKE COALESCE(@name_pattern, '%');",
+ "name": "GetAuthorByNamePattern",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name_pattern",
+ "length": -1,
+ "isNamedParam": true,
+ "type": {}
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "DELETE FROM authors\nWHERE name = @name;",
+ "name": "DeleteAuthor",
+ "cmd": ":exec",
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "DELETE FROM authors;",
+ "name": "DeleteAllAuthors",
+ "cmd": ":exec",
+ "filename": "query.sql"
+ },
+ {
+ "text": "UPDATE authors\nSET bio = @bio\nWHERE bio IS NOT NULL;",
+ "name": "UpdateAuthors",
+ "cmd": ":execrows",
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "bio",
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);",
+ "name": "GetAuthorsByIds",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "ids",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "isSqlcSlice": true,
+ "originalName": "id"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);",
+ "name": "GetAuthorsByIdsAndNames",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "ids",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "isSqlcSlice": true,
+ "originalName": "id"
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "names",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "isSqlcSlice": true,
+ "originalName": "name"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);",
+ "name": "CreateBook",
+ "cmd": ":execlastid",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "author_id",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "author_id"
+ }
+ }
+ ],
+ "filename": "query.sql",
+ "insert_into_table": {
+ "name": "books"
+ }
+ },
+ {
+ "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nORDER BY authors.name;",
+ "name": "ListAllAuthorsBooks",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "authors",
+ "length": -1,
+ "type": {},
+ "embedTable": {
+ "schema": "public",
+ "name": "authors"
+ }
+ },
+ {
+ "name": "books",
+ "length": -1,
+ "type": {},
+ "embedTable": {
+ "schema": "public",
+ "name": "books"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio\nFROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name\nWHERE authors1.id \u003c authors2.id;",
+ "name": "GetDuplicateAuthors",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "authors",
+ "length": -1,
+ "type": {},
+ "embedTable": {
+ "schema": "public",
+ "name": "authors"
+ }
+ },
+ {
+ "name": "authors",
+ "length": -1,
+ "type": {},
+ "embedTable": {
+ "schema": "public",
+ "name": "authors"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description\nFROM authors JOIN books ON authors.id = books.author_id\nWHERE books.name = @name;",
+ "name": "GetAuthorsByBookName",
+ "cmd": ":many",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "bigint"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio",
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "authors"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio"
+ },
+ {
+ "name": "books",
+ "length": -1,
+ "type": {},
+ "embedTable": {
+ "schema": "public",
+ "name": "books"
+ }
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 255,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "books"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);",
+ "name": "CreateNoteReturnId",
+ "cmd": ":execlastid",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "int"
+ },
+ "originalName": "id"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "body",
+ "notNull": true,
+ "length": 200,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "body"
+ }
+ }
+ ],
+ "filename": "query.sql",
+ "insert_into_table": {
+ "name": "notes"
+ }
+ },
+ {
+ "text": "SELECT id, body FROM notes WHERE id = @id;",
+ "name": "GetNote",
+ "cmd": ":one",
+ "columns": [
+ {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "int"
+ },
+ "originalName": "id"
+ },
+ {
+ "name": "body",
+ "notNull": true,
+ "length": 200,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "body"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "id",
+ "notNull": true,
+ "length": -1,
+ "isNamedParam": true,
+ "table": {
+ "schema": "public",
+ "name": "notes"
+ },
+ "type": {
+ "name": "int"
+ },
+ "originalName": "id"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "TRUNCATE TABLE notes;",
+ "name": "TruncateNotes",
+ "cmd": ":exec",
+ "filename": "query.sql"
+ },
+ {
+ "text": "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);",
+ "name": "CreateExtendedBio",
+ "cmd": ":exec",
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "author_name",
+ "notNull": true,
+ "length": 100,
+ "isNamedParam": true,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "author_name"
+ }
+ },
+ {
+ "number": 2,
+ "column": {
+ "name": "name",
+ "notNull": true,
+ "length": 100,
+ "isNamedParam": true,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ }
+ },
+ {
+ "number": 3,
+ "column": {
+ "name": "bio_type",
+ "length": 20,
+ "isNamedParam": true,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio_type"
+ }
+ }
+ ],
+ "filename": "query.sql",
+ "insert_into_table": {
+ "schema": "extended",
+ "name": "bios"
+ }
+ },
+ {
+ "text": "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;",
+ "name": "GetFirstExtendedBioByType",
+ "cmd": ":one",
+ "columns": [
+ {
+ "name": "author_name",
+ "notNull": true,
+ "length": 100,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "author_name"
+ },
+ {
+ "name": "name",
+ "notNull": true,
+ "length": 100,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "name"
+ },
+ {
+ "name": "bio_type",
+ "length": 20,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio_type"
+ }
+ ],
+ "parameters": [
+ {
+ "number": 1,
+ "column": {
+ "name": "bio_type",
+ "length": 20,
+ "isNamedParam": true,
+ "table": {
+ "schema": "extended",
+ "name": "bios"
+ },
+ "type": {
+ "name": "nvarchar"
+ },
+ "originalName": "bio_type"
+ }
+ }
+ ],
+ "filename": "query.sql"
+ },
+ {
+ "text": "TRUNCATE TABLE extended.bios;",
+ "name": "TruncateExtendedBios",
+ "cmd": ":exec",
+ "filename": "query.sql"
+ }
+ ],
+ "sqlc_version": "v1.31.1",
+ "plugin_options": "eyJvdmVycmlkZURyaXZlclZlcnNpb24iOiIiLCJnZW5lcmF0ZUNzcHJvaiI6dHJ1ZSwidGFyZ2V0RnJhbWV3b3JrIjoibmV0OC4wIiwibmFtZXNwYWNlTmFtZSI6Ik1zc3FsQXN5bmNFbnVtZXJhYmxlRXhhbXBsZUdlbiIsInVzZURhcHBlciI6ZmFsc2UsIm92ZXJyaWRlRGFwcGVyVmVyc2lvbiI6IiIsIm92ZXJyaWRlcyI6bnVsbCwiZGVidWdSZXF1ZXN0IjpmYWxzZSwidXNlQ2VudHJhbFBhY2thZ2VNYW5hZ2VtZW50IjpmYWxzZSwid2l0aEFzeW5jU3VmZml4Ijp0cnVlLCJ3aXRoQ2FuY2VsbGF0aW9uVG9rZW4iOnRydWUsInVzZUFzeW5jRW51bWVyYWJsZSI6dHJ1ZSwidXNlUHJvcGVyU2luZ3VsYXJpemF0aW9uIjpmYWxzZX0="
+}
\ No newline at end of file
diff --git a/examples/MssqlAsyncEnumerableExample/request.message b/examples/MssqlAsyncEnumerableExample/request.message
new file mode 100644
index 000000000..12f440d6b
Binary files /dev/null and b/examples/MssqlAsyncEnumerableExample/request.message differ
diff --git a/examples/MssqlDapperExample/Models.cs b/examples/MssqlDapperExample/Models.cs
new file mode 100644
index 000000000..5a332b9c9
--- /dev/null
+++ b/examples/MssqlDapperExample/Models.cs
@@ -0,0 +1,82 @@
+// auto-generated by sqlc - do not edit
+using Microsoft.SqlServer.Types;
+using NodaTime;
+using NodaTime.Extensions;
+using System;
+using System.Linq;
+
+namespace MssqlDapperExampleGen;
+public class Author
+{
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+};
+public class Book
+{
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public required long AuthorId { get; init; }
+ public string? Description { get; init; }
+};
+public class Note
+{
+ public required int Id { get; init; }
+ public required string Body { get; init; }
+};
+public class MssqlNumericType
+{
+ public bool? CBit { get; init; }
+ public byte? CTinyint { get; init; }
+ public short? CSmallint { get; init; }
+ public int? CInt { get; init; }
+ public long? CBigint { get; init; }
+ public float? CReal { get; init; }
+ public double? CFloat { get; init; }
+ public decimal? CDecimal { get; init; }
+ public decimal? CNumeric { get; init; }
+ public decimal? CMoney { get; init; }
+ public decimal? CSmallmoney { get; init; }
+};
+public class MssqlStringType
+{
+ public string? CChar { get; init; }
+ public string? CNchar { get; init; }
+ public string? CVarchar { get; init; }
+ public string? CNvarchar { get; init; }
+ public string? CVarcharMax { get; init; }
+ public string? CNvarcharMax { get; init; }
+};
+public class MssqlDatetimeType
+{
+ public DateTime? CDate { get; init; }
+ public DateTime? CDatetime { get; init; }
+ public DateTime? CDatetime2 { get; init; }
+ public DateTime? CSmalldatetime { get; init; }
+ public DateTimeOffset? CDatetimeoffset { get; init; }
+ public TimeSpan? CTime { get; init; }
+};
+public class MssqlBinaryType
+{
+ public byte[]? CBinary { get; init; }
+ public byte[]? CVarbinary { get; init; }
+ public byte[]? CVarbinaryMax { get; init; }
+ public Guid? CUniqueidentifier { get; init; }
+};
+public class MssqlSpatialType
+{
+ public SqlGeography? CGeography { get; init; }
+ public SqlGeometry? CGeometry { get; init; }
+ public SqlHierarchyId? CHierarchyid { get; init; }
+};
+public class MssqlNodaType
+{
+ public DateTime? CDatetime2Instant { get; init; }
+ public DateTimeOffset? CDatetimeoffsetInstant { get; init; }
+};
+public class ExtendedBio
+{
+ public required string AuthorName { get; init; }
+ public required string Name { get; init; }
+ public string? BioType { get; init; }
+};
\ No newline at end of file
diff --git a/examples/MssqlDapperExample/MssqlDapperExample.csproj b/examples/MssqlDapperExample/MssqlDapperExample.csproj
new file mode 100644
index 000000000..0914e6f74
--- /dev/null
+++ b/examples/MssqlDapperExample/MssqlDapperExample.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+ net9.0
+ MssqlDapperExampleGen
+ Library
+ enable
+ False
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/MssqlDapperExample/QuerySql.cs b/examples/MssqlDapperExample/QuerySql.cs
new file mode 100644
index 000000000..d35adae5e
--- /dev/null
+++ b/examples/MssqlDapperExample/QuerySql.cs
@@ -0,0 +1,1615 @@
+// auto-generated by sqlc - do not edit
+// ReSharper disable UseObjectOrCollectionInitializer
+// ReSharper disable UseAwaitUsing
+// ReSharper disable ConvertToUsingDeclaration
+// ReSharper disable NotAccessedPositionalProperty.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+using Dapper;
+using Microsoft.Data.SqlClient;
+using Microsoft.SqlServer.Types;
+using NodaTime;
+using NodaTime.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MssqlDapperExampleGen;
+public class QuerySql
+{
+ public QuerySql()
+ {
+ Utils.ConfigureSqlMapper();
+ DefaultTypeMap.MatchNamesWithUnderscores = true;
+ }
+
+ public QuerySql(string connectionString) : this()
+ {
+ this.ConnectionString = connectionString;
+ }
+
+ private QuerySql(SqlTransaction transaction) : this()
+ {
+ this.Transaction = transaction;
+ }
+
+ public static QuerySql WithTransaction(SqlTransaction transaction)
+ {
+ return new QuerySql(transaction);
+ }
+
+ private SqlTransaction? Transaction { get; }
+ private string? ConnectionString { get; }
+
+ private const string GetAuthorSql = @"SELECT TOP 1 id, name, bio FROM authors
+ WHERE name = @name;";
+ public class GetAuthorRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class GetAuthorArgs
+ {
+ public required string Name { get; init; }
+ };
+ public async Task GetAuthorAsync(GetAuthorArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("name", args.Name);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string ListAuthorsSql = @"SELECT id, name, bio
+ FROM authors
+ ORDER BY name
+ OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY;";
+ public class ListAuthorsRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class ListAuthorsArgs
+ {
+ public required int Offset { get; init; }
+ public required int Limit { get; init; }
+ };
+ public async Task> ListAuthorsAsync(ListAuthorsArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("offset", args.Offset);
+ queryParams.Add("limit", args.Limit);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryAsync(ListAuthorsSql, queryParams);
+ return result.AsList();
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return (await this.Transaction.Connection.QueryAsync(ListAuthorsSql, queryParams, transaction: this.Transaction)).AsList();
+ }
+
+ private const string CreateAuthorSql = "INSERT INTO authors (id, name, bio) VALUES (@id, @name, @bio);";
+ public class CreateAuthorArgs
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public async Task CreateAuthorAsync(CreateAuthorArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("id", args.Id);
+ queryParams.Add("name", args.Name);
+ queryParams.Add("bio", args.Bio);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(CreateAuthorSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(CreateAuthorSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string CreateAuthorReturnIdSql = "INSERT INTO authors (name, bio) OUTPUT INSERTED.id VALUES (@name, @bio);";
+ public class CreateAuthorReturnIdRow
+ {
+ public required long Id { get; init; }
+ };
+ public class CreateAuthorReturnIdArgs
+ {
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public async Task CreateAuthorReturnIdAsync(CreateAuthorReturnIdArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("name", args.Name);
+ queryParams.Add("bio", args.Bio);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ return await connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams);
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QuerySingleAsync(CreateAuthorReturnIdSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetAuthorByIdSql = @"SELECT TOP 1 id, name, bio FROM authors -- test rest of line comment
+ WHERE id = @id;";
+ public class GetAuthorByIdRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class GetAuthorByIdArgs
+ {
+ public required long Id { get; init; }
+ };
+ public async Task GetAuthorByIdAsync(GetAuthorByIdArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("id", args.Id);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetAuthorByIdSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetAuthorByNamePatternSql = @"SELECT id, name, bio FROM authors
+ WHERE name LIKE COALESCE(@name_pattern, '%');";
+ public class GetAuthorByNamePatternRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class GetAuthorByNamePatternArgs
+ {
+ public object? NamePattern { get; init; }
+ };
+ public async Task> GetAuthorByNamePatternAsync(GetAuthorByNamePatternArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("name_pattern", args.NamePattern);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryAsync(GetAuthorByNamePatternSql, queryParams);
+ return result.AsList();
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return (await this.Transaction.Connection.QueryAsync(GetAuthorByNamePatternSql, queryParams, transaction: this.Transaction)).AsList();
+ }
+
+ private const string DeleteAuthorSql = @"DELETE FROM authors
+ WHERE name = @name;";
+ public class DeleteAuthorArgs
+ {
+ public required string Name { get; init; }
+ };
+ public async Task DeleteAuthorAsync(DeleteAuthorArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("name", args.Name);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(DeleteAuthorSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(DeleteAuthorSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string DeleteAllAuthorsSql = "DELETE FROM authors;";
+ public async Task DeleteAllAuthorsAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(DeleteAllAuthorsSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(DeleteAllAuthorsSql, transaction: this.Transaction);
+ }
+
+ private const string UpdateAuthorsSql = @"UPDATE authors
+ SET bio = @bio
+ WHERE bio IS NOT NULL;";
+ public class UpdateAuthorsArgs
+ {
+ public string? Bio { get; init; }
+ };
+ public async Task UpdateAuthorsAsync(UpdateAuthorsArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("bio", args.Bio);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ return await connection.ExecuteAsync(UpdateAuthorsSql, queryParams);
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.ExecuteAsync(UpdateAuthorsSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetAuthorsByIdsSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids);";
+ public class GetAuthorsByIdsRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class GetAuthorsByIdsArgs
+ {
+ public required long[] Ids { get; init; }
+ };
+ public async Task> GetAuthorsByIdsAsync(GetAuthorsByIdsArgs args)
+ {
+ var transformedSql = GetAuthorsByIdsSql;
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids");
+ var queryParams = new Dictionary();
+ for (int i = 0; i < args.Ids.Length; i++)
+ queryParams.Add($"@idsArg{i}", args.Ids[i]);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryAsync(transformedSql, queryParams);
+ return result.AsList();
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList();
+ }
+
+ private const string GetAuthorsByIdsAndNamesSql = "SELECT id, name, bio FROM authors WHERE id IN (/*SLICE:ids*/@ids) AND name IN (/*SLICE:names*/@names);";
+ public class GetAuthorsByIdsAndNamesRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ };
+ public class GetAuthorsByIdsAndNamesArgs
+ {
+ public required long[] Ids { get; init; }
+ public required string[] Names { get; init; }
+ };
+ public async Task> GetAuthorsByIdsAndNamesAsync(GetAuthorsByIdsAndNamesArgs args)
+ {
+ var transformedSql = GetAuthorsByIdsAndNamesSql;
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Ids.Length, "ids");
+ transformedSql = Utils.TransformQueryForSliceArgs(transformedSql, args.Names.Length, "names");
+ var queryParams = new Dictionary();
+ for (int i = 0; i < args.Ids.Length; i++)
+ queryParams.Add($"@idsArg{i}", args.Ids[i]);
+ for (int i = 0; i < args.Names.Length; i++)
+ queryParams.Add($"@namesArg{i}", args.Names[i]);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryAsync(transformedSql, queryParams);
+ return result.AsList();
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return (await this.Transaction.Connection.QueryAsync(transformedSql, queryParams, transaction: this.Transaction)).AsList();
+ }
+
+ private const string CreateBookSql = "INSERT INTO books (name, author_id) OUTPUT INSERTED.id VALUES (@name, @author_id);";
+ public class CreateBookRow
+ {
+ public required long Id { get; init; }
+ };
+ public class CreateBookArgs
+ {
+ public required string Name { get; init; }
+ public required long AuthorId { get; init; }
+ };
+ public async Task CreateBookAsync(CreateBookArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("name", args.Name);
+ queryParams.Add("author_id", args.AuthorId);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ return await connection.QuerySingleAsync(CreateBookSql, queryParams);
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QuerySingleAsync(CreateBookSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string ListAllAuthorsBooksSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description
+ FROM authors JOIN books ON authors.id = books.author_id
+ ORDER BY authors.name;";
+ public class ListAllAuthorsBooksRow
+ {
+ public required Author? Author { get; init; }
+ public required Book? Book { get; init; }
+ };
+ public async Task> ListAllAuthorsBooksAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ using (var command = new SqlCommand(ListAllAuthorsBooksSql, connection))
+ {
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
+ return result;
+ }
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = ListAllAuthorsBooksSql;
+ command.Transaction = this.Transaction;
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
+ return result;
+ }
+ }
+ }
+
+ private const string GetDuplicateAuthorsSql = @"SELECT authors1.id, authors1.name, authors1.bio, authors2.id, authors2.name, authors2.bio
+ FROM authors authors1 JOIN authors authors2 ON authors1.name = authors2.name
+ WHERE authors1.id < authors2.id;";
+ public class GetDuplicateAuthorsRow
+ {
+ public required Author? Author { get; init; }
+ public required Author? Author2 { get; init; }
+ };
+ public async Task> GetDuplicateAuthorsAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ using (var command = new SqlCommand(GetDuplicateAuthorsSql, connection))
+ {
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } });
+ return result;
+ }
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetDuplicateAuthorsSql;
+ command.Transaction = this.Transaction;
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new GetDuplicateAuthorsRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Author2 = new Author { Id = reader.GetInt64(3), Name = reader.GetString(4), Bio = reader.IsDBNull(5) ? null : reader.GetString(5) } });
+ return result;
+ }
+ }
+ }
+
+ private const string GetAuthorsByBookNameSql = @"SELECT authors.id, authors.name, authors.bio, books.id, books.name, books.author_id, books.description
+ FROM authors JOIN books ON authors.id = books.author_id
+ WHERE books.name = @name;";
+ public class GetAuthorsByBookNameRow
+ {
+ public required long Id { get; init; }
+ public required string Name { get; init; }
+ public string? Bio { get; init; }
+ public required Book? Book { get; init; }
+ };
+ public class GetAuthorsByBookNameArgs
+ {
+ public required string Name { get; init; }
+ };
+ public async Task> GetAuthorsByBookNameAsync(GetAuthorsByBookNameArgs args)
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ using (var command = new SqlCommand(GetAuthorsByBookNameSql, connection))
+ {
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
+ return result;
+ }
+ }
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ using (var command = this.Transaction.Connection.CreateCommand())
+ {
+ command.CommandText = GetAuthorsByBookNameSql;
+ command.Transaction = this.Transaction;
+ command.Parameters.Add("@name", SqlDbType.NVarChar).Value = args.Name;
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ var result = new List();
+ while (await reader.ReadAsync())
+ result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
+ return result;
+ }
+ }
+ }
+
+ private const string CreateNoteReturnIdSql = "INSERT INTO notes (body) OUTPUT INSERTED.id VALUES (@body);";
+ public class CreateNoteReturnIdRow
+ {
+ public required int Id { get; init; }
+ };
+ public class CreateNoteReturnIdArgs
+ {
+ public required string Body { get; init; }
+ };
+ public async Task CreateNoteReturnIdAsync(CreateNoteReturnIdArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("body", args.Body);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ return await connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams);
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QuerySingleAsync(CreateNoteReturnIdSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetNoteSql = "SELECT id, body FROM notes WHERE id = @id;";
+ public class GetNoteRow
+ {
+ public required int Id { get; init; }
+ public required string Body { get; init; }
+ };
+ public class GetNoteArgs
+ {
+ public required int Id { get; init; }
+ };
+ public async Task GetNoteAsync(GetNoteArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("id", args.Id);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetNoteSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string TruncateNotesSql = "TRUNCATE TABLE notes;";
+ public async Task TruncateNotesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateNotesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateNotesSql, transaction: this.Transaction);
+ }
+
+ private const string CreateExtendedBioSql = "INSERT INTO extended.bios (author_name, name, bio_type) VALUES (@author_name, @name, @bio_type);";
+ public class CreateExtendedBioArgs
+ {
+ public required string AuthorName { get; init; }
+ public required string Name { get; init; }
+ public string? BioType { get; init; }
+ };
+ public async Task CreateExtendedBioAsync(CreateExtendedBioArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("author_name", args.AuthorName);
+ queryParams.Add("name", args.Name);
+ queryParams.Add("bio_type", args.BioType);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(CreateExtendedBioSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(CreateExtendedBioSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetFirstExtendedBioByTypeSql = "SELECT TOP 1 author_name, name, bio_type FROM extended.bios WHERE bio_type = @bio_type;";
+ public class GetFirstExtendedBioByTypeRow
+ {
+ public required string AuthorName { get; init; }
+ public required string Name { get; init; }
+ public string? BioType { get; init; }
+ };
+ public class GetFirstExtendedBioByTypeArgs
+ {
+ public string? BioType { get; init; }
+ };
+ public async Task GetFirstExtendedBioByTypeAsync(GetFirstExtendedBioByTypeArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("bio_type", args.BioType);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetFirstExtendedBioByTypeSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string TruncateExtendedBiosSql = "TRUNCATE TABLE extended.bios;";
+ public async Task TruncateExtendedBiosAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateExtendedBiosSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateExtendedBiosSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlNumericTypesSql = @"
+ INSERT INTO mssql_numeric_types
+ (
+ c_bit,
+ c_tinyint,
+ c_smallint,
+ c_int,
+ c_bigint,
+ c_real,
+ c_float,
+ c_decimal,
+ c_numeric,
+ c_money,
+ c_smallmoney
+ )
+ VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);";
+ public class InsertMssqlNumericTypesArgs
+ {
+ public bool? CBit { get; init; }
+ public byte? CTinyint { get; init; }
+ public short? CSmallint { get; init; }
+ public int? CInt { get; init; }
+ public long? CBigint { get; init; }
+ public float? CReal { get; init; }
+ public double? CFloat { get; init; }
+ public decimal? CDecimal { get; init; }
+ public decimal? CNumeric { get; init; }
+ public decimal? CMoney { get; init; }
+ public decimal? CSmallmoney { get; init; }
+ };
+ public async Task InsertMssqlNumericTypesAsync(InsertMssqlNumericTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_bit", args.CBit);
+ queryParams.Add("c_tinyint", args.CTinyint);
+ queryParams.Add("c_smallint", args.CSmallint);
+ queryParams.Add("c_int", args.CInt);
+ queryParams.Add("c_bigint", args.CBigint);
+ queryParams.Add("c_real", args.CReal);
+ queryParams.Add("c_float", args.CFloat);
+ queryParams.Add("c_decimal", args.CDecimal);
+ queryParams.Add("c_numeric", args.CNumeric);
+ queryParams.Add("c_money", args.CMoney);
+ queryParams.Add("c_smallmoney", args.CSmallmoney);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlNumericTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlNumericTypesBatchSql = @"INSERT INTO mssql_numeric_types
+ (
+ c_bit,
+ c_tinyint,
+ c_smallint,
+ c_int,
+ c_bigint,
+ c_real,
+ c_float,
+ c_decimal,
+ c_numeric,
+ c_money,
+ c_smallmoney
+ )
+ VALUES (@c_bit, @c_tinyint, @c_smallint, @c_int, @c_bigint, @c_real, @c_float, @c_decimal, @c_numeric, @c_money, @c_smallmoney);";
+ public class InsertMssqlNumericTypesBatchArgs
+ {
+ public bool? CBit { get; init; }
+ public byte? CTinyint { get; init; }
+ public short? CSmallint { get; init; }
+ public int? CInt { get; init; }
+ public long? CBigint { get; init; }
+ public float? CReal { get; init; }
+ public double? CFloat { get; init; }
+ public decimal? CDecimal { get; init; }
+ public decimal? CNumeric { get; init; }
+ public decimal? CMoney { get; init; }
+ public decimal? CSmallmoney { get; init; }
+ };
+ public async Task InsertMssqlNumericTypesBatchAsync(List args)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ var dataTable = new DataTable();
+ dataTable.Columns.Add("c_bit", typeof(object));
+ dataTable.Columns.Add("c_tinyint", typeof(object));
+ dataTable.Columns.Add("c_smallint", typeof(object));
+ dataTable.Columns.Add("c_int", typeof(object));
+ dataTable.Columns.Add("c_bigint", typeof(object));
+ dataTable.Columns.Add("c_real", typeof(object));
+ dataTable.Columns.Add("c_float", typeof(object));
+ dataTable.Columns.Add("c_decimal", typeof(object));
+ dataTable.Columns.Add("c_numeric", typeof(object));
+ dataTable.Columns.Add("c_money", typeof(object));
+ dataTable.Columns.Add("c_smallmoney", typeof(object));
+ foreach (var row in args)
+ dataTable.Rows.Add(row.CBit, row.CTinyint, row.CSmallint, row.CInt, row.CBigint, row.CReal, row.CFloat, row.CDecimal, row.CNumeric, row.CMoney, row.CSmallmoney);
+ using (var bulkCopy = new SqlBulkCopy(connection))
+ {
+ bulkCopy.DestinationTableName = "[mssql_numeric_types]";
+ bulkCopy.ColumnMappings.Add("c_bit", "c_bit");
+ bulkCopy.ColumnMappings.Add("c_tinyint", "c_tinyint");
+ bulkCopy.ColumnMappings.Add("c_smallint", "c_smallint");
+ bulkCopy.ColumnMappings.Add("c_int", "c_int");
+ bulkCopy.ColumnMappings.Add("c_bigint", "c_bigint");
+ bulkCopy.ColumnMappings.Add("c_real", "c_real");
+ bulkCopy.ColumnMappings.Add("c_float", "c_float");
+ bulkCopy.ColumnMappings.Add("c_decimal", "c_decimal");
+ bulkCopy.ColumnMappings.Add("c_numeric", "c_numeric");
+ bulkCopy.ColumnMappings.Add("c_money", "c_money");
+ bulkCopy.ColumnMappings.Add("c_smallmoney", "c_smallmoney");
+ await bulkCopy.WriteToServerAsync(dataTable);
+ }
+ }
+ }
+
+ private const string GetMssqlNumericTypesSql = "SELECT TOP 1 c_bit, c_tinyint, c_smallint, c_int, c_bigint, c_real, c_float, c_decimal, c_numeric, c_money, c_smallmoney FROM mssql_numeric_types;";
+ public class GetMssqlNumericTypesRow
+ {
+ public bool? CBit { get; init; }
+ public byte? CTinyint { get; init; }
+ public short? CSmallint { get; init; }
+ public int? CInt { get; init; }
+ public long? CBigint { get; init; }
+ public float? CReal { get; init; }
+ public double? CFloat { get; init; }
+ public decimal? CDecimal { get; init; }
+ public decimal? CNumeric { get; init; }
+ public decimal? CMoney { get; init; }
+ public decimal? CSmallmoney { get; init; }
+ };
+ public async Task GetMssqlNumericTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlNumericTypesCntSql = @"SELECT TOP 1
+ COUNT(*) AS cnt,
+ c_bit,
+ c_tinyint,
+ c_smallint,
+ c_int,
+ c_bigint,
+ c_real,
+ c_float,
+ c_decimal,
+ c_numeric,
+ c_money,
+ c_smallmoney
+ FROM mssql_numeric_types
+ GROUP BY
+ c_bit,
+ c_tinyint,
+ c_smallint,
+ c_int,
+ c_bigint,
+ c_real,
+ c_float,
+ c_decimal,
+ c_numeric,
+ c_money,
+ c_smallmoney;";
+ public class GetMssqlNumericTypesCntRow
+ {
+ public required int Cnt { get; init; }
+ public bool? CBit { get; init; }
+ public byte? CTinyint { get; init; }
+ public short? CSmallint { get; init; }
+ public int? CInt { get; init; }
+ public long? CBigint { get; init; }
+ public float? CReal { get; init; }
+ public double? CFloat { get; init; }
+ public decimal? CDecimal { get; init; }
+ public decimal? CNumeric { get; init; }
+ public decimal? CMoney { get; init; }
+ public decimal? CSmallmoney { get; init; }
+ };
+ public async Task GetMssqlNumericTypesCntAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNumericTypesCntSql, transaction: this.Transaction);
+ }
+
+ private const string TruncateMssqlNumericTypesSql = "TRUNCATE TABLE mssql_numeric_types;";
+ public async Task TruncateMssqlNumericTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateMssqlNumericTypesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateMssqlNumericTypesSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlStringTypesSql = @"
+ INSERT INTO mssql_string_types
+ (
+ c_char,
+ c_nchar,
+ c_varchar,
+ c_nvarchar,
+ c_varchar_max,
+ c_nvarchar_max
+ )
+ VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);";
+ public class InsertMssqlStringTypesArgs
+ {
+ public string? CChar { get; init; }
+ public string? CNchar { get; init; }
+ public string? CVarchar { get; init; }
+ public string? CNvarchar { get; init; }
+ public string? CVarcharMax { get; init; }
+ public string? CNvarcharMax { get; init; }
+ };
+ public async Task InsertMssqlStringTypesAsync(InsertMssqlStringTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_char", args.CChar);
+ queryParams.Add("c_nchar", args.CNchar);
+ queryParams.Add("c_varchar", args.CVarchar);
+ queryParams.Add("c_nvarchar", args.CNvarchar);
+ queryParams.Add("c_varchar_max", args.CVarcharMax);
+ queryParams.Add("c_nvarchar_max", args.CNvarcharMax);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlStringTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlStringTypesBatchSql = @"INSERT INTO mssql_string_types
+ (
+ c_char,
+ c_nchar,
+ c_varchar,
+ c_nvarchar,
+ c_varchar_max,
+ c_nvarchar_max
+ )
+ VALUES (@c_char, @c_nchar, @c_varchar, @c_nvarchar, @c_varchar_max, @c_nvarchar_max);";
+ public class InsertMssqlStringTypesBatchArgs
+ {
+ public string? CChar { get; init; }
+ public string? CNchar { get; init; }
+ public string? CVarchar { get; init; }
+ public string? CNvarchar { get; init; }
+ public string? CVarcharMax { get; init; }
+ public string? CNvarcharMax { get; init; }
+ };
+ public async Task InsertMssqlStringTypesBatchAsync(List args)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ var dataTable = new DataTable();
+ dataTable.Columns.Add("c_char", typeof(object));
+ dataTable.Columns.Add("c_nchar", typeof(object));
+ dataTable.Columns.Add("c_varchar", typeof(object));
+ dataTable.Columns.Add("c_nvarchar", typeof(object));
+ dataTable.Columns.Add("c_varchar_max", typeof(object));
+ dataTable.Columns.Add("c_nvarchar_max", typeof(object));
+ foreach (var row in args)
+ dataTable.Rows.Add(row.CChar, row.CNchar, row.CVarchar, row.CNvarchar, row.CVarcharMax, row.CNvarcharMax);
+ using (var bulkCopy = new SqlBulkCopy(connection))
+ {
+ bulkCopy.DestinationTableName = "[mssql_string_types]";
+ bulkCopy.ColumnMappings.Add("c_char", "c_char");
+ bulkCopy.ColumnMappings.Add("c_nchar", "c_nchar");
+ bulkCopy.ColumnMappings.Add("c_varchar", "c_varchar");
+ bulkCopy.ColumnMappings.Add("c_nvarchar", "c_nvarchar");
+ bulkCopy.ColumnMappings.Add("c_varchar_max", "c_varchar_max");
+ bulkCopy.ColumnMappings.Add("c_nvarchar_max", "c_nvarchar_max");
+ await bulkCopy.WriteToServerAsync(dataTable);
+ }
+ }
+ }
+
+ private const string GetMssqlStringTypesSql = "SELECT TOP 1 c_char, c_nchar, c_varchar, c_nvarchar, c_varchar_max, c_nvarchar_max FROM mssql_string_types;";
+ public class GetMssqlStringTypesRow
+ {
+ public string? CChar { get; init; }
+ public string? CNchar { get; init; }
+ public string? CVarchar { get; init; }
+ public string? CNvarchar { get; init; }
+ public string? CVarcharMax { get; init; }
+ public string? CNvarcharMax { get; init; }
+ };
+ public async Task GetMssqlStringTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlStringTypesCntSql = @"SELECT TOP 1
+ COUNT(*) AS cnt,
+ c_char,
+ c_nchar,
+ c_varchar,
+ c_nvarchar,
+ c_varchar_max,
+ c_nvarchar_max
+ FROM mssql_string_types
+ GROUP BY
+ c_char,
+ c_nchar,
+ c_varchar,
+ c_nvarchar,
+ c_varchar_max,
+ c_nvarchar_max;";
+ public class GetMssqlStringTypesCntRow
+ {
+ public required int Cnt { get; init; }
+ public string? CChar { get; init; }
+ public string? CNchar { get; init; }
+ public string? CVarchar { get; init; }
+ public string? CNvarchar { get; init; }
+ public string? CVarcharMax { get; init; }
+ public string? CNvarcharMax { get; init; }
+ };
+ public async Task GetMssqlStringTypesCntAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlStringTypesCntSql, transaction: this.Transaction);
+ }
+
+ private const string TruncateMssqlStringTypesSql = "TRUNCATE TABLE mssql_string_types;";
+ public async Task TruncateMssqlStringTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateMssqlStringTypesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateMssqlStringTypesSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlDatetimeTypesSql = @"
+ INSERT INTO mssql_datetime_types
+ (
+ c_date,
+ c_datetime,
+ c_datetime2,
+ c_smalldatetime,
+ c_datetimeoffset,
+ c_time
+ )
+ VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);";
+ public class InsertMssqlDatetimeTypesArgs
+ {
+ public DateTime? CDate { get; init; }
+ public DateTime? CDatetime { get; init; }
+ public DateTime? CDatetime2 { get; init; }
+ public DateTime? CSmalldatetime { get; init; }
+ public DateTimeOffset? CDatetimeoffset { get; init; }
+ public TimeSpan? CTime { get; init; }
+ };
+ public async Task InsertMssqlDatetimeTypesAsync(InsertMssqlDatetimeTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_date", new Utils.DateTime2Parameter(args.CDate));
+ queryParams.Add("c_datetime", new Utils.DateTime2Parameter(args.CDatetime));
+ queryParams.Add("c_datetime2", new Utils.DateTime2Parameter(args.CDatetime2));
+ queryParams.Add("c_smalldatetime", new Utils.DateTime2Parameter(args.CSmalldatetime));
+ queryParams.Add("c_datetimeoffset", args.CDatetimeoffset);
+ queryParams.Add("c_time", args.CTime);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlDatetimeTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlDatetimeTypesBatchSql = @"INSERT INTO mssql_datetime_types
+ (
+ c_date,
+ c_datetime,
+ c_datetime2,
+ c_smalldatetime,
+ c_datetimeoffset,
+ c_time
+ )
+ VALUES (@c_date, @c_datetime, @c_datetime2, @c_smalldatetime, @c_datetimeoffset, @c_time);";
+ public class InsertMssqlDatetimeTypesBatchArgs
+ {
+ public DateTime? CDate { get; init; }
+ public DateTime? CDatetime { get; init; }
+ public DateTime? CDatetime2 { get; init; }
+ public DateTime? CSmalldatetime { get; init; }
+ public DateTimeOffset? CDatetimeoffset { get; init; }
+ public TimeSpan? CTime { get; init; }
+ };
+ public async Task InsertMssqlDatetimeTypesBatchAsync(List args)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ var dataTable = new DataTable();
+ dataTable.Columns.Add("c_date", typeof(object));
+ dataTable.Columns.Add("c_datetime", typeof(object));
+ dataTable.Columns.Add("c_datetime2", typeof(object));
+ dataTable.Columns.Add("c_smalldatetime", typeof(object));
+ dataTable.Columns.Add("c_datetimeoffset", typeof(object));
+ dataTable.Columns.Add("c_time", typeof(object));
+ foreach (var row in args)
+ dataTable.Rows.Add(row.CDate ?? (object)DBNull.Value, row.CDatetime ?? (object)DBNull.Value, row.CDatetime2 ?? (object)DBNull.Value, row.CSmalldatetime ?? (object)DBNull.Value, row.CDatetimeoffset, row.CTime);
+ using (var bulkCopy = new SqlBulkCopy(connection))
+ {
+ bulkCopy.DestinationTableName = "[mssql_datetime_types]";
+ bulkCopy.ColumnMappings.Add("c_date", "c_date");
+ bulkCopy.ColumnMappings.Add("c_datetime", "c_datetime");
+ bulkCopy.ColumnMappings.Add("c_datetime2", "c_datetime2");
+ bulkCopy.ColumnMappings.Add("c_smalldatetime", "c_smalldatetime");
+ bulkCopy.ColumnMappings.Add("c_datetimeoffset", "c_datetimeoffset");
+ bulkCopy.ColumnMappings.Add("c_time", "c_time");
+ await bulkCopy.WriteToServerAsync(dataTable);
+ }
+ }
+ }
+
+ private const string GetMssqlDatetimeTypesSql = "SELECT TOP 1 c_date, c_datetime, c_datetime2, c_smalldatetime, c_datetimeoffset, c_time FROM mssql_datetime_types;";
+ public class GetMssqlDatetimeTypesRow
+ {
+ public DateTime? CDate { get; init; }
+ public DateTime? CDatetime { get; init; }
+ public DateTime? CDatetime2 { get; init; }
+ public DateTime? CSmalldatetime { get; init; }
+ public DateTimeOffset? CDatetimeoffset { get; init; }
+ public TimeSpan? CTime { get; init; }
+ };
+ public async Task GetMssqlDatetimeTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlDatetimeTypesCntSql = @"SELECT TOP 1
+ COUNT(*) AS cnt,
+ c_date,
+ c_datetime,
+ c_datetime2,
+ c_smalldatetime,
+ c_datetimeoffset,
+ c_time
+ FROM mssql_datetime_types
+ GROUP BY
+ c_date,
+ c_datetime,
+ c_datetime2,
+ c_smalldatetime,
+ c_datetimeoffset,
+ c_time;";
+ public class GetMssqlDatetimeTypesCntRow
+ {
+ public required int Cnt { get; init; }
+ public DateTime? CDate { get; init; }
+ public DateTime? CDatetime { get; init; }
+ public DateTime? CDatetime2 { get; init; }
+ public DateTime? CSmalldatetime { get; init; }
+ public DateTimeOffset? CDatetimeoffset { get; init; }
+ public TimeSpan? CTime { get; init; }
+ };
+ public async Task GetMssqlDatetimeTypesCntAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlDatetimeTypesCntSql, transaction: this.Transaction);
+ }
+
+ private const string TruncateMssqlDatetimeTypesSql = "TRUNCATE TABLE mssql_datetime_types;";
+ public async Task TruncateMssqlDatetimeTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateMssqlDatetimeTypesSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlBinaryTypesSql = @"
+ INSERT INTO mssql_binary_types
+ (
+ c_binary,
+ c_varbinary,
+ c_varbinary_max,
+ c_uniqueidentifier
+ )
+ VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);";
+ public class InsertMssqlBinaryTypesArgs
+ {
+ public byte[]? CBinary { get; init; }
+ public byte[]? CVarbinary { get; init; }
+ public byte[]? CVarbinaryMax { get; init; }
+ public Guid? CUniqueidentifier { get; init; }
+ };
+ public async Task InsertMssqlBinaryTypesAsync(InsertMssqlBinaryTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_binary", new Utils.BinaryParameter(args.CBinary));
+ queryParams.Add("c_varbinary", new Utils.BinaryParameter(args.CVarbinary));
+ queryParams.Add("c_varbinary_max", new Utils.BinaryParameter(args.CVarbinaryMax));
+ queryParams.Add("c_uniqueidentifier", args.CUniqueidentifier);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlBinaryTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlBinaryTypesBatchSql = @"INSERT INTO mssql_binary_types
+ (
+ c_binary,
+ c_varbinary,
+ c_varbinary_max,
+ c_uniqueidentifier
+ )
+ VALUES (@c_binary, @c_varbinary, @c_varbinary_max, @c_uniqueidentifier);";
+ public class InsertMssqlBinaryTypesBatchArgs
+ {
+ public byte[]? CBinary { get; init; }
+ public byte[]? CVarbinary { get; init; }
+ public byte[]? CVarbinaryMax { get; init; }
+ public Guid? CUniqueidentifier { get; init; }
+ };
+ public async Task InsertMssqlBinaryTypesBatchAsync(List args)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ var dataTable = new DataTable();
+ dataTable.Columns.Add("c_binary", typeof(object));
+ dataTable.Columns.Add("c_varbinary", typeof(object));
+ dataTable.Columns.Add("c_varbinary_max", typeof(object));
+ dataTable.Columns.Add("c_uniqueidentifier", typeof(object));
+ foreach (var row in args)
+ dataTable.Rows.Add(row.CBinary ?? (object)DBNull.Value, row.CVarbinary ?? (object)DBNull.Value, row.CVarbinaryMax ?? (object)DBNull.Value, row.CUniqueidentifier);
+ using (var bulkCopy = new SqlBulkCopy(connection))
+ {
+ bulkCopy.DestinationTableName = "[mssql_binary_types]";
+ bulkCopy.ColumnMappings.Add("c_binary", "c_binary");
+ bulkCopy.ColumnMappings.Add("c_varbinary", "c_varbinary");
+ bulkCopy.ColumnMappings.Add("c_varbinary_max", "c_varbinary_max");
+ bulkCopy.ColumnMappings.Add("c_uniqueidentifier", "c_uniqueidentifier");
+ await bulkCopy.WriteToServerAsync(dataTable);
+ }
+ }
+ }
+
+ private const string GetMssqlBinaryTypesSql = "SELECT TOP 1 c_binary, c_varbinary, c_varbinary_max, c_uniqueidentifier FROM mssql_binary_types;";
+ public class GetMssqlBinaryTypesRow
+ {
+ public byte[]? CBinary { get; init; }
+ public byte[]? CVarbinary { get; init; }
+ public byte[]? CVarbinaryMax { get; init; }
+ public Guid? CUniqueidentifier { get; init; }
+ };
+ public async Task GetMssqlBinaryTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlBinaryTypesCntSql = @"SELECT TOP 1
+ COUNT(*) AS cnt,
+ c_binary,
+ c_varbinary,
+ c_varbinary_max,
+ c_uniqueidentifier
+ FROM mssql_binary_types
+ GROUP BY
+ c_binary,
+ c_varbinary,
+ c_varbinary_max,
+ c_uniqueidentifier;";
+ public class GetMssqlBinaryTypesCntRow
+ {
+ public required int Cnt { get; init; }
+ public byte[]? CBinary { get; init; }
+ public byte[]? CVarbinary { get; init; }
+ public byte[]? CVarbinaryMax { get; init; }
+ public Guid? CUniqueidentifier { get; init; }
+ };
+ public async Task GetMssqlBinaryTypesCntAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlBinaryTypesCntSql, transaction: this.Transaction);
+ }
+
+ private const string TruncateMssqlBinaryTypesSql = "TRUNCATE TABLE mssql_binary_types;";
+ public async Task TruncateMssqlBinaryTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateMssqlBinaryTypesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateMssqlBinaryTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlFunctionsSql = @"
+ SELECT
+ MAX(c_int) AS max_int,
+ MAX(c_varchar) AS max_varchar,
+ MAX(c_datetime2) AS max_datetime2
+ FROM mssql_numeric_types
+ CROSS JOIN mssql_string_types
+ CROSS JOIN mssql_datetime_types;";
+ public class GetMssqlFunctionsRow
+ {
+ public int? MaxInt { get; init; }
+ public string? MaxVarchar { get; init; }
+ public required DateTime MaxDatetime2 { get; init; }
+ };
+ public async Task GetMssqlFunctionsAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlFunctionsSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlSpatialTypesSql = @"
+ INSERT INTO mssql_spatial_types (c_geography, c_geometry, c_hierarchyid) VALUES (@c_geography, @c_geometry, @c_hierarchyid);";
+ public class InsertMssqlSpatialTypesArgs
+ {
+ public SqlGeography? CGeography { get; init; }
+ public SqlGeometry? CGeometry { get; init; }
+ public SqlHierarchyId? CHierarchyid { get; init; }
+ };
+ public async Task InsertMssqlSpatialTypesAsync(InsertMssqlSpatialTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_geography", args.CGeography);
+ queryParams.Add("c_geometry", args.CGeometry);
+ queryParams.Add("c_hierarchyid", args.CHierarchyid);
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlSpatialTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlSpatialTypesSql = "SELECT TOP 1 c_geography, c_geometry, c_hierarchyid FROM mssql_spatial_types;";
+ public class GetMssqlSpatialTypesRow
+ {
+ public SqlGeography? CGeography { get; init; }
+ public SqlGeometry? CGeometry { get; init; }
+ public SqlHierarchyId? CHierarchyid { get; init; }
+ };
+ public async Task GetMssqlSpatialTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlSpatialTypesSql, transaction: this.Transaction);
+ }
+
+ private const string TruncateMssqlSpatialTypesSql = "TRUNCATE TABLE mssql_spatial_types;";
+ public async Task TruncateMssqlSpatialTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(TruncateMssqlSpatialTypesSql);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(TruncateMssqlSpatialTypesSql, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlNodaTypesSql = @"
+ INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);";
+ public class InsertMssqlNodaTypesArgs
+ {
+ public Instant? CDatetime2Instant { get; init; }
+ public Instant? CDatetimeoffsetInstant { get; init; }
+ };
+ public async Task InsertMssqlNodaTypesAsync(InsertMssqlNodaTypesArgs args)
+ {
+ var queryParams = new Dictionary();
+ queryParams.Add("c_datetime2_instant", new Utils.DateTime2Parameter(args.CDatetime2Instant is null ? null : (DateTime? )args.CDatetime2Instant.Value.ToDateTimeUtc()));
+ queryParams.Add("c_datetimeoffset_instant", args.CDatetimeoffsetInstant is null ? null : (DateTimeOffset? )args.CDatetimeoffsetInstant.Value.ToDateTimeOffset());
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams);
+ return;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ await this.Transaction.Connection.ExecuteAsync(InsertMssqlNodaTypesSql, queryParams, transaction: this.Transaction);
+ }
+
+ private const string InsertMssqlNodaTypesBatchSql = "INSERT INTO mssql_noda_types (c_datetime2_instant, c_datetimeoffset_instant) VALUES (@c_datetime2_instant, @c_datetimeoffset_instant);";
+ public class InsertMssqlNodaTypesBatchArgs
+ {
+ public Instant? CDatetime2Instant { get; init; }
+ public Instant? CDatetimeoffsetInstant { get; init; }
+ };
+ public async Task InsertMssqlNodaTypesBatchAsync(List args)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ await connection.OpenAsync();
+ var dataTable = new DataTable();
+ dataTable.Columns.Add("c_datetime2_instant", typeof(object));
+ dataTable.Columns.Add("c_datetimeoffset_instant", typeof(object));
+ foreach (var row in args)
+ dataTable.Rows.Add(row.CDatetime2Instant is null ? (object)DBNull.Value : (DateTime? )row.CDatetime2Instant.Value.ToDateTimeUtc(), row.CDatetimeoffsetInstant is null ? (object)DBNull.Value : (DateTimeOffset? )row.CDatetimeoffsetInstant.Value.ToDateTimeOffset());
+ using (var bulkCopy = new SqlBulkCopy(connection))
+ {
+ bulkCopy.DestinationTableName = "[mssql_noda_types]";
+ bulkCopy.ColumnMappings.Add("c_datetime2_instant", "c_datetime2_instant");
+ bulkCopy.ColumnMappings.Add("c_datetimeoffset_instant", "c_datetimeoffset_instant");
+ await bulkCopy.WriteToServerAsync(dataTable);
+ }
+ }
+ }
+
+ private const string GetMssqlNodaTypesSql = "SELECT TOP 1 c_datetime2_instant, c_datetimeoffset_instant FROM mssql_noda_types;";
+ public class GetMssqlNodaTypesRow
+ {
+ public Instant? CDatetime2Instant { get; init; }
+ public Instant? CDatetimeoffsetInstant { get; init; }
+ };
+ public async Task GetMssqlNodaTypesAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql);
+ return result;
+ }
+ }
+
+ if (this.Transaction?.Connection == null || this.Transaction?.Connection.State != ConnectionState.Open)
+ throw new InvalidOperationException("Transaction is provided, but its connection is null.");
+ return await this.Transaction.Connection.QueryFirstOrDefaultAsync(GetMssqlNodaTypesSql, transaction: this.Transaction);
+ }
+
+ private const string GetMssqlNodaTypesCntSql = @"SELECT TOP 1 COUNT(*) AS cnt, c_datetime2_instant, c_datetimeoffset_instant
+ FROM mssql_noda_types
+ GROUP BY c_datetime2_instant, c_datetimeoffset_instant;";
+ public class GetMssqlNodaTypesCntRow
+ {
+ public required int Cnt { get; init; }
+ public Instant? CDatetime2Instant { get; init; }
+ public Instant? CDatetimeoffsetInstant { get; init; }
+ };
+ public async Task GetMssqlNodaTypesCntAsync()
+ {
+ if (this.Transaction == null)
+ {
+ using (var connection = new SqlConnection(ConnectionString))
+ {
+ var result = await connection.QueryFirstOrDefaultAsync