use crate::graph::GraphStorage;
use crate::*;
struct StepBy<'g, S: GraphStorage + 'g> {
args: Option<By<'g, S>>,
}
impl<'g, S: GraphStorage + 'g> fmt::Display for StepBy<'g, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.args {
Some(by) => write!(f, "by({by})"),
None => f.write_str("by()"),
}
}
}
impl<'g, S: GraphStorage + 'g> Clone for StepBy<'g, S> {
fn clone(&self) -> Self {
Self {
args: self.args.clone(),
}
}
}
impl<'g, S: GraphStorage + 'g> GraphTraversalSource<'g, S> {
pub fn by<T: Into<By<'g, S>>>(self, args: T) -> Self {
self.push_step(GraphStep::Modulator(
"by",
Box::new(StepBy {
args: Some(args.into()),
}),
))
}
}
impl<'g, S: GraphStorage + 'g> AnonymousTraversal<'g, S> {
pub fn by<T: Into<By<'g, S>>>(self, args: T) -> Self {
let step = GraphStep::Modulator(
"by",
Box::new(StepBy {
args: Some(args.into()),
}),
);
self.push_step(step)
}
}
impl<'g, S: GraphStorage + 'g> GraphStepModulator<'g, S> for StepBy<'g, S> {
fn get_by(&mut self) -> Option<By<'g, S>> {
self.args.take()
}
fn clone_box(&self) -> Box<dyn GraphStepModulator<'g, S> + 'g> {
Box::new(self.clone())
}
}
impl<'g, S: GraphStorage + 'g> From<&str> for By<'g, S> {
fn from(value: &str) -> Self {
if value.eq("label") {
By::Label
} else if value.eq("id") {
By::Id
} else {
By::Property(CompactString::new(value))
}
}
}
impl<'g, S: GraphStorage + 'g> From<&String> for By<'g, S> {
fn from(value: &String) -> Self {
if value.eq("label") {
By::Label
} else if value.eq("id") {
By::Id
} else {
By::Property(CompactString::new(value))
}
}
}
impl<'g, S: GraphStorage + 'g> From<String> for By<'g, S> {
fn from(value: String) -> Self {
if value.eq("label") {
By::Label
} else if value.eq("id") {
By::Id
} else {
By::Property(CompactString::new(value))
}
}
}
impl<'g, S: GraphStorage + 'g> From<&CompactString> for By<'g, S> {
fn from(value: &CompactString) -> Self {
if value.eq("label") {
By::Label
} else if value.eq("id") {
By::Id
} else {
By::Property(value.clone())
}
}
}
impl<'g, S: GraphStorage + 'g> From<CompactString> for By<'g, S> {
fn from(value: CompactString) -> Self {
if value.eq("label") {
By::Label
} else if value.eq("id") {
By::Id
} else {
By::Property(value)
}
}
}
impl<'g, S: GraphStorage + 'g> From<crate::value::JsonPath> for By<'g, S> {
fn from(value: crate::value::JsonPath) -> Self {
By::JsonPath(value)
}
}
impl<'g, S: GraphStorage + 'g> From<&crate::value::JsonPath> for By<'g, S> {
fn from(value: &crate::value::JsonPath) -> Self {
By::JsonPath(value.clone())
}
}
impl<'g, S: GraphStorage + 'g> From<crate::value::PropKey> for By<'g, S> {
fn from(value: crate::value::PropKey) -> Self {
match value {
crate::value::PropKey::Name(name) => By::from(name),
crate::value::PropKey::Path(path) => By::JsonPath(path),
}
}
}
impl<'g, S: GraphStorage + 'g> From<AnonymousTraversal<'g, S>> for By<'g, S> {
fn from(value: AnonymousTraversal<'g, S>) -> Self {
By::Traversal(value)
}
}
impl<'g, S: GraphStorage + 'g> From<Order> for By<'g, S> {
fn from(value: Order) -> Self {
match value {
Order::Asc => By::Order(Order::Asc),
Order::Desc => By::Order(Order::Desc),
Order::Shuffle => By::Order(Order::Shuffle),
}
}
}
impl<'g, S: GraphStorage + 'g> From<crate::value::Column> for By<'g, S> {
fn from(value: crate::value::Column) -> Self {
By::Column(value)
}
}
impl<'g, S: GraphStorage + 'g> From<Vec<&str>> for By<'g, S> {
fn from(value: Vec<&str>) -> Self {
By::Labels(value.into_iter().map(CompactString::new).collect())
}
}
impl<'g, S: GraphStorage + 'g> From<&[&str]> for By<'g, S> {
fn from(value: &[&str]) -> Self {
By::Labels(
value
.iter()
.map(|label| CompactString::new(label))
.collect(),
)
}
}
impl<'g, S: GraphStorage + 'g, const N: usize> From<[&str; N]> for By<'g, S> {
fn from(value: [&str; N]) -> Self {
By::Labels(
value
.iter()
.map(|label| CompactString::new(label))
.collect(),
)
}
}
impl<'g, S: GraphStorage + 'g> From<Vec<String>> for By<'g, S> {
fn from(value: Vec<String>) -> Self {
By::Labels(value.into_iter().map(CompactString::from).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::TraversalGraph;
type TestBy = By<'static, TraversalGraph>;
fn labels(by: TestBy) -> Option<Vec<CompactString>> {
match by {
By::Labels(labels) => Some(labels),
By::Property(_)
| By::Traversal(_)
| By::Order(_)
| By::Label
| By::Id
| By::Value
| By::Identity
| By::Key
| By::Count
| By::JsonPath(_)
| By::Column(_) => None,
}
}
#[test]
fn label_lists_convert_to_by_labels() {
let expected = Some(vec![
CompactString::new("directory"),
CompactString::new("file"),
]);
assert_eq!(labels(TestBy::from(vec!["directory", "file"])), expected);
let slice: &[&str] = &["directory", "file"];
assert_eq!(labels(TestBy::from(slice)), expected);
assert_eq!(labels(TestBy::from(["directory", "file"])), expected);
assert_eq!(
labels(TestBy::from(vec![
"directory".to_string(),
"file".to_string()
])),
expected
);
}
#[test]
fn empty_label_list_converts() {
assert_eq!(labels(TestBy::from(Vec::<&str>::new())), Some(Vec::new()));
assert_eq!(labels(TestBy::from([] as [&str; 0])), Some(Vec::new()));
}
#[test]
fn label_named_strings_stay_scalar() {
assert_eq!(labels(TestBy::from("directory")), None);
}
#[test]
fn t_value_reads_property_elements_and_entries() {
test_dual!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g
.v(None)
.properties("age")
.order()
.by(By::Value)
.value()
.to_list(),
rhai: [
r#"g.V().properties("age").order().by(T.value).value()"#,
r#"g.v().properties("age").order().by(T::value).value()"#,
],
assert: |res| assert_eq!(res, &[27.into(), 29.into(), 32.into(), 35.into()]),
);
test_dual!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g
.v(None)
.group_count()
.by(By::Label)
.unfold()
.order()
.by(By::Value)
.select(crate::value::Column::Keys)
.to_list(),
rhai: [
r#"g.V().groupCount().by(T.label).unfold().order().by(T.value).select(Column.keys)"#,
r#"g.v().group_count().by(T::label).unfold().order().by(T::value).select(Column::keys)"#,
],
assert: |res| assert_eq!(res, &["software".into(), "person".into()]),
);
}
#[test]
fn t_value_of_a_plain_value_is_an_error() {
test_dual_err!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).order().by(By::Value).to_list(),
rhai: [
r#"g.V().order().by(T.value)"#,
r#"g.v().order().by(T::value)"#,
],
assert: |err| {
assert!(err.contains("got type 'vertex'"), "{err}");
assert!(err.contains("T.key and T.value read a property element"), "{err}");
},
);
test_dual_err!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).values("age").order().by(By::Value).to_list(),
rhai: [
r#"g.V().values("age").order().by(T.value)"#,
r#"g.v().values("age").order().by(T::value)"#,
],
assert: |err| {
assert!(err.contains("got type 'int64'"), "{err}");
assert!(err.contains("g.v().values(\"age\").order().by()"), "{err}");
assert!(!err.contains("typeOf"), "no type search for a token misuse: {err}");
},
);
test_dual_err!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.inject(vec![1]).project(vec!["x"]).by(By::Value).to_list(),
rhai: [
r#"g.inject(1).project("x").by(T.value)"#,
r#"g.inject(1).project("x").by(T::value)"#,
],
assert: |err| assert!(err.contains("got type 'int64'"), "{err}"),
);
test_dual_err!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).values("name").dedup().by(By::Key).to_list(),
rhai: [
r#"g.V().values("name").dedup().by(T.key)"#,
r#"g.v().values("name").dedup().by(T::key)"#,
],
assert: |err| {
assert!(err.contains("got type 'string'"), "{err}");
assert!(err.contains("T.key and T.value read a property element"), "{err}");
},
);
}
#[test]
fn group_without_by_keys_by_the_value_itself() {
test_dual!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).values("lang").group_count().unfold().to_list(),
rhai: [
r#"g.V().values("lang").groupCount().unfold()"#,
r#"g.v().values("lang").group_count().unfold()"#,
],
assert: |res| assert_eq!(res.len(), 1, "{res:?}"),
);
}
}