1use super::{ColumnField, OwnedColumn, Table};
2use crate::base::{
3 database::ColumnCoercionError, map::IndexMap, polynomial::compute_evaluation_vector,
4 scalar::Scalar,
5};
6use alloc::{vec, vec::Vec};
7use itertools::{EitherOrBoth, Itertools};
8use serde::{Deserialize, Serialize};
9use snafu::Snafu;
10use sqlparser::ast::Ident;
11
12#[derive(Snafu, Debug, PartialEq, Eq)]
14pub enum OwnedTableError {
15 #[snafu(display("Columns have different lengths"))]
17 ColumnLengthMismatch,
18}
19
20#[derive(Snafu, Debug, PartialEq, Eq)]
22pub(crate) enum TableCoercionError {
23 #[snafu(transparent)]
24 ColumnCoercionError { source: ColumnCoercionError },
25 #[snafu(display("Name mismatch between column and field"))]
27 NameMismatch,
28 #[snafu(display("Column count mismatch"))]
30 ColumnCountMismatch,
31}
32
33#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
39pub struct OwnedTable<S: Scalar> {
40 table: IndexMap<Ident, OwnedColumn<S>>,
41}
42impl<S: Scalar> OwnedTable<S> {
43 pub fn try_new(table: IndexMap<Ident, OwnedColumn<S>>) -> Result<Self, OwnedTableError> {
45 if table.is_empty() {
46 return Ok(Self { table });
47 }
48 let num_rows = table[0].len();
49 if table.values().any(|column| column.len() != num_rows) {
50 Err(OwnedTableError::ColumnLengthMismatch)
51 } else {
52 Ok(Self { table })
53 }
54 }
55 pub fn try_from_iter<T: IntoIterator<Item = (Ident, OwnedColumn<S>)>>(
57 iter: T,
58 ) -> Result<Self, OwnedTableError> {
59 Self::try_new(IndexMap::from_iter(iter))
60 }
61
62 #[expect(
63 clippy::missing_panics_doc,
64 reason = "Mapping from one table to another should not result in column mismatch"
65 )]
66 pub(crate) fn try_coerce_with_fields<T: IntoIterator<Item = ColumnField>>(
79 self,
80 fields: T,
81 ) -> Result<Self, TableCoercionError> {
82 self.into_inner()
83 .into_iter()
84 .zip_longest(fields)
85 .map(|p| match p {
86 EitherOrBoth::Left(_) | EitherOrBoth::Right(_) => {
87 Err(TableCoercionError::ColumnCountMismatch)
88 }
89 EitherOrBoth::Both((name, column), field) if name == field.name() => Ok((
90 name,
91 column.try_coerce_scalar_to_numeric(field.data_type())?,
92 )),
93 EitherOrBoth::Both(_, _) => Err(TableCoercionError::NameMismatch),
94 })
95 .process_results(|iter| {
96 Self::try_from_iter(iter).expect("Columns should have the same length")
97 })
98 }
99
100 #[must_use]
102 pub fn num_columns(&self) -> usize {
103 self.table.len()
104 }
105 #[must_use]
107 pub fn num_rows(&self) -> usize {
108 if self.table.is_empty() {
109 0
110 } else {
111 self.table[0].len()
112 }
113 }
114 #[must_use]
116 pub fn is_empty(&self) -> bool {
117 self.table.is_empty()
118 }
119 #[must_use]
121 pub fn into_inner(self) -> IndexMap<Ident, OwnedColumn<S>> {
122 self.table
123 }
124 #[must_use]
126 pub fn inner_table(&self) -> &IndexMap<Ident, OwnedColumn<S>> {
127 &self.table
128 }
129 pub fn column_names(&self) -> impl Iterator<Item = &Ident> {
131 self.table.keys()
132 }
133 #[must_use]
135 pub fn column_by_index(&self, index: usize) -> Option<&OwnedColumn<S>> {
136 self.table.get_index(index).map(|(_, v)| v)
137 }
138
139 pub(crate) fn mle_evaluations(&self, evaluation_point: &[S]) -> Vec<S> {
140 let mut evaluation_vector = vec![S::ZERO; self.num_rows()];
141 compute_evaluation_vector(&mut evaluation_vector, evaluation_point);
142 self.table
143 .values()
144 .map(|column| column.inner_product(&evaluation_vector))
145 .collect()
146 }
147}
148
149impl<S: Scalar> IntoIterator for OwnedTable<S> {
150 type Item = (Ident, OwnedColumn<S>);
151
152 type IntoIter = indexmap::map::IntoIter<Ident, OwnedColumn<S>>;
153
154 fn into_iter(self) -> Self::IntoIter {
155 self.table.into_iter()
156 }
157}
158
159impl<S: Scalar> TryFrom<IndexMap<Ident, OwnedColumn<S>>> for OwnedTable<S> {
160 type Error = OwnedTableError;
161
162 fn try_from(value: IndexMap<Ident, OwnedColumn<S>>) -> Result<Self, Self::Error> {
163 Self::try_new(value)
164 }
165}
166
167impl<S: Scalar> PartialEq for OwnedTable<S> {
170 fn eq(&self, other: &Self) -> bool {
171 self.table == other.table
172 && self
173 .table
174 .keys()
175 .zip(other.table.keys())
176 .all(|(a, b)| a == b)
177 }
178}
179
180#[cfg(test)]
181impl<S: Scalar> core::ops::Index<&str> for OwnedTable<S> {
182 type Output = OwnedColumn<S>;
183 fn index(&self, index: &str) -> &Self::Output {
184 self.table.get(&Ident::new(index)).unwrap()
185 }
186}
187
188impl<'a, S: Scalar> From<&Table<'a, S>> for OwnedTable<S> {
189 fn from(value: &Table<'a, S>) -> Self {
190 OwnedTable::try_from_iter(
191 value
192 .inner_table()
193 .iter()
194 .map(|(name, column)| (name.clone(), OwnedColumn::from(column))),
195 )
196 .expect("Tables should not have columns with differing lengths")
197 }
198}
199
200impl<'a, S: Scalar> From<Table<'a, S>> for OwnedTable<S> {
201 fn from(value: Table<'a, S>) -> Self {
202 OwnedTable::try_from_iter(
203 value
204 .into_inner()
205 .into_iter()
206 .map(|(name, column)| (name, OwnedColumn::from(&column))),
207 )
208 .expect("Tables should not have columns with differing lengths")
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::OwnedTable;
215 use crate::base::{
216 database::{
217 owned_table_utility::*, table_utility::*, ColumnCoercionError, OwnedColumn, Table,
218 TableCoercionError, TableOptions,
219 },
220 map::indexmap,
221 posql_time::{PoSQLTimeUnit, PoSQLTimeZone},
222 scalar::test_scalar::TestScalar,
223 IndexMap,
224 };
225 use bumpalo::Bump;
226
227 #[test]
228 fn test_conversion_from_table_to_owned_table() {
229 let alloc = Bump::new();
230
231 let borrowed_table = table::<TestScalar>([
232 borrowed_bigint(
233 "bigint",
234 [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX],
235 &alloc,
236 ),
237 borrowed_int128(
238 "decimal",
239 [0_i128, 1, 2, 3, 4, 5, 6, i128::MIN, i128::MAX],
240 &alloc,
241 ),
242 borrowed_varchar(
243 "varchar",
244 ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
245 &alloc,
246 ),
247 borrowed_scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8], &alloc),
248 borrowed_boolean(
249 "boolean",
250 [true, false, true, false, true, false, true, false, true],
251 &alloc,
252 ),
253 borrowed_timestamptz(
254 "time_stamp",
255 PoSQLTimeUnit::Second,
256 PoSQLTimeZone::utc(),
257 [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX],
258 &alloc,
259 ),
260 ]);
261
262 let expected_table = owned_table::<TestScalar>([
263 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
264 int128("decimal", [0_i128, 1, 2, 3, 4, 5, 6, i128::MIN, i128::MAX]),
265 varchar("varchar", ["0", "1", "2", "3", "4", "5", "6", "7", "8"]),
266 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8]),
267 boolean(
268 "boolean",
269 [true, false, true, false, true, false, true, false, true],
270 ),
271 timestamptz(
272 "time_stamp",
273 PoSQLTimeUnit::Second,
274 PoSQLTimeZone::utc(),
275 [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX],
276 ),
277 ]);
278
279 assert_eq!(OwnedTable::from(&borrowed_table), expected_table);
280 assert_eq!(OwnedTable::from(borrowed_table), expected_table);
281 }
282
283 #[test]
284 fn test_empty_and_no_columns_tables() {
285 let alloc = Bump::new();
286 let empty_table = table::<TestScalar>([borrowed_bigint("bigint", [0; 0], &alloc)]);
288 let expected_empty_table = owned_table::<TestScalar>([bigint("bigint", [0; 0])]);
289 assert_eq!(OwnedTable::from(&empty_table), expected_empty_table);
290 assert_eq!(OwnedTable::from(empty_table), expected_empty_table);
291
292 let no_columns_table_no_rows =
294 Table::try_new_with_options(indexmap! {}, TableOptions::new(Some(0))).unwrap();
295 let no_columns_table_two_rows =
296 Table::try_new_with_options(indexmap! {}, TableOptions::new(Some(2))).unwrap();
297 let expected_no_columns_table = owned_table::<TestScalar>([]);
298 assert_eq!(
299 OwnedTable::from(&no_columns_table_no_rows),
300 expected_no_columns_table
301 );
302 assert_eq!(
303 OwnedTable::from(no_columns_table_no_rows),
304 expected_no_columns_table
305 );
306 assert_eq!(
307 OwnedTable::from(&no_columns_table_two_rows),
308 expected_no_columns_table
309 );
310 assert_eq!(
311 OwnedTable::from(no_columns_table_two_rows),
312 expected_no_columns_table
313 );
314 }
315
316 #[test]
317 fn test_try_coerce_with_fields() {
318 use crate::base::database::{ColumnField, ColumnType};
319
320 let table = owned_table::<TestScalar>([
321 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
322 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8]),
323 ]);
324
325 let fields = vec![
326 ColumnField::new("bigint".into(), ColumnType::BigInt),
327 ColumnField::new("scalar".into(), ColumnType::Int),
328 ];
329
330 let coerced_table = table.clone().try_coerce_with_fields(fields).unwrap();
331
332 let expected_table = owned_table::<TestScalar>([
333 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
334 int("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8]),
335 ]);
336
337 assert_eq!(coerced_table, expected_table);
338 }
339
340 #[test]
341 fn test_try_coerce_with_fields_name_mismatch() {
342 use crate::base::database::{ColumnField, ColumnType};
343
344 let table = owned_table::<TestScalar>([
345 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
346 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8]),
347 ]);
348
349 let fields = vec![
350 ColumnField::new("bigint".into(), ColumnType::BigInt),
351 ColumnField::new("mismatch".into(), ColumnType::Int),
352 ];
353
354 let result = table.clone().try_coerce_with_fields(fields);
355
356 assert!(matches!(result, Err(TableCoercionError::NameMismatch)));
357 }
358
359 #[test]
360 fn test_try_coerce_with_fields_column_count_mismatch() {
361 use crate::base::database::{ColumnField, ColumnType};
362
363 let table = owned_table::<TestScalar>([
364 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
365 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, 8]),
366 ]);
367
368 let fields = vec![ColumnField::new("bigint".into(), ColumnType::BigInt)];
369
370 let result = table.clone().try_coerce_with_fields(fields);
371
372 assert!(matches!(
373 result,
374 Err(TableCoercionError::ColumnCountMismatch)
375 ));
376 }
377
378 #[test]
379 fn test_try_coerce_with_fields_overflow() {
380 use crate::base::database::{ColumnField, ColumnType};
381
382 let table = owned_table::<TestScalar>([
383 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
384 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, i64::MAX]),
385 ]);
386
387 let fields = vec![
388 ColumnField::new("bigint".into(), ColumnType::BigInt),
389 ColumnField::new("scalar".into(), ColumnType::TinyInt),
390 ];
391
392 let result = table.clone().try_coerce_with_fields(fields);
393
394 assert!(matches!(
395 result,
396 Err(TableCoercionError::ColumnCoercionError {
397 source: ColumnCoercionError::Overflow
398 })
399 ));
400 }
401
402 #[test]
403 fn test_try_from() {
404 let index_map = [
405 bigint("bigint", [0_i64, 1, 2, 3, 4, 5, 6, i64::MIN, i64::MAX]),
406 scalar("scalar", [0, 1, 2, 3, 4, 5, 6, 7, i64::MAX]),
407 ]
408 .into_iter()
409 .collect::<IndexMap<_, OwnedColumn<TestScalar>>>();
410 let table = OwnedTable::try_from(index_map.clone()).unwrap();
411
412 let iter_value: IndexMap<_, _> = table.into_iter().collect();
413 assert_eq!(iter_value, index_map);
414 }
415}