Architecture¶
Shared vocabulary lives in root files; implementations live in nine layer folders. Rust is the source of truth, and Python and JavaScript are native views of the same contracts.
root traits, enums, and values
│
├── types ─────────► arrow
│ │ │
│ └── expression ──┤
│ ▼
├── holder ◄── coding ◄── media
│ │
├── uri ──────────────────┘
├── text
└── xxhash
fix ── protocol vocabulary over types + holder
Root files and layer folders¶
Each rust/src/<name>.rs owns one shared trait, enum, or value. For example, iobase.rs owns
IOBase, codec.rs owns Codec, and media_type.rs owns MediaType. The crate root declares
and re-exports them; it contains no second implementation.
Each folder owns one implementation family:
| Layer | Owns |
|---|---|
types |
DataType, Field, Scalar, type families, protocol views, validation, and casting |
holder |
Buffer, local and generic filesystem handles, buffering, and storage implementations |
coding |
gzip, zlib/deflate, zstd, and transparent coded handles |
media |
record options, IPC, Parquet, Avro, plain-text records, and Iceberg |
text |
structured Scalar codecs for JSON, YAML, and TOML |
uri |
URI, URL, URN, path, glob, and partition syntax |
arrow |
Arrow schema, scalar, array, batch, and reader boundaries |
expression |
parsing, binding, row evaluation, Arrow evaluation, and pushdown |
xxhash |
digest values, one-shot and resumable hashes, streams, handles, and row hashes |
fix is a protocol module over those layers: FIX fields remain core Field values and
registry storage remains IOBase.
Tests, benchmarks, Python modules, JavaScript source groups, and documentation mirror these layer names. This makes a concept's implementation, validation, boundary, and contract discoverable by the same path.
A schema is a field¶
A non-null Struct Field is the only row schema. Scalar::Record is named input that
canonicalizes to an ordered Scalar::Sequence against that field; it is not another schema type.
Validation, canonicalization, metadata, comparison, Arrow projection, and casting therefore share
one source of truth.
Protocol metadata is a borrowed view of the same field. field.as_fix() and
field.as_iceberg() add typed foreign vocabulary, field.as_digest() defines row-digest roles,
and field.as_identity() / field.as_partition() provide generic field metadata. None copies state
or adds protocol fields to the core type.
Storage is one trait¶
IOBase is positional: pread and pwrite take explicit offsets, so independent
readers never coordinate a hidden cursor. Root traits describe storage roles; implementations in
holder supply memory, local, generic filesystem, and buffered behavior. The
generic FileSystem trait preserves Arrow's synchronous method signatures so
existing Arrow-compatible backends map onto it directly.
Construction is lazy. Missing reads return empty data, writes create resources and parents as a
consequence, and internal code acts before handling typed absence or conflict. clear preserves a
resource, remove deletes it, and open/close delimit the only cache lifetime. Listings are
deterministic, lazy, fused iterators over Result, with a bounded traversal frontier.
coding wraps handles without creating another storage trait. gzip, zlib, and zstd
retain only codec state and the current bounded chunk. media adds record behavior
through IOMedia: Arrow readers stream batches, writes cast once, and commit buffering is explicitly
bounded.
Traits say what; enums say which¶
Root traits define behavior and root enums dispatch among implementations. Codec selects a
content coding, MediaType selects record behavior, IOKind describes a resource, and IOMode
selects a write operation. Holder and Media are layer-owned concrete sums that carry one native
implementation across listings and binding boundaries.
Adding a backend or format implements the existing trait and extends its one dispatcher. It does not add a parallel trait, local enum, or binding implementation.
Arrow speaks batches; structured text speaks values¶
arrow carries columnar arrays and batches. IPC, Parquet, plain-text records, and
Iceberg expose bounded BatchReader streams rather than collected batches. text
parses and renders the generic Scalar through JSON, YAML, and TOML.
Field-directed conversion is the bridge: the exact field controls nullability, nested order, dictionaries, extension identity, and canonical scalar representation. Bindings use the same native boundary rather than constructing schemas or values recursively themselves.
One expression, three tiers¶
Expression parses once and binds to a field once. The resolved tree evaluates a
row Scalar, an Arrow batch, or container statistics. Arrow kernels accelerate supported nodes;
fallback row evaluation cannot change their meaning. Statistics return false only when they prove
that no row can match, so uncertainty costs a read instead of losing data.
One shape per hierarchy level¶
Collections use get, create, open_or_create, contains, lazy iteration, len, and
is_empty; resources expose identity, properties, and child collections. Dotted names descend
through collections. Iceberg catalogs, namespaces, and tables all use this shape, and storage
folders use the same lazy, typed absence/conflict rules.
Bindings are views¶
Python and JavaScript infer or coerce once at their boundary and redirect to Rust. Parsing, validation, comparison, hashing, storage routing, and recursive conversion remain native. Python public modules use the layer names; JavaScript keeps one package entry point while its source, tests, and benchmarks use the same groups.
Feature boundaries¶
arrow is enabled by default. parquet and iceberg remain optional because they add their format
stacks. A schema, identifier, hashing, FIX, and structured-text consumer still builds with
default-features = false on Rust 1.85.