From: Mike Bayer Date: Sun, 2 Feb 2014 22:48:15 +0000 (-0500) Subject: - add new section "the importance of naming constraints" X-Git-Tag: rel_0_6_3~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e93f2d3b7dbb4a7fe4dafcdab2f5967e8a915d9c;p=thirdparty%2Fsqlalchemy%2Falembic.git - add new section "the importance of naming constraints" - add a note that a new branching approach is planned, remove note about "splice" --- diff --git a/docs/build/tutorial.rst b/docs/build/tutorial.rst index fdab0758..b0e69e87 100644 --- a/docs/build/tutorial.rst +++ b/docs/build/tutorial.rst @@ -610,6 +610,7 @@ Autogenerate can't currently, but will *eventually* detect: like CHECK and FOREIGN KEY - these are not fully implemented. * Sequence additions, removals - not yet implemented. + .. _autogen_render_types: Rendering Custom Types in Autogenerate @@ -861,6 +862,157 @@ this within the ``run_migrations_offline()`` function:: else: run_migrations_online() +.. _tutorial_constraint_names: + +The Importance of Naming Constraints +==================================== + +An important topic worth mentioning is that of constraint naming conventions. +As we've proceeded here, we've talked about adding tables and columns, and +we've also hinted at lots of other operations listed in :ref:`ops` such as those +which support adding or dropping constraints like foreign keys and unique +constraints. The way these constraints are referred to in migration scripts +is by name, however these names by default are in most cases generated by +the relational database in use, when the constraint is created. For example, +if you emitted two CREATE TABLE statements like this on Postgresql:: + + test=> CREATE TABLE user_account (id INTEGER PRIMARY KEY); + CREATE TABLE + test=> CREATE TABLE user_order ( + test(> id INTEGER PRIMARY KEY, + test(> user_account_id INTEGER REFERENCES user_account(id)); + CREATE TABLE + +Suppose we wanted to DROP the REFERENCES that we just applied to the +``user_order.user_account_id`` column, how do we do that? At the prompt, +we'd use ``ALTER TABLE DROP CONSTRAINT ``, or if +using Alembic we'd be using :meth:`.Operations.drop_constraint`. But both +of those functions need a name - what's the name of this constraint? + +It does have a name, which in this case we can figure out by looking at the +Postgresql catalog tables:: + + test=> SELECT r.conname FROM + test-> pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + test-> JOIN pg_catalog.pg_constraint r ON c.oid = r.conrelid + test-> WHERE c.relname='user_order' AND r.contype = 'f' + test-> ; + conname + --------------------------------- + user_order_user_account_id_fkey + (1 row) + +The name above is not something that Alembic or SQLAlchemy created; +``user_order_user_account_id_fkey`` is a naming scheme used internally by +Postgresql to name constraints that are otherwise not named. + +This scheme doesn't seem so complicated, and we might want to just use our +knowledge of it so that we know what name to use for our +:meth:`.Operations.drop_constraint` call. But is that a good idea? What +if for example we needed our code to run on Oracle as well. OK, certainly +Oracle uses this same scheme, right? Or if not, something similar. Let's +check:: + + Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production + + SQL> CREATE TABLE user_account (id INTEGER PRIMARY KEY); + + Table created. + + SQL> CREATE TABLE user_order ( + 2 id INTEGER PRIMARY KEY, + 3 user_account_id INTEGER REFERENCES user_account(id)); + + Table created. + + SQL> SELECT constraint_name FROM all_constraints WHERE + 2 table_name='USER_ORDER' AND constraint_type in ('R'); + + CONSTRAINT_NAME + ----------------------------------------------------- + SYS_C0029334 + +Oh, we can see that is.....much worse. Oracle's names are entirely unpredictable +alphanumeric codes, and this will make being able to write migrations +quite tedious, as we'd need to look up all these names. + +The solution to having to look up names is to make your own names. This is +an easy, though tedious thing to do manually. For example, to create our model +in SQLAlchemy ensuring we use names for foreign key constraints would look like:: + + from sqlalchemy import MetaData, Table, Column, Integer, ForeignKey + + meta = MetaData() + + user_account = Table('user_account', meta, + Column('id', Integer, primary_key=True) + ) + + user_order = Table('user_order', meta, + Column('id', Integer, primary_key=True), + Column('user_order_id', Integer, + ForeignKey('user_account.id', name='fk_user_order_id')) + ) + +Simple enough, though this has some disadvantages. The first is that it's tedious; +we need to remember to use a name for every :class:`~sqlalchemy.schema.ForeignKey` object, +not to mention every :class:`~sqlalchemy.schema.UniqueConstraint`, :class:`~sqlalchemy.schema.CheckConstraint`, +:class:`~sqlalchemy.schema.Index`, and maybe even :class:`~sqlalchemy.schema.PrimaryKeyConstraint` +as well if we wish to be able to alter those too, and beyond all that, all the +names have to be globally unique. Even with all that effort, if we have a naming scheme in mind, +it's easy to get it wrong when doing it manually each time. + +What's worse is that manually naming constraints (and indexes) gets even more +tedious in that we can no longer use convenience features such as the ``.unique=True`` +or ``.index=True`` flag on :class:`~sqlalchemy.schema.Column`:: + + user_account = Table('user_account', meta, + Column('id', Integer, primary_key=True), + Column('name', String(50), unique=True) + ) + +Above, the ``unique=True`` flag creates a :class:`~sqlalchemy.schema.UniqueConstraint`, but again, +it's not named. If we want to name it, manually we have to forego the usage +of ``unique=True`` and type out the whole constraint:: + + user_account = Table('user_account', meta, + Column('id', Integer, primary_key=True), + Column('name', String(50)), + UniqueConstraint('name', name='uq_user_account_name') + ) + +There's a solution to all this naming work, which is to use an **automated +naming convention**. For some years, SQLAlchemy has encourgaged the use of +DDL Events in order to create naming schemes. The :meth:`~sqlalchemy.events.DDLEvents.after_parent_attach` +event in particular is the best place to intercept when :class:`~sqlalchemy.schema.Constraint` +and :class:`~sqlalchemy.schema.Index` objects are being associated with a parent +:class:`~sqlalchemy.schema.Table` object, and to assign a ``.name`` to the constraint while making +use of the name of the table and associated columns. + +But there is also a better way to go, which is to make use of a feature +new in SQLAlchemy 0.9.2 which makes use of the events behind the scenes known as +:paramref:`~sqlalchemy.schema.MetaData.naming_convention`. Here, we can +create a new :class:`~sqlalchemy.schema.MetaData` object while passing a dictionary referring +to a naming scheme:: + + convention = { + "ix": 'ix_%(column_0_label)s', + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" + } + + metadata = MetaData(naming_convention=convention) + +If we define our models using a :class:`~sqlalchemy.schema.MetaData` as above, the given +naming convention dictionary will be used to provide names for all constraints +and indexes. + +For more detail on the naming convention feature, see :ref:`sqla:constraint_naming_conventions`. + + + Working with Branches ===================== @@ -923,6 +1075,13 @@ We resolve this branch by editing the files to be in a straight line. In this # changed from 1975ea83b712 down_revision = 'ae1027a6acf' +.. sidebar:: The future of Branches + + As of this writing, a new approach to branching has been planned. When + implemented, the task of manually splicing files into a line will no longer + be needed; instead, a simple command along the lines of ``alembic merge`` + will be able to produce merges of migration files. Keep a lookout + for future Alembic versions! The ``branches`` command then shows no branches:: @@ -937,8 +1096,6 @@ And the history is similarly linear:: 1975ea83b712 -> ae1027a6acf, add a column None -> 1975ea83b712, add account table -.. note:: A future command called ``splice`` will automate this process. - .. _building_uptodate: