Expand description
§helix-enterprise-ql Query Guide
helix-enterprise-ql (crate name: helix_dsl) is a query builder centered on two entry points:
read_batch()for read-only transactionswrite_batch()for write-capable transactions
Everything in this crate is designed to be composed inside those batch chains.
You write one or more named traversals with .var_as(...) / .var_as_if(...), then
choose the final payload with .returning(...).
For shorter query code, import the curated builder API:
use helix_dsl::prelude::*;§Core Shape
Read chain:
read_batch() -> var_as / var_as_if -> returning
Write chain:
write_batch() -> var_as / var_as_if -> returning
Each var_as call accepts a traversal expression, usually starting with g().
Traversals can read, traverse, filter, aggregate, or mutate depending on whether
they are used in a read or write batch.
§Read Batches
read_batch()
.var_as(
"user",
g().n_where(SourcePredicate::eq("username", "alice")),
)
.var_as(
"friends",
g()
.n(NodeRef::var("user"))
.out(Some("FOLLOWS"))
.dedup()
.limit(100),
)
.returning(["user", "friends"]);read_batch()
.var_as(
"active_users",
g()
.n_with_label_where("User", SourcePredicate::eq("status", "active"))
.where_(Predicate::gt("score", 100i64))
.order_by("score", Order::Desc)
.limit(25)
.value_map(Some(vec!["$id", "name", "score"])),
)
.returning(["active_users"]);§Conditional Queries
Use BatchCondition with var_as_if to run later queries only when earlier
variables satisfy runtime conditions.
read_batch()
.var_as(
"user",
g().n_where(SourcePredicate::eq("username", "alice")),
)
.var_as_if(
"posts",
BatchCondition::VarNotEmpty("user".to_string()),
g().n(NodeRef::var("user")).out(Some("POSTED")),
)
.returning(["user", "posts"]);§Write Batches
write_batch()
.var_as(
"alice",
g().add_n("User", vec![("name", "Alice"), ("tier", "pro")]),
)
.var_as("bob", g().add_n("User", vec![("name", "Bob")]))
.var_as(
"linked",
g()
.n(NodeRef::var("alice"))
.add_e(
"FOLLOWS",
NodeRef::var("bob"),
vec![("since", "2026-01-01")],
)
.count(),
)
.returning(["alice", "bob", "linked"]);write_batch()
.var_as(
"inactive_users",
g().n_with_label_where(
"User",
SourcePredicate::eq("status", "inactive"),
),
)
.var_as_if(
"deactivated_count",
BatchCondition::VarNotEmpty("inactive_users".to_string()),
g()
.n(NodeRef::var("inactive_users"))
.set_property("deactivated", true)
.count(),
)
.returning(["deactivated_count"]);§Vector Search Operations (End-to-End)
The current Helix interpreter executes vector search as top-k nearest-neighbor lookup with these runtime semantics:
- returns up to
khits (top-k behavior) - hit order is ascending by
$distance(smaller is closer) - hit metadata can be read through virtual fields in projections:
- node hits:
$id,$distance - edge hits:
$id,$from,$to,$distance
- node hits:
§Result field contract
| Field | Type | Node hits | Edge hits | Meaning |
|---|---|---|---|---|
$id | integer | yes | yes* | Node ID (for node hits) or edge ID (for edge hits) |
$distance | floating-point | yes | yes | Vector distance from query (lower = closer) |
$from | integer | no | yes | Edge source node ID |
$to | integer | no | yes | Edge target node ID |
* For edge hits, $id is present when an edge ID is available in storage.
Contract scope in the current Helix interpreter:
- available on direct vector-hit streams and projection terminals
- available in
value_map,values,project, and (for edges)edge_properties - once a traversal step leaves the hit stream (
out,in_,both, etc.), downstream traversers no longer carry distance metadata
§1) Create indexes and insert vectors
write_batch()
.var_as(
"create_doc_index",
g().create_vector_index_nodes(
"Doc",
"embedding",
None::<&str>,
),
)
.var_as(
"create_similar_index",
g().create_vector_index_edges(
"SIMILAR",
"embedding",
None::<&str>,
),
)
.var_as(
"doc_a",
g().add_n(
"Doc",
vec![
("title", PropertyValue::from("A")),
("embedding", PropertyValue::from(vec![1.0f32, 0.0, 0.0])),
],
),
)
.var_as(
"doc_b",
g().add_n(
"Doc",
vec![
("title", PropertyValue::from("B")),
("embedding", PropertyValue::from(vec![0.9f32, 0.1, 0.0])),
],
),
)
.returning(["create_doc_index", "doc_a", "doc_b"]);§2) Node vector search: get ranked hits and fetch node properties
read_batch()
.var_as(
"doc_hits",
g().vector_search_nodes("Doc", "embedding", vec![1.0f32, 0.0, 0.0], 5, None)
.value_map(Some(vec!["$id", "$distance", "title"])),
)
.returning(["doc_hits"]);doc_hits rows (example shape):
[
{ "$id": 42, "$distance": 0.0031, "title": "A" },
{ "$id": 77, "$distance": 0.0198, "title": "B" }
]§3) Use project(...) on vector hits (including distance)
read_batch()
.var_as(
"ranked_docs",
g().vector_search_nodes("Doc", "embedding", vec![1.0f32, 0.0, 0.0], 10, None)
.project(vec![
PropertyProjection::renamed("$id", "doc_id"),
PropertyProjection::renamed("$distance", "score"),
PropertyProjection::new("title"),
]),
)
.returning(["ranked_docs"]);§4) Traverse from hit IDs to related entities
Store hit rows (with $id + $distance) and then use NodeRef::var(...) to
continue graph traversal from those hit IDs.
read_batch()
.var_as(
"doc_hit_rows",
g().vector_search_nodes("Doc", "embedding", vec![1.0f32, 0.0, 0.0], 5, None)
.value_map(Some(vec!["$id", "$distance", "title"])),
)
.var_as(
"authors",
g().n(NodeRef::var("doc_hit_rows"))
.out(Some("AUTHORED_BY"))
.value_map(Some(vec!["$id", "name"])),
)
.returning(["doc_hit_rows", "authors"]);§5) Edge vector search and endpoint/property extraction
read_batch()
.var_as(
"edge_hits",
g().vector_search_edges("SIMILAR", "embedding", vec![1.0f32, 0.0, 0.0], 10, None)
.edge_properties(),
)
.var_as(
"targets",
g().e(EdgeRef::var("edge_hits"))
.out_n()
.value_map(Some(vec!["$id", "title"])),
)
.returning(["edge_hits", "targets"]);edge_hits rows include $from, $to, and $distance (and $id when available),
so you can inspect ranking metadata and still traverse from those edges.
§6) Optional multitenancy
write_batch()
.var_as(
"create_mt_index",
g().create_vector_index_nodes(
"Doc",
"embedding",
Some("tenant_id"),
),
)
.var_as(
"insert_acme",
g().add_n(
"Doc",
vec![
("tenant_id", PropertyValue::from("acme")),
("title", PropertyValue::from("Acme doc")),
("embedding", PropertyValue::from(vec![1.0f32, 0.0, 0.0])),
],
),
)
.returning(["create_mt_index", "insert_acme"]);read_batch()
.var_as(
"acme_hits",
g().vector_search_nodes(
"Doc",
"embedding",
vec![1.0f32, 0.0, 0.0],
5,
Some(PropertyValue::from("acme")),
)
.value_map(Some(vec!["$id", "$distance", "title"])),
)
.returning(["acme_hits"]);Multitenant behavior in the current Helix interpreter:
- multitenant index + missing
tenant_valueon search => query error - multitenant index + unknown tenant => empty result set
- write with vector present but missing tenant property => write error
§Edge-First Reads
read_batch()
.var_as(
"heavy_edges",
g()
.e_where(SourcePredicate::gt("weight", 0.8f64))
.edge_has_label("FOLLOWS")
.order_by("weight", Order::Desc)
.limit(50),
)
.var_as(
"targets",
g()
.e(EdgeRef::var("heavy_edges"))
.out_n()
.dedup(),
)
.returning(["heavy_edges", "targets"]);§Branching and Repetition
read_batch()
.var_as(
"recommendations",
g()
.n(1u64)
.store("seed")
.repeat(RepeatConfig::new(sub().out(Some("FOLLOWS"))).times(2))
.without("seed")
.union(vec![sub().out(Some("LIKES"))])
.dedup()
.limit(30),
)
.returning(["recommendations"]);§Complete Function Coverage
The examples below are a catalog-style reference showing every public query-builder
function in a read_batch() / write_batch() flow.
§Sources, NodeRef, EdgeRef, and Vector Search
read_batch()
.var_as("n_id", g().n(NodeRef::id(1)))
.var_as("n_ids", g().n(NodeRef::ids([1u64, 2, 3])))
.var_as("n_var", g().n(NodeRef::var("n_ids")))
.var_as(
"n_where_all",
g().n_where(SourcePredicate::and(vec![
SourcePredicate::eq("kind", "user"),
SourcePredicate::neq("status", "deleted"),
SourcePredicate::gt("score", 10i64),
SourcePredicate::gte("score", 10i64),
SourcePredicate::lt("score", 100i64),
SourcePredicate::lte("score", 100i64),
SourcePredicate::between("age", 18i64, 65i64),
SourcePredicate::has_key("email"),
SourcePredicate::starts_with("name", "a"),
SourcePredicate::or(vec![
SourcePredicate::eq("tier", "pro"),
SourcePredicate::eq("tier", "team"),
]),
])),
)
.var_as("n_label", g().n_with_label("User"))
.var_as(
"n_label_where",
g().n_with_label_where("User", SourcePredicate::eq("active", true)),
)
.var_as("e_id", g().e(EdgeRef::id(10)))
.var_as("e_ids", g().e(EdgeRef::ids([10u64, 11, 12])))
.var_as("e_var", g().e(EdgeRef::var("e_ids")))
.var_as("e_where", g().e_where(SourcePredicate::gte("weight", 0.5f64)))
.var_as("e_label", g().e_with_label("FOLLOWS"))
.var_as(
"e_label_where",
g().e_with_label_where("FOLLOWS", SourcePredicate::lt("weight", 2.0f64)),
)
.var_as(
"vector_nodes",
g().vector_search_nodes("Doc", "embedding", vec![0.1f32; 4], 5, None),
)
.var_as(
"vector_edges",
g().vector_search_edges("SIMILAR", "embedding", vec![0.2f32; 4], 4, None),
)
.var_as(
"vector_nodes_tenant",
g().vector_search_nodes(
"Doc",
"embedding",
vec![0.1f32; 4],
5,
Some(PropertyValue::from("acme")),
),
)
.var_as(
"vector_edges_tenant",
g().vector_search_edges(
"SIMILAR",
"embedding",
vec![0.2f32; 4],
4,
Some(PropertyValue::from("acme")),
),
)
.returning(["n_id", "e_id", "vector_nodes"]);§Node Traversal, Filters, Predicates, Expressions, and Projections
read_batch()
.var_as(
"filtered",
g()
.n(1u64)
.out(Some("FOLLOWS"))
.in_(Some("MENTIONS"))
.both(None::<&str>)
.has(
"name",
PropertyValue::from("alice").as_str().unwrap_or("alice"),
)
.has("visits", PropertyValue::from(42i64).as_i64().unwrap_or(0i64))
.has("ratio", PropertyValue::from(1.5f64).as_f64().unwrap_or(0.0f64))
.has("active", PropertyValue::from(true).as_bool().unwrap_or(false))
.has_label("User")
.has_key("email")
.where_(Predicate::and(vec![
Predicate::eq("status", "active"),
Predicate::neq("tier", "banned"),
Predicate::gt("score", 10i64),
Predicate::gte("score", 10i64),
Predicate::lt("score", 100i64),
Predicate::lte("score", 100i64),
Predicate::between("age", 18i64, 65i64),
Predicate::has_key("email"),
Predicate::starts_with("name", "a"),
Predicate::ends_with("email", ".com"),
Predicate::contains("bio", "graph"),
Predicate::not(Predicate::or(vec![
Predicate::eq("role", "bot"),
Predicate::eq("role", "system"),
])),
Predicate::compare(
Expr::prop("price")
.mul(Expr::prop("qty"))
.add(Expr::val(10i64))
.sub(Expr::param("discount"))
.div(Expr::val(2i64))
.modulo(Expr::val(3i64))
.add(Expr::id().neg()),
CompareOp::Gt,
Expr::val(100i64),
),
Predicate::eq_param("region", "target_region"),
Predicate::neq_param("status", "blocked_status"),
Predicate::gt_param("score", "min_score"),
Predicate::gte_param("score", "min_score_inclusive"),
Predicate::lt_param("score", "max_score"),
Predicate::lte_param("score", "max_score_inclusive"),
]))
.as_("seed")
.store("seed_store")
.select("seed")
.inject("seed_store")
.within("seed_store")
.without("seed")
.dedup()
.order_by("score", Order::Desc)
.order_by_multiple(vec![("age", Order::Asc), ("score", Order::Desc)])
.limit(100)
.skip(5)
.range(0, 20),
)
.var_as("counted", g().n(NodeRef::var("filtered")).count())
.var_as("exists", g().n(NodeRef::var("filtered")).exists())
.var_as("ids", g().n(NodeRef::var("filtered")).id())
.var_as("labels", g().n(NodeRef::var("filtered")).label())
.var_as("values", g().n(NodeRef::var("filtered")).values(vec!["name", "email"]))
.var_as(
"value_map_some",
g().n(NodeRef::var("filtered"))
.value_map(Some(vec!["$id", "name", "email"])),
)
.var_as(
"value_map_all",
g().n(NodeRef::var("filtered")).value_map(None::<Vec<&str>>),
)
.var_as(
"projected",
g().n(NodeRef::var("filtered")).project(vec![
PropertyProjection::new("name"),
PropertyProjection::renamed("email", "contact"),
]),
)
.returning(["filtered", "projected"]);§Edge Traversal and Edge Terminals
read_batch()
.var_as(
"edge_ops",
g()
.e_where(SourcePredicate::gt("weight", 0.1f64))
.edge_has("weight", 1i64)
.edge_has_label("FOLLOWS")
.as_("edges_a")
.store("edges_b")
.dedup()
.order_by("weight", Order::Desc)
.limit(50)
.skip(2)
.range(0, 20),
)
.var_as("to_out_n", g().e(EdgeRef::var("edge_ops")).out_n())
.var_as("to_in_n", g().e(EdgeRef::var("edge_ops")).in_n())
.var_as("to_other_n", g().e(EdgeRef::var("edge_ops")).other_n())
.var_as("edge_count", g().e(EdgeRef::var("edge_ops")).count())
.var_as("edge_exists", g().e(EdgeRef::var("edge_ops")).exists())
.var_as("edge_ids", g().e(EdgeRef::var("edge_ops")).id())
.var_as("edge_labels", g().e(EdgeRef::var("edge_ops")).label())
.var_as("edge_props", g().e(EdgeRef::var("edge_ops")).edge_properties())
.returning(["edge_ops", "edge_props"]);§Branching, Sub-Traversals, Repeat, Grouping, Paths, and Sack
read_batch()
.var_as(
"advanced",
g()
.n(1u64)
.out_e(Some("FOLLOWS"))
.in_n()
.in_e(Some("MENTIONS"))
.out_n()
.both_e(None::<&str>)
.other_n()
.repeat(
RepeatConfig::new(sub().out(Some("FOLLOWS")))
.times(2)
.until(Predicate::has_key("stop"))
.emit_all()
.emit_before()
.emit_after()
.emit_if(Predicate::gt("score", 0i64))
.max_depth(8),
)
.union(vec![
sub().out(Some("LIKES")),
SubTraversal::new()
.out(Some("FOLLOWS"))
.in_(Some("MENTIONS"))
.both(None::<&str>)
.out_e(Some("REL"))
.in_e(Some("REL"))
.both_e(None::<&str>)
.out_n()
.in_n()
.other_n()
.has("active", true)
.has_label("User")
.has_key("email")
.where_(Predicate::eq("state", "ok"))
.dedup()
.within("allow")
.without("deny")
.edge_has("weight", 1i64)
.edge_has_label("REL")
.limit(10)
.skip(1)
.range(0, 5)
.as_("s1")
.store("s2")
.select("s1")
.order_by("score", Order::Desc)
.order_by_multiple(vec![("age", Order::Asc)])
.path()
.simple_path(),
])
.choose(
Predicate::eq("vip", true),
sub().out(Some("PREMIUM")),
Some(sub().out(Some("STANDARD"))),
)
.coalesce(vec![sub().out(Some("POSTED")), sub().out(Some("COMMENTED"))])
.optional(sub().out(Some("MENTIONED")))
.fold()
.unfold()
.path()
.simple_path()
.with_sack(PropertyValue::I64(0))
.sack_set("weight")
.sack_add("weight")
.sack_get()
.dedup(),
)
.var_as("grouped", g().n_with_label("User").group("team"))
.var_as("grouped_count", g().n_with_label("User").group_count("team"))
.var_as(
"aggregate_count",
g().n_with_label("User")
.aggregate_by(AggregateFunction::Count, "score"),
)
.var_as(
"aggregate_sum",
g().n_with_label("User").aggregate_by(AggregateFunction::Sum, "score"),
)
.var_as(
"aggregate_min",
g().n_with_label("User").aggregate_by(AggregateFunction::Min, "score"),
)
.var_as(
"aggregate_max",
g().n_with_label("User").aggregate_by(AggregateFunction::Max, "score"),
)
.var_as(
"aggregate_mean",
g().n_with_label("User")
.aggregate_by(AggregateFunction::Mean, "score"),
)
.returning(["advanced", "grouped", "grouped_count", "aggregate_count"]);§Read-Batch Conditions
read_batch()
.var_as("base", g().n_with_label("User"))
.var_as_if(
"if_not_empty",
BatchCondition::VarNotEmpty("base".to_string()),
g().n(NodeRef::var("base")).limit(10),
)
.var_as_if(
"if_empty",
BatchCondition::VarEmpty("base".to_string()),
g().n_with_label("FallbackUser"),
)
.var_as_if(
"if_min_size",
BatchCondition::VarMinSize("base".to_string(), 5),
g().n(NodeRef::var("base")).order_by("score", Order::Desc),
)
.var_as_if(
"if_prev_not_empty",
BatchCondition::PrevNotEmpty,
g().n(NodeRef::var("base")).count(),
)
.returning(["base", "if_not_empty", "if_empty", "if_min_size", "if_prev_not_empty"]);§Write Sources, Mutations, and Vector Index Configuration
write_batch()
.var_as("created_user", g().add_n("User", vec![("name", "Alice")]))
.var_as(
"created_team",
g().n(NodeRef::var("created_user"))
.add_n("Team", vec![("name", "Graph")]),
)
.var_as(
"connected",
g().n(NodeRef::var("created_user")).add_e(
"MEMBER_OF",
NodeRef::var("created_team"),
vec![("since", "2026-01-01")],
),
)
.var_as(
"updated",
g().n(NodeRef::var("created_user"))
.set_property("active", true)
.remove_property("old_field"),
)
.var_as(
"drop_some_edges",
g().n(NodeRef::var("created_user"))
.drop_edge(NodeRef::ids([2u64, 3]))
.drop_edge_by_id(EdgeRef::ids([40u64, 41])),
)
.var_as("drop_nodes", g().n(NodeRef::var("created_team")).drop())
.var_as("inject_from_empty", g().inject("created_user").has_label("User"))
.var_as("drop_edge_by_id_from_empty", g().drop_edge_by_id([90u64, 91]))
.var_as(
"create_vector_index_nodes",
g().create_vector_index_nodes(
"Doc",
"embedding",
Some("tenant_id"),
),
)
.var_as(
"create_vector_index_edges",
g().create_vector_index_edges(
"SIMILAR",
"embedding",
None::<&str>,
),
)
.var_as(
"create_vector_index_edges_alt",
g().create_vector_index_edges(
"RELATED",
"embedding",
None::<&str>,
),
)
.var_as_if(
"write_if_not_empty",
BatchCondition::VarNotEmpty("created_user".to_string()),
g().n(NodeRef::var("created_user")).set_property("verified", true),
)
.returning([
"created_user",
"created_team",
"connected",
"updated",
"drop_some_edges",
"drop_nodes",
"inject_from_empty",
"drop_edge_by_id_from_empty",
"create_vector_index_nodes",
"create_vector_index_edges",
"create_vector_index_edges_alt",
"write_if_not_empty",
]);§Traversal Building Inside var_as(...)
Common source steps:
n(...),n_where(...),n_with_label(...)e(...),e_where(...),e_with_label(...)vector_search_nodes(...),vector_search_edges(...)- current Helix runtime exposes vector hit metadata via virtual fields
(
$id,$distance,$from,$to) in terminal projections
- current Helix runtime exposes vector hit metadata via virtual fields
(
Common navigation and filtering:
out/in_/both,out_e/in_e/both_e,out_n/in_n/other_nhas,has_label,has_key,where_,within,without,deduplimit,skip,range,order_by,order_by_multiple
Common terminal projections:
count,exists,id,labelvalues,value_map,project,edge_properties
Write-only operations (usable in write_batch() traversals):
add_n,add_e,set_property,remove_property,drop,drop_edge,drop_edge_by_idcreate_vector_index_nodes,create_vector_index_edges
Modules§
- prelude
- Common query-builder imports.
Structs§
- Property
Projection - A property projection with optional renaming
- Query
Bundle - Versioned payload written to
queries.json. - Query
Parameter - Declared parameter for a registered query.
- Read
Batch - A batch of read-only queries for sequential execution in one transaction
- Registered
Read Query - Registered read-query function.
- Registered
Write Query - Registered write-query function.
- Repeat
Config - Configuration for repeat steps
- SubTraversal
- A sub-traversal for use in branching operations (union, choose, coalesce, optional, repeat).
- Traversal
- A complete traversal - a sequence of steps with compile-time state tracking.
- Write
Batch - A batch of write queries for sequential execution in one transaction
Enums§
- Aggregate
Function - Aggregation function for reduce operations
- Batch
Condition - Condition for conditional query execution within a batch
- Compare
Op - Comparison operators for expression-based predicates
- EdgeRef
- A reference to edges - can be concrete IDs or a variable name
- Emit
Behavior - Emit behavior for repeat steps
- Expr
- An expression for computed values, math operations, and property references
- Generate
Error - Errors returned while generating or loading query bundles.
- Index
Spec - Dynamic index declaration used by runtime index-management steps.
- NodeRef
- A reference to nodes - can be concrete IDs or a variable name
- Order
- Sort order for ordering steps
- Predicate
- A predicate for filtering nodes by properties
- Property
Input - Mutation input value for add/set property operations.
- Property
Value - A property value that can be stored on nodes or edges
- Query
Param Type - Declared shape of a registered query parameter.
- Source
Predicate - A predicate that can be used in source steps (
n_where/e_where). - Stream
Bound - A non-negative integer input used by stream-shaping steps like
limit,skip, andrange.
Constants§
- QUERY_
BUNDLE_ VERSION - Current wire-format version for generated query bundles.
Functions§
- build_
query_ bundle - Build the in-memory query bundle from all
#[register]registrations. - deserialize_
query_ bundle - Deserialize a query bundle from JSON bytes.
- g
- Create a new traversal - the entry point for building queries
- generate
- Generate
queries.jsonin the current working directory. - generate_
to_ path - Generate a query bundle and write it to the requested output path.
- read_
batch - Create a new read batch - the entry point for read-only multi-query transactions
- read_
query_ bundle_ from_ path - Read a query bundle from a file.
- serialize_
query_ bundle - Serialize a query bundle to JSON bytes.
- sub
- Create a new sub-traversal for use in branching operations
- write_
batch - Create a new write batch - the entry point for write multi-query transactions
- write_
query_ bundle_ to_ path - Write a query bundle to a file.
Type Aliases§
- EdgeId
- Type alias for edge IDs (separate namespace from node IDs)
- NodeId
- Type alias for node IDs
- Param
Object - Object-shaped parameter payload.
- Param
Value - Arbitrary nested parameter value.