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
180
181
182
//! The immutable table handle scripts narrow: a base plus a list of steps.
//!
//! Every narrowing verb returns a new [`Handle`] that shares its prefix with
//! the one it came from, so a handle stored in a variable never changes. A
//! [`Vista`] is only built when a terminal asks for one, through
//! [`Handle::resolve`].
//!
//! In a `modify:` script, `self` is a handle over the Vista being
//! changed, and it follows the same rule: `self.where(..)` returns a new
//! handle, and a statement whose result is dropped changes nothing. The
//! script's value is the handle it ends on.
mod query;
mod step;
use std::sync::Arc;
use vantage_core::{Context, Result, error};
pub(crate) use step::Step;
use crate::{NativeCondition, VistaResolver, vista::Vista};
#[derive(Clone)]
pub struct Handle {
base: Base,
steps: Arc<Vec<Step>>,
/// The resolver `table(name)` was called with, used when a caller
/// resolves without one.
resolver: Option<Arc<dyn VistaResolver>>,
/// The extension verb that last narrowed the base with a native
/// condition, which [`Handle::query`] can't express.
native_verb: Option<String>,
}
#[derive(Clone)]
enum Base {
/// Resolved by name through a [`VistaResolver`].
Name(String),
/// A Vista in hand, cloned on each resolve.
Vista(Arc<Vista>),
}
impl Handle {
/// A handle on the table `name`, resolved when a terminal reads it.
pub fn named(name: impl Into<String>) -> Self {
Self::with_base(Base::Name(name.into()))
}
/// A handle over `vista`: the `self` of a `modify:` script.
pub(crate) fn over(vista: Vista) -> Self {
Self::with_base(Base::Vista(Arc::new(vista)))
}
fn with_base(base: Base) -> Self {
Self {
base,
steps: Arc::new(Vec::new()),
resolver: None,
native_verb: None,
}
}
/// The resolver a named base goes through when a caller resolves without
/// one. A [`RecordDraft`](super::RecordDraft) reads and writes through it.
pub fn with_resolver(mut self, resolver: Arc<dyn VistaResolver>) -> Self {
self.resolver = Some(resolver);
self
}
/// The table name this handle starts from; `None` for a handle over a Vista.
pub fn table_name(&self) -> Option<&str> {
match &self.base {
Base::Name(name) => Some(name),
Base::Vista(_) => None,
}
}
/// A new handle with `step` appended; `self` is left as it was.
pub(crate) fn push(&self, step: Step) -> Handle {
let mut steps = Vec::with_capacity(self.steps.len() + 1);
steps.extend(self.steps.iter().cloned());
steps.push(step);
Handle {
steps: Arc::new(steps),
..self.clone()
}
}
/// Build the Vista: the base (resolved by name, or a clone of the Vista
/// in hand), then every step in order. A named base goes through
/// `resolver`, else the one `table(name)` was given.
pub fn resolve(&self, resolver: Option<&dyn VistaResolver>) -> Result<Vista> {
let mut vista = self.resolve_base(resolver)?;
let mut ref_cap = None;
for step in self.steps.iter() {
vista = step
.apply(vista, ref_cap)
.with_context(|| error!("Step can't be applied", step = step))?;
ref_cap = step.ref_cap_after(ref_cap);
}
Ok(vista)
}
/// The Vista writes go to: the whole narrowed handle — `where`, `search`
/// and `ref` all define the set, so a write only touches rows in it.
/// `sort` doesn't change membership and is skipped. `limit` defines rows
/// by position, not by condition, so a handle with one can't be written
/// through.
pub(crate) fn write_target(&self, resolver: Option<&dyn VistaResolver>) -> Result<Vista> {
if self.steps.iter().any(|s| matches!(s, Step::Limit(_))) {
return Err(
error!("writes need a set defined by conditions; drop limit(n)")
.mark_incorrect_usage(),
);
}
let mut vista = self.resolve_base(resolver)?;
for step in self.steps.iter() {
if matches!(step, Step::Sort { .. }) {
continue;
}
vista = step
.apply(vista, None)
.with_context(|| error!("Step can't be applied", step = step))?;
}
Ok(vista)
}
/// The smallest `limit(n)` applied after the last `ref` step.
pub(crate) fn row_limit(&self) -> Option<usize> {
self.steps
.iter()
.rev()
.take_while(|s| !matches!(s, Step::Ref(_)))
.filter_map(|s| match s {
Step::Limit(n) => Some(*n),
_ => None,
})
.min()
}
/// A handle whose Vista base is a copy narrowed by `condition`, keeping
/// its steps; `self` is left as it was. A backend's extension verb
/// (`verb`, such as `with_condition`) uses this. The base must be a
/// Vista in hand, so a handle built from `table(name)`, or one past a
/// `ref`, is an error naming the verb.
pub fn with_native_condition(&self, verb: &str, condition: NativeCondition) -> Result<Handle> {
let Base::Vista(source) = &self.base else {
return Err(error!(
"Extension verb only works on `self`, not on a handle from `table(...)`",
verb = verb
));
};
if self.steps.iter().any(|s| matches!(s, Step::Ref(_))) {
return Err(error!(
"Extension verb can't follow `ref(...)`",
verb = verb
));
}
let narrowed = Vista::clone(source).with_native_condition(condition)?;
Ok(Handle {
base: Base::Vista(Arc::new(narrowed)),
native_verb: Some(verb.to_string()),
..self.clone()
})
}
fn resolve_base(&self, resolver: Option<&dyn VistaResolver>) -> Result<Vista> {
match &self.base {
Base::Name(name) => resolver
.or(self.resolver.as_deref())
.ok_or_else(|| {
error!(
"Table can't be resolved here: no tables are available",
table = name
)
})?
.vista(name),
Base::Vista(vista) => Ok(Vista::clone(vista)),
}
}
}