Sorting¶
Source: src/memframe/wrappers/analytix/sorting.py
SortingWrapper is the public sorting interface exposed through a
ContextManager. It provides a pandas-like sort_values API backed by
SQL ORDER BY across DuckDB, PostgreSQL, and ClickHouse.
Users normally call sorting directly on a dataset context returned by an upload operation:
The same method is available asynchronously:
dataset = await mf.aupload_df(frame)
result = await dataset.asort_values(by="score", ascending=False)
The lower-level files are implementation details:
src/memframe/core/analytix/sorting/builds and executes backend-specific SQL.src/memframe/core/orchestrator/analytix/sorting.pyresolves the active dataset context and passes persistence metadata (withdeep_cacheso sorted tables are replayable).src/memframe/wrappers/analytix/sorting.pyexposes synchronous and asynchronous public methods.
Public API¶
Every sorting operation has synchronous and asynchronous forms:
| Synchronous | Asynchronous | Purpose |
|---|---|---|
sort_values(by, ascending=True, na_position="last", columns="*", chunk_size=None) |
await asort_values(...) |
Sort rows by one or more columns |
Public methods return the operation value directly (a DataFrame or an async
iterator). Invalid operations raise OperationError.
Usage Overview¶
dataset = mf.upload_df(frame)
sample = dataset.sort_values(by="score")
sample = dataset.sort_values(by=["region", "score"], ascending=[True, False])
nulls_first = dataset.sort_values(by="score", na_position="first")
subset = dataset.sort_values(by="score", columns=["name", "score"])
dataset = await mf.aupload_df(frame)
sample = await dataset.asort_values(by="score", ascending=False)
iterator = await dataset.asort_values(by="score", chunk_size=1000)
async for chunk in iterator:
process(chunk)
Sorting materializes a new transient table (<table>__op_<n>) that is a sorted
clone of the source table. The public return is a sample DataFrame (or an
async iterator when chunk_size is set); new_table metadata remains available
to the cache layer.
Parameters¶
| Parameter | Type | Description |
|---|---|---|
by |
str or list[str] |
Column(s) to sort by. |
ascending |
bool or list[bool] |
Sort direction per column. A scalar True/False is broadcast. Defaults to True. |
na_position |
"first" or "last" |
Where nulls sort. Defaults to "last". |
columns |
str, list[str], or "*" |
Columns to include in the result. by columns are always included even when a subset is requested. A str like "name" is treated as ["name"]. Defaults to "*" (all columns). |
chunk_size |
int or None |
If set, return an async iterator of DataFrame chunks of this size instead of a single sample. Must be a positive int. |
Validation:
ascendinglength must matchbylength.na_positionmust be"first"or"last".chunk_sizemust be a positiveint(boolis rejected;chunk_size=1is allowed).
Failed validation returns is_error True internally and raises OperationError
to the caller via ContextManager dispatch.
Null Handling¶
PostgreSQL and DuckDB use native NULLS FIRST / NULLS LAST in the generated
ORDER BY. ClickHouse has no NULLS FIRST/LAST — its defaults are ASC → NULLS LAST,
DESC → NULLS FIRST (matching Postgres/DuckDB), so the engine adds an
(col IS NULL) sentinel only when the requested position is non-default:
na_position="first"+ASC→(col IS NULL) DESC, col ASCna_position="last"+DESC→(col IS NULL) ASC, col DESC
All other combinations use plain col ASC/DESC.
Chunked Streaming¶
When chunk_size is set, the result contains iterator (an async_generator of
DataFrame chunks) and chunk_size, with result=None. Iterate it:
result = dataset.sort_values(by="score", chunk_size=1000)
# result["iterator"] is async iterable; result["result"] is None
async for chunk in result["iterator"]:
sink(chunk)
Or via the public ContextManager path (await dataset.asort_values(...)
unwraps result vs iterator automatically src/memframe/core/analytix/_response.py:49):
iterator = await dataset.asort_values(by="score", chunk_size=1000)
async for chunk in iterator:
print(chunk.head())
The iterator transparently handles the cache's transient-table move (upload
schema → memframe_transient) that happens between table creation and
consumption.
Generated Tables¶
Every sort_values is non-destructive to the source upload table:
- Resolves
column_typesforbyandcolumns. - Generates a transient name
<table>__op_<n>via the registry. CREATE TABLE <new> AS SELECT <select_clause> FROM <source> ORDER BY <order_sql>(ENGINE = MergeTree() ORDER BY tuple()+ORDER BYinside theSELECTon ClickHouse).- Non-chunked: sample via
SELECT *from the new table; chunked:LIMIT/OFFSETpages. deep_cache=Truepersists the sorted clone for replay.
Backend Behavior¶
Sorting supports DuckDB, PostgreSQL, and ClickHouse:
- Identifiers are sanitized via
SQLIdentifierSanitizerand quoted per backend ("vs`). ORDER BYterms are built from the same logical spec on all backends; only the null sentinel logic diverges (see above).- ClickHouse
MERGE TREEordering: the table'sORDER BY tuple()is a no-op for arbitrary user sorts, so theORDER BY <sort_cols>inside theSELECTcarries the actual ordering (standard pattern forCREATE TABLE … AS SELECT … ORDER BYon ClickHouse).
Errors¶
Sorting raises OperationError for validation or backend failures:
backend and data_id required— internal invariant, should not surface in normal use (missing active dataset).Length of 'ascending' must match 'by'na_position must be 'first' or 'last'chunk_size must be a positive integerUnsupported database backend for sorting operation— unknown adapter.
API Reference¶
memframe.wrappers.analytix.sorting.SortingWrapper
¶
Bases: SortingOrchestrator
Public sync/async wrapper over SortingOrchestrator.
Supports
- sync : .sort_values(...)
- async : .asort_values(...)