Skip to content

Operations Reference

The op object passed to every upgrade(op) and downgrade(op) function is an instance of GraphOperations. It wraps the active GraphAdapter 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.

INFO

FalkorDB, Neo4j, and Memgraph support most DDL natively via Cypher. ArcadeDB supports most DDL except vector indexes (which require the HTTP management API). Apache AGE does not support Cypher-level DDL: all op.* calls on an AGE adapter log a warning and do nothing — manage indexes and constraints via PostgreSQL DDL directly. Backend-specific differences are noted per section. The MANDATORY constraint kind and automatic constraint-ready polling are FalkorDB-only.


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.

ParameterDescription
labelNode label (or relationship type when rel=True).
propProperty name.
relTrue to index a relationship property instead of a node property.
python
def upgrade(op) -> None:
    op.create_range_index("Person", "email")
    op.create_range_index("KNOWS", "since", rel=True)

The generated Cypher is:

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

ParameterDescription
labelNode label or relationship type.
propProperty name.
relTrue for a relationship index.
python
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.

ParameterDescription
labelNode label.
propsOne or more property names.
languageOptional language for the text analyzer (e.g. "english", "german"). Defaults to "english" when omitted.
stopwordsOptional list of stopword strings.
python
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.

ParameterDescription
labelNode label.
propsProperty names (one per call internally).
python
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.

INFO

ArcadeDB vector indexes must be created via the ArcadeDB HTTP management API. Calling op.create_vector_index on an ArcadeDB adapter logs a warning and does nothing; configure vector indexes outside runic for that backend.

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

Create a vector index.

ParameterDescription
labelNode label.
propProperty name that stores the vector (list of floats).
dimensionVector dimensionality (e.g. 1536 for OpenAI text-embedding-3-small).
similarityDistance function — "cosine" or "euclidean".
mHNSW M parameter (max neighbours per layer). Default 16.
ef_constructionHNSW efConstruction (build-time search width). Default 200.
ef_runtimeHNSW efRuntime (query-time search width). Default 10.
python
def upgrade(op) -> None:
    op.create_vector_index(
        "Document",
        "embedding",
        dimension=1536,
        similarity="cosine",
    )

op.drop_vector_index(label, prop)

Drop a vector index.

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

Constraints

UNIQUE constraints ensure no two nodes of the same label share the same property value. MANDATORY constraints (FalkorDB only) ensure the property is always present.

KindFalkorDBArcadeDBNeo4jMemgraphApache AGE
UNIQUE✓ (single-prop, NODE only)✓ (single-prop, NODE only)✓ (single-prop, NODE only)
MANDATORY

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

Create a constraint.

ParameterDescription
kind"UNIQUE" or "MANDATORY".
entity"NODE" or "RELATIONSHIP".
labelNode label or relationship type.
propsList of property names.

INFO

FalkorDB: runic automatically calls create_range_index on each property before creating the constraint and polls CALL db.constraints() until the status is OPERATIONAL (30 retries × 0.5 s). You do not need to call create_range_index separately in upgrade for this case.

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

For FalkorDB, runic polls CALL db.constraints() 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.

ParameterDescription
kind"UNIQUE" or "MANDATORY".
entity"NODE" or "RELATIONSHIP".
labelNode label or relationship type.
propsList of property names.
python
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.

ParameterDescription
labelNode label.
oldCurrent property name.
newNew property name.
batchNumber of nodes processed per query. Default 10 000.
python
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.

ParameterDescription
oldCurrent label.
newNew label.
batchNodes per batch.
python
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>.

ParameterDescription
merge_queryCypher fragment starting after the UNWIND ... AS row clause (e.g. "MERGE (c:Country {code: row.code}) SET c.name = row.name").
rowsList of parameter dicts, one per row.
python
_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.

ParameterDescription
queryCypher string.
paramsOptional parameter dict.
ReturnsThe raw adapter result object (or None in preview mode).
python
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.

ConstraintFailedError

Raised when FalkorDB reports a constraint status of FAILED during polling.

ConstraintTimeoutError

Raised when a FalkorDB constraint does not reach OPERATIONAL status within 15 seconds (30 retries × 0.5 s).

runic - Graph schema migrations and OGM for Cypher-based graph databases. · Impressum