Cypher QueriesΒΆ

PyGraphDB includes an initial read-only Cypher API through GraphDB.query(cypher). The current implementation is intentionally small and maps directly to features that already have efficient database APIs: indexed label scans, anchored typed traversal, and typed path sampling.

Relationship types are read from edge.properties["type"]. Node labels are stored natively through Node(labels=[...]) and maintained in a sorted label index.

Supported Feature MatrixΒΆ

The table below distinguishes features available through the Python database API from features exposed through the Cypher API.

Legend: βœ… supported, 🟑 partially supported, ❌ not supported.

Current DB API and Cypher API supportΒΆ

Feature

DB API

Cypher API

Notes

Node and edge property storage

βœ…

βœ…

Cypher can return bound Node and Edge objects and project properties such as RETURN n.name or RETURN r.score.

Native node labels

βœ…

βœ…

DB API supports Node(labels=[...]) and nodes_by_label. Cypher supports MATCH (n:Label) RETURN n.

Exact-match node property indexes

βœ…

🟑

DB API supports explicit indexes via create_node_property_index. Cypher uses them for MATCH (n:Label {name: "..."}) RETURN n when registered.

Exact-match edge property indexes

βœ…

❌

DB API supports explicit indexes via create_edge_property_index. Cypher edge property predicates are not implemented yet.

Dedicated relationship type field

🟑

🟑

Typed traversal uses edge.properties["type"] instead of a dedicated Edge.type field.

Relationship type catalog

βœ…

❌

DB API supports edges_by_type. Cypher does not yet support unanchored MATCH ()-[:TYPE]->() scans.

Anchored one-hop typed traversal

βœ…

βœ…

DB API uses iter_typed_adjacency or neighbors_by_edge_type. Cypher supports MATCH (a {id: "..."})-[:TYPE]->(b).

Anchored multi-hop typed traversal

βœ…

βœ…

Cypher supports repeated outgoing typed hops from an anchored start node.

Reverse typed traversal

βœ…

βœ…

DB API supports direction="in". Cypher supports <-[:TYPE]- from an anchored node.

Undirected typed traversal

βœ…

βœ…

DB API supports direction="any". Cypher supports -[:TYPE]- from an anchored node.

Untyped BFS traversal

βœ…

❌

Available as GraphDB.bfs over legacy adjacency lists.

Single-hop typed neighbor sampling

βœ…

❌

Available as GraphDB.sample_neighbors.

Multi-hop typed path sampling

βœ…

βœ…

Cypher exposes this through CALL pg.sample_typed_paths(...) YIELD path RETURN path.

Materialized sampled subgraph

βœ…

❌

Available as GraphDB.sample_typed_subgraph.

Property filtering with WHERE

🟑

❌

DB API has exact-match index lookup helpers, but Cypher WHERE parsing is future work.

Result limiting

βœ…

βœ…

Cypher supports LIMIT on label scans, anchored typed traversals, and pg.sample_typed_paths calls.

Mutating Cypher queries

βœ…

❌

Use put_node, put_edge, put_edges_bulk, and ingestion APIs directly.

Indexed Label ScansΒΆ

Create nodes with native labels, then query by label without scanning every node.

graph_db.put_node(Node(node_id="drug-1", labels=["Drug"], properties={"name": "Aspirin"}))

result = graph_db.query('MATCH (d:Drug) RETURN d')

for record in result:
    print(record["d"].get_id)

Indexed Label and Property LookupΒΆ

Exact-match property indexes are explicit. Register an index before relying on it for performance-sensitive lookup.

graph_db.create_node_property_index("name")

result = graph_db.query('MATCH (d:Drug {name: "Aspirin"}) RETURN d')

for record in result:
    print(record["d"].properties["name"])

If a property index is not registered, Cypher still restricts the search to the label index and then filters decoded nodes in Python.

Property Projections and LimitsΒΆ

Use dot notation in RETURN to project values from bound nodes and relationships. Missing properties return None. The special fields id and labels are available on nodes; id, source, and target are available on relationships.

result = graph_db.query('MATCH (d:Drug) RETURN d.id, d.name LIMIT 10')

for record in result:
   print(record["d.id"], record["d.name"])

Anchored One-Hop TraversalΒΆ

Use GraphDB.query for an anchored outgoing typed traversal. The start node must be constrained by id.

result = graph_db.query(
    'MATCH (d {id: "drug-1"})-[:drug-to-protein]->(p) RETURN d, p'
)

for record in result:
    print(record["d"].get_id, record["p"].get_id)

The result object exposes columns and records:

print(result.columns)  # ("d", "p")
print(len(result))

Relationship VariablesΒΆ

Relationship variables can be bound and returned.

result = graph_db.query(
    'MATCH (d {id: "drug-1"})-[r:drug-to-disease]->(x) RETURN d, r, x'
)

for record in result:
   print(record["r"].get_id, record["r"].properties)

Relationship properties can be projected directly.

result = graph_db.query(
   'MATCH (d {id: "drug-1"})-[r:drug-to-disease]->(x) RETURN r.id, r.type LIMIT 1'
)

Anchored Multi-Hop TraversalΒΆ

Cypher supports repeated outgoing typed hops from the anchored start node.

result = graph_db.query(
    'MATCH (d {id: "drug-1"})-[:drug-to-protein]->(p)-[:protein-to-disease]->(x) RETURN d, p, x'
)

for record in result:
    print(record["d"].get_id, record["p"].get_id, record["x"].get_id)

Relationship variables can be used across multiple hops as well.

result = graph_db.query(
   'MATCH (d {id: "drug-1"})-[r1:drug-to-protein]->(p)-[r2:protein-to-disease]->(x) RETURN r1, r2, x'
)

Reverse and Undirected TraversalΒΆ

Anchored typed traversals can follow outgoing, incoming, or either-direction relationships.

incoming = graph_db.query(
   'MATCH (p {id: "protein-1"})<-[:drug-to-protein]-(d) RETURN p, d'
)

undirected = graph_db.query(
   'MATCH (p {id: "protein-1"})-[:drug-to-protein]-(n) RETURN n'
)

Direction can vary by hop:

result = graph_db.query(
   'MATCH (x {id: "disease-1"})<-[:protein-to-disease]-(p)<-[:drug-to-protein]-(d) RETURN x, p, d'
)

Sampling ProcedureΒΆ

Typed path sampling is exposed as a PyGraphDB-specific procedure call. This is not standard openCypher syntax; it delegates to GraphDB.sample_typed_paths.

result = graph_db.query(
    'CALL pg.sample_typed_paths(["drug-1"], '
    '[{"edge_type": "drug-to-protein", "direction": "out", "sample_size": 2}, '
    '{"edge_type": "protein-to-disease", "direction": "out", "sample_size": 1}]) '
    'YIELD path RETURN path'
)

for record in result:
    print(record["path"])

Current Cypher LimitationsΒΆ

Unsupported Cypher features raise ValueError with a message describing the supported subset. The current Cypher API does not yet support:

  • Multiple labels in one node pattern, such as (n:Drug:Approved).

  • Unanchored all-node scans such as MATCH (n) RETURN n.

  • WHERE predicates.

  • ORDER BY, aggregation, joins across separate patterns, or mutation clauses.