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
168
169
170
171
172
173
174
175
176
177
178
179
use indexmap::IndexMap;
use vantage_core::{Result, error};
use crate::{
capabilities::{Capability, VistaCapabilities, unsupported},
column::{Column, ColumnFlag},
metadata::VistaMetadata,
page::Preview,
query::VistaQuery,
reference::Reference,
shell::TableShell,
};
mod narrow;
mod read;
#[cfg(test)]
mod tests;
mod value_set;
/// Universal, schema-bearing data handle: the type-erased `Table<T, E>`.
///
/// A `Vista` is a value. It owns its query state, so narrowing a clone never
/// changes the original: `vista.clone().with_condition(..)` gives a new set.
/// The schema (columns, references, id column) lives on the wrapped
/// [`TableShell`]; every clause applied through the `Vista` is recorded in
/// [`query`](Self::query).
///
/// A `Vista` is single-persistence: every reference its shell exposes
/// resolves within the same backend. Joining across backends is the job of
/// the layer that holds the catalog.
#[derive(Clone)]
pub struct Vista {
name: String,
shell: Box<dyn TableShell>,
applied: VistaQuery,
}
impl Vista {
pub fn new(name: impl Into<String>, shell: impl TableShell) -> Self {
Self::from_box(name.into(), Box::new(shell))
}
pub(crate) fn from_box(name: String, shell: Box<dyn TableShell>) -> Self {
Self {
name,
shell,
applied: VistaQuery::default(),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
/// Wrap the shell, keeping the name and the applied query. Wrappers built
/// with [`forward_table_shell!`](crate::forward_table_shell) take the
/// inner shell this way.
pub fn map_shell<S: TableShell>(self, wrap: impl FnOnce(Box<dyn TableShell>) -> S) -> Self {
Self {
shell: Box::new(wrap(self.shell)),
..self
}
}
pub(crate) fn shell(&self) -> &dyn TableShell {
&*self.shell
}
pub(crate) fn shell_mut(&mut self) -> &mut dyn TableShell {
&mut *self.shell
}
// ---- introspection -----------------------------------------------------
pub fn name(&self) -> &str {
&self.name
}
/// Every clause applied through this Vista's `with_*` and `add_*`
/// methods. A refused clause is not recorded.
pub fn query(&self) -> &VistaQuery {
&self.applied
}
/// Whether a condition, search or native condition limits this set.
/// Order alone doesn't: it changes no membership.
pub fn is_narrowed(&self) -> bool {
self.applied.narrows()
}
/// The shell's capabilities, read live.
pub fn capabilities(&self) -> &VistaCapabilities {
self.shell.capabilities()
}
pub fn driver_name(&self) -> &'static str {
self.shell.driver_name()
}
/// The query the next read would send, without sending it; see
/// [`TableShell::preview`].
pub fn preview(&self) -> Preview {
self.shell.preview()
}
// ---- metadata ----------------------------------------------------------
fn metadata(&self) -> &VistaMetadata {
self.shell.metadata()
}
pub fn columns(&self) -> &IndexMap<String, Column> {
&self.metadata().columns
}
pub fn column(&self, name: &str) -> Option<&Column> {
self.columns().get(name)
}
pub fn id_column(&self) -> Option<&str> {
self.metadata().id_column.as_deref()
}
/// Columns flagged [`Title`](ColumnFlag::Title), in declaration order.
pub fn title_columns(&self) -> Vec<&str> {
self.columns()
.values()
.filter(|c| c.has_flag(ColumnFlag::Title))
.map(|c| c.name.as_str())
.collect()
}
pub fn references(&self) -> &IndexMap<String, Reference> {
&self.metadata().references
}
pub fn reference(&self, name: &str) -> Option<&Reference> {
self.references().get(name)
}
/// `unsupported(capability)` unless the shell advertises it: how `Vista`
/// gates a read or write before the shell is called.
pub(crate) fn require(&self, capability: Capability) -> Result<()> {
if self.capabilities().has(capability) {
Ok(())
} else {
unsupported(capability)
}
}
/// A write is refused unless the shell advertises `capability`, whatever
/// the shell itself would do: a read-only vista over a writable table
/// takes no writes. Writes through a narrowed Vista also need a shell
/// that keeps them inside the set.
pub(crate) fn require_write(&self, capability: Capability, verb: &'static str) -> Result<()> {
self.require(capability)?;
if !self.is_narrowed() || self.capabilities().has(Capability::ConfineWrites) {
return Ok(());
}
Err(error!(
"this backend can't keep writes inside a narrowed set",
verb = verb,
table = self.name.as_str(),
capability = Capability::ConfineWrites.as_str()
)
.mark_unsupported())
}
}
impl std::fmt::Debug for Vista {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Vista")
.field("name", &self.name)
.field("driver", &self.driver_name())
.field("query", &self.applied)
.finish()
}
}