Hi all,
I just discovered a bit of odd behaviour in SQLAlchemy that affects
Eventframe. This is
very important if you use Eventframe, so please read.
Eventframe makes use of a concept known as "
joined table inheritance" to construct its data types – pages, posts, fragments – off a base "node" type. Eventframe also uses mixin classes defined in
coaster.sqlalchemy to make class declarations simpler.
Turns out SQLAlchemy has some odd behaviour in the way mixins work with joined table inheritance. The essential construct is like this:
"""
class NameMixin(object):
id = Column(Integer, primary_key=True)
name = Column(Unicode(250), nullable=False)
title = Column(Unicode(250), nullable=False, default=u'')
class Node(NameMixin, Base):
type = Column(Unicode(20))
__mapper_args__ = {'polymorphic_on': type, 'polymorphic_identity': 'node'}
class Page(Node):
id = Column(Integer, ForeignKey('node.id'), primary_key=True, nullable=False) __mapper_args__ = {'polymorphic_identity': 'node'}
"""
When a class inherits from another, SQLAlchemy looks for columns in the base class and copies them into the subclass to construct an SQL table. However, when it finds a __mapper_args__ declaration with a polymorphic identity, it does not copy columns. Instead, it runs db queries against both classes, storing part of the data in the base class's table and part in the subclass's table. The way this is handled is completely transparent to you as the developer.
It turns out there is a bug here, or at least some undocumented behaviour. If the base class has columns that it acquired via a mixin, the subclass also gets them. When you write a value into this column, the value is stored to both tables. In the example above, Page is supposed to have a single column, "id". However, it will also get "name" and "title" from NameMixin.
The one app that is affected is Eventframe. All nodes (except the "node" table itself) now have four extra columns:
- created_at
- updated_at
- name
- title
If you update coaster, SQLAlchemy will no longer expect to find these tables. However, all four tables have a NOT NULL constraint, so you can no longer create new nodes. The solution is to drop these columns from these tables:
- event
- fragment
- funnel_link
- list
- map
- page
- participant_list
- post
- redirect
The final kicker: if you use SQLite for development, you can't drop columns. SQLite's
ALTER TABLE statement only supports renaming tables and adding new columns. The solution is to dump the data, remove the database, let Eventframe create a new db, edit the SQL dump to drop data for the columns, and re-import. This is messy, but there's no other way.
Let me know if you need help with your deployment.
Best,
Kiran
--
Kiran Jonnalagadda