Table of Contents

Analyzer Rules

Quarry ships two independent diagnostic systems that report issues at compile time:

  • QRY (Generator Diagnostics) -- Emitted by Quarry.Generator, the Roslyn source generator that builds SQL. These diagnostics cover schema errors, query translation failures, chain analyzability, migration integrity, and internal generator faults. They are always active when the generator package is referenced.
  • QRA (Analyzer Rules) -- Emitted by Quarry.Analyzers, a separate Roslyn analyzer package. These are optional, advisory rules that inspect query patterns for simplification opportunities, wasteful constructs, performance pitfalls, anti-patterns, and dialect-specific issues. Three rules (QRA101, QRA102, QRA201) include automatic code fixes.

Both systems surface diagnostics in the Error List, dotnet build output, and IDE squiggles like any other Roslyn diagnostic.


Installing Quarry.Analyzers

Add the analyzer and (optionally) its code fix companion to your .csproj:

<PackageReference Include="Quarry.Analyzers" Version="1.0.0"
    OutputItemType="Analyzer"
    ReferenceOutputAssembly="false" />

<!-- Optional: enables lightbulb code fixes in Visual Studio / Rider -->
<PackageReference Include="Quarry.Analyzers.CodeFixes" Version="1.0.0"
    OutputItemType="Analyzer"
    ReferenceOutputAssembly="false" />

No additional configuration is required. All QRA rules are enabled by default and can be tuned via EditorConfig or #pragma directives (see Suppressing Diagnostics below).


Generator Diagnostics (QRY)

Query (QRY001--QRY019)

Code Severity Description
QRY001 Warning Query chain not fully analyzable
QRY002 Error Missing Table property on schema
QRY003 Error Invalid column type
QRY004 Error Unknown navigation entity
QRY005 Error Unmapped projection property
QRY006 Error Unsupported Where operator
QRY007 Error Undefined join relationship
QRY008 Warning Sql.Raw usage risk
QRY009 Error Aggregate without GroupBy
QRY010 Error Composite key unsupported in this context
QRY011 Error Select clause required
QRY012 Error Where or All required for modifications
QRY013 Error GUID column needs ClientGenerated modifier
QRY014 Error Anonymous type unsupported in this context
QRY015 Warning Ambiguous context resolution
QRY016 Error Unbound parameter
QRY019 Warning Clause not translatable. The message format is "<clause-context>. The original runtime method will be used instead." — the clause-context substitution is supplied complete by the call-site translator, so contributors adding new translator error messages must not include trailing punctuation in the substituted text

Subquery (QRY020--QRY025)

Code Severity Description
QRY020 Error All() requires a predicate
QRY021 Error Subquery entity not found
QRY022 Error Foreign key not found for correlation
QRY023 Error Correlation ambiguous
QRY024 Error Subquery on non-Many property
QRY025 Error Composite PK not supported for subqueries

Entity Reader (QRY026--QRY027)

Code Severity Description
QRY026 Info Custom entity reader active
QRY027 Error Invalid entity reader type

Chain (QRY028--QRY037)

Code Severity Description
QRY028 Warning Redundant unique constraint on index
QRY029 Warning Sql.Raw placeholder mismatch
QRY030 Info Prebuilt dispatch optimization applied
QRY031 Error RawSqlAsync<T> with unresolvable generic type parameter
QRY032 Error Chain not analyzable at compile time
QRY033 Error Forked chain (multiple terminals)
QRY034 Warning .Trace() without QUARRY_TRACE symbol
QRY035 Error PreparedQuery escapes method scope
QRY036 Error .Prepare() with no terminal calls
QRY037 Error Generator carrier-assignment gap (internal self-check). Fires when the generator produces a carrier with a Px field but no matching __c.Px = ... assignment. By design — closes the silent default(T) parameter-binding hole the CS0649 warning could not catch on non-nullable reference-type captures. Should never surface in user code; if it does, file an issue — the generator regressed

SQL Manifest (QRY040)

Code Severity Description
QRY040 Warning SQL manifest write failure (see SQL Manifest)

Raw SQL Resolution (QRY041--QRY043)

Code Severity Description
QRY041 Warning RawSqlAsync literal SQL has an unresolvable column or un-aliased expression
QRY042 Info RawSqlAsync call is convertible to a chain query (code fix available)
QRY043 Error RawSqlAsync<T> / RawSqlScalarAsync<T> row entity T is not materializable — positional record, init-only property, abstract class, or interface. Project on a chain query with Select(x => new Dto { ... }) for immutable shapes

Project Setup (QRY044)

Code Severity Description
QRY044 Warning [QuarryContext] class's namespace is not listed in the MSBuild <InterceptorsNamespaces> property. Without it, C# 12 interceptors for that namespace are ignored and every terminal call fails with CS9137. The diagnostic message includes the exact csproj line to paste

Analyzer-only diagnostic (ships in Quarry.Analyzers); no code fix, because the target is the .csproj, not a source document. Quarry.Generated is auto-registered by the shipped build/Quarry.targets so consumers only need to list their own context namespaces.

Migration (QRY050--QRY055)

Code Severity Description
QRY050 Warning Schema drift detected
QRY051 Error Unknown table or column reference
QRY052 Error Version gap or duplicate
QRY053 Warning Pending migrations not applied
QRY054 Warning Destructive operation without backup
QRY055 Warning Nullable-to-non-null column change
Code Severity Description
QRY060 Error No FK column for One<T> navigation
QRY061 Error Ambiguous FK for One<T> navigation
QRY062 Error HasOne references invalid column
QRY063 Error Navigation target entity not found
QRY064 Error HasManyThrough invalid junction navigation
QRY065 Error HasManyThrough invalid target navigation

Set Operations (QRY070--QRY072)

Code Severity Description
QRY070 Error IntersectAll not supported on this dialect (e.g., SQLite, MySQL, SQL Server)
QRY071 Error ExceptAll not supported on this dialect (e.g., SQLite, MySQL, SQL Server)
QRY072 Error Set operation projection mismatch (column count or type)

QRY070, QRY071, and QRY072 were silently dropped by the generator before v0.4.0 due to a missing entry in s_deferredDescriptors; chains that previously compiled and produced runtime-failing SQL now fail at compile time.

QRY073 was introduced in v0.3.0 for cross-entity set-ops and retired in the same release when cross-entity support landed. Remove any #pragma warning disable QRY073 directives. The ID is intentionally skipped going forward so those pragmas keep their warning-free meaning.

Projection Subqueries (QRY074)

Code Severity Description
QRY074 Error Navigation aggregate (Sum/Min/Max/Avg/Average/Count) in a Select projection could not be resolved — the navigation property does not exist on the outer entity or its target entity is not registered on the context

Common Table Expressions (QRY080--QRY082)

Code Severity Description
QRY080 Error CTE inner query not analyzable
QRY081 Error FromCte without matching With
QRY082 Error Duplicate CTE name in chain

Internal

Code Severity Description
QRY900 Error Internal generator error

Migration Converters (QRM series)

Emitted by the Quarry.Migration package. Each diagnostic includes an IDE code fix that replaces the source call site with equivalent Quarry chain code.

Code Severity Source tool Description
QRM001 Info Dapper Dapper call convertible to Quarry
QRM002 Warning Dapper Converted with warnings
QRM003 Info Dapper Not convertible (falls back to Sql.Raw or manual migration)
QRM011 Info EF Core EF Core query convertible to Quarry
QRM012 Warning EF Core Converted with warnings
QRM013 Info EF Core Not convertible
QRM021 Info ADO.NET ADO.NET call convertible to Quarry
QRM022 Warning ADO.NET Converted with warnings
QRM023 Info ADO.NET Not convertible
QRM031 Info SqlKata SqlKata query convertible to Quarry
QRM032 Warning SqlKata Converted with warnings
QRM033 Info SqlKata Not convertible

Analyzers only activate when the source tool's framework type is present in the compilation. See Migrating to Quarry for the quarry convert --from <tool> CLI workflow.

Common QRY Diagnostics

These are the generator diagnostics you are most likely to encounter during normal development.

QRY032 -- Chain not analyzable at compile time

Quarry requires every query chain to be fully analyzable by the source generator. If the generator cannot trace the chain from entry point to terminal, it emits QRY032. Common causes:

  • Storing a builder in a field, property, or collection instead of a local variable.
  • Passing a builder across method boundaries (as a parameter or return value).
  • Building a chain inside a loop where the iteration variable influences the chain structure.
// QRY032 -- builder escapes method scope
IQueryBuilder<User> BuildQuery() =>
    db.Users().Where(u => u.IsActive);

// Fix: keep the entire chain in one method
var users = await db.Users()
    .Where(u => u.IsActive)
    .Select(u => u)
    .ExecuteFetchAllAsync();

Use conditional branches (if/else) instead of dynamic chain construction -- the generator handles those natively via bitmask dispatch.

QRY011 -- Select clause required

Every query chain must include a .Select() call before a terminal. The generator needs it to know which columns to emit.

// QRY011 -- missing Select
var users = await db.Users()
    .Where(u => u.IsActive)
    .ExecuteFetchAllAsync();

// Fix: add Select
var users = await db.Users()
    .Where(u => u.IsActive)
    .Select(u => u)
    .ExecuteFetchAllAsync();

QRY012 -- Where or All required for modifications

Update and Delete operations require an explicit Where() or All() call to prevent accidental full-table modifications.

// QRY012 -- no scope for delete
await db.Users().Delete().ExecuteNonQueryAsync();

// Fix: add Where or acknowledge All
await db.Users().Delete().Where(u => u.UserId == 1).ExecuteNonQueryAsync();
await db.Users().Delete().All().ExecuteNonQueryAsync(); // explicit full-table

Analyzer Rules (QRA)

These rules are provided by the Quarry.Analyzers package and are independent of the source generator. They perform pattern-based analysis of Quarry query call sites.

Simplification (QRA101--QRA106)

Rules that detect query patterns which can be written more simply.

