reifydb_type/value/frame/
row.rs

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