|
| 1 | +# <img src="../../docs/images/logo-128.png" height="48"> Quarry |
| 2 | + |
| 3 | +Type-safe SQL builder for .NET 10. Source generators + C# 12 interceptors emit all SQL at compile time. AOT compatible. Structured logging via Logsmith. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +# Quarry.Analyzers |
| 8 | + |
| 9 | +Compile-time SQL query analysis rules for Quarry. 18 Roslyn diagnostics detect performance issues, wasteful patterns, and dialect-specific problems in Quarry query call sites. |
| 10 | + |
| 11 | +## Packages |
| 12 | + |
| 13 | +| Name | NuGet | Description | |
| 14 | +|------|-------|-------------| |
| 15 | +| [`Quarry`](https://www.nuget.org/packages/Quarry) | [](https://www.nuget.org/packages/Quarry) | Runtime types: builders, schema DSL, dialects, executors. | |
| 16 | +| [`Quarry.Generator`](https://www.nuget.org/packages/Quarry.Generator) | [](https://www.nuget.org/packages/Quarry.Generator) | Roslyn incremental source generator + interceptor emitter. | |
| 17 | +| [`Quarry.Analyzers`](https://www.nuget.org/packages/Quarry.Analyzers) | [](https://www.nuget.org/packages/Quarry.Analyzers) | Compile-time SQL query analysis rules (QRA series) with code fixes. | |
| 18 | +| [`Quarry.Analyzers.CodeFixes`](https://www.nuget.org/packages/Quarry.Analyzers.CodeFixes) | [](https://www.nuget.org/packages/Quarry.Analyzers.CodeFixes) | Code fix providers for QRA diagnostics. | |
| 19 | +| [`Quarry.Tool`](https://www.nuget.org/packages/Quarry.Tool) | [](https://www.nuget.org/packages/Quarry.Tool) | CLI tool for migrations and database scaffolding (`quarry` command). | |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Installation |
| 24 | + |
| 25 | +```xml |
| 26 | +<PackageReference Include="Quarry.Analyzers" Version="1.0.0" |
| 27 | + OutputItemType="Analyzer" |
| 28 | + ReferenceOutputAssembly="false" /> |
| 29 | +``` |
| 30 | + |
| 31 | +Requires `Quarry` and `Quarry.Generator` to be referenced in the same project. |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## Diagnostic Rules |
| 36 | + |
| 37 | +All rules are enabled by default. Suppress individual rules via `#pragma`, `.editorconfig`, or `[SuppressMessage]`. |
| 38 | + |
| 39 | +### QRA1xx — Simplification |
| 40 | + |
| 41 | +| ID | Title | Severity | What it detects | |
| 42 | +|----|-------|----------|-----------------| |
| 43 | +| QRA101 | Count compared to zero | Info | `Count() > 0`, `Count() == 0` — use `Any()` instead | |
| 44 | +| QRA102 | Single-value IN clause | Info | `IN (@p0)` with one value — simplify to `==` | |
| 45 | +| QRA103 | Tautological condition | Info | Always-true conditions (`1 = 1`, `col = col`) | |
| 46 | +| QRA104 | Contradictory condition | Info | Always-false conditions (`x > 5 AND x < 3`) | |
| 47 | +| QRA105 | Redundant condition | Info | Subsumed conditions (`x > 5 AND x > 3`) | |
| 48 | +| QRA106 | Nullable without null check | Info | Nullable column in `==` comparison without null handling | |
| 49 | + |
| 50 | +### QRA2xx — Wasted Work |
| 51 | + |
| 52 | +| ID | Title | Severity | What it detects | |
| 53 | +|----|-------|----------|-----------------| |
| 54 | +| QRA201 | Unused join | Warning | Joined table not referenced in SELECT, WHERE, or ORDER BY | |
| 55 | +| QRA202 | Wide table SELECT * | Info | `Select(u => u)` on tables exceeding column threshold | |
| 56 | +| QRA203 | ORDER BY without LIMIT | Info | Sorting without pagination on unbounded result sets | |
| 57 | +| QRA204 | Duplicate projection column | Info | Same column projected multiple times in SELECT | |
| 58 | +| QRA205 | Cartesian product | Warning | JOIN with missing or trivial ON condition (`1 = 1`) | |
| 59 | + |
| 60 | +### QRA3xx — Performance |
| 61 | + |
| 62 | +| ID | Title | Severity | What it detects | |
| 63 | +|----|-------|----------|-----------------| |
| 64 | +| QRA301 | Leading wildcard LIKE | Info | `Contains()` → `LIKE '%…%'` prevents index usage | |
| 65 | +| QRA302 | Function on column in WHERE | Info | `LOWER()`, `UPPER()`, `TRIM()`, etc. on columns in WHERE | |
| 66 | +| QRA303 | OR across different columns | Info | `col1 = x OR col2 = y` prevents single-index scan | |
| 67 | +| QRA304 | WHERE on non-indexed column | Info | Filter on column not covered by any declared index | |
| 68 | + |
| 69 | +### QRA4xx — Patterns |
| 70 | + |
| 71 | +| ID | Title | Severity | What it detects | |
| 72 | +|----|-------|----------|-----------------| |
| 73 | +| QRA401 | Query inside loop | Warning | Execution method inside `for`/`foreach`/`while`/LINQ — N+1 risk | |
| 74 | +| QRA402 | Multiple queries on same table | Info | Multiple independent queries on the same entity in one method | |
| 75 | + |
| 76 | +### QRA5xx — Dialect |
| 77 | + |
| 78 | +| ID | Title | Severity | What it detects | |
| 79 | +|----|-------|----------|-----------------| |
| 80 | +| QRA501 | Dialect optimization available | Info | PostgreSQL: suggest `ILIKE` over `LOWER() + LIKE`; SQLite: suggest `COLLATE NOCASE` | |
| 81 | +| QRA502 | Suboptimal for dialect | Warning | SQLite: `RIGHT JOIN` unsupported; SQL Server: `OFFSET` requires `ORDER BY` | |
| 82 | + |
| 83 | +--- |
| 84 | + |
| 85 | +## Configuration |
| 86 | + |
| 87 | +### EditorConfig |
| 88 | + |
| 89 | +```ini |
| 90 | +# .editorconfig |
| 91 | +[*.cs] |
| 92 | +# Column threshold for QRA202 (wide table SELECT *), default: 10 |
| 93 | +quarry_analyzers.wide_table_column_count = 12 |
| 94 | +``` |
| 95 | + |
| 96 | +### Suppressing Rules |
| 97 | + |
| 98 | +```ini |
| 99 | +# .editorconfig — suppress a rule project-wide |
| 100 | +[*.cs] |
| 101 | +dotnet_diagnostic.QRA203.severity = none |
| 102 | +``` |
| 103 | + |
| 104 | +```csharp |
| 105 | +// Per-site suppression |
| 106 | +#pragma warning disable QRA301 |
| 107 | +var results = await db.Users |
| 108 | + .Where(u => u.UserName.Contains(search)) |
| 109 | + .ExecuteFetchAllAsync(); |
| 110 | +#pragma warning restore QRA301 |
| 111 | +``` |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## Severity Summary |
| 116 | + |
| 117 | +Four rules default to **Warning** — these indicate likely bugs or significant performance issues: |
| 118 | + |
| 119 | +| ID | Rule | |
| 120 | +|----|------| |
| 121 | +| QRA201 | Unused join | |
| 122 | +| QRA205 | Cartesian product | |
| 123 | +| QRA401 | Query inside loop (N+1) | |
| 124 | +| QRA502 | Suboptimal for dialect | |
| 125 | + |
| 126 | +The remaining 14 rules default to **Info** — suggestions that may or may not apply depending on context. |
0 commit comments