reifydb_value/value/frame/
extract.rs1use std::{
5 error,
6 fmt::{self, Display, Formatter},
7};
8
9use super::frame::Frame;
10use crate::value::{
11 column_view::ColumnView,
12 try_from::{FromValueError, TryFromValue},
13};
14
15#[derive(Debug, Clone, PartialEq)]
16pub enum FrameError {
17 ColumnNotFound {
18 name: String,
19 },
20
21 RowOutOfBounds {
22 row: usize,
23 len: usize,
24 },
25
26 ValueError {
27 column: String,
28 row: usize,
29 error: FromValueError,
30 },
31
32 InvalidColumn {
33 name: String,
34 message: String,
35 },
36}
37
38impl Display for FrameError {
39 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40 match self {
41 FrameError::ColumnNotFound {
42 name,
43 } => {
44 write!(f, "column not found: {}", name)
45 }
46 FrameError::RowOutOfBounds {
47 row,
48 len,
49 } => {
50 write!(f, "row {} out of bounds (frame has {} rows)", row, len)
51 }
52 FrameError::ValueError {
53 column,
54 row,
55 error,
56 } => {
57 write!(f, "error extracting column '{}' row {}: {}", column, row, error)
58 }
59 FrameError::InvalidColumn {
60 name,
61 message,
62 } => {
63 write!(f, "column '{}' does not match its field: {}", name, message)
64 }
65 }
66 }
67}
68
69impl error::Error for FrameError {}
70
71impl Frame {
72 pub fn column(&self, name: &str) -> Result<Option<ColumnView<'_>>, FrameError> {
73 let schema = self.batch.schema_ref();
74 let Some(index) = schema.fields().iter().position(|field| field.name() == name) else {
75 return Ok(None);
76 };
77 ColumnView::try_from((self.batch.column(index), schema.field(index))).map(Some).map_err(|error| {
78 FrameError::InvalidColumn {
79 name: name.to_string(),
80 message: error.to_string(),
81 }
82 })
83 }
84
85 pub fn try_column(&self, name: &str) -> Result<ColumnView<'_>, FrameError> {
86 self.column(name)?.ok_or_else(|| FrameError::ColumnNotFound {
87 name: name.to_string(),
88 })
89 }
90
91 pub fn row_count(&self) -> usize {
92 self.batch.num_rows()
93 }
94
95 pub fn get<T: TryFromValue>(&self, column: &str, row: usize) -> Result<Option<T>, FrameError> {
96 let col = self.try_column(column)?;
97 let len = col.len();
98
99 if row >= len {
100 return Err(FrameError::RowOutOfBounds {
101 row,
102 len,
103 });
104 }
105
106 if !col.is_defined(row) {
107 return Ok(None);
108 }
109
110 let value = col.get_value(row);
111 T::try_from_value(&value).map(Some).map_err(|e| FrameError::ValueError {
112 column: column.to_string(),
113 row,
114 error: e,
115 })
116 }
117}
118
119#[cfg(test)]
120pub mod tests {
121 use std::sync::Arc;
122
123 use arrow_array::{ArrayRef, Int32Array, Int64Array, LargeStringArray, RecordBatch, UInt64Array};
124 use arrow_schema::{FieldRef, Schema};
125
126 use super::*;
127 use crate::value::{
128 system_columns::{SystemColumn, with_system_column},
129 value_type::{
130 ValueType,
131 field::{FieldType, to_field},
132 },
133 };
134
135 fn column(name: &str, value_type: ValueType, array: ArrayRef) -> (FieldRef, ArrayRef) {
136 let field_type = FieldType {
137 value_type: Some(value_type),
138 ..FieldType::default()
139 };
140 (Arc::new(to_field(name, &field_type)), array)
141 }
142
143 fn make_test_frame() -> Frame {
144 let (fields, arrays): (Vec<FieldRef>, Vec<ArrayRef>) = vec![
145 column("id", ValueType::Int8, Arc::new(Int64Array::from(vec![1i64, 2, 3]))),
146 column(
147 "name",
148 ValueType::Utf8,
149 Arc::new(LargeStringArray::from(vec![
150 "Alice".to_string(),
151 "Bob".to_string(),
152 String::new(),
153 ])),
154 ),
155 column("score", ValueType::Int4, Arc::new(Int32Array::from(vec![100i32, 85, 92]))),
156 ]
157 .into_iter()
158 .unzip();
159 let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), arrays).unwrap();
160 let batch = with_system_column(
161 batch,
162 SystemColumn::RowNumbers,
163 Arc::new(UInt64Array::from(vec![1u64, 2, 3])),
164 )
165 .unwrap();
166 Frame::from(batch)
167 }
168
169 #[test]
170 fn test_column_by_name() {
171 let frame = make_test_frame();
172 assert!(frame.column("id").unwrap().is_some());
173 assert!(frame.column("name").unwrap().is_some());
174 assert!(frame.column("nonexistent").unwrap().is_none());
175 }
176
177 #[test]
178 fn test_row_count() {
179 let frame = make_test_frame();
180 assert_eq!(frame.row_count(), 3);
181
182 let empty = Frame::from(RecordBatch::new_empty(Arc::new(Schema::empty())));
183 assert_eq!(empty.row_count(), 0);
184 }
185
186 #[test]
187 fn test_get_value() {
188 let frame = make_test_frame();
189
190 let id: Option<i64> = frame.get("id", 0).unwrap();
191 assert_eq!(id, Some(1i64));
192
193 let name: Option<String> = frame.get("name", 0).unwrap();
194 assert_eq!(name, Some("Alice".to_string()));
195
196 let name_at_2: Option<String> = frame.get("name", 2).unwrap();
198 assert_eq!(name_at_2, Some(String::new()));
199 }
200
201 #[test]
202 fn test_errors() {
203 let frame = make_test_frame();
204
205 let err = frame.get::<i64>("nonexistent", 0).unwrap_err();
208 assert!(matches!(err, FrameError::ColumnNotFound { .. }));
209
210 let err = frame.get::<i64>("id", 100).unwrap_err();
211 assert!(matches!(err, FrameError::RowOutOfBounds { .. }));
212
213 let err = frame.get::<i32>("id", 0).unwrap_err();
214 assert!(matches!(err, FrameError::ValueError { .. }));
215 }
216}