#![allow(dead_code)]
use std::collections::BTreeMap;
use lora_database::{Database, ExecuteOptions, InMemoryGraph, LoraValue, ResultFormat};
pub struct BenchDb {
pub service: Database<InMemoryGraph>,
}
impl Default for BenchDb {
fn default() -> Self {
Self::new()
}
}
impl BenchDb {
pub fn new() -> Self {
Self {
service: Database::in_memory(),
}
}
pub fn run(&self, cypher: &str) {
let options = Some(ExecuteOptions {
format: ResultFormat::Rows,
});
self.service
.execute(cypher, options)
.unwrap_or_else(|e| panic!("bench setup failed: {cypher}\nerror: {e}"));
}
pub fn run_with_params(&self, cypher: &str, params: BTreeMap<String, LoraValue>) {
let options = Some(ExecuteOptions {
format: ResultFormat::Rows,
});
self.service
.execute_with_params(cypher, options, params)
.unwrap_or_else(|e| panic!("bench setup failed: {cypher}\nerror: {e}"));
}
}
pub struct Scale;
impl Scale {
pub const TINY: usize = 100;
pub const SMALL: usize = 1_000;
pub const MEDIUM: usize = 10_000;
pub const LARGE: usize = 50_000;
}
const BULK_BATCH: usize = 2_000;
const RICH_BATCH: usize = 500;
pub fn build_node_graph(n: usize) -> BenchDb {
let db = BenchDb::new();
let batch = BULK_BATCH;
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i CREATE (:Node {{id: i, name: 'node_' + toString(i), value: i % 100}})",
end - 1
));
i = end;
}
db
}
pub fn build_chain(len: usize) -> BenchDb {
let db = BenchDb::new();
let batch = BULK_BATCH;
let mut i = 0usize;
while i < len {
let end = (i + batch).min(len);
db.run(&format!(
"UNWIND range({i}, {}) AS i CREATE (:Chain {{idx: i}})",
end - 1
));
i = end;
}
if len > 1 {
let mut i = 0usize;
while i < len - 1 {
let end = (i + batch).min(len - 1);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Chain {{idx: i}}), (b:Chain {{idx: i + 1}}) \
CREATE (a)-[:NEXT]->(b)",
end - 1
));
i = end;
}
}
db
}
pub fn build_cycle(len: usize) -> BenchDb {
let db = BenchDb::new();
let batch = BULK_BATCH;
let mut i = 0usize;
while i < len {
let end = (i + batch).min(len);
db.run(&format!(
"UNWIND range({i}, {}) AS i CREATE (:Ring {{idx: i}})",
end - 1
));
i = end;
}
if len > 1 {
let mut i = 0usize;
while i < len - 1 {
let end = (i + batch).min(len - 1);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Ring {{idx: i}}), (b:Ring {{idx: i + 1}}) \
CREATE (a)-[:LOOP]->(b)",
end - 1
));
i = end;
}
}
if len > 1 {
db.run(&format!(
"MATCH (a:Ring {{idx: {}}}), (b:Ring {{idx: 0}}) CREATE (a)-[:LOOP]->(b)",
len - 1
));
}
db
}
pub fn build_star(spokes: usize) -> BenchDb {
let db = BenchDb::new();
db.run("CREATE (:Hub {name: 'center'})");
let batch = BULK_BATCH;
let mut i = 0usize;
while i < spokes {
let end = (i + batch).min(spokes);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (h:Hub) CREATE (h)-[:ARM]->(:Leaf {{id: i}})",
end - 1
));
i = end;
}
db
}
pub fn build_social_graph(n: usize, avg_friends: usize) -> BenchDb {
let db = BenchDb::new();
let cities = ["London", "Berlin", "Paris", "Tokyo", "Amsterdam"];
let batch = BULK_BATCH;
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:Person {{id: i, name: 'person_' + toString(i), age: 20 + (i % 41), city: CASE i % 5 \
WHEN 0 THEN '{c0}' WHEN 1 THEN '{c1}' WHEN 2 THEN '{c2}' \
WHEN 3 THEN '{c3}' ELSE '{c4}' END}})",
end - 1,
c0 = cities[0],
c1 = cities[1],
c2 = cities[2],
c3 = cities[3],
c4 = cities[4],
));
i = end;
}
if n > 1 {
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
for j in 1..=avg_friends.min(n - 1) {
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Person {{id: i}}), (b:Person {{id: (i + {j}) % {n}}}) \
CREATE (a)-[:KNOWS {{strength: (i + {j}) % 10}}]->(b)",
end - 1,
));
}
i = end;
}
}
db
}
pub fn build_tree(depth: usize, branch: usize) -> BenchDb {
let db = BenchDb::new();
db.run("CREATE (:Tree {id: 0, depth: 0})");
let mut next_id = 1u64;
let mut current_ids: Vec<u64> = vec![0];
for d in 0..depth {
let mut new_ids = Vec::new();
for &parent_id in ¤t_ids {
for _ in 0..branch {
let child_id = next_id;
next_id += 1;
db.run(&format!(
"MATCH (p:Tree {{id: {parent_id}}}) \
CREATE (p)-[:CHILD]->(:Tree {{id: {child_id}, depth: {}}})",
d + 1
));
new_ids.push(child_id);
}
}
current_ids = new_ids;
}
db
}
pub fn build_dependency_graph(n: usize) -> BenchDb {
let db = BenchDb::new();
let batch = BULK_BATCH;
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:Package {{id: i, name: 'pkg_' + toString(i), version: '1.' + toString(i % 10)}})",
end - 1
));
i = end;
}
if n > 1 {
for i in 1..n {
let deps = (i % 3) + 1;
for d in 1..=deps {
let target = i.saturating_sub(d);
if target < i {
db.run(&format!(
"MATCH (a:Package {{id: {i}}}), (b:Package {{id: {target}}}) \
CREATE (a)-[:DEPENDS_ON]->(b)"
));
}
}
}
}
db
}
pub fn build_org_graph() -> BenchDb {
let db = BenchDb::new();
db.run("CREATE (:Person {name:'Alice', age:35, dept:'Engineering'})");
db.run("CREATE (:Person {name:'Bob', age:28, dept:'Engineering'})");
db.run("CREATE (:Person {name:'Carol', age:42, dept:'Marketing'})");
db.run("CREATE (:Person {name:'Dave', age:31, dept:'Marketing'})");
db.run("CREATE (:Person {name:'Eve', age:26, dept:'Engineering'})");
db.run("CREATE (:Person:Manager {name:'Frank', age:50, dept:'Engineering'})");
db.run("CREATE (:Company {name:'Acme', founded: 2010})");
db.run("CREATE (:Project {name:'Alpha', budget: 100000})");
db.run("CREATE (:Project {name:'Beta', budget: 50000})");
db.run("CREATE (:City {name:'London'})");
db.run("CREATE (:City {name:'Berlin'})");
db.run("CREATE (:City {name:'Tokyo'})");
for (person, since) in [
("Alice", 2018),
("Bob", 2020),
("Carol", 2015),
("Dave", 2021),
("Eve", 2022),
("Frank", 2012),
] {
db.run(&format!(
"MATCH (p:Person {{name:'{person}'}}), (c:Company {{name:'Acme'}}) \
CREATE (p)-[:WORKS_AT {{since:{since}}}]->(c)"
));
}
for (mgr, sub) in [
("Frank", "Alice"),
("Frank", "Bob"),
("Frank", "Eve"),
("Carol", "Dave"),
] {
db.run(&format!(
"MATCH (m:Person {{name:'{mgr}'}}), (s:Person {{name:'{sub}'}}) \
CREATE (m)-[:MANAGES]->(s)"
));
}
for (person, project, role) in [
("Alice", "Alpha", "lead"),
("Bob", "Alpha", "dev"),
("Carol", "Beta", "lead"),
("Eve", "Beta", "dev"),
] {
db.run(&format!(
"MATCH (p:Person {{name:'{person}'}}), (pr:Project {{name:'{project}'}}) \
CREATE (p)-[:ASSIGNED_TO {{role:'{role}'}}]->(pr)"
));
}
for (person, city) in [
("Alice", "London"),
("Bob", "Berlin"),
("Carol", "London"),
("Dave", "Tokyo"),
("Eve", "Berlin"),
("Frank", "London"),
] {
db.run(&format!(
"MATCH (p:Person {{name:'{person}'}}), (c:City {{name:'{city}'}}) \
CREATE (p)-[:LIVES_IN]->(c)"
));
}
db
}
pub fn build_temporal_graph(n: usize) -> BenchDb {
let db = BenchDb::new();
let batch = RICH_BATCH;
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:Event {{id: i, \
name: 'event_' + toString(i), \
event_date: date('2024-01-' + CASE WHEN (i % 28) + 1 < 10 THEN '0' + toString((i % 28) + 1) ELSE toString((i % 28) + 1) END), \
start_time: time(CASE WHEN i % 24 < 10 THEN '0' + toString(i % 24) ELSE toString(i % 24) END + ':00:00'), \
created_at: datetime('2024-' + CASE WHEN (i % 12) + 1 < 10 THEN '0' + toString((i % 12) + 1) ELSE toString((i % 12) + 1) END + '-15T10:00:00Z'), \
priority: i % 5}})",
end - 1
));
i = end;
}
if n > 1 {
let mut i = 0usize;
while i < n - 1 {
let end = (i + batch).min(n - 1);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Event {{id: i}}), (b:Event {{id: i + 1}}) \
CREATE (a)-[:FOLLOWS {{gap_days: (i % 7) + 1}}]->(b)",
end - 1
));
i = end;
}
}
db
}
pub fn build_spatial_graph(n: usize) -> BenchDb {
let db = BenchDb::new();
let batch = RICH_BATCH;
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:Location {{id: i, \
name: 'loc_' + toString(i), \
pos: point({{x: toFloat(i % 100), y: toFloat(i / 100)}}), \
geo: point({{latitude: 48.0 + toFloat(i % 50) / 10.0, longitude: 2.0 + toFloat(i / 50) / 10.0}}), \
category: CASE i % 4 WHEN 0 THEN 'restaurant' WHEN 1 THEN 'hotel' WHEN 2 THEN 'museum' ELSE 'park' END \
}})",
end - 1
));
i = end;
}
if n > 1 {
let mut i = 0usize;
while i < n {
let end = (i + batch).min(n);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Location {{id: i}}), (b:Location {{id: (i + 1) % {n}}}) \
CREATE (a)-[:CONNECTS_TO {{weight: (i % 10) + 1}}]->(b)",
end - 1
));
i = end;
}
}
db
}
pub fn build_recommendation_graph(n_users: usize, n_products: usize) -> BenchDb {
let db = BenchDb::new();
let batch = BULK_BATCH;
let categories = ["Electronics", "Books", "Clothing", "Food", "Sports"];
let mut i = 0usize;
while i < n_users {
let end = (i + batch).min(n_users);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:User {{id: i, \
name: 'user_' + toString(i), \
age: 18 + (i % 50), \
tier: CASE i % 3 WHEN 0 THEN 'gold' WHEN 1 THEN 'silver' ELSE 'bronze' END \
}})",
end - 1
));
i = end;
}
let mut i = 0usize;
while i < n_products {
let end = (i + batch).min(n_products);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
CREATE (:Product {{id: i, \
name: 'product_' + toString(i), \
price: 10 + (i % 200), \
category: CASE i % 5 \
WHEN 0 THEN '{c0}' WHEN 1 THEN '{c1}' WHEN 2 THEN '{c2}' \
WHEN 3 THEN '{c3}' ELSE '{c4}' END \
}})",
end - 1,
c0 = categories[0],
c1 = categories[1],
c2 = categories[2],
c3 = categories[3],
c4 = categories[4],
));
i = end;
}
if n_products > 0 {
let mut i = 0usize;
while i < n_users {
let end = (i + batch).min(n_users);
let n_buys = 5; for b in 0..n_buys {
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (u:User {{id: i}}), (p:Product {{id: (i * {n_buys} + {b}) % {n_products}}}) \
CREATE (u)-[:BOUGHT {{quantity: (i + {b}) % 5 + 1}}]->(p)",
end - 1,
));
}
for b in 0..2usize {
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (u:User {{id: i}}), (p:Product {{id: (i * {n_buys} + {b}) % {n_products}}}) \
CREATE (u)-[:REVIEWED {{rating: (i + {b}) % 5 + 1}}]->(p)",
end - 1,
));
}
i = end;
}
}
if n_products > 5 {
let mut i = 0usize;
while i < n_products {
let end = (i + batch).min(n_products);
db.run(&format!(
"UNWIND range({i}, {}) AS i \
MATCH (a:Product {{id: i}}), (b:Product {{id: (i + 5) % {n_products}}}) \
WHERE a.category = b.category \
CREATE (a)-[:SIMILAR_TO {{score: toFloat((i % 10) + 1) / 10.0}}]->(b)",
end.min(n_products) - 1,
));
i = end;
}
}
db
}