Docs
What it’s for
arrow-mlake holds Arrow data in Mojo and moves it in and out: to another library in the same process over the C Data Interface, to a file or socket as an IPC stream, or to another process through a memory-region mapping. parquet returns its batches as arrow-mlake RecordBatches and re-exports the module as parquet.arrow. It is a stopgap: when marrow, the community Arrow for Mojo, builds on Mojo 1.x, magmalake moves to it, so expect this API to change.
Install
A new project needs Modular’s channel, pixi’s build preview and the compiler before the tin:
pixi init app -c https://conda.modular.com/max -c conda-forge && cd app
sed -i.bak 's/^\[workspace\]/[workspace]\npreview = ["pixi-build"]/' pixi.toml
pixi add "mojo-compiler==1.2.0"
pixi shelf add arrow-mlake-mojo # brings memory-region-mojo
Quick start
Build a two-column batch by hand, write it as an Arrow IPC stream and read it back:
from arrow_mlake import (
AT_INT64, AT_UTF8, ArrayData, ArrowType, IpcWriter, RecordBatch,
bit_set, read_ipc_stream, store_u64,
)
def main() raises:
var ids = ArrayData(ArrowType(AT_INT64), "id")
ids.length = 3
ids.null_count = 1
for i in range(3):
bit_set(ids.validity, i, i != 1) # row 1 is null
store_u64(ids.values, UInt64(i * 10))
var names = ArrayData(ArrowType(AT_UTF8), "name")
names.length = 3
names.offsets.append(0)
for s in ["ada", "bo", "cy"]:
names.values.extend(s.as_bytes())
names.offsets.append(Int32(len(names.values)))
var batch = RecordBatch()
batch.roots.append(batch.arena.add(ids^))
batch.roots.append(batch.arena.add(names^))
batch.num_rows = 3
var w = IpcWriter(batch) # the schema comes from this batch
w.write(batch)
var bytes = w^.finish() # pyarrow.ipc.open_stream reads this
var s = read_ipc_stream(Span(bytes))
ref back = s.batches[0]
var col = back.column_i64(0) # (values, validity)
var strs = back.column_str(1)
for i in range(back.num_rows):
var v = String(col[0][i]) if col[1][i] else String("null")
print(v, strs[0][i])
API
Everything below is exported from arrow_mlake except the shared-mapping export.
ArrowType(id: Int): a logical type, id one of the AT_* constants (AT_INT64, AT_UTF8, AT_LIST, AT_STRUCT, …). at_decimal(precision: Int, scale: Int), at_timestamp(unit: Int, var tz: String), at_time(unit: Int) and at_fixed(width: Int) build the parameterized ones; format(self) -> String is its C format string.
ArrayData(var type: ArrowType, var name: String): one array as the columnar spec lays it out. Its fields are length, null_count, nullable, field_id, validity, offsets (List[Int32]), large_offsets, values (raw bytes) and children, which are indices into the arena. bit_set, bit_get and store_u64 help fill the buffers.
ArrayArena: owns every array of a batch, nested ones included. add(mut self, var node: ArrayData) -> Int appends one and returns its index.
RecordBatch: arena, roots (one index per column) and num_rows.
num_columns(self) -> Int,name(self, i: Int) -> String,type(self, i: Int) -> ArrowTypeandcolumn(ref self, i: Int): the columns.column_i64(self, i: Int) raises -> Tuple[List[Int64], List[Bool]], andcolumn_f64,column_bool,column_str: a column as values plus validity.
IPC streams:
IpcWriter(schema: RecordBatch) raises, thenwrite(mut self, batch: RecordBatch) raisesper batch andfinish(deinit self) -> List[UInt8].write_ipc_stream(batches: List[RecordBatch]) raises -> List[UInt8]: the same in one call.read_ipc_stream(data: Span[UInt8, _]) raises -> IpcStream:schema(a batch of no rows) and every batch inbatches.
C Data Interface, with addresses passed as Int:
export_c(arena: ArrayArena, root: Int) raises -> ExportedArray: one array out, buffers copied.into_raw(mut self) -> Tuple[Int, Int]hands theArrowArrayandArrowSchemato the consumer, which must release them.import_c(mut arena: ArrayArena, array: Int, schema: Int) raises -> Int: copies a pair into the arena and releases nothing.ImportedArray(array: Int, schema: Int): takes ownership of a pair;into_batch(self) raises -> RecordBatch, and the release callbacks run once, onrelease()or at destruction.ImportedStream(stream: Int) raises: takes anArrowArrayStream;next(mut self) raises -> Optional[RecordBatch]until it returns None.export_stream(arena: ArrayArena, roots: List[Int]) raises -> Int: these arrays as a stream the consumer pulls and releases.
Across processes (from arrow_mlake.carrow_shared import ...): export_shared(arena: ArrayArena, roots: List[Int], names: List[String], path: String) raises -> String writes flat columns into a new shared mapping at path and returns a JSON manifest of where each buffer landed, as offsets from the base. export_shared_into and shared_size put several batches in one mapping.
Errors and limits
- Types it can’t represent raise with the format string:
arrow_mlake.carrow: <why> — Arrow format string "<fmt>". That covers unions, run-end encoding, list views, fixed-size lists,date64, durations, intervals, decimal32/64/256, the view types and dictionary encoding. - IPC errors start with
ipc:. Dictionaries, compression and the IPC file format aren’t implemented and are refused by name; the reader bounds-checks every FlatBuffers offset and buffer. - Every import copies the producer’s buffers into
ArrayData; nothing is borrowed. A producer’soffsetis honored by copying the window it names. - What the C interface lets you check is checked (buffer and child counts, offsets, nesting up to
MAX_IMPORT_DEPTH), but it carries no buffer sizes, so a truncated data buffer can’t be detected. get_nextfailing raises with the producer’sget_last_errormessage; the end of a stream is a released array, never an error.export_sharedtakes primitive,utf8andbinarycolumns only, and raises on nested ones.
Tests
pixi run test builds every layout by hand against the columnar spec and round-trips it through C export, import and IPC, with negative controls the comparators must catch. The pyarrow gates need uv: verify-c-import has pyarrow produce 63 cases and compare what comes back, verify-c-stream has pyarrow drain a Mojo ArrowArrayStream, and verify-ipc checks IPC streams both ways. CI runs the tests on Linux and macOS with Mojo 1.2.0 and nightly, and the three pyarrow gates with nightly.
The full reference is the README on GitHub.