1use std::{iter, sync::Arc};
5
6use arrow_array::{ArrayRef, RecordBatch};
7use arrow_schema::{FieldRef, Schema};
8use reifydb_core::{
9 interface::catalog::{column::Column, sumtype::SumType},
10 internal_error,
11 value::{batch::batch_with, column::builder::ColumnBuilder},
12};
13use reifydb_value::{
14 error::Error,
15 fragment::Fragment,
16 params::Params,
17 value::{
18 Value,
19 column_view::{ColumnView, ViewData},
20 system_columns::{column_view, is_system_field},
21 },
22};
23
24use super::coerce::RowCoercer;
25use crate::{Result, error::EngineError, vm::instruction::dml::series_insert::VariantTags};
26
27const TAG: &str = "tag";
28
29struct Segment<'a> {
30 columns: &'a [Column],
31 tagged: bool,
32 source_name: &'a str,
33 coercer: &'a RowCoercer,
34}
35
36pub type CoercedTags = (Vec<Option<u8>>, Option<(usize, Error)>);
37
38pub(super) fn coerce_columns(
39 rows: &[Params],
40 batches: &[(usize, RecordBatch)],
41 columns: &[Column],
42 sumtype: Option<&SumType>,
43 source_name: &str,
44 coercer: &RowCoercer,
45) -> Result<(Vec<(FieldRef, ArrayRef)>, Option<CoercedTags>)> {
46 let segment = Segment {
47 columns,
48 tagged: sumtype.is_some(),
49 source_name,
50 coercer,
51 };
52 let total = rows.len() + batches.iter().map(|(_, batch)| batch.num_rows()).sum::<usize>();
53 let mut builders: Vec<ColumnBuilder> =
54 columns.iter().map(|col| ColumnBuilder::with_capacity(col.constraint.get_type(), total)).collect();
55 let mut tags = sumtype.map(|sumtype| VariantTags::new(sumtype, total));
56 let mut taken = 0;
57 let mut first_row = 0;
58 for (at, batch) in batches {
59 first_row +=
60 collect_rows_to_columns(&segment, &rows[taken..*at], first_row, &mut builders, tags.as_mut())?;
61 taken = *at;
62 first_row += coerce_batch(&segment, batch, first_row, &mut builders, tags.as_mut())?;
63 }
64 collect_rows_to_columns(&segment, &rows[taken..], first_row, &mut builders, tags.as_mut())?;
65 let columns = builders.into_iter().zip(columns).map(|(builder, column)| builder.finish(&column.name)).collect();
66 Ok((columns, tags.map(VariantTags::finish)))
67}
68
69fn coerce_batch(
70 segment: &Segment<'_>,
71 batch: &RecordBatch,
72 first_row: usize,
73 builders: &mut [ColumnBuilder],
74 tags: Option<&mut VariantTags<'_>>,
75) -> Result<usize> {
76 let rows = batch.num_rows();
77 if rows == 0 {
78 return Ok(0);
79 }
80
81 let views = segment.columns.iter().map(|col| column_view(batch, &col.name)).collect::<Result<Vec<_>>>()?;
82 let mut failure: Option<(usize, Error)> = None;
83 for ((builder, column), view) in builders.iter_mut().zip(segment.columns).zip(&views) {
84 let limit = failure.as_ref().map_or(rows, |(row, _)| *row);
85 let Some(view) = view else {
86 for _ in 0..rows {
87 builder.push_none();
88 }
89 continue;
90 };
91 let per_cell =
92 matches!(view.data, ViewData::Any { .. } | ViewData::Digest { .. } | ViewData::None { .. });
93 if per_cell {
94 if let Some(found) = coerce_cells(segment, column, view, first_row, limit, builder) {
95 failure = Some(found);
96 }
97 continue;
98 }
99 if view.base_type() == *column.constraint.get_type().inner_type() {
100 builder.append_values(view)?;
101 continue;
102 }
103 match segment.coercer.cast_column(view, column) {
104 Ok(cast) => builder.append_values(&ColumnView::try_from(&cast)?)?,
105 Err(_) => match coerce_cells(segment, column, view, first_row, limit, builder) {
106 Some(found) => failure = Some(found),
107 None if failure.is_some() => {}
108 None => {
109 return Err(internal_error!(
110 "bulk column {} failed its column cast where every cell cast passes",
111 column.name
112 ));
113 }
114 },
115 }
116 }
117 if let Some((_, error)) = failure {
118 return Err(error);
119 }
120
121 if let Some(field) = batch.schema().fields().iter().find(|field| {
122 is_system_field(field)
123 || !(segment.columns.iter().any(|c| &c.name == field.name())
124 || (segment.tagged && field.name() == TAG))
125 }) {
126 return Err(EngineError::BulkInsertColumnNotFound {
127 fragment: Fragment::None,
128 table_name: segment.source_name.to_string(),
129 column: field.name().to_string(),
130 }
131 .into());
132 }
133
134 if let Some(tags) = tags {
135 match column_view(batch, TAG)? {
136 Some(view) => {
137 for row in 0..rows {
138 tags.push(view.get_value(row));
139 }
140 }
141 None => {
142 for _ in 0..rows {
143 tags.push(Value::none());
144 }
145 }
146 }
147 }
148 Ok(rows)
149}
150
151fn coerce_cells(
152 segment: &Segment<'_>,
153 column: &Column,
154 view: &ColumnView<'_>,
155 first_row: usize,
156 limit: usize,
157 builder: &mut ColumnBuilder,
158) -> Option<(usize, Error)> {
159 for row in 0..limit {
160 match segment.coercer.coerce(view.get_value(row), column, segment.source_name, first_row + row) {
161 Ok(value) => builder.push_value(value),
162 Err(error) => return Some((row, error)),
163 }
164 }
165 None
166}
167
168fn collect_rows_to_columns(
169 segment: &Segment<'_>,
170 rows: &[Params],
171 first_row: usize,
172 builders: &mut [ColumnBuilder],
173 tags: Option<&mut VariantTags<'_>>,
174) -> Result<usize> {
175 let columns = segment.columns;
176 let source_name = segment.source_name;
177 let num_cols = columns.len();
178 let too_many =
179 rows.iter().position(|params| matches!(params, Params::Positional(vals) if vals.len() > num_cols));
180 let accepted = &rows[..too_many.unwrap_or(rows.len())];
181 match params_batch(segment, accepted)? {
182 Some(batch) => {
183 coerce_batch(segment, &batch, first_row, builders, None)?;
184 }
185 None => coerce_rows(segment, accepted, first_row, builders)?,
186 }
187 if let Some(Params::Positional(vals)) = too_many.map(|row| &rows[row]) {
188 return Err(EngineError::BulkInsertTooManyValues {
189 fragment: Fragment::None,
190 expected: num_cols,
191 actual: vals.len(),
192 }
193 .into());
194 }
195
196 for params in rows {
197 if let Params::Named(map) = params {
198 for name in map.keys() {
199 if !(columns.iter().any(|c| &c.name == name) || (segment.tagged && name == TAG)) {
200 return Err(EngineError::BulkInsertColumnNotFound {
201 fragment: Fragment::None,
202 table_name: source_name.to_string(),
203 column: name.to_string(),
204 }
205 .into());
206 }
207 }
208 }
209 }
210
211 if let Some(tags) = tags {
212 for params in rows {
213 tags.push(match params {
214 Params::Named(map) => map.get(TAG).cloned().unwrap_or(Value::none()),
215 _ => Value::none(),
216 });
217 }
218 }
219
220 Ok(rows.len())
221}
222
223fn params_batch(segment: &Segment<'_>, rows: &[Params]) -> Result<Option<RecordBatch>> {
224 let cell = |params: &Params, index: usize, name: &str| match params {
225 Params::Named(map) => map.get(name).cloned(),
226 Params::Positional(vals) => vals.get(index).cloned(),
227 Params::None => None,
228 };
229 let mut fields = Vec::with_capacity(segment.columns.len());
230 for (index, column) in segment.columns.iter().enumerate() {
231 match params_column(&column.name, rows.iter().map(|params| cell(params, index, &column.name)).collect())
232 {
233 ParamsColumn::Typed(field) => fields.push(field),
234 ParamsColumn::Absent => {}
235 ParamsColumn::Mixed => return Ok(None),
236 }
237 }
238 batch_with(&Arc::new(Schema::empty()), fields, rows.len()).map(Some)
239}
240
241enum ParamsColumn {
242 Typed((FieldRef, ArrayRef)),
243 Absent,
244 Mixed,
245}
246
247fn params_column(name: &str, values: Vec<Option<Value>>) -> ParamsColumn {
248 let present = |value: &&Option<Value>| matches!(value, Some(value) if !matches!(value, Value::None { .. }));
249 let mut types = values.iter().filter(present).flatten().map(Value::get_type);
250 let Some(first) = types.next() else {
251 return ParamsColumn::Absent;
252 };
253 if !types.all(|other| other == first) {
254 return ParamsColumn::Mixed;
255 }
256 let mut builder = ColumnBuilder::with_capacity(first, values.len());
257 for value in values {
258 match value {
259 Some(value) if !matches!(value, Value::None { .. }) => builder.push_value(value),
260 _ => builder.push_none(),
261 }
262 }
263 ParamsColumn::Typed(builder.finish(name))
264}
265
266fn coerce_rows(segment: &Segment<'_>, rows: &[Params], first_row: usize, builders: &mut [ColumnBuilder]) -> Result<()> {
267 let columns = segment.columns;
268 let source_name = segment.source_name;
269 let coercer = segment.coercer;
270
271 for (row_idx, params) in rows.iter().enumerate() {
272 match params {
273 Params::Named(map) => {
274 for (col_idx, col) in columns.iter().enumerate() {
275 let value = map.get(&col.name).cloned().unwrap_or(Value::none());
276 builders[col_idx].push_value(coercer.coerce(
277 value,
278 col,
279 source_name,
280 first_row + row_idx,
281 )?);
282 }
283 }
284 Params::Positional(vals) => {
285 for ((col_data, col), val) in builders
286 .iter_mut()
287 .zip(columns.iter())
288 .zip(vals.iter().map(Some).chain(iter::repeat(None)))
289 {
290 let value = val.cloned().unwrap_or(Value::none());
291 col_data.push_value(coercer.coerce(
292 value,
293 col,
294 source_name,
295 first_row + row_idx,
296 )?);
297 }
298 }
299 Params::None => {
300 for col_data in builders.iter_mut() {
301 col_data.push_none();
302 }
303 }
304 }
305 }
306 Ok(())
307}