Alembic must be installed in the virtualenv in order to use right python paths,
so it's installed with pip. Commands described in this little documentation
must be executed from gargantext root directory, ie. /srv/gargantext.

Keep in mind that Alembic only handles SQLAlchemy models: tables created from
Django ORM must be put out of Alembic sight. See [alembic:exclude] section in
alembic.ini.

To bootstrap Alembic where a gargantext database is already existing see
below: TELL ALEMBIC TO NOT START FROM SCRATCH.


USUAL WORKFLOW WITH ALEMBIC

1. Make change to models in gargantext/models
2. Autogenerate revision (see below GENERATE A REVISION)
3. Manually check and edit revision file in alembic/versions
4. Commit alembic revision (it should never be reverted)
5. Commit changes in models (it can be reverted if needed)

To create, drop or modify views, schemas, roles, stored procedures, triggers or
policies see below: REPLACEABLE OBJECTS.


TELL ALEMBIC TO NOT START FROM SCRATCH

    # To upgrade a database populated before Alembic usage in Gargantext,
    # don't forget to tell Alembic your current version before to run
    # "upgrade head" command. If you don't want to do this, you can of course
    # drop your database and really start from scratch.

    alembic stamp bedce47c9e34


UPGRADE TO LATEST DATABASE VERSION

    alembic upgrade head


DOWNGRADE TO INITIAL DATABASE STATE

    # /!\ RUNNING THIS COMMAND WILL CAUSE ALL DATA LOST WITHOUT ASKING !!

    alembic downgrade base


GENERATE A REVISION

    alembic revision --autogenerate -m "Message for this migration"

    # A migration script is then created in alembic/versions directory. For
    # example alembic/versions/3adcc9a56557_message_for_this_migration.py
    # where 3adcc9a56557 is the revision id generated by Alembic.
    #
    # Alembic should generate a script reflecting changes already made in
    # models or database. However it is always a good idea to check it and edit
    # it manually, Alembic is not always accurate and can't see all alterations.
    # It should work with basic changes such as model or column creation. See
    # http://alembic.zzzcomputing.com/en/latest/autogenerate.html#what-does-autogenerate-detect-and-what-does-it-not-detect


GENERATE AN EMPTY REVISION

    alembic revision -m "Message for this migration"

    # This script must be edited to write the migration itself, mainly
    # in `upgrade` and `downgrade` functions. See Alembic documentation for
    # further details.


REPLACEABLE OBJECTS

There is no specific way no handle views, schemas, roles, stored procedures,
triggers or policies with Alembic. To ease revisions of such objects, avoid
boilerplate code and too much op.execute we use an enhanced version of
ReplaceableObject recipe (see Alembic documentation).

To create, drop or modify such object you need to make a ReplaceableObject
instance, and then use create_*, drop_* or replace_* method of alembic.op.
Conversion between ReplaceableObject and SQL is implemented in
gargantext/util/alembic.py.

* Views: create_view(ReplaceableObject(<name>, <query>))
* Roles: create_role(ReplaceableObject(<name>, <options>))
* Schemas: create_schema(ReplaceableObject(<name>))
* Stored procedures: create_sp(ReplaceableObject(<name(arguments)>, <body>)
* Triggers: create_trigger(ReplaceableObject(<name>, <when>, <table>, <body>))
* Policies: create_policy(ReplaceableObject(<name>, <table>, <body>))

Here is an example with a stored procedure:

    ...
    from gargantext.util.alembic import ReplaceableObject

    revision = '08230100f512'
    ...

    my_function_sp = ReplaceableObject(
        "my_function()", "RETURNS integer AS $$ SELECT 42 $$ LANGUAGE sql")

    def upgrade():
        op.create_sp(my_function_sp)

    def downgrade():
        op.drop_sp(my_function_sp)

To modify this stored procedure in a later revision:

    ...
    from gargantext.util.alembic import ReplaceableObject

    my_function_sp = ReplaceableObject(
        "my_function()", "RETURNS integer AS $$ SELECT 43 $$ LANGUAGE sql")

    def upgrade():
        op.replace_sp(my_function_sp, replaces="08230100f512.my_function_sp")

    def downgrade():
        op.replace_sp(my_function_sp, replace_with="08230100f512.my_function_sp")
