Skip to main content

reifydb_type/value/frame/
row.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{collections::HashMap, rc::Rc};
5
6use super::{extract::FrameError, frame::Frame};
7use crate::value::{
8	Value,
9	row_number::RowNumber,
10	try_from::{TryFromValue, TryFromValueCoerce},
11};
12
13#[derive(Debug)]
14struct ColumnIndex {
15	by_name: HashMap<String, usize>,
16}
17
18impl ColumnIndex {
19	fn new(frame: &Frame) -> Self {
20		let mut by_name = HashMap::with_capacity(frame.columns.len());
21		for (idx, col) in frame.columns.iter().enumerate() {
22			by_name.insert(col.name.clone(), idx);
23		}
24		Self {
25			by_name,
26		}
27	}
28
29	fn get(&self, name: &str) -> Option<usize> {
30		self.by_name.get(name).copied()
31	}
32}
33
34#[derive(Debug)]
35pub struct FrameRow<'a> {
36	frame: &'a Frame,
37	index: Rc<ColumnIndex>,
38	row_idx: usize,
39}
40
41impl<'a> FrameRow<'a> {
42	pub fn get<T: TryFromValue>(&self, column: &str) -> Result<Option<T>, FrameError> {
43		let col_idx = self.index.get(column).ok_or_else(|| FrameError::ColumnNotFound {
44			name: column.to_string(),
45		})?;
46
47		let col = &self.frame.columns[col_idx];
48
49		if !col.data.is_defined(self.row_idx) {
50			return Ok(None);
51		}
52
53		let value = col.data.get_value(self.row_idx);
54		T::try_from_value(&value).map(Some).map_err(|e| FrameError::ValueError {
55			column: column.to_string(),
56			row: self.row_idx,
57			error: e,
58		})
59	}
60
61	pub fn get_coerce<T: TryFromValueCoerce>(&self, column: &str) -> Result<Option<T>, FrameError> {
62		let col_idx = self.index.get(column).ok_or_else(|| FrameError::ColumnNotFound {
63			name: column.to_string(),
64		})?;
65
66		let col = &self.frame.columns[col_idx];
67
68		if !col.data.is_defined(self.row_idx) {
69			return Ok(None);
70		}
71
72		let value = col.data.get_value(self.row_idx);
73		T::try_from_value_coerce(&value).map(Some).map_err(|e| FrameError::ValueError {
74			column: column.to_string(),
75			row: self.row_idx,
76			error: e,
77		})
78	}
79
80	pub fn get_value(&self, column: &str) -> Option<Value> {
81		self.index.get(column).map(|col_idx| self.frame.columns[col_idx].data.get_value(self.row_idx))
82	}
83
84	pub fn index(&self) -> usize {
85		self.row_idx
86	}
87
88	pub fn row_number(&self) -> Option<RowNumber> {
89		self.frame.row_numbers.get(self.row_idx).copied()
90	}
91
92	pub fn is_defined(&self, column: &str) -> Option<bool> {
93		self.index.get(column).map(|col_idx| self.frame.columns[col_idx].data.is_defined(self.row_idx))
94	}
95}
96
97pub struct FrameRows<'a> {
98	frame: &'a Frame,
99	index: Rc<ColumnIndex>,
100	current: usize,
101	len: usize,
102}
103
104impl<'a> FrameRows<'a> {
105	pub(super) fn new(frame: &'a Frame) -> Self {
106		let len = frame.columns.first().map(|c| c.data.len()).unwrap_or(0);
107		Self {
108			frame,
109			index: Rc::new(ColumnIndex::new(frame)),
110			current: 0,
111			len,
112		}
113	}
114}
115
116impl<'a> Iterator for FrameRows<'a> {
117	type Item = FrameRow<'a>;
118
119	fn next(&mut self) -> Option<Self::Item> {
120		if self.current >= self.len {
121			return None;
122		}
123
124		let row = FrameRow {
125			frame: self.frame,
126			index: Rc::clone(&self.index),
127			row_idx: self.current,
128		};
129
130		self.current += 1;
131		Some(row)
132	}
133
134	fn size_hint(&self) -> (usize, Option<usize>) {
135		let remaining = self.len.saturating_sub(self.current);
136		(remaining, Some(remaining))
137	}
138}
139
140impl ExactSizeIterator for FrameRows<'_> {}
141
142impl<'a> DoubleEndedIterator for FrameRows<'a> {
143	fn next_back(&mut self) -> Option<Self::Item> {
144		if self.current >= self.len {
145			return None;
146		}
147
148		self.len -= 1;
149
150		Some(FrameRow {
151			frame: self.frame,
152			index: Rc::clone(&self.index),
153			row_idx: self.len,
154		})
155	}
156}
157
158impl Frame {
159	pub fn rows(&self) -> FrameRows<'_> {
160		FrameRows::new(self)
161	}
162}
163
164#[cfg(test)]
165pub mod tests {
166	use super::*;
167	use crate::value::{
168		container::{number::NumberContainer, utf8::Utf8Container},
169		frame::{column::FrameColumn, data::FrameColumnData},
170	};
171
172	fn make_test_frame() -> Frame {
173		Frame::with_row_numbers(
174			vec![
175				FrameColumn {
176					name: "id".to_string(),
177					data: FrameColumnData::Int8(NumberContainer::from_vec(vec![1i64, 2, 3])),
178				},
179				FrameColumn {
180					name: "name".to_string(),
181					data: FrameColumnData::Utf8(Utf8Container::new(vec![
182						"Alice".to_string(),
183						"Bob".to_string(),
184						String::new(),
185					])),
186				},
187			],
188			vec![100.into(), 200.into(), 300.into()],
189		)
190	}
191
192	#[test]
193	fn test_rows_iterator() {
194		let frame = make_test_frame();
195		let rows: Vec<_> = frame.rows().collect();
196
197		assert_eq!(rows.len(), 3);
198		assert_eq!(rows[0].index(), 0);
199		assert_eq!(rows[1].index(), 1);
200		assert_eq!(rows[2].index(), 2);
201	}
202
203	#[test]
204	fn test_row_get() {
205		let frame = make_test_frame();
206		let mut rows = frame.rows();
207
208		let row0 = rows.next().unwrap();
209		assert_eq!(row0.get::<i64>("id").unwrap(), Some(1i64));
210		assert_eq!(row0.get::<String>("name").unwrap(), Some("Alice".to_string()));
211
212		let row2 = rows.nth(1).unwrap(); // Skip to index 2
213		assert_eq!(row2.get::<i64>("id").unwrap(), Some(3i64));
214		assert_eq!(row2.get::<String>("name").unwrap(), Some(String::new())); // All values are defined
215	}
216
217	#[test]
218	fn test_row_get_coerce() {
219		let frame = make_test_frame();
220		let row = frame.rows().next().unwrap();
221
222		// i64 coerced to f64
223		let id: Option<f64> = row.get_coerce("id").unwrap();
224		assert_eq!(id, Some(1.0f64));
225	}
226
227	#[test]
228	fn test_row_get_value() {
229		let frame = make_test_frame();
230		let row = frame.rows().next().unwrap();
231
232		let value = row.get_value("id");
233		assert!(matches!(value, Some(Value::Int8(1))));
234
235		let missing = row.get_value("nonexistent");
236		assert!(missing.is_none());
237	}
238
239	#[test]
240	fn test_row_number() {
241		let frame = make_test_frame();
242		let rows: Vec<_> = frame.rows().collect();
243
244		assert_eq!(rows[0].row_number(), Some(100.into()));
245		assert_eq!(rows[1].row_number(), Some(200.into()));
246		assert_eq!(rows[2].row_number(), Some(300.into()));
247	}
248
249	#[test]
250	fn test_is_defined() {
251		let frame = make_test_frame();
252		let rows: Vec<_> = frame.rows().collect();
253
254		assert_eq!(rows[0].is_defined("name"), Some(true));
255		assert_eq!(rows[2].is_defined("name"), Some(true)); // All values are defined
256		assert_eq!(rows[0].is_defined("nonexistent"), None);
257	}
258
259	#[test]
260	fn test_exact_size_iterator() {
261		let frame = make_test_frame();
262		let rows = frame.rows();
263
264		assert_eq!(rows.len(), 3);
265	}
266
267	#[test]
268	fn test_double_ended_iterator() {
269		let frame = make_test_frame();
270		let mut rows = frame.rows();
271
272		let last = rows.next_back().unwrap();
273		assert_eq!(last.index(), 2);
274
275		let first = rows.next().unwrap();
276		assert_eq!(first.index(), 0);
277	}
278}