summaryrefslogtreecommitdiff
path: root/doc/source/versioning.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/source/versioning.rst')
-rw-r--r--doc/source/versioning.rst641
1 files changed, 0 insertions, 641 deletions
diff --git a/doc/source/versioning.rst b/doc/source/versioning.rst
deleted file mode 100644
index 7b30cd3..0000000
--- a/doc/source/versioning.rst
+++ /dev/null
@@ -1,641 +0,0 @@
-.. _versioning-system:
-.. currentmodule:: migrate.versioning
-.. highlight:: console
-
-***********************************
-Database schema versioning workflow
-***********************************
-
-SQLAlchemy migrate provides the :mod:`migrate.versioning` API that is
-also available as the :ref:`migrate <command-line-usage>` command.
-
-Purpose of this package is frontend for migrations. It provides commands to
-manage migrate :term:`repository` and database selection as well as script
-versioning.
-
-
-Project setup
-=============
-
-.. _create_change_repository:
-
-Create a change repository
---------------------------
-
-To begin, we'll need to create a :term:`repository` for our project.
-
-All work with repositories is done using the :ref:`migrate
-<command-line-usage>` command. Let's create our project's repository::
-
- $ migrate create my_repository "Example project"
-
-This creates an initially empty :term:`repository` relative to current
-directory at :file:`my_repository/` named `Example project`.
-
-The :term:`repository` directory contains a sub directory :file:`versions` that
-will store the :ref:`schema versions <changeset-system>`, a configuration file
-:file:`migrate.cfg` that contains :ref:`repository configuration
-<repository_configuration>` and a script :ref:`manage.py
-<project_management_script>` that has the same functionality as the
-:ref:`migrate <command-line-usage>` command but is preconfigured with
-repository specific parameters.
-
-.. note::
-
- Repositories are associated with a single database schema, and store
- collections of change scripts to manage that schema. The scripts in a
- :term:`repository` may be applied to any number of databases. Each
- :term:`repository` has an unique name. This name is used to identify the
- :term:`repository` we're working with.
-
-
-Version control a database
---------------------------
-
-Next we need to declare database to be under version control. Information on a
-database's version is stored in the database itself; declaring a database to be
-under version control creates a table named **migrate_version** and associates
-it with your :term:`repository`.
-
-The database is specified as a `SQLAlchemy database url`_.
-
-.. _`sqlalchemy database url`:
- http://www.sqlalchemy.org/docs/core/engines.html#database-urls
-
-The :option:`version_control` command assigns a specified database with a
-:term:`repository`::
-
- $ python my_repository/manage.py version_control sqlite:///project.db my_repository
-
-We can have any number of databases under this :term:`repository's
-<repository>` version control.
-
-Each schema has a :term:`version` that SQLAlchemy Migrate manages. Each change
-script applied to the database increments this version number. You can retrieve
-a database's current :term:`version`::
-
- $ python my_repository/manage.py db_version sqlite:///project.db my_repository
- 0
-
-A freshly versioned database begins at version 0 by default. This assumes the
-database is empty or does only contain schema elements (tables, views,
-constraints, indices, ...) that will not be affected by the changes in the
-:term:`repository`. (If this is a bad assumption, you can specify the
-:term:`version` at the time the database is put under version control, with the
-:option:`version_control` command.) We'll see that creating and applying change
-scripts changes the database's :term:`version` number.
-
-Similarly, we can also see the latest :term:`version` available in a
-:term:`repository` with the command::
-
- $ python my_repository/manage.py version my_repository
- 0
-
-We've entered no changes so far, so our :term:`repository` cannot upgrade a
-database past version 0.
-
-Project management script
--------------------------
-
-.. _project_management_script:
-
-Many commands need to know our project's database url and :term:`repository`
-path - typing them each time is tedious. We can create a script for our project
-that remembers the database and :term:`repository` we're using, and use it to
-perform commands::
-
- $ migrate manage manage.py --repository=my_repository --url=sqlite:///project.db
- $ python manage.py db_version
- 0
-
-The script :file:`manage.py` was created. All commands we perform with it are
-the same as those performed with the :ref:`migrate <command-line-usage>` tool,
-using the :term:`repository` and database connection entered above. The
-difference between the script :file:`manage.py` in the current directory and
-the script inside the repository is, that the one in the current directory has
-the database URL preconfigured.
-
-.. note::
-
- Parameters specified in manage.py should be the same as in :ref:`versioning
- api <versioning-api>`. Preconfigured parameter should just be omitted from
- :ref:`migrate <command-line-usage>` command.
-
-
-Making schema changes
-=====================
-
-All changes to a database schema under version control should be done via
-change scripts - you should avoid schema modifications (creating tables, etc.)
-outside of change scripts. This allows you to determine what the schema looks
-like based on the version number alone, and helps ensure multiple databases
-you're working with are consistent.
-
-Create a change script
-----------------------
-
-Our first change script will create a simple table
-
-.. code-block:: python
-
- account = Table(
- 'account', meta,
- Column('id', Integer, primary_key=True),
- Column('login', String(40)),
- Column('passwd', String(40)),
- )
-
-This table should be created in a change script. Let's create one::
-
- $ python manage.py script "Add account table"
-
-This creates an empty change script at
-:file:`my_repository/versions/001_Add_account_table.py`. Next, we'll
-edit this script to create our table.
-
-
-Edit the change script
-----------------------
-
-Our change script predefines two functions, currently empty:
-:py:func:`upgrade` and :py:func:`downgrade`. We'll fill those in:
-
-.. code-block:: python
-
- from sqlalchemy import Table, Column, Integer, String, MetaData
-
- meta = MetaData()
-
- account = Table(
- 'account', meta,
- Column('id', Integer, primary_key=True),
- Column('login', String(40)),
- Column('passwd', String(40)),
- )
-
-
- def upgrade(migrate_engine):
- meta.bind = migrate_engine
- account.create()
-
-
- def downgrade(migrate_engine):
- meta.bind = migrate_engine
- account.drop()
-
-.. note::
-
- The generated script contains * imports from sqlalchemy and migrate. You
- should tailor the imports to fit your actual demand.
-
-As you might have guessed, :py:func:`upgrade` upgrades the database to the next
-version. This function should contain the :ref:`schema changes
-<changeset-system>` we want to perform (in our example we're creating a
-table).
-
-:py:func:`downgrade` should reverse changes made by :py:func:`upgrade`. You'll
-need to write both functions for every change script. (Well, you don't *have*
-to write downgrade, but you won't be able to revert to an older version of the
-database or test your scripts without it.) If you really don't want to support
-downgrades it is a good idea to raise a :py:class:`NotImplementedError` or some
-equivalent custom exception. If you let :py:func:`downgrade` pass silently you
-might observe undesired behaviour for subsequent downgrade operations if
-downgrading multiple :term:`versions <version>`.
-
-
-.. note::
-
- As you can see, **migrate_engine** is passed to both functions. You should
- use this in your change scripts, rather than creating your own engine.
-
-.. warning::
-
- You should be very careful about importing files from the rest of your
- application, as your change scripts might break when your application
- changes. Read more about `writing scripts with consistent behavior`_.
-
-
-Test the change script
-------------------------
-
-Change scripts should be tested before they are committed. Testing a script
-will run its :func:`upgrade` and :func:`downgrade` functions on a specified
-database; you can ensure the script runs without error. You should be testing
-on a test database - if something goes wrong here, you'll need to correct it by
-hand. If the test is successful, the database should appear unchanged after
-:func:`upgrade` and :func:`downgrade` run.
-
-To test the script::
-
- $ python manage.py test
- Upgrading... done
- Downgrading... done
- Success
-
-Our script runs on our database (:file:`sqlite:///project.db`, as specified in
-:file:`manage.py`) without any errors.
-
-Our :term:`repository's <repository>` :term:`version` is::
-
- $ python manage.py version
- 1
-
-.. note::
-
- Due to #41 the database must be exactly one :term:`version` behind the
- :term:`repository` :term:`version`.
-
-.. _production testing warning:
-
-.. warning::
-
- The :option:`test` command executes actual scripts, be sure you are *NOT*
- doing this on production database.
-
- If you need to test production changes you should:
-
- #. get a dump of your production database
- #. import the dump into an empty database
- #. run :option:`test` or :option:`upgrade` on that copy
-
-
-Upgrade the database
---------------------
-
-Now, we can apply this change script to our database::
-
- $ python manage.py upgrade
- 0 -> 1...
- done
-
-This upgrades the database (:file:`sqlite:///project.db`, as specified when we
-created :file:`manage.py` above) to the latest available :term:`version`. (We
-could also specify a version number if we wished, using the :option:`--version`
-option.) We can see the database's :term:`version` number has changed, and our
-table has been created::
-
- $ python manage.py db_version
- 1
- $ sqlite3 project.db
- sqlite> .tables
- account migrate_version
- sqlite> .schema account
- CREATE TABLE account (
- id INTEGER NOT NULL,
- login VARCHAR(40),
- passwd VARCHAR(40),
- PRIMARY KEY (id)
- );
-
-Our account table was created - success!
-
-Modifying existing tables
--------------------------
-
-After we have initialized the database schema we now want to add another Column
-to the `account` table that we already have in our schema.
-
-First start a new :term:`changeset` by the commands learned above::
-
- $ python manage.py script "Add email column"
-
-This creates a new :term:`changeset` template. Edit the resulting script
-:file:`my_repository/versions/002_Add_email_column.py`:
-
-.. code-block:: python
-
- from sqlalchemy import Table, MetaData, String, Column
-
-
- def upgrade(migrate_engine):
- meta = MetaData(bind=migrate_engine)
- account = Table('account', meta, autoload=True)
- emailc = Column('email', String(128))
- emailc.create(account)
-
-
- def downgrade(migrate_engine):
- meta = MetaData(bind=migrate_engine)
- account = Table('account', meta, autoload=True)
- account.c.email.drop()
-
-As we can see in this example we can (and should) use SQLAlchemy's schema
-reflection (autoload) mechanism to reference existing schema objects. We could
-have defined the table objects as they are expected before upgrade or downgrade
-as well but this would have been more work and is not as convenient.
-
-We can now apply the changeset to :file:`sqlite:///project.db`::
-
- $ python manage.py upgrade
- 1 -> 2...
- done
-
-and get the following expected result::
-
- $ sqlite3 project.db
- sqlite> .schema account
- CREATE TABLE account (
- id INTEGER NOT NULL,
- login VARCHAR(40),
- passwd VARCHAR(40), email VARCHAR(128),
- PRIMARY KEY (id)
- );
-
-
-Writing change scripts
-======================
-
-As our application evolves, we can create more change scripts using a similar
-process.
-
-By default, change scripts may do anything any other SQLAlchemy program can do.
-
-SQLAlchemy Migrate extends SQLAlchemy with several operations used to change
-existing schemas - ie. ``ALTER TABLE`` stuff. See :ref:`changeset
-<changeset-system>` documentation for details.
-
-
-Writing scripts with consistent behavior
-----------------------------------------
-
-Normally, it's important to write change scripts in a way that's independent of
-your application - the same SQL should be generated every time, despite any
-changes to your app's source code. You don't want your change scripts' behavior
-changing when your source code does.
-
-.. warning::
-
- **Consider the following example of what NOT to do**
-
- Let's say your application defines a table in the :file:`model.py` file:
-
- .. code-block:: python
-
- from sqlalchemy import *
-
- meta = MetaData()
- table = Table('mytable', meta,
- Column('id', Integer, primary_key=True),
- )
-
- ... and uses this file to create a table in a change script:
-
- .. code-block:: python
-
- from sqlalchemy import *
- from migrate import *
- import model
-
- def upgrade(migrate_engine):
- model.meta.bind = migrate_engine
-
- def downgrade(migrate_engine):
- model.meta.bind = migrate_engine
- model.table.drop()
-
- This runs successfully the first time. But what happens if we change the
- table definition in :file:`model.py`?
-
- .. code-block:: python
-
- from sqlalchemy import *
-
- meta = MetaData()
- table = Table('mytable', meta,
- Column('id', Integer, primary_key=True),
- Column('data', String(42)),
- )
-
- We'll create a new column with a matching change script
-
- .. code-block:: python
-
- from sqlalchemy import *
- from migrate import *
- import model
-
- def upgrade(migrate_engine):
- model.meta.bind = migrate_engine
- model.table.create()
-
- def downgrade(migrate_engine):
- model.meta.bind = migrate_engine
- model.table.drop()
-
- This appears to run fine when upgrading an existing database - but the
- first script's behavior changed! Running all our change scripts on a new
- database will result in an error - the first script creates the table based
- on the new definition, with both columns; the second cannot add the column
- because it already exists.
-
- To avoid the above problem, you should use SQLAlchemy schema reflection as
- shown above or copy-paste your table definition into each change script
- rather than importing parts of your application.
-
- .. note::
- Sometimes it is enough to just reflect tables with SQLAlchemy instead
- of copy-pasting - but remember, explicit is better than implicit!
-
-
-Writing for a specific database
--------------------------------
-
-Sometimes you need to write code for a specific database. Migrate scripts can
-run under any database, however - the engine you're given might belong to any
-database. Use engine.name to get the name of the database you're working with
-
-.. code-block:: python
-
- >>> from sqlalchemy import *
- >>> from migrate import *
- >>>
- >>> engine = create_engine('sqlite:///:memory:')
- >>> engine.name
- 'sqlite'
-
-
-Writings .sql scripts
----------------------
-
-You might prefer to write your change scripts in SQL, as .sql files, rather
-than as Python scripts. SQLAlchemy-migrate can work with that::
-
- $ python manage.py version
- 1
- $ python manage.py script_sql postgresql
-
-This creates two scripts
-:file:`my_repository/versions/002_postgresql_upgrade.sql` and
-:file:`my_repository/versions/002_postgresql_downgrade.sql`, one for each
-*operation*, or function defined in a Python change script - upgrade and
-downgrade. Both are specified to run with PostgreSQL databases - we can add
-more for different databases if we like. Any database defined by SQLAlchemy may
-be used here - ex. sqlite, postgresql, oracle, mysql...
-
-
-.. _command-line-usage:
-
-Command line usage
-==================
-
-.. currentmodule:: migrate.versioning.shell
-
-:command:`migrate` command is used for API interface. For list of commands and
-help use::
-
- $ migrate --help
-
-:command:`migrate` command executes :func:`main` function.
-For ease of usage, generate your own :ref:`project management script
-<project_management_script>`, which calls :func:`main
-<migrate.versioning.shell.main>` function with keywords arguments. You may want
-to specify `url` and `repository` arguments which almost all API functions
-require.
-
-If api command looks like::
-
- $ migrate downgrade URL REPOSITORY VERSION [--preview_sql|--preview_py]
-
-and you have a project management script that looks like
-
-.. code-block:: python
-
- from migrate.versioning.shell import main
-
- main(url='sqlite://', repository='./project/migrations/')
-
-you have first two slots filed, and command line usage would look like::
-
- # preview Python script
- $ migrate downgrade 2 --preview_py
-
- # downgrade to version 2
- $ migrate downgrade 2
-
-.. versionchanged:: 0.5.4
- Command line parsing refactored: positional parameters usage
-
-Whole command line parsing was rewriten from scratch with use of OptionParser.
-Options passed as kwargs to :func:`~migrate.versioning.shell.main` are now
-parsed correctly. Options are passed to commands in the following priority
-(starting from highest):
-
-- optional (given by :option:`--some_option` in commandline)
-- positional arguments
-- kwargs passed to :func:`migrate.versioning.shell.main`
-
-
-Python API
-==========
-
-.. currentmodule:: migrate.versioning.api
-
-All commands available from the command line are also available for
-your Python scripts by importing :mod:`migrate.versioning.api`. See the
-:mod:`migrate.versioning.api` documentation for a list of functions;
-function names match equivalent shell commands. You can use this to
-help integrate SQLAlchemy Migrate with your existing update process.
-
-For example, the following commands are similar:
-
-*From the command line*::
-
- $ migrate help help
- /usr/bin/migrate help COMMAND
-
- Displays help on a given command.
-
-*From Python*
-
-.. code-block:: python
-
- import migrate.versioning.api
- migrate.versioning.api.help('help')
- # Output:
- # %prog help COMMAND
- #
- # Displays help on a given command.
-
-
-.. _migrate.versioning.api: module-migrate.versioning.api.html
-
-.. _repository_configuration:
-
-
-Experimental commands
-=====================
-
-Some interesting new features to create SQLAlchemy db models from existing
-databases and vice versa were developed by Christian Simms during the
-development of SQLAlchemy-migrate 0.4.5. These features are roughly documented
-in a `thread in migrate-users`_.
-
-.. _`thread in migrate-users`:
- http://groups.google.com/group/migrate-users/browse_thread/thread/a5605184e08abf33#msg_85c803b71b29993f
-
-Here are the commands' descriptions as given by ``migrate help <command>``:
-
-- ``compare_model_to_db``: Compare the current model (assumed to be a
- module level variable of type sqlalchemy.MetaData) against the
- current database.
-- ``create_model``: Dump the current database as a Python model to
- stdout.
-- ``make_update_script_for_model``: Create a script changing the old
- Python model to the new (current) Python model, sending to stdout.
-
-As this sections headline says: These features are *EXPERIMENTAL*. Take the
-necessary arguments to the commands from the output of ``migrate
-help <command>``.
-
-
-Repository configuration
-========================
-
-SQLAlchemy-migrate :term:`repositories <repository>` can be configured in their
-:file:`migrate.cfg` files. The initial configuration is performed by the
-`migrate create` call explained in :ref:`Create a change repository
-<create_change_repository>`. The following options are available currently:
-
-- :option:`repository_id` Used to identify which repository this database is
- versioned under. You can use the name of your project.
-- :option:`version_table` The name of the database table used to track the
- schema version. This name shouldn't already be used by your project. If this
- is changed once a database is under version control, you'll need to change
- the table name in each database too.
-- :option:`required_dbs` When committing a change script, SQLAlchemy-migrate
- will attempt to generate the sql for all supported databases; normally, if
- one of them fails - probably because you don't have that database installed -
- it is ignored and the commit continues, perhaps ending successfully.
- Databases in this list MUST compile successfully during a commit, or the
- entire commit will fail. List the databases your application will actually be
- using to ensure your updates to that database work properly. This must be a
- list; example: `['postgres', 'sqlite']`
-- :option:`use_timestamp_numbering` When creating new change scripts, Migrate
- will stamp the new script with a version number. By default this is
- latest_version + 1. You can set this to 'true' to tell Migrate to use the UTC
- timestamp instead.
-
- .. versionadded:: 0.7.2
-
-.. _custom-templates:
-
-
-Customize templates
-===================
-
-Users can pass ``templates_path`` to API functions to provide customized
-templates path. Path should be a collection of templates, like
-``migrate.versioning.templates`` package directory.
-
-One may also want to specify custom themes. API functions accept
-``templates_theme`` for this purpose (which defaults to `default`)
-
-Example::
-
- /home/user/templates/manage $ ls
- default.py_tmpl
- pylons.py_tmpl
-
- /home/user/templates/manage $ migrate manage manage.py --templates_path=/home/user/templates --templates_theme=pylons
-
-.. versionadded:: 0.6.0