HyperCypher & query coverage
HyperCypher is the Cypher-like language Virtual compiles to SQL. It is deliberately a subset: every construct it accepts compiles to deterministic, reviewable SQL, and anything that can’t be represented faithfully is rejected with a clear error rather than silently mis-compiled.
Hyperedge pattern
Section titled “Hyperedge pattern”The primary pattern names a hyperedge and its members:
MATCH (e:Alert){m:Machine, u:User, f:File, ip:IP} RETURN e(e:Alert)binds the hyperedge variableeto edges of labelAlert.{m:Machine, u:User, …}binds member variables to node labels. The member names (m,u, …) must match themembersdeclared in the spec’s edge.RETURN ereturns the hydrated hyperedge records.
Binary pattern
Section titled “Binary pattern”Cypher-style binary patterns are supported as a convenience and are normalised into a two-member hyperedge:
MATCH (a:Machine)-[r:Login]-(b:User) RETURN rThis binds exactly the two named members (here the src and dst roles); any
other members the edge would normally carry are dropped from the result.
Member predicates (push-down)
Section titled “Member predicates (push-down)”You can filter on a member’s properties in the WHERE clause. Predicates are
pushed down into the generated SQL rather than filtered in Python:
-- row shape: filters the underlying rowsMATCH (e:Login){m:Machine, u:User, ip:IP} WHERE m.id = 'i-1' RETURN e
-- group shape: an *existence* filter — keeps groups that contain the member,-- and still returns the full member set of those groupsMATCH (e:Alert){m:Machine, u:User, f:File, ip:IP} WHERE u.id = 'alice' RETURN e
-- star shape: filters via the dimension joinMATCH (e:Order){c:Customer, p:Product} WHERE c.name = 'ACME' RETURN eThe semantics differ by shape, by design:
| Shape | WHERE m.id = … means |
|---|---|
row | a row-level filter (WHERE on the fact row) |
group | an existence filter (HAVING BOOL_OR(...)) — the whole group is returned if any member matches |
star | a filter on the joined dimension value |
Other clauses
Section titled “Other clauses”LIMIT— caps the number of hyperedges returned. Alimitpassed to the executor/REST call overrides anyLIMITin the query text.- Size / arity predicates — constrain the number of members on an edge.
- Edge predicates — filter on edge-level properties (e.g. timestamp).
Each edge spec declares a ts_column; the resulting hyperedges carry that as
their event timestamp, so virtual results slot directly into temporal analytics
and time-window reasoning alongside native data.
Inspecting the compiled SQL
Section titled “Inspecting the compiled SQL”Before running anything, you can compile a query to see the exact SQL and the logical plan — useful for review, debugging, and trust:
curl -X POST localhost:8000/v1/virtual/graphs/net/compile \ -H 'content-type: application/json' \ -d '{"query": "MATCH (e:Login){m:Machine, u:User, ip:IP} WHERE m.id = '\''i-1'\'' RETURN e"}'The response contains sql and a structured plan (member bindings, edge
predicates, member predicates, size predicates, star joins). The compiler is
golden-snapshot tested, so the SQL is byte-for-byte deterministic.
Current limits
Section titled “Current limits”HyperCypher is honest about what it does not yet support:
-
Multi-pattern
MATCH— conjunctive joins across multiple hyperedge patterns in oneMATCHare rejected with aPlanErrorexplaining the result-model limitation, rather than returning a wrong answer:-- rejected: multi-pattern MATCHMATCH (e:Login){m:Machine, u:User, ip:IP},(f:Login){m:Machine, u:User, ip:IP} RETURN e -
Backends — DuckDB is the supported dialect today (point it at Parquet, CSV, or an attached database). The compiler/dialect layer is structured so Postgres / Snowflake can follow.
If a query is rejected, the error names the reason so you can rewrite it — there is no “best-effort” mode that quietly degrades.