1use std::collections::BTreeMap;
9
10use crate::catalog::CatalogQuery;
11use crate::catalog::error::CatalogError;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum Value {
16 Null,
18 Bool(bool),
20 SmallInt(i16),
22 Integer(i64),
24 Char(char),
26 Text(String),
28 TextArray(Vec<String>),
30 IntegerArray(Vec<i64>),
32 Bytes(Vec<u8>),
34}
35
36#[derive(Debug, Clone, Default)]
38pub struct Row {
39 cols: BTreeMap<String, Value>,
40}
41
42impl Row {
43 #[must_use]
45 pub const fn new() -> Self {
46 Self {
47 cols: BTreeMap::new(),
48 }
49 }
50
51 #[must_use]
53 pub fn with(mut self, key: impl Into<String>, value: Value) -> Self {
54 self.cols.insert(key.into(), value);
55 self
56 }
57
58 pub fn insert(&mut self, key: impl Into<String>, value: Value) {
60 self.cols.insert(key.into(), value);
61 }
62
63 pub fn get_value(&self, query: CatalogQuery, key: &str) -> Result<&Value, CatalogError> {
65 self.cols
66 .get(key)
67 .ok_or_else(|| CatalogError::MissingColumn {
68 query,
69 column: key.to_string(),
70 })
71 }
72
73 pub fn is_null(&self, key: &str) -> bool {
75 matches!(self.cols.get(key), None | Some(Value::Null))
76 }
77
78 pub fn get_int(&self, query: CatalogQuery, key: &str) -> Result<i64, CatalogError> {
80 match self.get_value(query, key)? {
81 Value::Integer(i) => Ok(*i),
82 Value::SmallInt(i) => Ok(i64::from(*i)),
83 other => Err(CatalogError::BadColumnType {
84 query,
85 column: key.to_string(),
86 message: format!("expected integer, got {other:?}"),
87 }),
88 }
89 }
90
91 pub fn get_opt_int(&self, query: CatalogQuery, key: &str) -> Result<Option<i64>, CatalogError> {
93 if self.is_null(key) {
94 return Ok(None);
95 }
96 self.get_int(query, key).map(Some)
97 }
98
99 pub fn get_text(&self, query: CatalogQuery, key: &str) -> Result<String, CatalogError> {
101 match self.get_value(query, key)? {
102 Value::Text(s) => Ok(s.clone()),
103 other => Err(CatalogError::BadColumnType {
104 query,
105 column: key.to_string(),
106 message: format!("expected text, got {other:?}"),
107 }),
108 }
109 }
110
111 pub fn get_opt_text(
113 &self,
114 query: CatalogQuery,
115 key: &str,
116 ) -> Result<Option<String>, CatalogError> {
117 if self.is_null(key) {
118 return Ok(None);
119 }
120 self.get_text(query, key).map(Some)
121 }
122
123 pub fn get_bool(&self, query: CatalogQuery, key: &str) -> Result<bool, CatalogError> {
125 match self.get_value(query, key)? {
126 Value::Bool(b) => Ok(*b),
127 other => Err(CatalogError::BadColumnType {
128 query,
129 column: key.to_string(),
130 message: format!("expected boolean, got {other:?}"),
131 }),
132 }
133 }
134
135 pub fn get_char(&self, query: CatalogQuery, key: &str) -> Result<char, CatalogError> {
137 match self.get_value(query, key)? {
138 Value::Char(c) => Ok(*c),
139 Value::Text(s) if s.chars().count() == 1 => {
140 Ok(s.chars()
142 .next()
143 .unwrap_or_else(|| unreachable!("count==1 has one char")))
144 }
145 other => Err(CatalogError::BadColumnType {
146 query,
147 column: key.to_string(),
148 message: format!("expected single-char, got {other:?}"),
149 }),
150 }
151 }
152
153 pub fn get_int_array(&self, query: CatalogQuery, key: &str) -> Result<Vec<i64>, CatalogError> {
155 match self.get_value(query, key)? {
156 Value::IntegerArray(v) => Ok(v.clone()),
157 other => Err(CatalogError::BadColumnType {
158 query,
159 column: key.to_string(),
160 message: format!("expected integer array, got {other:?}"),
161 }),
162 }
163 }
164
165 pub fn get_text_array(
167 &self,
168 query: CatalogQuery,
169 key: &str,
170 ) -> Result<Vec<String>, CatalogError> {
171 match self.get_value(query, key)? {
172 Value::TextArray(v) => Ok(v.clone()),
173 other => Err(CatalogError::BadColumnType {
174 query,
175 column: key.to_string(),
176 message: format!("expected text array, got {other:?}"),
177 }),
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::catalog::CatalogQuery;
186
187 #[test]
188 fn missing_column_errors() {
189 let r = Row::new();
190 let err = r.get_int(CatalogQuery::Schemas, "oid").unwrap_err();
191 assert!(matches!(err, CatalogError::MissingColumn { .. }));
192 }
193
194 #[test]
195 fn integer_widening_works() {
196 let r = Row::new()
197 .with("a", Value::SmallInt(7))
198 .with("b", Value::Integer(42));
199 assert_eq!(r.get_int(CatalogQuery::Schemas, "a").unwrap(), 7);
200 assert_eq!(r.get_int(CatalogQuery::Schemas, "b").unwrap(), 42);
201 }
202
203 #[test]
204 fn null_is_treated_as_absent() {
205 let r = Row::new().with("c", Value::Null);
206 assert!(r.is_null("c"));
207 assert!(
208 r.get_opt_text(CatalogQuery::Schemas, "c")
209 .unwrap()
210 .is_none()
211 );
212 }
213}