| Crates.io | hedl-neo4j |
| lib.rs | hedl-neo4j |
| version | 1.2.0 |
| created_at | 2026-01-09 00:06:54.184844+00 |
| updated_at | 2026-01-21 03:02:29.784832+00 |
| description | HEDL to/from Neo4j graph database conversion |
| homepage | https://dweve.com |
| repository | https://github.com/dweve/hedl |
| max_upload_size | |
| id | 2031323 |
| size | 877,720 |
Bidirectional HEDL ↔ Neo4j integration—export structured data to graph databases and import query results back to HEDL with full type preservation.
Graph databases excel at relationship queries. Neo4j powers knowledge graphs, fraud detection, recommendation engines. But loading structured data from HEDL into Neo4j shouldn't require custom ETL scripts. Querying Neo4j and converting results back to HEDL for further processing shouldn't lose type information or structural semantics.
hedl-neo4j provides bidirectional integration between HEDL and Neo4j. Export HEDL documents as Cypher CREATE/MERGE statements with automatic relationship detection from references. Generate uniqueness constraints for entity IDs. Batch large imports with UNWIND for optimal performance. Import Neo4j query results back to HEDL with full schema preservation. Stream large exports without loading entire documents into memory.
Comprehensive Neo4j integration with security and performance:
@Type:id) become Neo4j relationships[dependencies]
hedl-neo4j = "1.2"
Export HEDL document as Cypher statements:
use hedl_core::parse;
use hedl_neo4j::{to_cypher, ToCypherConfig};
let doc = parse(br#"
%VERSION: 1.0
%STRUCT: User: [id, name, email]
%STRUCT: Post: [id, author, title, content]
%NEST: User: Post
---
users: @User
| alice, Alice Smith, alice@example.com
| bob, Bob Jones, bob@example.com
posts: @Post
| post1, @User:alice, Hello World, My first post
| post2, @User:alice, Second Post, Another post
| post3, @User:bob, Bob's Thoughts, Thinking...
"#)?;
let config = ToCypherConfig::new();
let cypher = to_cypher(&doc, &config)?;
println!("{}", cypher);
Generated Cypher:
// Uniqueness constraints
CREATE CONSTRAINT user_id IF NOT EXISTS FOR (n:User) REQUIRE n.id IS UNIQUE;
CREATE CONSTRAINT post_id IF NOT EXISTS FOR (n:Post) REQUIRE n.id IS UNIQUE;
// Create nodes
CREATE (alice:User {id: "alice", name: "Alice Smith", email: "alice@example.com"});
CREATE (bob:User {id: "bob", name: "Bob Jones", email: "bob@example.com"});
CREATE (post1:Post {id: "post1", title: "Hello World", content: "My first post"});
CREATE (post2:Post {id: "post2", title: "Second Post", content: "Another post"});
CREATE (post3:Post {id: "post3", title: "Bob's Thoughts", content: "Thinking..."});
// Create relationships (from references)
MATCH (post1:Post {id: "post1"}), (alice:User {id: "alice"}) CREATE (post1)-[:AUTHOR]->(alice);
MATCH (post2:Post {id: "post2"}), (alice:User {id: "alice"}) CREATE (post2)-[:AUTHOR]->(alice);
MATCH (post3:Post {id: "post3"}), (bob:User {id: "bob"}) CREATE (post3)-[:AUTHOR]->(bob);
// Create relationships (from NEST pattern)
MATCH (alice:User {id: "alice"}), (post1:Post {id: "post1"}) CREATE (alice)-[:HAS_POST]->(post1);
MATCH (alice:User {id: "alice"}), (post2:Post {id: "post2"}) CREATE (alice)-[:HAS_POST]->(post2);
MATCH (bob:User {id: "bob"}), (post3:Post {id: "post3"}) CREATE (bob)-[:HAS_POST]->(post3);
use hedl_neo4j::{to_cypher, ToCypherConfig};
let config = ToCypherConfig::builder()
.use_merge(true) // Use MERGE instead of CREATE
.batch_size(5000) // 5000 nodes per UNWIND
.create_constraints(true) // Generate uniqueness constraints
.build();
let cypher = to_cypher(&doc, &config)?;
Import Neo4j query results back to HEDL:
use hedl_neo4j::{neo4j_to_hedl, Neo4jRecord, Neo4jNode};
// Build records from Neo4j query results
let node = Neo4jNode::new("User", "alice")
.with_property("name", "Alice Smith");
let record = Neo4jRecord::new(node);
let records = vec![record];
// Convert to HEDL
let doc = neo4j_to_hedl(&records)?;
// Use HEDL's structured API
println!("Imported {} matrix lists", doc.root.len());
Creates or updates existing nodes:
MERGE (alice:User {id: "alice"})
ON CREATE SET alice.name = "Alice", alice.created = timestamp()
ON MATCH SET alice.name = "Alice", alice.updated = timestamp();
MERGE (bob:User {id: "bob"})
ON CREATE SET bob.name = "Bob", bob.created = timestamp()
ON MATCH SET bob.name = "Bob", bob.updated = timestamp();
Use When:
Trade-off: Slower than CREATE (requires existence check)
Creates new nodes unconditionally (use ToCypherConfig::new().with_create()):
CREATE (alice:User {id: "alice", name: "Alice"});
CREATE (bob:User {id: "bob", name: "Bob"});
Use When:
Trade-off: Fails if nodes already exist
Reference values in fields automatically become relationships:
posts: @Post[id, author, title]
| post1, @User:alice, Hello World
Generated:
CREATE (post1:Post {id: "post1", title: "Hello World"});
MATCH (post1:Post {id: "post1"}), (alice:User {id: "alice"})
CREATE (post1)-[:AUTHOR]->(alice);
Relationship Type: Field name uppercased (e.g., author → AUTHOR)
Parent-child nesting becomes HAS_* relationships:
%NEST: User: Post
---
users: @User
| alice, Alice
posts: @Post
| post1, Hello
Generated:
CREATE (alice:User {id: "alice", name: "Alice"});
CREATE (post1:Post {id: "post1", title: "Hello"});
MATCH (alice:User {id: "alice"}), (post1:Post {id: "post1"})
CREATE (alice)-[:HAS_POST]->(post1);
Pattern: HAS_{CHILD_TYPE} (e.g., HAS_POST, HAS_COMMENT, HAS_ITEM)
Automatic uniqueness constraints on entity IDs:
CREATE CONSTRAINT user_id IF NOT EXISTS
FOR (n:User) REQUIRE n.id IS UNIQUE;
CREATE CONSTRAINT post_id IF NOT EXISTS
FOR (n:Post) REQUIRE n.id IS UNIQUE;
Benefits:
Configuration:
.create_constraints(true) // Enable (default: true)
.create_constraints(false) // Disable
UNWIND-based batching for large imports:
// Instead of many CREATE statements...
UNWIND [
{id: "alice", name: "Alice"},
{id: "bob", name: "Bob"},
{id: "carol", name: "Carol"},
// ... 1000 nodes
] AS row
CREATE (n:User)
SET n = row;
Benefits:
Configuration:
.batch_size(1000) // Default: 1000 nodes per UNWIND
.batch_size(5000) // Larger batches for high-throughput
.batch_size(100) // Smaller batches for constrained environments
Recommendation: 1000-5000 for most use cases
Process large documents without full buffering:
use hedl_neo4j::{to_cypher_stream, ToCypherConfig};
use std::fs::File;
let output = File::create("import.cypher")?;
let config = ToCypherConfig::new();
to_cypher_stream(&doc, output, &config)?;
Memory Usage: O(batch_size) regardless of total document size
Use Cases:
HEDL types map to Neo4j property types:
// HEDL Value → Neo4j Property
Value::Int(42) → 42 (Long)
Value::Float(3.14) → 3.14 (Double)
Value::String("alice") → "alice" (String)
Value::Bool(true) → true (Boolean)
Value::Null → null
// References become relationships (not properties)
Value::Reference(...) → (relationship edge)
// Expressions evaluated then converted
Value::Expression("$(1+2)") → 3 (Long)
Special Cases:
null""All strings normalized to NFC form:
// Input: "café" (e + combining accent)
// Output: "café" (single composed character)
Prevents:
Invisible characters removed:
// Input: "alice\u{200B}smith" (contains zero-width space)
// Output: "alicesmith"
Prevents:
Maximum string lengths enforced:
pub const DEFAULT_MAX_STRING_LENGTH: usize = 100 * 1024 * 1024; // 100 MB default
// ToCypherConfig::for_untrusted_input() enforces 1 MB limit (1_000_000 bytes)
Protection Against:
Configuration:
.max_string_length(10 * 1024 * 1024) // 10 MB limit
use hedl_neo4j::{to_cypher, ToCypherConfig};
let config = ToCypherConfig::new();
let cypher = to_cypher(&hedl_doc, &config)?;
// Execute cypher statements in Neo4j
// Query Neo4j
let result = session.run("MATCH (u:User) RETURN u", None).await?;
// Convert to HEDL
let hedl_doc = from_neo4j_records(&result.records)?;
// Now use HEDL APIs
let users = &hedl_doc.entities["User"];
Preserved:
Not Preserved:
use hedl_neo4j::ToCypherConfig;
let config = ToCypherConfig::builder()
.use_merge(true) // CREATE or MERGE (default: true/MERGE)
.batch_size(1000) // Nodes per UNWIND (default: 1000)
.create_constraints(true) // Uniqueness constraints (default: true)
.build();
use hedl_neo4j::FromNeo4jConfig;
let config = FromNeo4jConfig::builder()
.build();
use hedl_neo4j::{to_cypher, ToCypherConfig, Neo4jError};
match to_cypher(&doc, &ToCypherConfig::default()) {
Ok(cypher) => println!("{}", cypher),
Err(Neo4jError::StringLengthExceeded { length, max_length, property }) => {
eprintln!("String too long in property '{}': {} bytes (max: {})",
property, length, max_length);
}
Err(Neo4jError::InvalidReference(msg)) => {
eprintln!("Invalid reference: {}", msg);
}
Err(Neo4jError::UnresolvedReference { type_name, id }) => {
eprintln!("Unresolved reference: @{}:{}",
type_name.as_deref().unwrap_or(""), id);
}
Err(Neo4jError::MissingSchema(type_name)) => {
eprintln!("Missing schema for type: {}", type_name);
}
Err(e) => eprintln!("Error: {}", e),
}
MissingSchema(String) - Missing required schema informationInvalidReference(String) - Malformed reference formatUnresolvedReference { type_name, id } - Reference to non-existent nodeInvalidNodeId(String) - Invalid node ID (must be a string)EmptyMatrixList(String) - Empty matrix list (no rows to convert)InconsistentData(String) - Inconsistent data structureInvalidIdentifier(String) - Invalid Cypher identifierRecordParseError(String) - Failed to parse Neo4j recordMissingProperty { label, property } - Missing required property in Neo4j nodeStringLengthExceeded { length, max_length, property } - String exceeds max lengthNodeCountExceeded { count, max_count } - Node count limit exceededIntegerOverflow { context } - Integer overflow during calculationTypeConversion(String) - Type conversion errorCircularReference(String) - Circular reference detectedRecursionLimitExceeded { depth, max_depth } - NEST hierarchy too deepJsonError(serde_json::Error) - JSON serialization errorHedlError(String) - HEDL core errorFor applications requiring high concurrency or non-blocking I/O, enable the async feature:
[dependencies]
hedl-neo4j = { version = "1.2", features = ["async"] }
use hedl_neo4j::{AsyncNeo4jClient, ToCypherConfig};
use hedl_core::Document;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to Neo4j
let client = AsyncNeo4jClient::connect(
"bolt://localhost:7687",
"neo4j",
"password",
).await?;
// Import HEDL document with concurrent execution
let doc: Document = todo!();
client.import_document(&doc).await?;
Ok(())
}
The async API provides significant benefits for concurrent workloads:
| Operation Type | Sync Time | Async Time | Speedup |
|---|---|---|---|
| Single document (500 nodes) | 750ms | 250ms | 3.0× |
| Single document (5000 nodes) | 7500ms | 1500ms | 5.0× |
| 10 concurrent documents | 7500ms | 1200ms | 6.25× |
Key Benefits:
Use async when:
Use sync when:
// Basic import (concurrent batch execution)
client.import_document(&doc).await?;
// Transactional import (all-or-nothing)
client.import_document_transactional(&doc).await?;
// Manual statement execution
let stmts = to_cypher_statements(&doc, &config)?;
client.execute_statements_concurrent(&stmts).await?;
// Raw query execution
client.execute_query("MATCH (n) RETURN count(n)").await?;
let client = AsyncNeo4jClient::connect(uri, user, password)
.await?
.with_config(ToCypherConfig::new().with_batch_size(500))
.with_max_retries(5)
.with_initial_retry_delay(Duration::from_millis(100));
Retry Logic: Automatically retries transient errors (connection failures, timeouts) with exponential backoff.
Connection Pooling: Uses Neo4j's built-in connection pooling. Optimal pool size depends on workload:
Before (sync, requires feature="async"):
let cypher = hedl_neo4j::to_cypher(&doc, &config)?;
// Manually execute with your preferred driver
After (async with automatic execution, requires feature="async"):
let client = AsyncNeo4jClient::connect(uri, user, password).await?;
client.import_document(&doc).await?;
Both approaches are supported. Choose based on your needs.
Knowledge Graphs: Export HEDL-structured knowledge bases to Neo4j for graph queries, path finding, centrality analysis.
Fraud Detection: Load transaction data from HEDL into Neo4j, run pattern-matching queries to detect fraud rings, export results back to HEDL for reporting.
Recommendation Engines: Import user-product interactions from HEDL, compute collaborative filtering in Neo4j, export recommendations as HEDL for integration.
ETL Pipelines: Read structured data from various sources (JSON/CSV/XML), convert to HEDL, transform with HEDL tools, export to Neo4j for graph analytics.
Data Migration: Migrate from other graph formats to Neo4j via HEDL intermediate representation. Export Neo4j databases to HEDL for backup or transformation.
Graph Visualization: Export Neo4j query results to HEDL, convert to JSON/XML for visualization tools, preserve full type information.
Complex Cypher Queries: Generates CREATE/MERGE statements, not arbitrary Cypher. For custom queries, use Neo4j driver directly and convert results with from_neo4j_records.
Schema Evolution: Doesn't handle schema migrations or versioning. For evolving schemas, manage migrations externally.
Relationship Properties: Relationships map from references (no properties). For rich relationships with properties, use Neo4j driver directly.
Transaction Management: Doesn't manage Neo4j transactions. Wrap generated Cypher in transactions via Neo4j driver.
Multi-Database: Targets single Neo4j database. For multi-database scenarios, run conversions separately per database.
Cypher Generation: O(n) where n = total entities + relationships. Single linear pass.
Batch Processing: Reduces Neo4j import time by 60-80% vs individual CREATE statements.
Streaming: O(batch_size) memory usage regardless of document size.
Unicode Normalization: O(string_length) per string. Adds <2% overhead.
From Neo4j: O(n * m) where n = records, m = average properties per record.
hedl-core - Core HEDL implementationthiserror - Error typesserde, serde_json - Serialization supportunicode-normalization 0.1 - NFC normalizationdashmap - Concurrent hash map for cachingrayon - Parallel processing supportfutures - Async supporttokio (optional, with async feature) - Async runtimeneo4rs (optional, with async feature) - Neo4j driver for async operationsApache-2.0