logic
Pure transformations, with no storage or infrastructure dependencies.
Entity Aggregation
Fold a stream of statement dicts into entities, without building FtM objects – ftmq.aggregate:
from ftmq.aggregate import aggregate_statement_payloads
for entity in aggregate_statement_payloads(statement_dicts, "my_dataset"):
print(f"{entity.id}: {entity.to_dict()['caption']}")
The input must be contiguous per entity_id, as the parquet store's reads are.
Parquet helpers
The DuckDB SQL behind ParquetStore. Reads, merges and re-shards name a partition's files directly (partition_source_sql); a read's statement view is a plain scan over merged partitions (live_rows_sql) and the dedupe query otherwise (dedupe_rows_sql). raw_view_sql is the connection-level LakeStore's statement_raw view over delta_scan; statements sql runs on ParquetStore.cursor, the whole table registered like a read.
ftm_lakehouse.logic.parquet.duckdb_config()
DuckDB config from the lakehouse settings – memory limit, spill and extension directories.
Call once per DuckDB instance: each call gets its own spill subdirectory,
as instances sharing one overwrite each other's spill files. Warns once
when that directory is RAM-backed. No TimeZone – at connect time it
installs icu (fails offline); sessions pin UTC after connecting.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.raw_view_sql(dt)
SELECT body for the statement_raw view: every physical row,
tombstones and duplicates included.
ftm_lakehouse.logic.parquet.live_rows_sql(source)
Live rows of a clean source – a filtered scan. canonical_id is
entity_id, for ftmq's query layer.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.dedupe_rows_sql(source)
Live rows of a source that may hold un-merged files: _dedupe_sql,
canonical_id as in live_rows_sql. Row for row what
the same partition reads once merged.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.delta_scan_sql(table_uri)
delta_scan('<uri>'), the uri quote-escaped (no prepared parameters).
ftm_lakehouse.logic.parquet.build_merge_sql(shard, bucket, origin, grace_cutoff, source=TABLE_RAW.name, select='*')
_dedupe_sql collapsing one partition for a merge, ordered by the file
sort key. Tombstones within grace survive to keep shadowing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shard
|
str
|
The partition's shard. |
required |
bucket
|
str
|
The partition's bucket. |
required |
origin
|
str
|
The partition's origin – validated before interpolation. |
required |
grace_cutoff
|
datetime
|
Tombstones with |
required |
source
|
str
|
Relation holding the rows – |
name
|
select
|
str
|
Projection of the output, e.g. without the partition columns. |
'*'
|
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.partition_source_sql(files, shard, bucket, origin)
One partition's files as a SHARDED_SCHEMA relation, read without
delta_scan – relation_source_sql over the
files, columns they predate reading as NULL.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.relation_source_sql(relation, shard, bucket, origin)
relation – rows of a partition's files, the partition columns absent –
as a SHARDED_SCHEMA relation: the partition columns as constants,
fragment coalesced to ''. origin is validated before
interpolation.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.read_parquet_sql(files)
read_parquet over files, unioned by name. No hive partitioning:
it would parse a shard like 10 as an integer.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.worker_duckdb_config(workers)
duckdb_config for one of workers processes, one call
per task.
Memory limit and threads are split between the workers.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.build_shard_sql(shard, bucket, origin, shards, source=TABLE_RAW.name)
One partition's raw rows with shard recomputed for shards shards.
Unordered and un-deduped – the follow-up merge does both. origin is
validated before interpolation.
Source code in ftm_lakehouse/logic/parquet.py
ftm_lakehouse.logic.parquet.shard_expr_sql(shards, column='entity_id')
DuckDB expression for column's shard key – the SQL twin of
entity_shard, pinned to agree by test_shard_expr_sql_parity.
Source code in ftm_lakehouse/logic/parquet.py
Merge helpers
The pure Arrow side of ParquetStore.merge: a partition is read as entity_id-sorted streams and cut into entity-closed chunks, each deduped by build_merge_sql on its own – every window key holds entity_id, so the chunks give the rows the whole partition would, with memory bounded by the chunk rather than the partition.
ftm_lakehouse.logic.merge.entity_chunks(streams, target)
Cut entity_id-sorted streams into entity-closed chunks of about
target Arrow bytes, in ascending entity_id ranges.
A chunk holds every row of each of its entities – across all streams – and
no two chunks share an entity, so a query keyed on entity_id gives the
same rows per chunk as over the whole. Rows within a chunk are not sorted.
Memory is the chunk plus about one batch per stream.
Each step advances the stream whose newest batch ends lowest; every row below the lowest such end is complete, as no stream has more of it to give.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
streams
|
Iterable[Iterator[RecordBatch]]
|
Record batches sharing one schema, each stream ascending by
|
required |
target
|
int
|
Arrow bytes to read between two chunks. |
required |
Yields:
| Type | Description |
|---|---|
Table
|
|
Source code in ftm_lakehouse/logic/merge.py
Statement Serialization
Statements are packed once, columnwise, by ftm_lakehouse.model.statement.statements_to_arrow – see Model.