Skip to content

Cypher Query Language Reference

HyperMesh DB uses a Cypher-compatible query language extended for native N-ary hyperedges. Every statement is passed to Connection.execute() or the /v1/query REST endpoint.


Temporal range query (TPI Bucket Pushdown)

Section titled “Temporal range query (TPI Bucket Pushdown)”
MATCH HYPEREDGE (he:<Table>)
WHERE he.event_ts >= <start> AND he.event_ts <= <end>
RETURN *

The query planner pushes the time range into the TPI (Temporal Property Index) and reads only the buckets that overlap the range. No full-table scan occurs. This is the primary read path for time-series workloads.

-- Example: all coalitions in the last 60 seconds
MATCH HYPEREDGE (he:CoProximity)
WHERE he.event_ts >= 940 AND he.event_ts <= 1000
RETURN *
MATCH HYPEREDGE (he:<Table>) WHERE <node_id> IN he.members RETURN *

The FMI (Forward Member Index) is a persistent inverted index. Membership lookup is O(log N) and returns all hyperedges that contain the given node.

-- All coalitions node 42 participated in
MATCH HYPEREDGE (he:CoProximity) WHERE 42 IN he.members RETURN *
MATCH HYPEREDGE (he:<Table>) RETURN *

Returns every record. Use with care on large tables.

After a temporal range or membership scan, additional predicates filter in-memory results:

PredicateMeaning
he.weight > 0.8Built-in float column — greater than
he.weight >= 0.5 AND he.weight < 1.0Range on float
he.formation = "V"String equality
he.<custom_col> = valueUser-defined column (indexed via PSI)
-- Temporal range + weight filter
MATCH HYPEREDGE (he:Flights)
WHERE he.event_ts >= 1000 AND he.event_ts <= 2000
AND he.weight > 0.9
RETURN *

Single-pattern MATCH HYPEREDGE queries support boolean WHERE expressions using AND, OR, NOT, parentheses, and the inequality operators <> / != (in addition to =, >, >=, <, <=):

MATCH HYPEREDGE (he:Sensors)
WHERE he.label = 'alpha' AND (he.severity = 4 OR he.severity = 5)
RETURN he.event_ts
MATCH HYPEREDGE (he:Sensors)
WHERE NOT he.label = 'alpha' AND he.severity <> 1
RETURN *

Semantics & limits:

  • Standard precedence: NOT binds tightest, then AND, then OR; use parentheses to group.
  • A comparison against a missing column or a NULL value is false (so NOT he.x = 1 keeps rows where x is absent or ≠ 1).
  • These boolean predicates are evaluated in the Python layer, so a query using OR / NOT / <> performs a full table scan (TPI/PSI pushdown is not applied to disjunctive predicates). Pure AND queries keep index pushdown.
  • Supported for single-pattern MATCH HYPEREDGE reads only. Joins and DELETE / UPDATE remain AND-only, and IN / members predicates cannot be combined with OR / NOT (a clear error is raised).

Every hyperedge table exposes these columns:

ColumnTypeDescription
event_tsuint32Event timestamp (seconds since epoch or mission start)
membersuint32[]Ordered member node IDs
weightfloat32Edge weight (default 0.0)
mean_dist_mfloat32Mean pairwise distance in metres (default 0.0)
formationstring(16)Formation label (default "")

User-defined columns are added via ALTER TABLE … ADD COLUMN and appear after the built-in columns in RETURN * results.

RETURN queries support projection/aggregation, ORDER BY, and pagination with LIMIT and SKIP / OFFSET (OFFSET is a synonym for SKIP). The clauses are evaluated in standard order — ORDER BYSKIPLIMIT — regardless of the order they appear textually, so the result is a stable page.

-- Rows 101150 of the time-ordered result (page 3, page size 50)
MATCH HYPEREDGE (he:Flights)
WHERE he.event_ts >= 1000 AND he.event_ts <= 2000
RETURN he.event_ts, he.weight
ORDER BY he.event_ts ASC
SKIP 100 LIMIT 50

SKIP / OFFSET also work without an explicit RETURN (like LIMIT) and with parameter substitution (SKIP $offset). They are applied over the materialised result set; to iterate very large ranges with bounded memory, prefer hypermesh.scan_windows().


MATCH HYPEREDGE (a:<Table1>), HYPEREDGE (b:<Table2>)
WHERE a.event_ts >= T1 AND a.event_ts <= T2
AND b.event_ts >= T1 AND b.event_ts <= T2
AND <join-predicate>
RETURN *

Supported join predicates:

Join typePredicate syntaxDescription
Temporal overlapa.event_ts = b.event_tsBoth edges share the same timestamp
Member intersectiona.members = b.membersBoth edges share a common member
Property equalitya.<col> = b.<col>Any shared property value
Causal chaina.event_ts < b.event_ts AND a.members = b.membersMember appears later
-- Drones that were in CoProximity AND also had a CommLink event
MATCH HYPEREDGE (c:CoProximity), HYPEREDGE (l:CommLink)
WHERE c.event_ts >= 100 AND c.event_ts <= 200
AND l.event_ts >= 100 AND l.event_ts <= 200
AND c.members = l.members
RETURN *

Results are returned with alias-prefixed column names: c.event_ts, c.members, l.event_ts, l.members, etc.


INSERT INTO <Table> (event_ts, members [, col ...])
VALUES (<ts>, [<m1>, <m2>, ...] [, <val> ...])

Writes one hyperedge to the WAL. The record is immediately visible to all subsequent reads (WAL merging).

INSERT INTO CoProximity (event_ts, members, weight, formation)
VALUES (1000, [1, 2, 3], 0.85, "delta")

UPDATE <Table> SET <col> = <val> [, <col> = <val> ...]
WHERE event_ts = <ts> AND members = [<m1>, <m2>, ...]

Semantics:

  • UPDATE requires the compound key (event_ts, members) in the WHERE clause to identify a specific hyperedge. There is no other primary key.
  • Internally, UPDATE issues a WAL DELETE tombstone for the old record then writes a new WAL INSERT with the modified fields.
  • The updated record is not visible in MATCH queries until compact() is called. This is by design: the WAL is sequential-append, so UPDATE is always safe to call, but visibility requires a compaction cycle.
  • If the WHERE clause matches zero records, rows_affected in the result will be 0. Callers must check this value — no exception is raised.
-- Update weight for a specific hyperedge
UPDATE CoProximity SET weight = 0.95, formation = "V"
WHERE event_ts = 1000 AND members = [1, 2, 3]

Checking for successful update:

r = db.execute("UPDATE CoProximity SET weight = 0.95 WHERE event_ts = 1000 AND members = [1,2,3]")
if r.rows[0]["rows_affected"] == 0:
print("WARNING: no record matched the UPDATE predicate")
db.compact(table="CoProximity") # make the change visible

DELETE HYPEREDGE FROM <Table>
WHERE event_ts = <ts> AND members = [<m1>, <m2>, ...]

Writes a WAL tombstone. The record is hidden from subsequent reads immediately (WAL merge applies tombstones in real-time).

DELETE HYPEREDGE FROM CoProximity
WHERE event_ts = 1000 AND members = [1, 2, 3]

CREATE HYPEREDGE TABLE <name> (<MemberType> [, ...])
[BUCKET_SECONDS <n>]
[COMPACT_THRESHOLD <n>]
OptionDefaultDescription
BUCKET_SECONDS60TPI time-bucket width in seconds. Smaller = finer granularity, more buckets.
COMPACT_THRESHOLD0 (off)Auto-compact after N WAL entries. 0 = disabled.
CREATE HYPEREDGE TABLE SwarmEvents (Drone)
BUCKET_SECONDS 30
COMPACT_THRESHOLD 500
DROP HYPEREDGE TABLE SwarmEvents
ALTER TABLE <t> ADD COLUMN <col> <type> [DEFAULT <value>]
ALTER TABLE <t> DROP COLUMN <col>
ALTER TABLE <t> RENAME TO <new_name>

Supported column types: FLOAT, INT, STRING.

ALTER TABLE SwarmEvents ADD COLUMN altitude FLOAT DEFAULT 0.0
ALTER TABLE SwarmEvents DROP COLUMN altitude
ALTER TABLE SwarmEvents RENAME TO DroneEvents

Indexes (Property Secondary Index — PSI)

Section titled “Indexes (Property Secondary Index — PSI)”
CREATE INDEX ON <table> (<column>)
DROP INDEX ON <table> (<column>)
SHOW INDEXES [ON <table>]

A PSI enables sub-linear property filtering on user-defined float or int columns. Without an index, WHERE he.<col> = v is a full scan of in-memory results; with an index it becomes a binary search.

CREATE INDEX ON CoProximity (altitude)
CALL SHOW_HYPEREDGE_TABLES() RETURN *

BEGIN [TRANSACTION]
-- ... one or more INSERT / DELETE statements ...
COMMIT [TRANSACTION]
-- or:
ROLLBACK [TRANSACTION]

All INSERTs and DELETEs issued between BEGIN and COMMIT are buffered in memory and applied atomically. If ROLLBACK is called, all buffered writes are discarded and WAL tombstones are written for any inserts that were already flushed.

Python context-manager shorthand:

with db.transaction():
db.execute("INSERT INTO T (event_ts, members) VALUES (100, [1,2])")
db.execute("INSERT INTO T (event_ts, members) VALUES (101, [3,4])")
# auto-commits on success, auto-rolls-back on exception

EXPLAIN <query> -- returns query plan, no data rows
ANALYZE <query> -- returns data rows AND query plan

The query plan contains:

FieldDescription
strategyTPI_RANGE, FMI_LOOKUP, FULL_SCAN, or JOIN
index_typeTPI, FMI, PSI, or NONE
buckets_scannedNumber of TPI buckets read
total_bucketsTotal TPI buckets in the table
estimated_rowsEstimated result count before filtering
wal_entries_mergedWAL entries merged with the TPI result
EXPLAIN MATCH HYPEREDGE (he:CoProximity)
WHERE he.event_ts >= 100 AND he.event_ts <= 200
RETURN *

COPY <Table> FROM '<path>' [(<options>)]

Loads a CSV, Parquet, or JSON/NDJSON file into a table. The file is read on the server filesystem; for client-side files use the Python SDK’s copy_from_df() or copy_from_parquet().

OptionDefaultDescription
HEADER true/falseauto-detectWhether the CSV has a header row
DELIMITER ',',CSV field separator
IGNORE ERRORSfalseSkip malformed rows instead of raising
COPY CoProximity FROM '/data/events.csv' (HEADER true, DELIMITER ',')
COPY CoProximity FROM '/data/events.parquet'
COPY CoProximity FROM '/data/events.ndjson' (IGNORE ERRORS)

Use $name placeholders in any query; pass a parameters dict to execute():

result = db.execute(
"MATCH HYPEREDGE (he:T) WHERE he.event_ts >= $start AND he.event_ts <= $end RETURN *",
parameters={"start": 1000, "end": 2000},
)