The lazy CSV source: scan_csv(path) builds a LazyFrame whose leaf is a
deferred read of path — the push-down-aware counterpart of eager
read_csv. Nothing is read or parsed until collect: the builder is
total, like every other LazyFrame constructor, so a missing file or a
malformed CSV only surfaces then (as the eager reader's IoError /
ParseError / DuplicateColumn). It is not a streaming read — the reader
still tokenises the whole file, and streaming it is tracked as future
work.
The payoff over LazyFrame::LazyFrame(read_csv(path)) is that this leaf
absorbs both push-downs, so the work it skips is work the eager pipeline
does:
- projection — the optimizer narrows the leaf to the columns the
pipeline provably consumes, and only those are parsed, so
scan_csv("sales.csv").select([col("region"), col("revenue")]).collect()
never builds the columns it drops;
- predicate — a filter sitting on the leaf moves into the read: the
reader builds the predicate's columns, asks which rows survive, then parses
the remaining columns for the survivors alone. Only the first such filter
is absorbed; a second stays a node above the scan.
Both prune cells an eager read-then-filter would have parsed, which is the
one way a narrowed scan diverges from a full eager read: a ParseError
confined to a dropped column, or to a row the predicate drops in a column the
predicate does not read, does not surface. Dtype inference still walks the
whole file, so dtypes and the surviving cells match the eager read exactly.
options (delimiter, header, null strings, inference window, parse-error
policy, strict quotes, …) defaults to CsvReadOptions::CsvReadOptions(), mirroring eager
read_csv. It is captured into the plan and applied by the reader at
collect time; projection push-down narrows which columns those options
parse, never which options apply. The projection starts None (read every
column) — only the optimizer fills it.