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
use std::sync::Arc;
use ciborium::Value as CborValue;
use serde::{Deserialize, Serialize};
use vantage_core::Result;
use vantage_types::Record;
mod family;
mod flag;
pub use family::ColumnFamily;
pub use flag::ColumnFlag;
/// Computes one column value from a record: the record as read so far goes
/// in, the column's value comes out.
pub(crate) type LazyValueFn = Arc<dyn Fn(&Record<CborValue>) -> Result<CborValue> + Send + Sync>;
/// Display-relevant column metadata held by a `Vista`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Column {
pub name: String,
/// The backend's or the spec's name for the column's type; `None` when
/// nothing declares one (a YAML column without `type:`, a schemaless
/// store).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_type: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
flags: Vec<ColumnFlag>,
/// Set on a computed column; see `Column::with_expression` (`rhai` feature).
#[serde(skip)]
computed: Option<Computed>,
}
/// A computed column's script, kept for introspection, and its compiled form.
#[derive(Clone)]
struct Computed {
code: String,
eval: LazyValueFn,
}
impl std::fmt::Debug for Computed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Computed").field(&self.code).finish()
}
}
impl Column {
/// A column whose type is `original_type`.
pub fn new(name: impl Into<String>, original_type: impl Into<String>) -> Self {
Self::with_type(name.into(), Some(original_type.into()))
}
/// A column with no declared type.
pub fn untyped(name: impl Into<String>) -> Self {
Self::with_type(name.into(), None)
}
fn with_type(name: String, original_type: Option<String>) -> Self {
Self {
name,
original_type,
flags: Vec::new(),
computed: None,
}
}
/// Make this a computed column: `code` is a Rhai expression over `row`,
/// the record as read so far, and its value becomes this column's value.
///
/// ```rhai
/// row.contents.split("\n").len() - 1
/// ```
///
/// The `Vista` fills computed columns on every read, in declaration
/// order, so one sees the values of those declared before it. The backend
/// never sees them: writes drop them, and filtering or ordering on one is
/// an error. The column is flagged [`Calculated`](ColumnFlag::Calculated)
/// and loses [`Orderable`](ColumnFlag::Orderable) and
/// [`Searchable`](ColumnFlag::Searchable).
///
/// The script compiles here, so one that doesn't parse fails the build
/// rather than the first read.
#[cfg(feature = "rhai")]
pub fn with_expression(mut self, code: impl Into<String>) -> Result<Self> {
use vantage_core::{Context, error};
let code = code.into();
let eval = crate::rhai::lazy_value_closure(&code).with_context(|| {
error!(
"Computed column expression doesn't compile",
column = self.name.clone()
)
})?;
self.flags
.retain(|f| *f != ColumnFlag::Orderable && *f != ColumnFlag::Searchable);
self = self.with_flag(ColumnFlag::Calculated);
self.computed = Some(Computed { code, eval });
Ok(self)
}
/// This column with `flag` added; a flag it already has is kept once.
/// A flag is stored in its canonical form (`Other("id")` as `Id`).
pub fn with_flag(mut self, flag: impl Into<ColumnFlag>) -> Self {
let flag = ColumnFlag::from(flag.into().as_str());
if !self.flags.contains(&flag) {
self.flags.push(flag);
}
self
}
pub fn has_flag(&self, flag: impl Into<ColumnFlag>) -> bool {
self.flags.contains(&flag.into())
}
pub fn flags(&self) -> &[ColumnFlag] {
&self.flags
}
/// Whether the `Vista` computes this column rather than reading it.
///
/// The expression isn't serialized: a column that goes through serde
/// comes back stored, keeping only its [`Calculated`](ColumnFlag::Calculated)
/// flag.
pub fn is_computed(&self) -> bool {
self.computed.is_some()
}
/// The script of a computed column.
pub fn expression(&self) -> Option<&str> {
self.computed.as_ref().map(|c| c.code.as_str())
}
/// This computed column's value for `row`; `None` for a stored column.
pub(crate) fn compute(&self, row: &Record<CborValue>) -> Option<Result<CborValue>> {
self.computed.as_ref().map(|c| (c.eval)(row))
}
}