//! Row representation for SQL tables. use super::types::SqlValue; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Unique row identifier. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct RowId(pub u64); impl RowId { /// Creates a new row ID. pub fn new(id: u64) -> Self { RowId(id) } /// Returns the inner ID value. pub fn inner(&self) -> u64 { self.0 } } impl std::fmt::Display for RowId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } /// A single row in a SQL table. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Row { /// Row identifier. pub id: RowId, /// Column values indexed by column name. values: HashMap, /// Ordered column names (preserves insertion order). columns: Vec, } impl Row { /// Creates a new empty row. pub fn new(id: RowId) -> Self { Self { id, values: HashMap::new(), columns: Vec::new(), } } /// Creates a row with given columns. pub fn with_columns(id: RowId, columns: Vec) -> Self { let mut values = HashMap::with_capacity(columns.len()); for col in &columns { values.insert(col.clone(), SqlValue::Null); } Self { id, values, columns, } } /// Sets a column value. pub fn set(&mut self, column: &str, value: SqlValue) { if !self.values.contains_key(column) { self.columns.push(column.to_string()); } self.values.insert(column.to_string(), value); } /// Gets a column value. pub fn get(&self, column: &str) -> Option<&SqlValue> { self.values.get(column) } /// Gets a column value or returns Null. pub fn get_or_null(&self, column: &str) -> SqlValue { self.values.get(column).cloned().unwrap_or(SqlValue::Null) } /// Returns all column names. pub fn columns(&self) -> &[String] { &self.columns } /// Returns all values in column order. pub fn values(&self) -> Vec<&SqlValue> { self.columns .iter() .map(|c| self.values.get(c).unwrap()) .collect() } /// Returns the number of columns. pub fn len(&self) -> usize { self.columns.len() } /// Returns true if the row has no columns. pub fn is_empty(&self) -> bool { self.columns.is_empty() } /// Projects to specific columns. pub fn project(&self, columns: &[String]) -> Row { let mut row = Row::new(self.id); for col in columns { if let Some(value) = self.values.get(col) { row.set(col, value.clone()); } } row } /// Converts to a map. pub fn to_map(&self) -> HashMap { self.values.clone() } /// Converts from a map. pub fn from_map(id: RowId, map: HashMap) -> Self { let columns: Vec = map.keys().cloned().collect(); Self { id, values: map, columns, } } } impl PartialEq for Row { fn eq(&self, other: &Self) -> bool { if self.columns.len() != other.columns.len() { return false; } for col in &self.columns { if self.get(col) != other.get(col) { return false; } } true } } /// Builder for creating rows. pub struct RowBuilder { id: RowId, values: Vec<(String, SqlValue)>, } impl RowBuilder { /// Creates a new row builder. pub fn new(id: RowId) -> Self { Self { id, values: Vec::new(), } } /// Adds a column value. pub fn column(mut self, name: impl Into, value: SqlValue) -> Self { self.values.push((name.into(), value)); self } /// Adds an integer column. pub fn int(self, name: impl Into, value: i64) -> Self { self.column(name, SqlValue::Integer(value)) } /// Adds a real column. pub fn real(self, name: impl Into, value: f64) -> Self { self.column(name, SqlValue::Real(value)) } /// Adds a text column. pub fn text(self, name: impl Into, value: impl Into) -> Self { self.column(name, SqlValue::Text(value.into())) } /// Adds a boolean column. pub fn boolean(self, name: impl Into, value: bool) -> Self { self.column(name, SqlValue::Boolean(value)) } /// Adds a null column. pub fn null(self, name: impl Into) -> Self { self.column(name, SqlValue::Null) } /// Builds the row. pub fn build(self) -> Row { let mut row = Row::new(self.id); for (name, value) in self.values { row.set(&name, value); } row } } #[cfg(test)] mod tests { use super::*; #[test] fn test_row_basic() { let mut row = Row::new(RowId(1)); row.set("name", SqlValue::Text("Alice".to_string())); row.set("age", SqlValue::Integer(30)); assert_eq!(row.get("name"), Some(&SqlValue::Text("Alice".to_string()))); assert_eq!(row.get("age"), Some(&SqlValue::Integer(30))); assert_eq!(row.get("missing"), None); assert_eq!(row.len(), 2); } #[test] fn test_row_builder() { let row = RowBuilder::new(RowId(1)) .text("name", "Bob") .int("age", 25) .boolean("active", true) .build(); assert_eq!(row.get("name"), Some(&SqlValue::Text("Bob".to_string()))); assert_eq!(row.get("age"), Some(&SqlValue::Integer(25))); assert_eq!(row.get("active"), Some(&SqlValue::Boolean(true))); } #[test] fn test_row_projection() { let row = RowBuilder::new(RowId(1)) .text("a", "1") .text("b", "2") .text("c", "3") .build(); let projected = row.project(&["a".to_string(), "c".to_string()]); assert_eq!(projected.len(), 2); assert!(projected.get("a").is_some()); assert!(projected.get("b").is_none()); assert!(projected.get("c").is_some()); } }