Nathan Cai
10/17/2025, 4:00 PMtests/connect/test_io.py
@pytest.mark.skip(reason="<https://github.com/Eventual-Inc/Daft/issues/3786>")
def test_write_csv_with_delimiter(make_df, make_spark_df, spark_session, tmp_path):
pass
But the thing is, I don't know what these arguments mean, and I don't see that function being invoked anywhere else so I don't know where it's being provided those arguments.
But the other thing is, its function signature is different from the ones above:
def test_csv_basic_roundtrip(make_spark_df, assert_spark_equals, spark_session, tmp_path):
And I think I might need the assert_spark_equals function in my scenario, I was wondering if someone is willing to guide me in the right direction.Malcolm Greaves
10/20/2025, 11:47 PMNathan Cai
10/21/2025, 8:05 PMNathan Cai
10/21/2025, 9:04 PMMalcolm Greaves
10/21/2025, 9:04 PMNathan Cai
10/21/2025, 9:09 PMNathan Cai
10/21/2025, 9:09 PMMalcolm Greaves
10/21/2025, 9:17 PMMalcolm Greaves
10/21/2025, 9:17 PMI just realized it only works for empty csv filesWhat kind of test can you add to this PR to check this? 🤔
Nathan Cai
10/21/2025, 9:36 PMDataFrame.write_csv
builder = self._builder.write_tabular(
root_dir=root_dir,
partition_cols=cols,
write_mode=WriteMode.from_str(write_mode),
file_format=FileFormat.Csv,
io_config=io_config,
)
In the LogicPlanBuilder
class, I looked deeper into the rust bindings, and it calls the table_write method of the LogicPlanBuilder,
But in table_write method, I don't see anywhere that handles the logic of writing to files,
pub fn table_write(
&self,
root_dir: &str,
write_mode: WriteMode,
file_format: FileFormat,
partition_cols: Option<Vec<ExprRef>>,
compression: Option<String>,
io_config: Option<IOConfig>,
) -> DaftResult<Self> {
let expr_resolver = ExprResolver::default();
let partition_cols = partition_cols
.map(|cols| expr_resolver.resolve(cols, self.plan.clone()))
.transpose()?;
let sink_info = SinkInfo::OutputFileInfo(OutputFileInfo::new(
root_dir.into(),
write_mode,
file_format,
partition_cols,
compression,
io_config,
));
let logical_plan: LogicalPlan =
ops::Sink::try_new(self.plan.clone(), sink_info.into())?.into();
Ok(self.with_new_plan(logical_plan))
}
The next step I'd assume is under logical_plan but to me it seems to simply return the fields when I look into the try_new function?Malcolm Greaves
10/21/2025, 9:53 PMcollect (bring everything into memory on a single machine), show (get the first 8 records, print them, then stop), and write_<XYZ> (write the results of the dataframe as format <XYZ>).
So you're not finding these things when you goto definition because you're just seeing the logical plan being constructed.Malcolm Greaves
10/21/2025, 9:57 PMMalcolm Greaves
10/21/2025, 10:33 PMdaft.DataFrame.write_csv follows this path:
### 1. Python Entry Point
- daft/dataframe/dataframe.py:846 - DataFrame.write_csv() method
### 2. Rust Physical Plan Node
- src/daft-physical-plan/src/ops/csv.rs - Defines TabularWriteCsv struct that represents the CSV write operation in the physical plan
### 3. Rust Writer Factory
- src/daft-writers/src/lib.rs - make_physical_writer_factory() creates writer factories based on file format
- src/daft-writers/src/physical.rs - PhysicalWriterFactory routes to the appropriate writer
### 4. Actual CSV Writing Implementation
For CSV writes, the implementation uses PyArrow (not native Rust):
- Rust side: src/daft-writers/src/pyarrow.rs:48-75 - PyArrowWriter::new_csv_writer() creates a Python-based CSV writer
- Python side: daft/io/writer.py:213-246 - CSVFileWriter class uses PyArrow's pacsv.CSVWriter to write CSV files
The key implementation is in the CSVFileWriter class at daft/io/writer.py:213, which:
- Uses PyArrow's pacsv.CSVWriter (Arrow CSV writer)
- Calls write_table() on PyArrow tables
- Handles file creation, partitioning, and metadata
Note: Unlike Parquet and JSON which have native Rust implementations, CSV writing currently delegates to PyArrow through the Python/Rust bridge (PyO3).Nathan Cai
10/23/2025, 6:28 PMMalcolm Greaves
10/25/2025, 10:45 PMyou said that Rust doesn't have native CSV support but there is a crate for that?Ah sorry, wasn't clear enough I suppose -- it's not that Rust can't do it. It's just that we implemented a different way to do this in Daft.
Malcolm Greaves
10/25/2025, 10:45 PM