Skip to content

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.

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 variable e to edges of label Alert.
  • {m:Machine, u:User, …} binds member variables to node labels. The member names (m, u, …) must match the members declared in the spec’s edge.
  • RETURN e returns the hydrated hyperedge records.

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 r

This 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.

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 rows
MATCH (e:Login){m:Machine, u:User, ip:IP} WHERE m.id = 'i-1' RETURN e
-- group shape: an *existence* filterkeeps groups that contain the member,
-- and still returns the full member set of those groups
MATCH (e:Alert){m:Machine, u:User, f:File, ip:IP} WHERE u.id = 'alice' RETURN e
-- star shape: filters via the dimension join
MATCH (e:Order){c:Customer, p:Product} WHERE c.name = 'ACME' RETURN e

The semantics differ by shape, by design:

ShapeWHERE m.id = … means
rowa row-level filter (WHERE on the fact row)
groupan existence filter (HAVING BOOL_OR(...)) — the whole group is returned if any member matches
stara filter on the joined dimension value
  • LIMIT — caps the number of hyperedges returned. A limit passed to the executor/REST call overrides any LIMIT in 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.

Before running anything, you can compile a query to see the exact SQL and the logical plan — useful for review, debugging, and trust:

Terminal window
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.

HyperCypher is honest about what it does not yet support:

  • Multi-pattern MATCH — conjunctive joins across multiple hyperedge patterns in one MATCH are rejected with a PlanError explaining the result-model limitation, rather than returning a wrong answer:

    -- rejected: multi-pattern MATCH
    MATCH (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.