Skip to main content
Coverage ingestion in mehen is language-agnostic: the coverage metrics attach to any analyzed file that a report measures, and .sql files are no exception. Two mainstream stacks produce real coverage for SQL sources: Both need one deliberate step in the pipeline before mehen sees usable data. This page covers those two steps; the metric semantics live on the line, branch, and function pages.

What to expect from SQL coverage

Oracle’s instrumentation (DBMS_PLSQL_CODE_COVERAGE, which utPLSQL drives) reports lines only: utPLSQL’s Cobertura output marks every line branch="false" and emits no <method> elements. A measured .sql file therefore publishes coverage.line, coverage.line.covered, and coverage.line.total — and nothing else. coverage.branch and coverage.function stay absent, not zero: a gate on an unmeasured dimension skips rather than fails, per the measured-or-absent rule. SQLCover reports statement coverage, which likewise lands on the line dimension after the Cobertura transform. Coverage attaches at two granularities for SQL. The file root carries the totals, and every routine — a standalone CREATE FUNCTION/PROCEDURE/TRIGGER, a routine inside a package or type body, or a subprogram declared in another routine’s DECLARE section — is a function space in the metric tree, nested under its statement’s span. Each routine space receives coverage.line (and coverage.branch, when measured) scoped to its own line range, exactly like functions in any other language: a package body at 50% stops being one opaque number and becomes “get_a 100%, set_b 0%”. Statement spaces themselves carry no coverage keys — the routine is the meaningful unit, and it is the granularity the planned CRAP composite consumes. top-offenders ranks files, so gates stay file-level; the per-routine values live on the spaces in the JSON report.

utPLSQL: map database objects to file names

This is the step most pipelines miss. Run without file mapping, utPLSQL writes database object identifiers where Cobertura expects file paths:
hr.betwnstr is schema.unit from the Oracle dictionary. No spelling of a repository path ends in it, so path matching correctly refuses to attribute the data — every .sql file reports “unmeasured”, and mehen metrics -v logs that the report’s entries matched nothing. utPLSQL’s fix is project-based coverage: hand utPLSQL-cli your source tree with -source_path, and it maps database objects back to the files that created them, writing repository paths into the report:
Run utPLSQL-cli from the repository root so the emitted paths are root-relative, then:
Naming the output cobertura.xml (or coverage.xml) also makes it eligible for auto-discovery, so bare --coverage works too.
utPLSQL’s default mapping expects owner.object_name.type file names (hr.betwnstr.fnc). If your layout encodes owner or type in directories instead (sources/hr/functions/betwnstr.sql), pass the documented -regex_expression, -owner_subexpression, -name_subexpression, and -type_mapping options to describe it — see file mapping using custom regular expressions.
Report line numbers come from the stored unit source, which begins at the unit header (FUNCTION betwnstr(...)) — Oracle strips the CREATE OR REPLACE prefix. utPLSQL’s object-file mapping rules require each file to hold exactly one object “as is”, with no commands or blank lines before CREATE: that discipline is what keeps report line numbers aligned with file line numbers. A prologue of SET commands or license comments shifts every measured line.

SQLCover: transform OpenCover output to Cobertura

SQLCover (the coverage layer usually paired with tSQLt) emits OpenCover-format XML, which is not one of mehen’s ingested formats. The standard .NET bridge closes the gap: ReportGenerator converts OpenCover to Cobertura. Add one step between test run and mehen:
The same caveat applies as with utPLSQL: SQLCover only knows object names the database knows. Its reports name objects like [dbo].[betwnstr], and the paths that reach the Cobertura output depend on how your deployment scripts fed SQLCover. Inspect one filename attribute from the transformed report and confirm a repository path can end with it before wiring a gate — the path-matching page explains exactly which spellings reconcile.

Gating

Once report entries carry repository paths, SQL files participate in coverage thresholds and ranking like any other language:
Unmeasured files skip the gate entirely — so a mapping regression degrades to “no data”, never to a spurious red build. If a previously measured directory suddenly reports nothing, that is the signal to re-check the file-name mapping, not the tests.

References

  • utPLSQL user guide: Code coverage — project-based coverage, file mapping parameters, and object-file mapping rules. utPLSQL project.
  • utPLSQL-cli README-source_path, -regex_expression, and reporter options. utPLSQL project.
  • DBMS_PLSQL_CODE_COVERAGE — the Oracle-supplied instrumentation utPLSQL drives (basic-block granularity). Oracle Database documentation.
  • SQLCover — coverage collection for SQL Server, OpenCover output. Ed Elliott.
  • tSQLt — the SQL Server unit-testing framework SQLCover pairs with.
  • ReportGenerator — OpenCover-to-Cobertura conversion (-reporttypes:Cobertura). Daniel Palme.