Skip to content

Add Parquet storage schema and a PyArrow example - #30

Open
rok wants to merge 6 commits into
mainfrom
feature/measurement-result-schema
Open

rok wants to merge 6 commits into
mainfrom
feature/measurement-result-schema

Conversation

@rok

@rok rok commented Sep 10, 2026 •

Copy link
Copy Markdown
Member

The Parquet form of a validated measurement result (#2, §5.2 ingest object): the columns a result is stored under, which the local store's row begins with. It defines nothing about the message itself; where it and the schema differ, the schema wins.

  • docs/design/measurement-result-parquet.md: the mapping from the message JSON Schema to Arrow and Parquet types, the resulting schema for message schema 0.1.0, the file metadata, the columns a store adds beside them, and how to read the file, with a DuckDB query over the six example results. One row is one result; closed objects are structs, open objects are canonical JSON columns, and observations are one typed list column.
  • src/benchx/parquet.py: the mapping already lives here on main. This PR adds message_table() and MESSAGE_SCHEMA (the message columns alone), and records benchx.message_schema_sha256 in the file metadata, so files written under different drafts of 0.1.0 can be told apart. This includes store.parquet.
  • tools/json_to_parquet.py: a small script built on benchx. It reads and checks each document the way bx validate does, then writes message_table() to one Parquet file. Nothing is written if any document is invalid.
  • tests/test_parquet.py: the mapping over the examples (row order, nullability, canonical JSON, observations, metadata), the schema nodes the mapping must reject, and the tool.
  • Indexes: the document is listed in README.md, docs/design/README.md, and the site navigation. The "Add Parquet storage schema and a PyArrow example #30" references in the code and docs now name the document instead.
pip install benchx
python tools/json_to_parquet.py results.parquet \
    src/benchx/schemas/measurement-result/0.1.0/examples/*.json

@rok rok changed the title Add measurement result schema WIP: Add measurement result schema Sep 10, 2026
@rok
rok force-pushed the feature/measurement-result-schema branch from 6390849 to 2a4906d Compare September 11, 2026 09:51
@rok
rok force-pushed the feature/measurement-result-schema branch from d4715d3 to c276fde Compare September 18, 2026 17:44
@rok rok changed the title WIP: Add measurement result schema Add Parquet storage schema and a PyArrow example Sep 18, 2026
@rok
rok changed the base branch from main to design-schema September 18, 2026 17:45
@rok

rok commented Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

I'm not sure this is ready to merge, but would invite reviews!

@rok
rok force-pushed the feature/measurement-result-schema branch from a71ca1b to a835cec Compare September 24, 2026 22:00
@rok
rok marked this pull request as ready for review September 24, 2026 22:09
@rok
rok changed the base branch from design-schema to main September 24, 2026 22:10
rok added a commit that referenced this pull request Sep 25, 2026
Scope: a thin end-to-end slice in line with the merged design documents
and the work order of #35. Two prepared Google Benchmark build
directories are alternated over rounds by the calling script, one work
order per side and round; bx run measures and delivers the results to
the local store; the comparator evaluates them in run mode. Both the
revisions (UC-03) and environments (UC-04 shape) profiles are
demonstrated, ad hoc (no project, thin results) and tracked. Building
and environment policy are out of scope (benchmark-environments.md);
history mode is deferred. Eight success criteria and the decisions
locked for the prototype.

Design: one CLI over a Python API; the #35 work order, extended only by
the round and slot it lists as open; a runner that records the
zero-configuration snapshot, source identity, CMake build configuration,
and declared facts, and refuses what it cannot apply exactly; a local
store that is a single Parquet file in one place,
~/.benchx/store.parquet, holding #30's row per result plus the canonical
document and its work order, rewritten atomically on ingest, with
entities and series computed on query; run-mode comparison with profile
invariants and the paired method benchx/paired-relative/v1; bx head for
the latest results; a demonstration of both profiles; deliberate
simplifications and implementation notes.

Also adds docs/design/README.md, the reviewer-facing index of the merged
design documents.
@MarcoGorelli

Copy link
Copy Markdown
Collaborator

I think this has drifted a bit since main, do you want to update and we can review?

rok added a commit that referenced this pull request Nov 11, 2026
main has since taken the mapping into src/benchx/parquet.py and moved the
schemas into the package, and the result schema has changed under it.

- benchx.parquet: add message_table() and MESSAGE_SCHEMA (the message
  columns alone), and record benchx.message_schema_sha256 in the file
  metadata of both the message table and the store, as the document says.
- tools/json_to_parquet.py: a thin wrapper over benchx; it reads and checks
  documents as `bx validate` does and writes message_table().
- tests/test_parquet.py: pytest tests of benchx.parquet and the tool,
  replacing tests/test_json_to_parquet.py; strict reading is already
  covered by test_loader.py.
- measurement-result-parquet.md: regenerate §3 for the current schema
  (string schema_version, quantity under measurement, protocol, system,
  measurement.kind), describe the store's derived columns and document
  column, use the shipped paths, and rerun the DuckDB query over the six
  examples.
- List the document in the design index and the site nav, and replace
  the "#30" references in the code and docs with the document's name.
|---|---|
| closed object (`additionalProperties: false`) | `struct` with the same field order; required properties are non-nullable |
| open object, or a value of mixed primitive types | `json`: the Arrow JSON extension type, written with the Parquet JSON logical type, holding RFC 8785 canonical text |
| `oneOf` of closed objects (`summaries`) | one `struct` with every property any alternative declares, in first-appearance order; non-nullable only when every alternative requires it; alternatives that give one property different types are an error, not a silent choice |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here it says

oneOf of closed objects

However, is that what the code does?

if "oneOf" in node: # union of closed objects, or number-or-object observation
branches = [defs[b["$ref"].rsplit("/", 1)[-1]] if "$ref" in b else b for b in node["oneOf"]]
objects = [b for b in branches if b.get("type") == "object"]
if not objects:
raise ValueError(f"unsupported oneOf without an object alternative: {node}")
required = set.intersection(*(set(b.get("required", [])) for b in objects))
types = {} # in first-appearance order
for b in objects:
for k, v in b["properties"].items():
t = nested(v)
if types.setdefault(k, t) != t:
raise ValueError(f"oneOf alternatives disagree on the type of {k!r}: {types[k]} and {t}")
return pa.struct([pa.field(k, t, k not in required) for k, t in types.items()])

does it check that they're actually closed (i.e. additionalProperties: false)?

| integer, integer const | `int64` |
| number | `float64` |
| boolean | `bool` |
| any other `const`, `enum`, or `oneOf` | an error, so that a schema change the rules do not cover is noticed rather than stored loosely |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks different to what's enforced?

it looks like we reject the case where no objects are present

if not objects:
raise ValueError(f"unsupported oneOf without an object alternative: {node}")

but then wouldn't oneOf: [{"type": "string"}, <closed object>] still get through?

rok added 6 commits November 15, 2026 01:11
Describe the schema under which a validated measurement result is stored
in Parquet, and add a small program that derives the Arrow schema from
the message JSON Schema and writes messages to one Parquet file.
Reject duplicate object keys, non-finite number tokens, and numbers
outside the RFC 8785 domain, including integers beyond the I-JSON safe
range, before JSON Schema validation. Document why in the Parquet notes.
main has since taken the mapping into src/benchx/parquet.py and moved the
schemas into the package, and the result schema has changed under it.

- benchx.parquet: add message_table() and MESSAGE_SCHEMA (the message
  columns alone), and record benchx.message_schema_sha256 in the file
  metadata of both the message table and the store, as the document says.
- tools/json_to_parquet.py: a thin wrapper over benchx; it reads and checks
  documents as `bx validate` does and writes message_table().
- tests/test_parquet.py: pytest tests of benchx.parquet and the tool,
  replacing tests/test_json_to_parquet.py; strict reading is already
  covered by test_loader.py.
- measurement-result-parquet.md: regenerate §3 for the current schema
  (string schema_version, quantity under measurement, protocol, system,
  measurement.kind), describe the store's derived columns and document
  column, use the shipped paths, and rerun the DuckDB query over the six
  examples.
- List the document in the design index and the site nav, and replace
  the "#30" references in the code and docs with the document's name.
A oneOf was accepted whenever it had an object alternative. An open object
was merged into the struct, dropping undeclared keys, and any non-object
alternative was stored as if it were a bare observation number. Accept only
closed objects, or one number beside one closed object with a number value.
@rok
rok force-pushed the feature/measurement-result-schema branch from 4778ea5 to 815bebc Compare November 15, 2026 00:12

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants