use crate::model::{BindingsDef, ParsedOp, ScenarioNode, Workload, WorkloadPhase};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PolydatMatter {
None,
Readonly,
Definitions,
}
pub trait HasPolydatMatter {
fn polydat_matter(&self) -> PolydatMatter;
}
fn bindings_def_matter(b: &BindingsDef) -> PolydatMatter {
if b.is_empty() {
PolydatMatter::None
} else {
PolydatMatter::Definitions
}
}
fn has_inline_expr(op: &ParsedOp) -> bool {
fn scan(v: &serde_json::Value) -> bool {
match v {
serde_json::Value::String(s) => s.contains("{{") && s.contains("}}"),
serde_json::Value::Array(arr) => arr.iter().any(scan),
serde_json::Value::Object(map) => map.values().any(scan),
_ => false,
}
}
op.op.values().any(scan) || op.params.values().any(scan)
}
impl HasPolydatMatter for ParsedOp {
fn polydat_matter(&self) -> PolydatMatter {
let by_bindings = bindings_def_matter(&self.bindings);
if by_bindings == PolydatMatter::Definitions {
return PolydatMatter::Definitions;
}
if has_inline_expr(self) {
return PolydatMatter::Definitions;
}
if !self.metrics.is_empty() {
return PolydatMatter::Definitions;
}
if self.result.as_ref().is_some_and(|r| !r.is_empty()) {
return PolydatMatter::Definitions;
}
if !self.captures.is_empty() {
return PolydatMatter::Definitions;
}
if self.condition.is_some() || self.delay.is_some() {
PolydatMatter::Readonly
} else {
PolydatMatter::None
}
}
}
impl HasPolydatMatter for WorkloadPhase {
fn polydat_matter(&self) -> PolydatMatter {
let by_bindings = bindings_def_matter(&self.bindings);
if by_bindings == PolydatMatter::Definitions {
return PolydatMatter::Definitions;
}
if !self.metrics.is_empty() {
return PolydatMatter::Definitions;
}
if self.for_each.is_some() {
return PolydatMatter::Definitions;
}
let refs_parent = [&self.cycles, &self.concurrency].iter().any(|opt| {
opt.as_ref()
.is_some_and(|s| s.contains('{') && s.contains('}'))
});
if refs_parent || self.rate.is_some() {
return PolydatMatter::Readonly;
}
PolydatMatter::None
}
}
impl HasPolydatMatter for ScenarioNode {
fn polydat_matter(&self) -> PolydatMatter {
match self {
ScenarioNode::Comprehension { .. }
| ScenarioNode::DoWhile { .. }
| ScenarioNode::DoUntil { .. } => PolydatMatter::Definitions,
ScenarioNode::Phase(_) | ScenarioNode::IncludedScenario { .. } => PolydatMatter::None,
ScenarioNode::Bindings { .. } => PolydatMatter::Definitions,
}
}
}
impl HasPolydatMatter for Workload {
fn polydat_matter(&self) -> PolydatMatter {
let by_bindings = bindings_def_matter(&self.bindings);
if by_bindings == PolydatMatter::Definitions {
return PolydatMatter::Definitions;
}
if !self.params.is_empty() {
return PolydatMatter::Definitions;
}
PolydatMatter::None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{MetricSpec, ResultSpec};
fn empty_op(name: &str) -> ParsedOp {
ParsedOp::simple(name, "noop")
}
#[test]
fn parsed_op_with_no_polydat_content_is_none() {
let op = empty_op("x");
assert_eq!(op.polydat_matter(), PolydatMatter::None);
}
#[test]
fn parsed_op_with_bindings_is_definitions() {
let mut op = empty_op("x");
op.bindings = BindingsDef::PolydatSource("k := 5".into());
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_empty_bindings_string_is_none() {
let mut op = empty_op("x");
op.bindings = BindingsDef::PolydatSource(" \n ".into());
assert_eq!(op.polydat_matter(), PolydatMatter::None);
}
#[test]
fn parsed_op_with_inline_expr_is_definitions() {
let mut op = empty_op("x");
op.op.insert(
"stmt".into(),
serde_json::Value::String("SELECT {{cycle}}".into()),
);
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_metrics_bare_value_is_definitions() {
let mut op = empty_op("x");
op.metrics.insert(
"foo".into(),
MetricSpec {
value: "existing_wire".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_metrics_dotted_name_is_definitions() {
let mut op = empty_op("x");
op.metrics.insert(
"foo".into(),
MetricSpec {
value: "phase.recall_at_10".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_metrics_expression_is_definitions() {
let mut op = empty_op("x");
op.metrics.insert(
"foo".into(),
MetricSpec {
value: "factor * 2.0".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_with_result_is_definitions() {
let mut op = empty_op("x");
let mut entries = std::collections::BTreeMap::new();
entries.insert("rows_returned".into(), "count".into());
op.result = Some(ResultSpec::Map(entries));
assert_eq!(op.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn parsed_op_with_only_condition_is_readonly() {
let mut op = empty_op("x");
op.condition = Some("ok".into());
assert_eq!(op.polydat_matter(), PolydatMatter::Readonly);
}
#[test]
fn workload_phase_with_phase_bindings_is_definitions() {
let phase = WorkloadPhase {
cycles: None,
concurrency: None,
rate: None,
adapter: None,
errors: None,
tags: None,
ops: vec![],
for_each: None,
loop_scope: None,
iter_scope: None,
checkpoint: None,
status_metrics: vec![],
metrics: Default::default(),
bindings: BindingsDef::PolydatSource("k := 5".into()),
poll: None,
..Default::default()
};
assert_eq!(phase.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn workload_phase_with_for_each_is_definitions() {
let phase = WorkloadPhase {
cycles: None,
concurrency: None,
rate: None,
adapter: None,
errors: None,
tags: None,
ops: vec![],
for_each: Some("k in 1,2,3".into()),
loop_scope: None,
iter_scope: None,
checkpoint: None,
status_metrics: vec![],
metrics: Default::default(),
bindings: BindingsDef::default(),
poll: None,
..Default::default()
};
assert_eq!(phase.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn workload_phase_with_metrics_is_definitions() {
let mut metrics = std::collections::HashMap::new();
metrics.insert(
"time_to_index".to_string(),
crate::model::MetricSpec {
value: "current_epoch_millis() - phase_start".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
let phase = WorkloadPhase {
cycles: None,
concurrency: None,
rate: None,
adapter: None,
errors: None,
tags: None,
ops: vec![],
for_each: None,
loop_scope: None,
iter_scope: None,
checkpoint: None,
status_metrics: vec![],
metrics,
bindings: BindingsDef::default(),
poll: None,
..Default::default()
};
assert_eq!(phase.polydat_matter(), PolydatMatter::Definitions);
}
#[test]
fn workload_phase_bare_is_none() {
let phase = WorkloadPhase {
cycles: None,
concurrency: None,
rate: None,
adapter: None,
errors: None,
tags: None,
ops: vec![],
for_each: None,
loop_scope: None,
iter_scope: None,
checkpoint: None,
status_metrics: vec![],
metrics: Default::default(),
bindings: BindingsDef::default(),
poll: None,
..Default::default()
};
assert_eq!(phase.polydat_matter(), PolydatMatter::None);
}
#[test]
fn workload_phase_cycles_param_ref_is_readonly() {
let phase = WorkloadPhase {
cycles: Some("{train_count}".into()),
concurrency: None,
rate: None,
adapter: None,
errors: None,
tags: None,
ops: vec![],
for_each: None,
loop_scope: None,
iter_scope: None,
checkpoint: None,
status_metrics: vec![],
metrics: Default::default(),
bindings: BindingsDef::default(),
poll: None,
..Default::default()
};
assert_eq!(phase.polydat_matter(), PolydatMatter::Readonly);
}
#[test]
fn scenario_node_phase_is_none() {
let node = ScenarioNode::Phase("p".into());
assert_eq!(node.polydat_matter(), PolydatMatter::None);
}
#[test]
fn scenario_node_do_while_is_definitions() {
let node = ScenarioNode::DoWhile {
condition: "ok".into(),
counter: None,
children: vec![],
};
assert_eq!(node.polydat_matter(), PolydatMatter::Definitions);
}
}