1#![cfg_attr(feature = "parquet", doc = include_str!("../README.md"))]
7
8use std::cell::{RefCell, RefMut};
9use std::path::PathBuf;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12use anyhow::{Context, Result};
13#[cfg(feature = "arrow")]
14use arrow::array::StructArray;
15use rayon::prelude::*;
16use thread_local::ThreadLocal;
17#[cfg(feature = "arrow")]
18pub use arrow;
19
20#[cfg(feature = "csv")]
21mod csv;
22#[cfg(feature = "csv")]
23pub use csv::*;
24
25#[cfg(feature = "arrow-ipc")]
26mod ipc;
27#[cfg(feature = "arrow-ipc")]
28pub use ipc::*;
29
30#[cfg(feature = "parquet")]
31mod parquet_;
32#[cfg(feature = "parquet")]
33pub use parquet_::*;
34
35mod partitioned;
36pub use partitioned::*;
37
38#[cfg(feature = "zstd")]
39mod zstd;
40#[cfg(feature = "zstd")]
41pub use zstd::*;
42
43#[cfg(feature = "arrow")]
44#[allow(clippy::len_without_is_empty)]
45pub trait StructArrayBuilder {
46 fn len(&self) -> usize;
48 fn buffer_size(&self) -> usize;
50 fn finish(&mut self) -> Result<StructArray>;
51}
52
53pub struct ParallelDatasetWriter<W: TableWriter + Send> {
55 num_files: AtomicU64,
56 schema: W::Schema,
57 path: PathBuf,
58 writers: ThreadLocal<RefCell<W>>,
59 pub config: W::Config,
60}
61
62impl<W: TableWriter<Schema = ()> + Send> ParallelDatasetWriter<W>
63where
64 W::Config: Default,
65{
66 pub fn new(path: PathBuf) -> Result<Self> {
67 std::fs::create_dir_all(&path)
68 .with_context(|| format!("Could not create {}", path.display()))?;
69 Ok(ParallelDatasetWriter {
70 num_files: AtomicU64::new(0),
71 schema: (),
72 path,
73 writers: ThreadLocal::new(),
74 config: W::Config::default(),
75 })
76 }
77}
78
79impl<W: TableWriter + Send> ParallelDatasetWriter<W>
80where
81 W::Config: Default,
82{
83 pub fn with_schema(path: PathBuf, schema: W::Schema) -> Result<Self> {
84 std::fs::create_dir_all(&path)
85 .with_context(|| format!("Could not create {}", path.display()))?;
86 Ok(ParallelDatasetWriter {
87 num_files: AtomicU64::new(0),
88 schema,
89 path,
90 writers: ThreadLocal::new(),
91 config: W::Config::default(),
92 })
93 }
94
95 fn get_new_seq_writer(&self) -> Result<RefCell<W>> {
96 let path = self
97 .path
98 .join(self.num_files.fetch_add(1, Ordering::Relaxed).to_string());
99 Ok(RefCell::new(W::new(
100 path,
101 self.schema.clone(),
102 self.config.clone(),
103 )?))
104 }
105
106 pub fn get_thread_writer(&self) -> Result<RefMut<W>> {
113 self.writers
114 .get_or_try(|| self.get_new_seq_writer())
115 .map(|writer| writer.borrow_mut())
116 }
117
118 pub fn flush(&mut self) -> Result<()> {
120 self.writers
121 .iter_mut()
122 .collect::<Vec<_>>()
123 .into_par_iter()
124 .map(|writer| writer.get_mut().flush())
125 .collect::<Result<Vec<()>>>()
126 .map(|_: Vec<()>| ())
127 }
128
129 pub fn close(mut self) -> Result<Vec<W::CloseResult>> {
131 let mut tmp = ThreadLocal::new();
132 std::mem::swap(&mut tmp, &mut self.writers);
133 tmp.into_iter()
134 .collect::<Vec<_>>()
135 .into_par_iter()
136 .map(|writer| writer.into_inner().close())
137 .collect()
138 }
139}
140
141impl<W: TableWriter + Send> Drop for ParallelDatasetWriter<W> {
142 fn drop(&mut self) {
143 let mut tmp = ThreadLocal::new();
144 std::mem::swap(&mut tmp, &mut self.writers);
145 tmp.into_iter()
146 .collect::<Vec<_>>()
147 .into_par_iter()
148 .try_for_each(|writer| writer.into_inner().close().map(|_| ()))
149 .expect("Could not close ParallelDatasetWriter");
150 }
151}
152
153pub trait TableWriter {
154 type Schema: Clone;
155 type CloseResult: Send;
156 type Config: Clone;
157
158 fn new(path: PathBuf, schema: Self::Schema, config: Self::Config) -> Result<Self>
159 where
160 Self: Sized;
161
162 fn flush(&mut self) -> Result<()>;
164
165 fn close(self) -> Result<Self::CloseResult>;
166}