Autogenerate ============ runic can compare a *desired* schema described in Python (the ``SchemaManifest``) with the *actual* schema currently in your graph and generate migration scripts for the difference — no hand-written Cypher required. ---- How it works ------------ 1. You declare the target schema as a :class:`~runic.migrate.manifest.SchemaManifest` object and pass it to ``context.configure()`` in ``env.py``. 2. ``runic revision --autogenerate`` reads the live schema via the adapter's ``read_live_schema()`` and diffs it against the manifest. 3. It generates an ``upgrade``/``downgrade`` body and writes a new revision file marked ``# AUTOGENERATED — review before applying; cannot detect renames``. 4. **Always review the generated file before applying it.** Autogenerate cannot detect renames and may generate incorrect ``drop`` + ``create`` pairs in place of a rename (see :doc:`limitations`). .. note:: Live-schema introspection (step 2) is currently implemented only for FalkorDB, which calls ``CALL db.indexes()`` and ``CALL db.constraints()``. All other adapters return an empty live schema; ``--autogenerate`` with those backends treats the entire manifest as new and generates a create-all script. See :doc:`limitations`. Declaring a SchemaManifest -------------------------- Import the manifest classes from ``runic``: .. code-block:: python from runic.migrate.manifest import ( FulltextIndex, MandatoryConstraint, RangeIndex, SchemaManifest, UniqueConstraint, VectorIndex, ) Build the manifest: .. code-block:: python TARGET = SchemaManifest( range_indexes=[ RangeIndex("Person", "email"), RangeIndex("Person", "name"), RangeIndex("KNOWS", "since", rel=True), ], fulltext_indexes=[ FulltextIndex("Article", ("title", "body")), FulltextIndex("Review", ("text",), language="german"), ], vector_indexes=[ VectorIndex("Document", "embedding", dimension=1536, similarity="cosine"), ], constraints=[ UniqueConstraint("NODE", "Person", ["email"]), MandatoryConstraint("NODE", "Person", ["name"]), ], ) Configuring env.py for autogenerate ------------------------------------- Pass ``target_manifest`` to ``context.configure()``: .. code-block:: python # runic/env.py import os from runic.migrate import context from runic.migrate.adapters import create_adapter from runic.migrate.manifest import RangeIndex, SchemaManifest, UniqueConstraint TARGET = SchemaManifest( range_indexes=[RangeIndex("Person", "email")], constraints=[UniqueConstraint("NODE", "Person", ["email"])], ) adapter = create_adapter( "falkordb", url=os.getenv("FALKORDB_URL", "falkor://localhost:6379"), graph_name=os.getenv("FALKORDB_GRAPH", "my_graph"), ) context.configure(adapter, target_manifest=TARGET) Generating a migration script ------------------------------ Run ``runic revision --autogenerate``: .. code-block:: bash $ runic revision --autogenerate -m "add person constraints" Created revision: runic/versions/b2c3d4e5_add_person_constraints.py [CANDIDATE — review before applying] Open the generated file: .. code-block:: python def upgrade(op) -> None: # AUTOGENERATED — review before applying; cannot detect renames op.create_range_index("Person", "email") op.create_constraint("UNIQUE", "NODE", "Person", ["email"]) def downgrade(op) -> None: # AUTOGENERATED — review before applying; cannot detect renames op.drop_constraint("UNIQUE", "NODE", "Person", ["email"]) op.drop_range_index("Person", "email") .. important:: The ``[CANDIDATE — review before applying]`` notice in the CLI output is a reminder that the generated file must be reviewed before being applied. **Never apply an autogenerated migration to production without reviewing it.** Using ``runic check`` in CI ---------------------------- ``runic check`` exits with code 1 if the live schema has drifted from ``target_manifest``: .. code-block:: bash $ runic check Schema up-to-date. $ runic check Pending schema changes (run `runic revision --autogenerate -m "..."` to generate): + op.create_range_index("Order", "placed_at") Add it to your CI pipeline to fail the build when uncommitted schema changes are present (example using FalkorDB; substitute your backend's env vars): .. code-block:: yaml # GitHub Actions example - name: Check schema drift run: | FALKORDB_URL=${{ secrets.FALKORDB_URL }} \ FALKORDB_GRAPH=${{ secrets.FALKORDB_GRAPH }} \ runic check What autogenerate detects -------------------------- Autogenerate compares canonical keys for each schema object type: +---------------+-------------------------------------------+ | Object type | Key | +===============+===========================================+ | Range index | entity type, label, property | +---------------+-------------------------------------------+ | Fulltext index| label, properties (tuple) | +---------------+-------------------------------------------+ | Vector index | label, property | +---------------+-------------------------------------------+ | Constraint | kind (UNIQUE/MANDATORY), entity, label, | | | properties (tuple) | +---------------+-------------------------------------------+ Drop order in the generated ``upgrade`` follows the dependency rule: constraints first, then indexes. Create order is the reverse: indexes first, constraints second (because UNIQUE constraints require a prior range index). What autogenerate does NOT detect ----------------------------------- See :doc:`limitations` for a full list. Key gaps: * **Renames** — a renamed property or label appears as ``drop + create``, which would delete and recreate the index but would not rename data. * **Property type changes** — FalkorDB does not expose property type information via ``db.indexes()``; runic cannot diff on type. * **Node/relationship data** — only schema objects (indexes and constraints) are compared. * **Index options drift** — changes to HNSW parameters (``m``, ``ef_construction``) on an existing vector index are not detected. Manifest classes reference --------------------------- .. autoclass:: runic.migrate.manifest.SchemaManifest :members: :show-inheritance: .. autoclass:: runic.migrate.manifest.RangeIndex :members: :show-inheritance: .. autoclass:: runic.migrate.manifest.FulltextIndex :members: :show-inheritance: .. autoclass:: runic.migrate.manifest.VectorIndex :members: :show-inheritance: .. autoclass:: runic.migrate.manifest.UniqueConstraint :members: :show-inheritance: .. autoclass:: runic.migrate.manifest.MandatoryConstraint :members: :show-inheritance: