QueryResult & MutationPlan#

Two small result objects returned by Database: the holder a finished query produces, and the staged handle a mutation plan produces.

QueryResult#

Returned by Database.sql(). The query has already run; this holds its Arrow result and converts on demand, so data stays in Arrow until you ask for a specific frame type. For a query that has not run yet, see the DataFrame builder.

res = db.sql("SELECT symbol, vwap(price, size) AS v FROM trades GROUP BY symbol")
res.to_pandas()     # -> pandas.DataFrame
len(res)            # -> row count

QueryResult.to_arrow#

to_arrow() -> pyarrow.Table

The underlying Arrow table, types preserved exactly (timestamps keep unit and timezone). Zero-copy.

QueryResult.to_pandas#

to_pandas() -> pandas.DataFrame

Convert to a pandas DataFrame.

QueryResult.to_polars#

to_polars() -> polars.DataFrame

Convert to a Polars DataFrame.

Raises

ImportError
Polars is not installed (it is an optional dependency).

Dunder methods#

len(res)     # row count
repr(res)    # repr of the underlying Arrow table

MutationPlan#

A previewable, not-yet-published mutation, returned by plan_replace_range / plan_delete_range and list_plans. The staged segments already exist on disk; publishing is a metadata-only atomic swap.

plan = db.plan_delete_range("trades", t0_us, t1_us, note="strip bad ticks")

plan.summary            # {"rows_affected": 12481, "segments_reused": 127, …}
plan.before_sample      # pyarrow.Table of rows as they are now
plan.after_sample       # pyarrow.Table of rows as they would become

plan.apply()            # publish (or plan.discard())

Attributes#

table (str)
Table the plan targets.
plan_id (str)
UUID, also usable from the CLI (h5i-db plan apply …) and the review UI.
summary (dict)
Machine-readable impact: affected rows, segments rewritten vs. reused.
raw (dict)
The full plan document as stored.

MutationPlan.before_sample#

before_sample -> pyarrow.Table | None

A property holding a sample of the affected rows before the mutation, or None when the plan carries no sample.

MutationPlan.after_sample#

after_sample -> pyarrow.Table | None

A property holding the same rows after the mutation would apply.

MutationPlan.apply#

apply() -> dict

Publish the plan as a new version.

Returns

dict with commit metadata for the new version.

Raises

ConflictError
The table head moved since the plan was made. Re-plan instead of retrying: the plan was computed against a base version that no longer reflects reality.

MutationPlan.discard#

discard() -> None

Drop the plan; its staged segments become vacuum candidates immediately. Abandoned plans (neither applied nor discarded) expire after 7 days; see plan hygiene.

Policy interaction

With the mutation policy gating direct deletes/writes, the plan flow is the only way to mutate. That is the point: every destructive change gets a previewed, auditable checkpoint.