Operations Reference

The op object passed to every upgrade(op) and downgrade(op) function is an instance of GraphOperations. It wraps the FalkorDB client and exposes a safe, preview-aware API for all supported schema operations.

In preview mode (runic upgrade --preview) none of the methods below touch the database; instead each operation is recorded as a string in op.preview_log and printed to the console.


Range indexes

Range indexes support equality and range queries on node or relationship properties (WHERE n.prop = $x, WHERE n.prop > $x).

op.create_range_index(label, prop, *, rel=False)

Create a range index on label.prop.

Parameters:
  • label – Node label (or relationship type when rel=True).

  • prop – Property name.

  • relTrue to index a relationship property instead of a node property.

def upgrade(op) -> None:
    op.create_range_index("Person", "email")
    op.create_range_index("KNOWS", "since", rel=True)

The generated Cypher is:

CREATE INDEX FOR (n:Person) ON (n.email)
CREATE INDEX FOR ()-[r:KNOWS]->() ON (r.since)
op.drop_range_index(label, prop, *, rel=False)

Drop a range index.

Parameters:
  • label – Node label or relationship type.

  • prop – Property name.

  • relTrue for a relationship index.

def downgrade(op) -> None:
    op.drop_range_index("Person", "email")

Fulltext indexes

Fulltext indexes enable substring and token search via CALL db.idx.fulltext.queryNodes().

op.create_fulltext_index(label, *props, language=None, stopwords=None)

Create a fulltext index on one or more properties of a node label.

Parameters:
  • label – Node label.

  • props – One or more property names.

  • language – Optional language for the text analyzer (e.g. "english", "german"). Defaults to "english" when omitted.

  • stopwords – Optional list of stopword strings.

def upgrade(op) -> None:
    op.create_fulltext_index("Article", "title", "body")
    op.create_fulltext_index(
        "Review",
        "text",
        language="german",
        stopwords=["und", "oder"],
    )
op.drop_fulltext_index(label, *props)

Drop a fulltext index.

Parameters:
  • label – Node label.

  • props – Property names (one per call internally).

def downgrade(op) -> None:
    op.drop_fulltext_index("Article", "title", "body")

Vector indexes

Vector indexes enable approximate nearest-neighbour search (ANN) via HNSW. Used for semantic similarity queries.

op.create_vector_index(label, prop, dimension, similarity, *, m=16, ef_construction=200, ef_runtime=10)

Create a vector index.

Parameters:
  • label – Node label.

  • prop – Property name that stores the vector (list of floats).

  • dimension – Vector dimensionality (e.g. 1536 for OpenAI text-embedding-3-small).

  • similarity – Distance function — "cosine" or "euclidean".

  • m – HNSW M parameter (max neighbours per layer). Default 16.

  • ef_construction – HNSW efConstruction (build-time search width). Default 200.

  • ef_runtime – HNSW efRuntime (query-time search width). Default 10.

def upgrade(op) -> None:
    op.create_vector_index(
        "Document",
        "embedding",
        dimension=1536,
        similarity="cosine",
    )
op.drop_vector_index(label, prop)

Drop a vector index.

def downgrade(op) -> None:
    op.drop_vector_index("Document", "embedding")

Constraints

FalkorDB supports two constraint kinds: UNIQUE (ensures no two nodes of the same label share the same property value) and MANDATORY (ensures the property is always present).

op.create_constraint(kind, entity, label, props)

Create a constraint and poll until it becomes OPERATIONAL.

Parameters:
  • kind"UNIQUE" or "MANDATORY".

  • entity"NODE" or "RELATIONSHIP".

  • label – Node label or relationship type.

  • props – List of property names.

Note

For UNIQUE constraints, runic automatically calls create_range_index on each property before creating the constraint. You do not need to call create_range_index separately in upgrade for this case.

def upgrade(op) -> None:
    op.create_constraint("UNIQUE", "NODE", "Person", ["email"])
    op.create_constraint("MANDATORY", "NODE", "Person", ["name"])

runic polls CALL db.constraints() in a loop (30 retries × 0.5 s) and raises ConstraintFailedError if the status becomes FAILED, or ConstraintTimeoutError if it does not become OPERATIONAL within 15 seconds.

op.drop_constraint(kind, entity, label, props)

Drop a constraint.

Parameters:
  • kind"UNIQUE" or "MANDATORY".

  • entity"NODE" or "RELATIONSHIP".

  • label – Node label or relationship type.

  • props – List of property names.

def downgrade(op) -> None:
    op.drop_constraint("UNIQUE", "NODE", "Person", ["email"])
    op.drop_range_index("Person", "email")

Data transformation

These helpers perform batched Cypher queries for common data-level changes. They are safe to run on large graphs because they operate in configurable batch sizes.

op.rename_property(label, old, new, batch=10_000)

Rename a property on all nodes of a given label. Runs in a loop until no more nodes are affected.

Parameters:
  • label – Node label.

  • old – Current property name.

  • new – New property name.

  • batch – Number of nodes processed per query. Default 10 000.

def upgrade(op) -> None:
    op.rename_property("User", "email_address", "email")

Warning

Property renames are not detected by autogenerate. You must write them manually in both upgrade and downgrade.

op.relabel_nodes(old, new, batch=10_000)

Rename a node label across the entire graph. Adds the new label and removes the old one for each matching node in batches.

Parameters:
  • old – Current label.

  • new – New label.

  • batch – Nodes per batch.

def upgrade(op) -> None:
    op.relabel_nodes("User", "Person")
op.seed(merge_query, rows)

Insert or merge reference data. Wraps each row with UNWIND $rows AS row <merge_query>.

Parameters:
  • merge_query – Cypher fragment starting after the UNWIND ... AS row clause (e.g. "MERGE (c:Country {code: row.code}) SET c.name = row.name").

  • rows – List of parameter dicts, one per row.

_COUNTRIES = [
    {"code": "DE", "name": "Germany"},
    {"code": "FR", "name": "France"},
]

def upgrade(op) -> None:
    op.seed(
        "MERGE (c:Country {code: row.code}) SET c.name = row.name",
        _COUNTRIES,
    )

def downgrade(op) -> None:
    op.run_cypher(
        "MATCH (c:Country) WHERE c.code IN $codes DETACH DELETE c",
        {"codes": [r["code"] for r in _COUNTRIES]},
    )

Raw Cypher

For anything not covered by the helpers above:

op.run_cypher(query, params=None)

Execute an arbitrary Cypher query against the graph.

Parameters:
  • query – Cypher string.

  • params – Optional parameter dict.

Returns:

The raw adapter result object (or None in preview mode).

def upgrade(op) -> None:
    op.run_cypher(
        "MATCH (n:Person) SET n.active = true"
    )
    op.run_cypher(
        "MATCH (n:Person) WHERE n.score < $threshold DELETE n",
        {"threshold": 0},
    )

Error classes

These exceptions are raised by op.create_constraint() during constraint polling. They live in runic.migrate.exceptions and are exported from the top-level runic package.

class runic.migrate.exceptions.ConstraintFailedError[source]
class runic.migrate.exceptions.ConstraintTimeoutError[source]