Skip to content

Emitting a conforming print

dbprint the command line is one producer of the dbprint format. It is not meant to be the only one. A warehouse tool that already knows a table's cardinality, an internal profiler that runs where dbprint cannot connect, a data-catalog exporter — any of them can emit a print, and every consumer of the format will read it.

This page is what such a producer needs: where the contract is written down, how to validate output against it, and what conforming does and does not certify.

The contract

The format specification is normative and self-contained. It defines the directory layout, every artifact, every field, the classification rules, and how a reader is meant to interpret an absence. Nothing on this page restates it.

Three sections are the ones a producer reads first:

SectionAnswers
1. Directory layoutwhat files go where, and how an identifier becomes a path segment
2.2 statistics.yamlthe artifact carrying the measurements, and the per-classification field matrix
7. Reading an absencewhat a consumer concludes from a field you did not emit — which is what makes omission a decision rather than a default

A worked print, generated by the reference producer, is in the repository. Reading one is faster than reading the specification cold. It is not carried in the wheel — the installed package ships the specification itself and the assertion grammar, not the examples.

The statistics required-field matrix is the one exception to "nothing on this page restates it": a table generated from the same module the engine and the conformance validator both read, so it cannot drift from what they actually enforce the way hand-written prose could. Diff your own emission logic against it directly.

depends_on sits outside that matrix entirely - it is a table-level field, not a per-column one, and MUST NOT appear on a plain table: only a view or matview reads other objects. [] and an omitted key are different claims. [] means the catalog answered and the object reads nothing else in the print; an omitted key means the producer could not ask the dependency catalog at all - unrelated to whether the object's rows were queried. depends_on is catalog-derived, on the same footing as physical_layout, never a query result, so a catalog_only view is exactly where it is normally still expected to be present, not where its absence is explained.

The schemas

Seven JSON Schemas, draft 2020-12, one per artifact. Each is published at its own canonical address and ships inside the wheel:

Artifact$id
statistics.yamlhttps://jakubro.github.io/dbprint/spec/v1/statistics.schema.json
relationships.yamlhttps://jakubro.github.io/dbprint/spec/v1/relationships.schema.json
manifest.yamlhttps://jakubro.github.io/dbprint/spec/v1/manifest.schema.json
diff.yamlhttps://jakubro.github.io/dbprint/spec/v1/diff.schema.json
statistics.annotations.yamlhttps://jakubro.github.io/dbprint/spec/v1/statistics_annotations.schema.json
relationships.annotations.yamlhttps://jakubro.github.io/dbprint/spec/v1/relationships_annotations.schema.json
manifest.annotations.yamlhttps://jakubro.github.io/dbprint/spec/v1/manifest_annotations.schema.json

Fetch them over HTTPS, or read them out of an installed package without a network:

import importlib.resources
schema = importlib.resources.files("dbprint.spec.v1").joinpath("statistics.schema.json").read_text()

The two are the same bytes. A producer in another language can vendor them from either.

Validating output

Schema validity is necessary and not sufficient: a large share of the format's rules are cross-field arithmetic a JSON Schema cannot express — that a null_rate agrees with its own null_count over the rows that were scanned, that a value list is ordered, that a relationship's two endpoints agree with each other. The conformance validator checks both layers.

from dbprint.conformance import validate_print, Issue
issues: list[Issue] = validate_print("/path/to/prints/connection_name/")
errors = [i for i in issues if i.severity == "error"]
warnings = [i for i in issues if i.severity == "warning"]
conforms = not errors

Each Issue carries a path, a stable code, a severity, a human-readable detail, and the specification section it was raised against. Issues come back ordered by path then code, so two runs over the same tree produce byte-identical output and a diff of validator results is meaningful.

Every code the validator can raise is listed at conformance codes, with its severity and a link to the section defining it.

A producer written in another language has the same two options a schema does: run this validator over its output as a CI step, or reimplement the checks from SPEC 6.3. The first is cheaper and stays in step.

What conforming certifies

Conforming means the artifact is well-formed and internally consistent: it is shaped the way the format says, and its numbers agree with each other.

It does not mean the artifact is true. Nothing in the validator connects to a database, so a producer that measured the wrong table, or measured it wrongly, produces a conforming print of the wrong thing. The parts of the format that carry a claim about how a number was obtained — row_count_method, cardinality_method, values_coverage_method, the scope block — exist because that gap is real, and a producer that fills them honestly is the difference between a print a consumer can reason about and one it can only read.

Two severities, and only one of them gates:

  • error — the directory does not conform. Either the producer is buggy or the artifact is corrupt.
  • warning — it conforms with anomalies. Typically a forward-compatibility case, such as a classification value this validator does not know, or a disagreement the format expects on a live database taking writes.

SPEC 6.1 defines both.

Passing validation is not proof that every normative MUST in the specification was checked: some cross-field rules are not mechanically enforced by validate_print(), and where that gap matters, correctness is on the producer alone.

Versioning, and what a consumer has to tolerate

Every artifact YAML carries a format_version integer, versioned independently of this tool — the package version and the format version move on separate schedules, and a producer implements a format version rather than a dbprint release.

The rules that matter for staying compatible are in SPEC 5. The short version from a producer's side:

  • Additions come in MINOR releases and are additive only. Existing codes keep their meanings.
  • A consumer tolerates values it does not recognise: an unknown classification, an unknown looks_like, an unknown diff kind, an unknown conformance code, an unknown severity. SPEC 5.3 makes that a requirement on the reader, which is what makes an addition safe to ship.
  • A validator cannot validate an artifact whose MAJOR it does not know; it says so with version.unknown-format-version rather than guessing.

The practical consequence for a producer: emitting a field this version of the format does not define is not a way to extend it. A consumer is not required to preserve it, and the annotation files (SPEC 2.7) reject unknown keys at the root outright. Extensions belong in the specification.

Where to ask

The format is developed in the open, in this repository. A producer that hits a case the specification does not cover has found a gap worth filing rather than working around — the format is meant to describe more than one producer's output, and it only gets there by meeting them.