Skip to main content

reifydb_type/value/frame/
row.rs

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