nodedb_array/query/
project.rs1use crate::error::{ArrayError, ArrayResult};
12use crate::tile::sparse_tile::SparseTile;
13
14#[derive(Debug, Clone)]
18pub struct Projection {
19 pub attr_indices: Vec<usize>,
20}
21
22impl Projection {
23 pub fn new(attr_indices: Vec<usize>) -> Self {
24 Self { attr_indices }
25 }
26}
27
28pub fn project_sparse(tile: &SparseTile, proj: &Projection) -> ArrayResult<SparseTile> {
32 for &i in &proj.attr_indices {
33 if i >= tile.attr_cols.len() {
34 return Err(ArrayError::InvalidAttr {
35 array: String::new(),
36 attr: format!("idx {i}"),
37 detail: format!("attr index out of range (have {})", tile.attr_cols.len()),
38 });
39 }
40 }
41 let attr_cols = proj
42 .attr_indices
43 .iter()
44 .map(|&i| tile.attr_cols[i].clone())
45 .collect();
46 let attr_stats = proj
47 .attr_indices
48 .iter()
49 .map(|&i| tile.mbr.attr_stats[i].clone())
50 .collect();
51 let mut mbr = tile.mbr.clone();
52 mbr.attr_stats = attr_stats;
53 Ok(SparseTile {
54 dim_dicts: tile.dim_dicts.clone(),
55 attr_cols,
56 surrogates: tile.surrogates.clone(),
57 valid_from_ms: tile.valid_from_ms.clone(),
58 valid_until_ms: tile.valid_until_ms.clone(),
59 row_kinds: tile.row_kinds.clone(),
60 mbr,
61 })
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::schema::ArraySchema;
68 use crate::schema::ArraySchemaBuilder;
69 use crate::schema::attr_spec::{AttrSpec, AttrType};
70 use crate::schema::dim_spec::{DimSpec, DimType};
71 use crate::tile::sparse_tile::SparseTileBuilder;
72 use crate::types::cell_value::value::CellValue;
73 use crate::types::coord::value::CoordValue;
74 use crate::types::domain::{Domain, DomainBound};
75
76 fn schema() -> ArraySchema {
77 ArraySchemaBuilder::new("g")
78 .dim(DimSpec::new(
79 "x",
80 DimType::Int64,
81 Domain::new(DomainBound::Int64(0), DomainBound::Int64(15)),
82 ))
83 .attr(AttrSpec::new("a", AttrType::Int64, true))
84 .attr(AttrSpec::new("b", AttrType::Float64, true))
85 .attr(AttrSpec::new("c", AttrType::String, true))
86 .tile_extents(vec![16])
87 .build()
88 .unwrap()
89 }
90
91 fn tile() -> SparseTile {
92 let s = schema();
93 let mut b = SparseTileBuilder::new(&s);
94 b.push(
95 &[CoordValue::Int64(0)],
96 &[
97 CellValue::Int64(1),
98 CellValue::Float64(1.5),
99 CellValue::String("x".into()),
100 ],
101 )
102 .unwrap();
103 b.push(
104 &[CoordValue::Int64(1)],
105 &[
106 CellValue::Int64(2),
107 CellValue::Float64(2.5),
108 CellValue::String("y".into()),
109 ],
110 )
111 .unwrap();
112 b.build()
113 }
114
115 #[test]
116 fn project_keeps_subset_in_order() {
117 let t = tile();
118 let p = Projection::new(vec![2, 0]);
119 let out = project_sparse(&t, &p).unwrap();
120 assert_eq!(out.attr_cols.len(), 2);
121 assert_eq!(out.attr_cols[0][0], CellValue::String("x".into()));
122 assert_eq!(out.attr_cols[1][0], CellValue::Int64(1));
123 assert_eq!(out.mbr.attr_stats.len(), 2);
124 }
125
126 #[test]
127 fn project_rejects_out_of_range() {
128 let t = tile();
129 let p = Projection::new(vec![5]);
130 assert!(project_sparse(&t, &p).is_err());
131 }
132}