1use rudb_common::{Error, Field, LogicalType, Result};
4use rudb_plan::ColumnBinding;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Schema {
14 fields: Vec<Field>,
15 bindings: Vec<ColumnBinding>,
16}
17
18impl Schema {
19 pub fn new(fields: Vec<Field>, bindings: Vec<ColumnBinding>) -> Result<Self> {
26 if fields.len() != bindings.len() {
27 return Err(Error::internal(format!(
28 "a schema of {} fields and {} bindings",
29 fields.len(),
30 bindings.len()
31 )));
32 }
33 Ok(Self { fields, bindings })
34 }
35
36 #[must_use]
46 pub fn numbered(fields: Vec<Field>, table: u32) -> Self {
47 let bindings = (0..fields.len())
48 .map(|at| {
49 ColumnBinding::new(
50 table,
51 u32::try_from(at).expect("a schema this wide cannot be built"),
52 )
53 })
54 .collect();
55 Self { fields, bindings }
56 }
57
58 #[must_use]
60 pub fn empty() -> Self {
61 Self { fields: Vec::new(), bindings: Vec::new() }
62 }
63
64 #[must_use]
66 pub fn fields(&self) -> &[Field] {
67 &self.fields
68 }
69
70 #[must_use]
72 pub fn bindings(&self) -> &[ColumnBinding] {
73 &self.bindings
74 }
75
76 #[must_use]
78 pub fn width(&self) -> usize {
79 self.fields.len()
80 }
81
82 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.fields.is_empty()
86 }
87
88 #[must_use]
90 pub fn types(&self) -> Vec<LogicalType> {
91 self.fields.iter().map(|field| field.ty.clone()).collect()
92 }
93
94 #[must_use]
96 pub fn names(&self) -> Vec<String> {
97 self.fields.iter().map(|field| field.name.clone()).collect()
98 }
99
100 #[must_use]
106 pub fn position_of(&self, binding: ColumnBinding) -> Option<usize> {
107 self.bindings.iter().position(|held| *held == binding)
108 }
109
110 #[must_use]
112 pub fn concat(left: &Self, right: &Self) -> Self {
113 let mut fields = left.fields.clone();
114 fields.extend(right.fields.iter().cloned());
115 let mut bindings = left.bindings.clone();
116 bindings.extend(right.bindings.iter().copied());
117 Self { fields, bindings }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn two() -> Schema {
126 Schema::numbered(
127 vec![Field::new("a", LogicalType::Integer), Field::new("b", LogicalType::Varchar)],
128 7,
129 )
130 }
131
132 #[test]
133 fn a_numbered_schema_binds_its_columns_in_order() {
134 let schema = two();
135 assert_eq!(schema.position_of(ColumnBinding::new(7, 0)), Some(0));
136 assert_eq!(schema.position_of(ColumnBinding::new(7, 1)), Some(1));
137 assert_eq!(schema.position_of(ColumnBinding::new(7, 2)), None);
138 assert_eq!(schema.position_of(ColumnBinding::new(6, 0)), None);
139 }
140
141 #[test]
142 fn a_schema_reports_its_names_and_types_in_output_order() {
143 let schema = two();
144 assert_eq!(schema.names(), vec!["a".to_string(), "b".to_string()]);
145 assert_eq!(schema.types(), vec![LogicalType::Integer, LogicalType::Varchar]);
146 assert_eq!(schema.width(), 2);
147 }
148
149 #[test]
152 fn concatenating_keeps_both_sides_distinguishable() {
153 let left = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 0);
154 let right = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 1);
155 let joined = Schema::concat(&left, &right);
156 assert_eq!(joined.width(), 2);
157 assert_eq!(joined.position_of(ColumnBinding::new(0, 0)), Some(0));
158 assert_eq!(joined.position_of(ColumnBinding::new(1, 0)), Some(1));
159 }
160
161 #[test]
162 fn a_schema_whose_halves_disagree_is_caught() {
163 let error = Schema::new(vec![Field::new("a", LogicalType::Integer)], Vec::new())
164 .expect_err("one field and no bindings");
165 assert!(error.message().contains("1 fields and 0 bindings"), "{error}");
166 }
167}