use super::saved_query::{fail, graph_fail, optional_text};
use super::*;
use crate::error::ScriptError;
use crate::graph::definition::{Definition, DefinitionKind};
use crate::graph::{
AccessRequest, Action, ElementKind, PropertyIndexDefinition, PropertyIndexTransform,
PropertyIndexType, Resource,
};
const KEYS: [&str; 6] = ["name", "element", "label", "key", "type", "transform"];
const STORAGE_SERVES_NONE: &str = "this storage keeps the definition but serves no property \
indexes; queries scan";
fn index_entry(
definition: &Definition,
stats: Option<crate::graph::PropertyIndexStats>,
) -> Option<rhai::Map> {
let index = definition.as_property_index()?;
let mut map = rhai::Map::new();
map.insert("name".into(), definition.name().to_string().into());
map.insert("element".into(), index.element_name().to_string().into());
map.insert("label".into(), index.label().to_string().into());
map.insert("key".into(), index.key().to_string().into());
map.insert(
"type".into(),
index.index_type().as_str().to_string().into(),
);
map.insert(
"transform".into(),
index
.transform()
.map(|transform| Dynamic::from(transform.as_str().to_string()))
.unwrap_or(Dynamic::UNIT),
);
map.insert("served".into(), Dynamic::from_bool(stats.is_some()));
if stats.is_none() {
let reason = index
.unserved_reason()
.unwrap_or_else(|| STORAGE_SERVES_NONE.to_string());
map.insert("reason".into(), reason.into());
}
let stats = stats.unwrap_or_default();
let int = |n: u64| Dynamic::from_int(i64::try_from(n).unwrap_or(i64::MAX));
map.insert("entries".into(), int(stats.entries));
map.insert("distinct_values".into(), int(stats.distinct_values));
map.insert("skipped".into(), int(stats.skipped));
map.insert("bytes".into(), int(stats.bytes));
Some(map)
}
impl ScriptGraphTraversalSource {
fn require_index(
&self,
operation: &str,
action: Action<'_>,
name: Option<&str>,
) -> Result<(), Box<EvalAltResult>> {
self.require(
operation,
AccessRequest::new(
action,
Resource::definition(Some(DefinitionKind::PROPERTY_INDEX), name),
),
)
}
fn index_graph(&self, operation: &str) -> Result<&Arc<dyn AnyGraph>, Box<EvalAltResult>> {
self.g.as_ref().ok_or_else(|| {
Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(format!("{operation}: no graph associated with this source")),
rhai::Position::NONE,
))
})
}
pub fn create_index(&mut self, spec: Dynamic) -> Result<(), Box<EvalAltResult>> {
let operation = "create_index";
let actual = type_name_of(&spec);
let mismatch = |expected: String, actual: String| {
fail(ScriptError::ArgumentMismatch {
step_name: operation.to_string(),
expected,
actual,
})
};
let Some(mut spec) = spec.try_cast::<rhai::Map>() else {
return Err(mismatch(
"a map #{label, key, element?, name?, type?, transform?}".into(),
actual,
));
};
if let Some(unknown) = spec.keys().find(|key| !KEYS.contains(&key.as_str())) {
return Err(mismatch(
format!("only the keys {}", KEYS.join(", ")),
format!("the key '{unknown}'"),
));
}
let mut take = |key: &str| spec.remove(key).unwrap_or(Dynamic::UNIT);
let label = optional_text(operation, "label", take("label"))?.ok_or_else(|| {
mismatch(
"the key label (the vertex or edge label)".into(),
"no label".into(),
)
})?;
let key = optional_text(operation, "key", take("key"))?.ok_or_else(|| {
mismatch(
"the key key (the top-level property name)".into(),
"no key".into(),
)
})?;
let element = match optional_text(operation, "element", take("element"))?.as_deref() {
None | Some("vertex") => ElementKind::Vertex,
Some("edge") => ElementKind::Edge,
Some(other) => {
return Err(mismatch(
"element \"vertex\" or \"edge\"".into(),
format!("\"{other}\""),
));
}
};
let index_type = match optional_text(operation, "type", take("type"))? {
None => PropertyIndexType::MemoryOrdered,
Some(name) => PropertyIndexType::from_name(&name),
};
let transform = optional_text(operation, "transform", take("transform"))?
.map(|name| PropertyIndexTransform::from_name(&name));
let index = PropertyIndexDefinition::new(element, label, key)
.with_type(index_type)
.with_transform(transform);
let name = match optional_text(operation, "name", take("name"))? {
Some(name) => name,
None => index.default_name(),
};
let definition = Definition::property_index(name.as_str(), index.clone());
definition
.validate()
.map_err(|err| graph_fail(crate::error::GraphError::from(err)))?;
let graph = self.index_graph(operation)?;
let exists = graph
.definition(DefinitionKind::PROPERTY_INDEX, &name)
.is_some();
let action = if exists {
Action::Update
} else {
Action::Create
};
self.require_index(operation, action, Some(&name))?;
if !graph.capabilities().property_indexes {
return Err(graph_fail(crate::error::GraphError::unsupported(
"property indexes",
)));
}
index
.check_create(&name, graph.get_schema().as_ref())
.map_err(|err| graph_fail(crate::error::GraphError::from(err)))?;
graph
.set_definitions(vec![definition])
.map_err(script_error)
}
pub fn indexes(&mut self) -> Result<rhai::Array, Box<EvalAltResult>> {
self.require_index("indexes", Action::Read, None)?;
let graph = self.index_graph("indexes")?;
let served = graph.property_indexes();
Ok(graph
.definitions(Some(DefinitionKind::PROPERTY_INDEX))
.iter()
.filter_map(|definition| {
let stats = served
.iter()
.find(|info| info.name == definition.name())
.map(|info| info.stats);
index_entry(definition, stats)
})
.map(Dynamic::from_map)
.collect())
}
pub fn drop_index(&mut self, name: &str) -> Result<bool, Box<EvalAltResult>> {
self.require_index("drop_index", Action::Delete, Some(name))?;
self.index_graph("drop_index")?
.remove_definition(DefinitionKind::PROPERTY_INDEX, name)
.map_err(script_error)
}
}