1use std::collections::HashMap;
4use std::sync::Arc;
5
6use arrow::array::RecordBatch;
7use arrow::datatypes::Schema;
8use futures::{Stream, StreamExt};
9use lance::Dataset;
10use lance_file::version::LanceFileVersion;
11use nd_arrow_array::NdRecordBatch;
12
13use crate::cast::{Casting, cast_to};
14use crate::error::{Error, Result};
15use crate::format::add_format;
16use crate::options::{ColumnOptions, column_defaults, with_column_options};
17use crate::schema::conform_batch;
18use crate::stats::{add_stats, has_stats};
19use crate::store::StorageOptions;
20use crate::writer::{NdWriter, open_existing};
21
22pub(crate) const FILE_VERSION: LanceFileVersion = LanceFileVersion::V2_2;
25
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
28pub enum WriteMode {
29 #[default]
31 Create,
32 Append,
34 Overwrite,
36}
37
38#[derive(Debug, Clone)]
40pub struct WriteOptions {
41 pub mode: WriteMode,
43 pub max_rows_per_group: usize,
45 pub max_values_per_group: usize,
48 pub column_defaults: Option<ColumnOptions>,
52 pub column_options: HashMap<String, ColumnOptions>,
55 pub stats: bool,
59 pub casting: Casting,
62 pub target_fragment_bytes: usize,
69 pub write_parallelism: usize,
71 pub storage_options: StorageOptions,
75}
76
77impl Default for WriteOptions {
78 fn default() -> Self {
79 Self {
80 mode: WriteMode::Create,
81 max_rows_per_group: 1024,
82 max_values_per_group: 1 << 26,
83 column_defaults: None,
84 column_options: HashMap::new(),
85 stats: false,
86 casting: Casting::No,
87 target_fragment_bytes: 128 << 20,
88 write_parallelism: 4,
89 storage_options: StorageOptions::new(),
90 }
91 }
92}
93
94#[tracing::instrument(skip_all, fields(uri = %uri, mode = ?options.mode), err)]
101pub async fn write(
102 uri: &str,
103 rows: impl Stream<Item = Result<NdRecordBatch>>,
104 options: &WriteOptions,
105) -> Result<Dataset> {
106 let writer = match options.mode {
107 WriteMode::Append
108 if open_existing(uri, &options.storage_options)
109 .await?
110 .is_some() =>
111 {
112 NdWriter::append(uri, options).await?
113 }
114 WriteMode::Append => {
115 let options = WriteOptions {
116 mode: WriteMode::Create,
117 ..options.clone()
118 };
119 NdWriter::create(uri, &options).await?
120 }
121 WriteMode::Create | WriteMode::Overwrite => NdWriter::create(uri, options).await?,
122 };
123 write_all(writer, rows).await
124}
125
126#[tracing::instrument(skip_all, fields(uri = %dataset.uri()), err)]
132pub async fn append(
133 dataset: &mut Dataset,
134 rows: impl Stream<Item = Result<NdRecordBatch>>,
135 options: &WriteOptions,
136) -> Result<()> {
137 let mut options = options.clone();
138 if options.storage_options.is_empty() {
139 options.storage_options = dataset
140 .initial_storage_options()
141 .cloned()
142 .unwrap_or_default();
143 }
144 let writer = NdWriter::append(dataset.uri(), &options).await?;
145 *dataset = write_all(writer, rows).await?;
146 Ok(())
147}
148
149async fn write_all(
150 mut writer: NdWriter,
151 rows: impl Stream<Item = Result<NdRecordBatch>>,
152) -> Result<Dataset> {
153 if let Err(error) = Box::pin(writer.write(rows)).await {
156 writer.abort();
157 return Err(error);
158 }
159 Box::pin(writer.finish()).await
160}
161
162pub(crate) fn table_defaults(dataset: &Dataset, options: &WriteOptions) -> Result<ColumnOptions> {
164 let stored = column_defaults(dataset)?;
165 match &options.column_defaults {
166 Some(defaults) if *defaults != stored => Err(Error::InvalidArgument(
167 "the table has other column defaults, use set_column_defaults to change them"
168 .to_string(),
169 )),
170 _ => Ok(stored),
171 }
172}
173
174pub(crate) fn table_has_stats(dataset: &Dataset) -> bool {
176 has_stats(&Schema::from(dataset.schema()))
177}
178
179pub(crate) fn prepare_batch(
183 mut batch: RecordBatch,
184 defaults: &ColumnOptions,
185 options: &WriteOptions,
186 stats: bool,
187 table: Option<&Schema>,
188) -> Result<RecordBatch> {
189 if let Some(table) = table {
191 batch = cast_to(batch, table, options.casting)?;
192 }
193 if stats {
194 batch = add_stats(&batch)?;
195 }
196 let mut metadata = batch.schema().metadata().clone();
197 metadata.extend(defaults.to_table_metadata());
198 add_format(&mut metadata);
199 let schema = with_column_options(&batch.schema(), defaults, &options.column_options)
200 .with_metadata(metadata);
201 conform_batch(&batch, &Arc::new(schema))
202}
203
204pub(crate) async fn next_group(
206 rows: &mut (impl Stream<Item = Result<NdRecordBatch>> + Unpin),
207 options: &WriteOptions,
208) -> Result<Option<Vec<NdRecordBatch>>> {
209 let mut group = Vec::new();
210 let mut values = 0;
211 while group.len() < options.max_rows_per_group.max(1) && values < options.max_values_per_group {
212 let Some(row) = rows.next().await else {
213 break;
214 };
215 let row = row?;
216 values += row.columns().iter().map(|c| c.len()).sum::<usize>();
217 group.push(row);
218 }
219 Ok((!group.is_empty()).then_some(group))
220}