AS FRAME keeps query results in host-resident columnar storage:
frame := FROM Booking
WHERE created_at >= cutoff
SELECT { id, nights }
AS FRAME?The result is Frame[Row]. Row is the compile-time schema used when rows are
materialized. The frame itself is a thin, generation-tagged resource handle;
it does not contain guest rows.
Engines
Two engines have distinct jobs:
| Layer | Engine | Responsibility |
|---|---|---|
| Query | datasource engine | Execute FROM Model ...; DataFusion is one possible query engine |
| Manipulation | Polars | Execute every Df[T] operation after frame.df() |
A DataFusion result bridges its Arrow batches into Polars without copying the columns. Results from Turso, PostgreSQL, and MySQL are converted from their host-side cell grids. The Atoll surface is identical in both cases.
Frame operations
count := frame.count()
rows := frame.collect()?
for index in 0..frame.batches() {
batch := frame.collect_batch(index)?
consume(batch)
}
frame.release()Dropping a frame releases its host entry; release() does so early. Generation
tagging prevents a stale handle from reading a later frame.
count() reads only metadata. collect() copies and decodes every row into
guest memory, while collect_batch() bounds that copy to one host batch. A
row-engine frame currently reports one logical batch; a columnar result may
have many.
An out-of-range batch or stale frame returns QueryError from collection.
count() and batches() report 0 for a stale handle. release() is
idempotent, and Drop releases a frame on normal exit, error propagation, or
cancellation.
Lazy plans
frame.df() creates Df[T], a lazy Polars plan:
top := frame.df()
.filter(col("nights").gt(lit(3)))
.sort("nights", true)
.head(10)
rows := top.collect()?Deriving a plan does not scan the data. collect() executes and copies rows to
the guest; to_frame() executes into another host-resident Frame[T].
Schema-preserving operations include head, tail, slice, unique, sort,
sort_by, and filter. Schema-changing operations include select[R],
with_columns[R], inner and left joins, and
group_by(...).agg[R](...).
The result type R is a plain row struct. Output columns bind to its fields by
name, not expression order. Right-side fields made nullable by a left join
must be optional in R.
Expressions
col("name") and lit(value) create Expr leaves. Comparisons are methods
such as .eq, .lt, and .ge; Atoll comparison operators themselves return
bool and therefore cannot build an expression tree. Combine predicates with
&, |, and !:
matches := frame.df().filter(
col("nights").ge(lit(3))
& col("status").eq(lit("confirmed"))
)Expressions also support arithmetic, aliases, aggregates such as .sum() and
.count(), scalar-list membership, and literal substring predicates.
Schema-changing expressions must be named with a bare column or
.alias("field_name") so they can bind to R.
Frame queries
A local Frame[Row] or Df[Row] can be a FROM source:
rows := FROM frame
WHERE nights > minimum
ORDER BY nights DESC?This syntax lowers to dataframe operations, not database SQL. AS DF is
available for frame-local queries and returns the lazy plan without Result.
AS DF on a table query is rejected.
Frame-source terminals mirror table-source shapes:
| Terminal | Type |
|---|---|
| default | Result[[]Row, QueryError] |
ONE FROM |
Result[Row?, QueryError] |
AS FRAME |
Result[Frame[Row], QueryError] |
AS DF |
Df[Row] |
The supported frame-query subset includes predicates, projections, grouping, having, ordering, paging, and same-named-key joins. CTEs, windows, set operations, runtime sort directions, conditional clauses, right/cross joins, and renamed join keys currently diagnose.
Files
read_parquet[T](path) and read_csv[T](path) return lazy Df[T] plans. The
host validates the file schema against T; extra columns are projected away,
while missing or incompatible required columns return QueryError. CSV uses
its header and the declared T rather than inferring a changing public shape.
These scans are lazy: opening metadata validates the schema, and a terminal executes the scan. File ingestion does not require a configured datasource. Writing frames to files, raw dataframe SQL, regular-expression predicates, cloud or glob scans, and window operations over frames are not part of the current surface.