Code Severity Description Code Fix
QRA101 Info Count compared to zero -- Count() > 0 or Count() == 0 can be replaced with Any() Yes
QRA102 Info Single-value IN clause -- new[] { x }.Contains(col) simplifies to col == x Yes
QRA103 Info Tautological condition -- always-true conditions like 1 == 1 or col == col
QRA104 Info Contradictory condition -- always-false conditions like x > 5 && x < 3
QRA105 Info Redundant condition -- a condition subsumed by a stronger one (e.g., x > 5 && x > 3)
QRA106 Info Nullable column without null check -- nullable column compared with == without null handling

QRA101 code fix rewrites Count() > 0 to Any() and Count() == 0 to !Any(), handling async variants.

QRA102 code fix converts new[] { x }.Contains(col) to col == x.

Wasteful Patterns (QRA201--QRA205)

Rules that detect unnecessary work in query construction.

Code Severity Description Code Fix
QRA201 Warning Unused join -- joined table not referenced in SELECT, WHERE, or ORDER BY Yes
QRA202 Info Wide table SELECT -- Select(u => u) on a table exceeding the column threshold (default: 10)
QRA203 Info ORDER BY without LIMIT -- sorting without pagination on an unbounded result set
QRA204 Info Duplicate projection column -- same column projected multiple times in SELECT
QRA205 Warning Cartesian product -- JOIN with missing or trivial ON condition (1 == 1)

QRA201 code fix removes the unused .Join(...) call from the query chain, preserving the receiver.

Performance (QRA301--QRA304)

Rules that flag potential performance concerns, primarily around index usage.

Code Severity Description
QRA301 Info Leading wildcard LIKE -- Contains() translates to LIKE '%...%', preventing index usage
QRA302 Info Function on column in WHERE -- ToLower(), ToUpper(), Substring(), etc. on a column prevents index usage
QRA303 Info OR across different columns -- col1 == x \|\| col2 == y prevents single-index scan
QRA304 Info WHERE on non-indexed column -- filtering on a column not covered by any declared schema index

Pattern Issues (QRA401--QRA404)

Rules that detect common anti-patterns in how queries are used.

Code Severity Description Code Fix
QRA401 Warning Query inside loop -- execution method called inside for/foreach/while or LINQ .Select(), indicating an N+1 risk
QRA402 Info Multiple queries on same table -- multiple independent queries on the same entity within one method; consider combining
QRA403 Warning ThenBy without preceding OrderBy -- the receiver chain has no .OrderBy(...), so .ThenBy(...) emits ORDER BY <key> in isolation. Walks the receiver chain backward, transparent to intervening calls (Where, Select, Distinct, Limit, Offset, Trace, set ops) Yes
QRA404 Warning Having without preceding GroupBy -- the receiver chain has no .GroupBy(...), so .Having(...) applies HAVING <pred> to the whole result

QRA403 code fix (ThenByToOrderByCodeFix) renames the method-name token from ThenBy to OrderBy, preserving any TypeArgumentList on a generic-name call.

Dialect (QRA501--QRA503)

Rules that detect dialect-specific issues or missed optimizations.

Code Severity Description
QRA501 Info Dialect-specific optimization available -- e.g., PostgreSQL ILIKE instead of LOWER() + LIKE, SQLite COLLATE NOCASE
QRA502 Warning Suboptimal for dialect -- feature is supported but produces a worse plan than an alternative on the target dialect
QRA503 Error Capability gap for dialect -- feature is unsupported or produces engine-rejected SQL on the target dialect. Fires for MySQL FullOuterJoin<T>(...) and SQL Server .Offset(N) without .OrderBy(...) at every execution terminal (ExecuteFetchAllAsync, ToAsyncEnumerable, etc.). Promoted from QRA502 Warning in v0.4.0 — the rule produces SQL the engine physically rejects, not merely a perf hint

Suppressing Diagnostics

When a diagnostic is intentional or not applicable, suppress it with standard C# mechanisms.

EditorConfig (project-wide)

Use .editorconfig to change severity or disable rules across the project:

# Disable leading-wildcard LIKE warnings entirely
dotnet_diagnostic.QRA301.severity = none

# Promote tautological condition from Info to Warning
dotnet_diagnostic.QRA103.severity = warning

The Quarry.Analyzers package also supports an EditorConfig option for the wide-table column threshold:

# QRA202: column threshold for wide-table detection (default: 10)
quarry_wide_table_column_threshold = 15

Pragma (per-site)

Use #pragma warning to suppress a specific diagnostic at a single call site:

#pragma warning disable QRA301
var results = await db.Users()
    .Where(u => u.UserName.Contains(searchTerm))
    .Select(u => u)
    .ExecuteFetchAllAsync();
#pragma warning restore QRA301

SuppressMessage attribute

For method-level or class-level suppression:

[System.Diagnostics.CodeAnalysis.SuppressMessage(
    "QuarryAnalyzer", "QRA401",
    Justification = "Batch size is bounded and intentional")]
public async Task ProcessItems(List<int> ids) { ... }