1use std::io;
2use std::num::NonZeroUsize;
3use std::ops::{Deref, DerefMut};
4use std::sync::Arc;
5
6use polars_error::{PolarsResult, feature_gated, polars_err};
7use polars_utils::file::close_file;
8use polars_utils::io::create_file;
9use polars_utils::mmap::ensure_not_mapped;
10use polars_utils::pl_path::{PlRefPath, format_file_uri};
11
12use super::sync_on_close::SyncOnCloseType;
13use crate::cloud::CloudOptions;
14use crate::metrics::IOMetrics;
15use crate::resolve_homedir;
16
17pub trait WritableTrait: std::io::Write {
19 fn close(&mut self) -> std::io::Result<()>;
20 fn sync_all(&self) -> std::io::Result<()>;
21 fn sync_data(&self) -> std::io::Result<()>;
22}
23
24#[allow(clippy::large_enum_variant)] pub enum Writable {
31 Dyn(Box<dyn WritableTrait + Send>),
35 Local(std::fs::File),
36 #[cfg(feature = "cloud")]
37 Cloud(crate::cloud::cloud_writer::CloudWriterIoTraitWrap),
38}
39
40impl Writable {
41 pub fn try_new(
42 path: PlRefPath,
43 #[cfg_attr(not(feature = "cloud"), expect(unused))] cloud_options: Option<&CloudOptions>,
44 #[cfg_attr(not(feature = "cloud"), expect(unused))] cloud_upload_chunk_size: Option<
45 NonZeroUsize,
46 >,
47 #[cfg_attr(not(feature = "cloud"), expect(unused))] cloud_upload_concurrency: usize,
48 io_metrics: Option<Arc<IOMetrics>>,
49 ) -> PolarsResult<Self> {
50 Ok(if path.has_scheme() {
51 feature_gated!("cloud", {
52 use polars_core::runtime::ASYNC;
53
54 use crate::cloud::cloud_writer::CloudWriterIoTraitWrap;
55
56 let writer = ASYNC.block_in_place_on(new_cloud_writer(
57 path,
58 cloud_options,
59 cloud_upload_chunk_size,
60 cloud_upload_concurrency.try_into().unwrap(),
61 io_metrics,
62 ))?;
63
64 Self::Cloud(CloudWriterIoTraitWrap::from(writer))
65 })
66 } else if polars_config::config().force_async() {
67 feature_gated!("cloud", {
68 let path = resolve_homedir(path.as_std_path());
69 create_file(&path)?;
70 let path = std::fs::canonicalize(&path)?;
71
72 ensure_not_mapped(&path.metadata()?)?;
73
74 let path = path.to_str().ok_or_else(|| polars_err!(non_utf8_path))?;
75 let path = format_file_uri(path);
76
77 use polars_core::runtime::ASYNC;
78
79 use crate::cloud::cloud_writer::CloudWriterIoTraitWrap;
80
81 let writer = ASYNC.block_in_place_on(new_cloud_writer(
82 path,
83 cloud_options,
84 cloud_upload_chunk_size,
85 cloud_upload_concurrency.try_into().unwrap(),
86 io_metrics,
87 ))?;
88
89 Self::Cloud(CloudWriterIoTraitWrap::from(writer))
90 })
91 } else {
92 let path = resolve_homedir(path.as_std_path());
93 create_file(&path)?;
94
95 Self::Local(polars_utils::io::open_file_write(&path)?)
96 })
97 }
98
99 #[cfg(feature = "cloud")]
102 pub async fn write_all_owned<T>(&mut self, src: &mut T) -> io::Result<()>
103 where
104 T: AsRef<[u8]> + Default + Drop, bytes::Bytes: From<T>,
106 {
107 match self {
108 Self::Cloud(v) => {
109 v.write_all_owned(bytes::Bytes::from(std::mem::take(src)))
110 .await
111 },
112 Self::Dyn(_) | Self::Local(_) => self.write_all(src.as_ref()),
113 }
114 }
115
116 pub fn as_buffered(&mut self) -> BufferedWritable<'_> {
117 match self {
118 Writable::Dyn(v) => BufferedWritable::BufWriter(std::io::BufWriter::new(v.as_mut())),
119 Writable::Local(v) => BufferedWritable::BufWriter(std::io::BufWriter::new(v)),
120 #[cfg(feature = "cloud")]
121 Writable::Cloud(v) => BufferedWritable::Direct(v as _),
122 }
123 }
124
125 pub fn sync_all(&self) -> io::Result<()> {
126 match self {
127 Self::Dyn(v) => v.sync_all(),
128 Self::Local(v) => v.sync_all(),
129 #[cfg(feature = "cloud")]
130 Self::Cloud(v) => v.sync_all(),
131 }
132 }
133
134 pub fn sync_data(&self) -> io::Result<()> {
135 match self {
136 Self::Dyn(v) => v.sync_data(),
137 Self::Local(v) => v.sync_data(),
138 #[cfg(feature = "cloud")]
139 Self::Cloud(v) => v.sync_data(),
140 }
141 }
142
143 pub fn close(self, sync: SyncOnCloseType) -> std::io::Result<()> {
144 match sync {
145 SyncOnCloseType::All => self.sync_all()?,
146 SyncOnCloseType::Data => self.sync_data()?,
147 SyncOnCloseType::None => {},
148 }
149
150 match self {
151 Self::Dyn(mut v) => v.close(),
152 Self::Local(v) => close_file(v),
153 #[cfg(feature = "cloud")]
154 Self::Cloud(mut v) => v.close(),
155 }
156 }
157}
158
159impl io::Write for Writable {
160 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
161 match self {
162 Self::Dyn(v) => v.write(buf),
163 Self::Local(v) => v.write(buf),
164 #[cfg(feature = "cloud")]
165 Self::Cloud(v) => v.write(buf),
166 }
167 }
168
169 fn flush(&mut self) -> io::Result<()> {
170 match self {
171 Self::Dyn(v) => v.flush(),
172 Self::Local(v) => v.flush(),
173 #[cfg(feature = "cloud")]
174 Self::Cloud(v) => v.flush(),
175 }
176 }
177}
178
179impl Deref for Writable {
180 type Target = dyn io::Write + Send;
181
182 fn deref(&self) -> &Self::Target {
183 match self {
184 Self::Dyn(v) => v,
185 Self::Local(v) => v,
186 #[cfg(feature = "cloud")]
187 Self::Cloud(v) => v,
188 }
189 }
190}
191
192impl DerefMut for Writable {
193 fn deref_mut(&mut self) -> &mut Self::Target {
194 match self {
195 Self::Dyn(v) => v,
196 Self::Local(v) => v,
197 #[cfg(feature = "cloud")]
198 Self::Cloud(v) => v,
199 }
200 }
201}
202
203pub enum BufferedWritable<'a> {
205 BufWriter(std::io::BufWriter<&'a mut (dyn std::io::Write + Send)>),
206 Direct(&'a mut (dyn std::io::Write + Send)),
207}
208
209impl<'a> Deref for BufferedWritable<'a> {
210 type Target = dyn io::Write + Send + 'a;
211
212 fn deref(&self) -> &Self::Target {
213 match self {
214 Self::BufWriter(v) => v as _,
215 Self::Direct(v) => v,
216 }
217 }
218}
219
220impl DerefMut for BufferedWritable<'_> {
221 fn deref_mut(&mut self) -> &mut Self::Target {
222 match self {
223 Self::BufWriter(v) => v as _,
224 Self::Direct(v) => v,
225 }
226 }
227}
228
229#[cfg(feature = "cloud")]
230async fn new_cloud_writer(
231 path: PlRefPath,
232 cloud_options: Option<&CloudOptions>,
233 cloud_upload_chunk_size: Option<NonZeroUsize>,
234 cloud_upload_concurrency: NonZeroUsize,
235 io_metrics: Option<Arc<IOMetrics>>,
236) -> PolarsResult<crate::cloud::cloud_writer::CloudWriter> {
237 use crate::cloud::cloud_writer::CloudWriter;
238 use crate::cloud::object_path_from_str;
239
240 let (cloud_location, object_store) =
241 crate::cloud::build_object_store(path, cloud_options, false).await?;
242
243 let mut writer = CloudWriter::new(
244 object_store,
245 object_path_from_str(&cloud_location.prefix)?,
246 cloud_upload_chunk_size,
247 cloud_upload_concurrency,
248 io_metrics,
249 );
250
251 writer.start().await?;
252
253 Ok(writer)
254}