1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! What an operator produces: names, types, and the bindings downstream expressions use.
use rudb_common::{Error, Field, LogicalType, Result};
use rudb_plan::ColumnBinding;
/// One operator's output columns.
///
/// The fields and the bindings are parallel and always the same length, which [`Schema::new`]
/// checks. They are two vectors rather than a vector of pairs because a caller almost always wants
/// one of them whole: the result set wants the names and the types, and the expression evaluator
/// wants to search the bindings.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Schema {
fields: Vec<Field>,
bindings: Vec<ColumnBinding>,
}
impl Schema {
/// A schema of `fields`, bound at `bindings`.
///
/// # Errors
///
/// If the two are not the same length, which would make a binding resolve to the wrong column
/// or to no column at all.
pub fn new(fields: Vec<Field>, bindings: Vec<ColumnBinding>) -> Result<Self> {
if fields.len() != bindings.len() {
return Err(Error::internal(format!(
"a schema of {} fields and {} bindings",
fields.len(),
bindings.len()
)));
}
Ok(Self { fields, bindings })
}
/// A schema of `fields` numbered from zero against `table`.
///
/// The common case, since an operator that introduces columns numbers them in the order it
/// produces them and the binder does the same.
///
/// # Panics
///
/// If there are more than `u32::MAX` fields, which is the bound a binding's position already
/// carries.
#[must_use]
pub fn numbered(fields: Vec<Field>, table: u32) -> Self {
let bindings = (0..fields.len())
.map(|at| {
ColumnBinding::new(
table,
u32::try_from(at).expect("a schema this wide cannot be built"),
)
})
.collect();
Self { fields, bindings }
}
/// A schema with no columns, which is what [`Node::Dummy`](rudb_plan::Node::Dummy) produces.
#[must_use]
pub fn empty() -> Self {
Self { fields: Vec::new(), bindings: Vec::new() }
}
/// The fields, in output order.
#[must_use]
pub fn fields(&self) -> &[Field] {
&self.fields
}
/// The bindings, in output order.
#[must_use]
pub fn bindings(&self) -> &[ColumnBinding] {
&self.bindings
}
/// How many columns.
#[must_use]
pub fn width(&self) -> usize {
self.fields.len()
}
/// Whether there are no columns.
#[must_use]
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
/// The column types, in order.
#[must_use]
pub fn types(&self) -> Vec<LogicalType> {
self.fields.iter().map(|field| field.ty.clone()).collect()
}
/// The column names, in order.
#[must_use]
pub fn names(&self) -> Vec<String> {
self.fields.iter().map(|field| field.name.clone()).collect()
}
/// Where a binding sits in the output.
///
/// A linear scan. An operator's schema is as wide as the query is, which is tens of columns on
/// the widest ClickBench query, and a hash map of tens of entries rebuilt per operator costs
/// more than the scans it saves.
#[must_use]
pub fn position_of(&self, binding: ColumnBinding) -> Option<usize> {
self.bindings.iter().position(|held| *held == binding)
}
/// The left schema's columns followed by the right schema's, which is what a join produces.
#[must_use]
pub fn concat(left: &Self, right: &Self) -> Self {
let mut fields = left.fields.clone();
fields.extend(right.fields.iter().cloned());
let mut bindings = left.bindings.clone();
bindings.extend(right.bindings.iter().copied());
Self { fields, bindings }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn two() -> Schema {
Schema::numbered(
vec![Field::new("a", LogicalType::Integer), Field::new("b", LogicalType::Varchar)],
7,
)
}
#[test]
fn a_numbered_schema_binds_its_columns_in_order() {
let schema = two();
assert_eq!(schema.position_of(ColumnBinding::new(7, 0)), Some(0));
assert_eq!(schema.position_of(ColumnBinding::new(7, 1)), Some(1));
assert_eq!(schema.position_of(ColumnBinding::new(7, 2)), None);
assert_eq!(schema.position_of(ColumnBinding::new(6, 0)), None);
}
#[test]
fn a_schema_reports_its_names_and_types_in_output_order() {
let schema = two();
assert_eq!(schema.names(), vec!["a".to_string(), "b".to_string()]);
assert_eq!(schema.types(), vec![LogicalType::Integer, LogicalType::Varchar]);
assert_eq!(schema.width(), 2);
}
/// Two tables that both number their columns from zero is the ordinary case, and it is the case
/// a join would get wrong if the position were the whole of the answer.
#[test]
fn concatenating_keeps_both_sides_distinguishable() {
let left = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 0);
let right = Schema::numbered(vec![Field::new("id", LogicalType::Integer)], 1);
let joined = Schema::concat(&left, &right);
assert_eq!(joined.width(), 2);
assert_eq!(joined.position_of(ColumnBinding::new(0, 0)), Some(0));
assert_eq!(joined.position_of(ColumnBinding::new(1, 0)), Some(1));
}
#[test]
fn a_schema_whose_halves_disagree_is_caught() {
let error = Schema::new(vec![Field::new("a", LogicalType::Integer)], Vec::new())
.expect_err("one field and no bindings");
assert!(error.message().contains("1 fields and 0 bindings"), "{error}");
}
}