uqa_execution/
projected_row.rs1use uqa_core::Value;
10use uqa_sql::expr::RowLookup;
11
12use crate::RowSchema;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ProjectedValueSlot {
17 Field(usize),
19 Extra(usize),
21 Missing,
23}
24
25impl ProjectedValueSlot {
26 #[must_use]
28 pub fn compile(columns: &[String], fields: &[String], extras: &[&str]) -> Vec<Self> {
29 columns
30 .iter()
31 .map(|column| {
32 fields
33 .iter()
34 .position(|field| field == column)
35 .map(Self::Field)
36 .or_else(|| {
37 extras
38 .iter()
39 .position(|extra| *extra == column)
40 .map(Self::Extra)
41 })
42 .unwrap_or(Self::Missing)
43 })
44 .collect()
45 }
46}
47
48pub struct ProjectedRow<'schema, 'row> {
52 schema: &'schema RowSchema,
53 slots: &'schema [ProjectedValueSlot],
54 fields: &'row [&'row Value],
55 extras: &'row [Option<Value>],
56}
57
58impl<'schema, 'row> ProjectedRow<'schema, 'row> {
59 #[must_use]
60 pub fn new(
61 schema: &'schema RowSchema,
62 slots: &'schema [ProjectedValueSlot],
63 fields: &'row [&'row Value],
64 extras: &'row [Option<Value>],
65 ) -> Self {
66 debug_assert_eq!(schema.len(), slots.len());
67 Self {
68 schema,
69 slots,
70 fields,
71 extras,
72 }
73 }
74
75 #[inline]
76 #[must_use]
77 pub fn positional_column(&self, index: usize) -> Option<&Value> {
78 match self.slots.get(index)? {
79 ProjectedValueSlot::Field(index) => self.fields.get(*index).copied(),
80 ProjectedValueSlot::Extra(index) => self.extras.get(*index).and_then(Option::as_ref),
81 ProjectedValueSlot::Missing => None,
82 }
83 }
84
85 #[must_use]
87 pub fn into_values(self) -> Vec<Value> {
88 (0..self.slots.len())
89 .map(|index| {
90 self.positional_column(index)
91 .cloned()
92 .unwrap_or(Value::Null)
93 })
94 .collect()
95 }
96}
97
98impl RowLookup for ProjectedRow<'_, '_> {
99 fn column(&self, name: &str) -> Option<&Value> {
100 self.schema
101 .unqualified_position(name)
102 .and_then(|index| self.positional_column(index))
103 }
104
105 fn column_is_ambiguous(&self, name: &str) -> bool {
106 self.schema.column_is_ambiguous(name)
107 }
108
109 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value> {
110 self.schema
111 .qualified_position(qualifier, column)
112 .and_then(|index| self.positional_column(index))
113 }
114
115 fn qualified_column_is_ambiguous(&self, qualifier: &str, column: &str) -> bool {
116 self.schema.qualified_column_is_ambiguous(qualifier, column)
117 }
118
119 #[inline]
120 fn positional_column(&self, index: usize) -> Option<&Value> {
121 Self::positional_column(self, index)
122 }
123
124 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
125 for (index, column) in self.schema.columns().iter().enumerate() {
126 visitor(
127 column,
128 self.positional_column(index).unwrap_or(&Value::Null),
129 );
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn projected_row_resolves_fields_extras_and_missing_slots() {
140 let columns = vec!["name".into(), "score".into(), "missing".into()];
141 let fields = vec!["name".into()];
142 let slots = ProjectedValueSlot::compile(&columns, &fields, &["score"]);
143 let schema = RowSchema::new(columns);
144 let name = Value::Str("Ada".into());
145 let values = [&name];
146 let extras = [Some(Value::Float(0.75))];
147 let row = ProjectedRow::new(&schema, &slots, &values, &extras);
148
149 assert_eq!(row.positional_column(0), Some(&name));
150 assert_eq!(row.positional_column(1), Some(&Value::Float(0.75)));
151 assert_eq!(row.positional_column(2), None);
152 assert_eq!(row.column("name"), Some(&name));
153 }
154
155 #[test]
156 fn projected_row_preserves_structured_qualification() {
157 let columns = vec!["id".into(), "id".into()];
158 let schema = RowSchema::with_identities(
159 columns.clone(),
160 vec![
161 crate::ColumnIdentity::qualified("left.dot", "id.dot"),
162 crate::ColumnIdentity::qualified("right", "id.dot"),
163 ],
164 vec![None, None],
165 );
166 let slots = vec![ProjectedValueSlot::Field(0), ProjectedValueSlot::Field(1)];
167 let left = Value::Int(1);
168 let right = Value::Int(2);
169 let values = [&left, &right];
170 let row = ProjectedRow::new(&schema, &slots, &values, &[]);
171
172 assert!(row.column_is_ambiguous("id.dot"));
173 assert_eq!(
174 row.qualified_column("left.dot", "id.dot"),
175 Some(&Value::Int(1))
176 );
177 assert_eq!(row.qualified_column("left", "dot.id.dot"), None);
178 }
179}