use super::*;
pub const ORIGIN_CAP: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Origin {
pub node: NodeId,
pub stage: u32,
}
impl Default for Origin {
fn default() -> Self {
Origin {
node: NodeId(0),
stage: 0,
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Origins {
pub known: Vec<Origin>,
pub more: u32,
}
impl Origins {
pub fn at(node: NodeId, stage: u32) -> Origins {
Origins {
known: vec![Origin { node, stage }],
more: 0,
}
}
pub fn is_empty(&self) -> bool {
self.known.is_empty() && self.more == 0
}
pub fn insert(&mut self, o: Origin) {
if self.known.contains(&o) {
return;
}
if self.known.len() < ORIGIN_CAP {
self.known.push(o);
} else {
self.more = self.more.saturating_add(1);
}
}
pub fn union(&mut self, other: &Origins) {
for o in &other.known {
self.insert(*o);
}
self.more = self.more.saturating_add(other.more);
}
pub fn nodes(&self) -> impl Iterator<Item = NodeId> + '_ {
self.known.iter().map(|o| o.node)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Prov {
pub origins: Origins,
pub parts: Vec<(String, Prov)>,
}
impl Prov {
pub fn at(node: NodeId, stage: u32) -> Prov {
Prov {
origins: Origins::at(node, stage),
parts: Vec::new(),
}
}
pub fn leaf(origins: Origins) -> Prov {
Prov {
origins,
parts: Vec::new(),
}
}
pub fn record(parts: Vec<(String, Prov)>) -> Prov {
let mut origins = Origins::default();
for (_, p) in &parts {
origins.union(&p.origins);
}
Prov { origins, parts }
}
pub fn list(items: Vec<Prov>) -> Prov {
Prov::record(
items
.into_iter()
.enumerate()
.map(|(i, p)| (i.to_string(), p))
.collect(),
)
}
pub fn part(&self, key: &str) -> Prov {
self.parts
.iter()
.find(|(k, _)| k == key)
.map(|(_, p)| p.clone())
.unwrap_or_else(|| Prov::leaf(self.origins.clone()))
}
pub fn item(&self, i: usize) -> Prov {
self.part(&i.to_string())
}
pub fn flat(&self) -> Prov {
Prov::leaf(self.origins.clone())
}
}
#[derive(Clone, Debug)]
pub struct Traced {
pub node: NodeId,
pub topic: Option<Value>,
pub prov: Prov,
}
pub fn resolved_provenance<A: AstAdapter + ?Sized>(adapter: &A, node: NodeId) -> ProvenanceList {
let mut list = adapter.provenance_list(node);
if list.entries.is_empty() {
list.entries.push(Provenance::default());
}
let (reproducible, access) = run_option(|o| (o.reproducible, o.now));
let mut path: Option<String> = None;
for e in &mut list.entries {
if e.path.is_none() {
e.path = Some(path.get_or_insert_with(|| name_path(adapter, node)).clone());
}
if reproducible {
if matches!(
e.instant_from,
Some(InstantFrom::Source | InstantFrom::Access)
) {
e.instant = None;
e.instant_from = None;
}
} else if e.instant.is_none()
&& let Some((secs, nanos)) = access
{
e.instant = Some((secs, nanos, Some(0)));
e.instant_from = Some(InstantFrom::Access);
}
}
list
}
pub(super) fn origins_meta(adapter: &impl AstAdapter, o: &Origins, key: &str) -> Option<Value> {
let (entries, elided) = origin_entries(adapter, o);
match key {
"source" => Some(
entries
.first()
.and_then(|p| p.source.clone())
.map_or(Value::Null, Value::Str),
),
"dpid" => Some(
entries
.first()
.and_then(|p| p.dpid.clone())
.map_or(Value::Null, Value::Str),
),
"path" => Some(
entries
.first()
.and_then(|p| p.path.clone())
.map_or(Value::Null, Value::Str),
),
"instant" => Some(
entries
.iter()
.filter_map(|p| p.instant)
.max_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)))
.map_or(Value::Null, |(secs, nanos, offset_min)| Value::Instant {
secs,
nanos,
offset_min,
}),
),
"provenance" => {
Some(Provenance::canonical_list(&entries, elided).map_or(Value::Null, Value::Str))
}
"%provenance" => {
let newest = entries
.iter()
.filter(|p| p.instant.is_some())
.max_by(|a, b| {
(a.instant.unwrap().0, a.instant.unwrap().1)
.cmp(&(b.instant.unwrap().0, b.instant.unwrap().1))
});
Some(prov_record(&Provenance {
source: entries.first().and_then(|p| p.source.clone()),
path: entries.first().and_then(|p| p.path.clone()),
instant: newest.and_then(|p| p.instant),
instant_from: newest.and_then(|p| p.instant_from),
dpid: entries.first().and_then(|p| p.dpid.clone()),
}))
}
"@provenance" => Some(Value::list(entries.iter().map(prov_record).collect())),
"origin" => Some(
o.known
.first()
.map_or(Value::Null, |x| Value::Int(x.node.0 as i64)),
),
"%origin" => Some(o.known.first().map_or(Value::Null, |x| origin_record(*x))),
"@origin" => Some(Value::list(
o.known.iter().map(|x| origin_record(*x)).collect(),
)),
"elided" => Some(Value::Int(elided as i64)),
_ => None,
}
}
pub(super) fn prov_record(p: &Provenance) -> Value {
Value::Record(vec![
(
"source".to_string(),
p.source.clone().map_or(Value::Null, Value::Str),
),
(
"path".to_string(),
p.path.clone().map_or(Value::Null, Value::Str),
),
(
"instant".to_string(),
p.instant
.map_or(Value::Null, |(secs, nanos, offset_min)| Value::Instant {
secs,
nanos,
offset_min,
}),
),
(
"instant-from".to_string(),
p.instant_from
.map_or(Value::Null, |f| Value::Str(f.name().to_string())),
),
(
"dpid".to_string(),
p.dpid.clone().map_or(Value::Null, Value::Str),
),
])
}
pub(super) fn origin_record(o: Origin) -> Value {
Value::Record(vec![
("id".to_string(), Value::Int(o.node.0 as i64)),
("stage".to_string(), Value::Int(o.stage as i64)),
])
}
pub(super) fn origin_entries(adapter: &impl AstAdapter, o: &Origins) -> (Vec<Provenance>, u32) {
let mut out: Vec<Provenance> = Vec::new();
let mut elided = o.more;
for n in o.nodes() {
let list = resolved_provenance(adapter, n);
elided = elided.saturating_add(list.elided);
for p in list.entries {
let key =
|e: &Provenance| (e.source.clone(), e.dpid.clone().or_else(|| e.path.clone()));
let pk = key(&p);
match out.iter_mut().find(|q| key(q) == pk) {
Some(q) => {
if p.instant.map(|i| (i.0, i.1)) > q.instant.map(|i| (i.0, i.1)) {
q.instant = p.instant;
q.instant_from = p.instant_from;
}
}
None => out.push(p),
}
}
}
(out, elided)
}