1use std::sync::Arc;
8
9use arrow::array::{Array, ArrayRef, AsArray, RecordBatch, RecordBatchOptions, StructArray};
10use arrow::datatypes::{Field, FieldRef, Schema};
11use nd_arrow_array::extension::{
12 GLOBAL_ATTRIBUTES_FIELD, global_attributes_field, is_global_attributes_field, is_nd_field,
13 logical_field,
14};
15use nd_arrow_array::{Attributes, NdColumn, NdColumnBuilder, NdRecordBatch};
16
17use crate::error::{Error, Result};
18use crate::options::without_encoding_metadata;
19use crate::schema::conform_batch;
20use crate::stats::{STATS_PREFIX, is_stats_name};
21
22pub fn encode_rows(rows: &[NdRecordBatch]) -> Result<RecordBatch> {
27 let mut variables: Vec<FieldRef> = Vec::new();
28 for field in rows.iter().flat_map(|r| r.schema().fields().iter()) {
29 if field.name() == GLOBAL_ATTRIBUTES_FIELD || is_stats_name(field.name()) {
30 return Err(Error::SchemaMismatch(format!(
31 "variable name '{}' is reserved, also each name that starts with {STATS_PREFIX}",
32 field.name()
33 )));
34 }
35 match variables.iter().find(|v| v.name() == field.name()) {
36 Some(existing) if existing.data_type() != field.data_type() => {
37 return Err(Error::SchemaMismatch(format!(
38 "variable '{}' has value types {} and {}",
39 field.name(),
40 existing.data_type(),
41 field.data_type()
42 )));
43 }
44 Some(_) => {}
45 None => variables.push(field.clone()),
46 }
47 }
48
49 let mut fields: Vec<Field> = Vec::with_capacity(variables.len() + 1);
50 let mut columns: Vec<ArrayRef> = Vec::with_capacity(variables.len() + 1);
51 for variable in &variables {
52 let mut builder = NdColumnBuilder::with_value_type(variable.data_type().clone());
53 for row in rows {
54 builder.append_option(row.column_by_name(variable.name()))?;
55 }
56 let column = builder.finish()?;
57 let field = column.to_field(variable.name());
58 let mut metadata = variable.metadata().clone();
60 metadata.extend(field.metadata().clone());
61 fields.push(field.with_metadata(metadata));
62 columns.push(column.into_array_ref());
63 }
64
65 if rows.iter().any(|r| !r.attributes().is_empty()) {
66 let global = Attributes::union_fields(rows.iter().map(NdRecordBatch::attributes))?;
67 let attrs: Vec<Option<&Attributes>> = rows.iter().map(|r| Some(r.attributes())).collect();
68 columns.push(Arc::new(Attributes::encode_rows(&attrs, &global)?));
69 fields.push(global_attributes_field(global).with_nullable(true));
71 }
72
73 let options = RecordBatchOptions::new().with_row_count(Some(rows.len()));
74 Ok(RecordBatch::try_new_with_options(
75 Arc::new(Schema::new(fields)),
76 columns,
77 &options,
78 )?)
79}
80
81pub fn variable_field(encoded: &Field) -> Result<Field> {
83 Ok(logical_field(&without_encoding_metadata(encoded))?)
84}
85
86#[derive(Debug, Clone)]
91pub struct RowDecoder {
92 variables: Vec<(FieldRef, NdColumn)>,
93 global: Option<StructArray>,
94 num_rows: usize,
95}
96
97impl RowDecoder {
98 pub fn try_new(batch: &RecordBatch) -> Result<Self> {
102 let fields: Vec<Field> = batch
103 .schema()
104 .fields()
105 .iter()
106 .filter(|field| !is_stats_name(field.name()))
107 .map(|field| without_encoding_metadata(field))
108 .collect();
109 let schema = Arc::new(Schema::new(fields));
110 let batch = &conform_batch(batch, &schema)?;
111 let mut variables = Vec::new();
112 let mut global = None;
113 for (field, column) in schema.fields().iter().zip(batch.columns()) {
114 if is_global_attributes_field(field) {
115 global = column.as_struct_opt().cloned();
116 } else if is_nd_field(field) {
117 let logical = Arc::new(logical_field(field)?);
118 variables.push((logical, NdColumn::try_from_array(column.as_ref())?));
119 } else {
120 return Err(Error::InvalidTable(format!(
121 "column '{}' is not an nd column",
122 field.name()
123 )));
124 }
125 }
126 Ok(Self {
127 variables,
128 global,
129 num_rows: batch.num_rows(),
130 })
131 }
132
133 pub fn num_rows(&self) -> usize {
135 self.num_rows
136 }
137
138 pub fn row(&self, row: usize) -> Result<NdRecordBatch> {
140 let mut fields = Vec::new();
141 let mut arrays = Vec::new();
142 for (field, column) in &self.variables {
143 if !column.is_null(row) {
144 fields.push(field.clone());
145 arrays.push(column.value(row));
146 }
147 }
148 let mut batch = NdRecordBatch::try_new_inferred(Arc::new(Schema::new(fields)), arrays)?;
149 if let Some(global) = self.global.as_ref().filter(|g| g.is_valid(row)) {
150 batch = batch.with_attributes(Attributes::from_struct_row(global, row));
151 }
152 Ok(batch)
153 }
154
155 pub fn into_rows(self) -> impl Iterator<Item = Result<NdRecordBatch>> + Send + 'static {
157 (0..self.num_rows).map(move |row| self.row(row))
158 }
159}