feat: unify graph JSON output on one Arrow writer and offer Arrow IPC as a query result format - #627
Merged
azimafroozeh merged 4 commits intoSep 5, 2026
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
… as a query result format
azimafroozeh
force-pushed
the
json-results-via-arrow-json
branch
from
September 5, 2026 10:03
4399c80 to
d75232b
Compare
… as a query result format
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes #618. This PR implements RFC 0051 (#622): every JSON body OmniGraph writes for graph data is the
arrow-jsonrendering of the result batches with the crate's default options, both hand-written cell encoders are deleted, and the query routes serve Arrow IPC on request.QueryResult::to_json_bytes()isarrow_json::WriterBuilderwith default options over the result batches;to_rust_json()anddeserialize()parse those bytes.ReadOutput.rowsandLegacyReadOutput.rowscarry the rendered text asBox<RawValue>: the three query handlers pass it through, the CLI parses it fortable,csv, andkv, and the OpenAPI schema forrowsis unchanged. The compiler's per-type encoder,JsonIntegerMode, and everyto_sdk_jsonare removed.QueryResult::to_json_lines()(one object per line) renders the exportdataobject,entity_at, and thebefore/afterimages ofPOST /changes; the engine's encoder is deleted. OmniGraph keeps four pieces of its own: thebase64:blob substitution before the writer, the export line envelope, the date-range check below, and the re-insertion of explicit nulls into change images (there an absent key means the property was outside that commit's schema). Export renders 256 rows per writer call; thedatakey order becomesidfirst then the catalog's column order (it was alphabetical).omnigraph-api-typescarriesARROW_STREAM_MEDIA_TYPEand the four envelope header constants, the server the 406 constructor, the CLI thearrowformat variant. TheAcceptnegotiation inPOST /queryand stored-query invocation, the CLI--format arrowbyte lane, their tests, and the OpenAPI regeneration land before this PR opens;POST /readignoresAcceptand a stored mutation answers JSON whateverAcceptsays.DateTimedrops theZand a zero fractional part,F32andVector(N)print at 32-bit width (the bug: F32 values come back with widened digits #618 defect), exponent form is1.0e20from 1e10 up and below 1e-5, a non-finite computed float isnull; export and entity fetch spell dates as strings instead of raw counts (the loader already read both forms). The release note anddocs/user/queries/index.md§JSON result spelling carry the full list.DateorDateTimecountarrow-jsoncannot format (it writes anERROR:string and returnsOk) is refused before the writer, on the routes with status 500 naming the column,rows[i]across the whole result, and the count, on export, entity fetch, and change images prefixed with the entity id; the loader refuses the same counts on import. All go through the one predicate pairdate32_renderable/date64_renderable.every_catalog_type_renders_to_the_documented_spellingpins checked-in bytes for every Arrow type the catalog can produce, so anarrow-jsonupgrade that changes a spelling fails a test instead of changing the wire.implementation: complete, the change feed named as the third consumer, the export key order, theT25refusal that landed in fix(compiler): refuse two projections that execute to one result column name #621, and one decision-log entry.Backing issue / RFC
docs/rfcs/0051-json-output-via-arrow.md(rfc: add RFC 0051, JSON output via Arrow #622); closes bug: F32 values come back with widened digits #618, carried byissue_618_json_f32_datetime_null_spelling.gqt(# red_on:main@a09176d7)Checklist
row_index_counts_across_batches_and_only_rendered_slots_are_checked; loader refusal ati32/i64MIN and MAX; signed-yearDateTimestrings re-import;export_jsonl_round_trips_branch_snapshotpins one line's bytes and key order;export_jsonl_with_blob_typeassertsbase64:, the external URI, and the omitted null key onentity_at; CLIjson_format_prints_the_envelope_pretty_and_the_rows_compact; serverquery_rows_omit_null_cells; the.gqtcase; every in-tree caller ofto_rust_jsontakes theResult)docs/releases/v0.11.0.mdphase 1 and phase 2 bullets,docs/user/queries/index.md§JSON result spelling,docs/user/cli/reference.md,skills/omnigraph/SKILL.md, RFC 0045's comparison sentence, RFC 0051 amendment,openapi.jsondescriptions)""or a silentnullfor a display or downcast failure, the writer path returns a typed error; the IPC route serializes batches after the same policy and projection as JSON)Local verification
cargo test --workspace --locked --no-fail-fast: 102 binaries, 2949 passed, 24 ignored; one failure,blob::tests::external_blob_file_policy_rejects_special_files, sandbox-only (UnixListener::bindEPERM), file untouchedcargo test -p omnigraph-gqt: 11 cases pass;issue_618_json_f32_datetime_null_spelling.gqtfails onmain@a09176d7(the bug: F32 values come back with widened digits #618 symptom)cargo test -p omnigraph-server openapiafter regeneratingopenapi.jsonwithOMNIGRAPH_UPDATE_OPENAPI=1: green, includingerror_code_schema_has_expected_variants; the vocabulary guard'sopenapi_inventory_matchestest: greencargo clippy --workspace --all-targets: onedead_codewarning,ApiError::not_acceptable, until the phase 3 handlers wire itcargo fmt --all --check: cleancargo check --workspace --all-targets --locked: clean (arrow-json = "58"adds a workspace edge only; the crate was already in the lock througharrow)scripts/check-docs.pyon the amended RFC: not yet run; the RFC edit lands after the rebase ontomains3_storagetests: not run (bucket-gated and long-running); both compile under--all-targetsrust-stringsurface: not run against this base; it reports 160 pre-existing findings at the merge base, none in a file this PR touchesNotes for reviewers
with_explicit_nulls, no date format strings. Each default is one of the listed spelling changes; a per-type override would be OmniGraph deciding a spelling again, which is what the RFC removes. The one exception is the change feed, wherelogical_row_imagere-insertsnullafter the writer because the CLI diff's<absent>marker means "outside that commit's schema".ApiError::internal(500), not theOmniError::Compiler400 path: a value the writer cannot render is a server-side failure, not a bad request. On export the stream ends at a 256-row render-window boundary before the failing row, every emitted line complete, and the call returns the typed error naming the entity; the file carries no in-band truncation signal.DateorDateTimecount loaded before this release that the writer cannot format fails every read, export, entity fetch, and change image touching that column, each naming the entity orrows[i]and the count, until that row is updated. Accepted: the renderable range sits about 30x beyond any calendar-expressible date, no literal, parameter, or arithmetic route can produce such a count, and the named row plusupdateis the repair. The Arrow IPC route serializes the stored count without the range check and is the read-side escape hatch.date()/datetime()literals,now(), andDate/DateTimeparameters all pass. What it does not stop, by design: a filter on an unprojected date column runs on the raw count, which is also the repair route; the IPC route carries the count unformatted.RawValue::from_stringis one validation pass over the rendered bytes, so rows are validated once and never parsed into a value tree; the RFC's "carried as bytes" is one pass, not zero.load_jsonl-level refusal of an out-of-range count is not pinned in the diff.&RawValuefrom those bytes, one more validation pass per line, andexport_jsonl_round_trips_branch_snapshotpins the grammar at the byte level.ErrorCodegains noNotAcceptablevariant: the enum is a closed wire contract, so 406 carriesBadRequest, the 415 precedent.entity_atis now awith_row_idbounded scan rendered by the same writer, so the read-only.dataset()count fordb/omnigraph/export.rsin theforbidden_apischokepoint registry goes from 1 to 2 (the second call is the entity blob read).columnandrows; no vocabulary-inventory row is added, since therust-stringguard cannot run at this merge base and the terms name a result column, not storage.