Dear Michael, I have been trying to add row versioning to our existing multi-table polymorphism setup and I think I may have found an issue that I hope you can help with.
The following example appears to generate invalid SQL when it tries to update the base class table, but there are no columns to fetch; it just wants to get the current version number: from sqlalchemy import Column, FetchedValue, ForeignKey, Integer, Text, create_engine from sqlalchemy.ext.declarative import declarative_base, declared_attr from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.attributes import InstrumentedAttribute Base = declarative_base() class Animal(Base): __tablename__ = 'animal' id = Column(Integer, primary_key=True) name = Column(Text) species = Column(Text) _version = Column("xmin", Integer, server_default=FetchedValue(), server_onupdate=FetchedValue(), system=True) # _version = Column("xmin", Integer) @declared_attr def __mapper_args__(cls): if isinstance(cls._version, InstrumentedAttribute): version_col = cls._version.property.columns[0] else: version_col = cls._version return { 'polymorphic_on': cls.species, 'polymorphic_identity': cls.__name__, 'version_id_col': version_col, 'version_id_generator': False, } class Dog(Animal): __tablename__ = 'dog' id_animal = Column(Integer, ForeignKey(Animal.id), primary_key=True) toy = Column(Text) engine = create_engine('postgresql://user@host/database') engine.echo = True Base.metadata.create_all(engine) DBSession = sessionmaker(bind=engine) session = DBSession(autocommit=True) with session.begin(): dog = Dog(name="fido") session.add(dog) with session.begin(): dog.toy = 'Bone' Which raises the following exception: File sqlalchemy_issue_repro_2.py, line 59, in : dog.toy = 'Bone' <file:\user!wilsoc!nexus!versioning!sqlalchemy_issue_repro_2.py:59:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 490, in __exit__ : self.rollback() <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:490:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\langhelpers.py, line 60, in __exit__ : compat.reraise(exc_type, exc_value, exc_tb) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\langhelpers.py:60:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 487, in __exit__ : self.commit() <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:487:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 392, in commit : self._prepare_impl() <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:392:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 372, in _prepare_impl : self.session.flush() <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:372:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 2015, in flush : self._flush(objects) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:2015:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 2133, in _flush : transaction.rollback(_capture_exception=True) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:2133:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\langhelpers.py, line 60, in __exit__ : compat.reraise(exc_type, exc_value, exc_tb) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\langhelpers.py:60:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py, line 2097, in _flush : flush_context.execute() <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\session.py:2097:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\unitofwork.py, line 373, in execute : rec.execute(self) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\unitofwork.py:373:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\unitofwork.py, line 532, in execute : uow <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\unitofwork.py:532:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\persistence.py, line 170, in save_obj : mapper, table, update) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\persistence.py:170:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\persistence.py, line 672, in _emit_update_statements : execute(statement, params) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\orm\persistence.py:672:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py, line 914, in execute : return meth(self, multiparams, params) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py:914:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\sql\elements.py, line 323, in _execute_on_connection : return connection._execute_clauseelement(self, multiparams, params) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\sql\elements.py:323:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py, line 1010, in _execute_clauseelement : compiled_sql, distilled_params <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py:1010:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py, line 1146, in _execute_context : context) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py:1146:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py, line 1341, in _handle_dbapi_exception : exc_info <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py:1341:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\compat.py, line 199, in raise_from_cause : reraise(type(exception), exception, tb=exc_tb) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\util\compat.py:199:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py, line 1139, in _execute_context : context) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\base.py:1139:exception> File sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\default.py, line 450, in do_execute : cursor.execute(statement, parameters) <file:\R:\sw\external\20160915-0-64\python27\lib\site-packages\sqlalchemy-1.0.9-py2.7-win-amd64.egg\sqlalchemy\engine\default.py:450:exception> ProgrammingError: (psycopg2.ProgrammingError) syntax error at or near "WHERE" LINE 1: UPDATE animal SET WHERE animal.id = 2 AND animal.xmin = '63... ^ [SQL: 'UPDATE animal SET WHERE animal.id = %(animal_id)s AND animal.xmin = %(animal_xmin)s RETURNING animal.xmin'] [parameters: {'animal_id': 2, 'animal_xmin': '635104190'}] I think the problem is in sqlalchemy\orm\persistence.py, in the _collect_update_commands() function. It tries to generate a dict of columns that need updating, but none of the columns in the base table have changed, so the dict is empty. Normally it would skip updating this table here: elif not (params or value_params): continue But because versioning is enabled, it takes this branch instead: if update_version_id is not None and \ mapper.version_id_col in mapper._cols_by_table[table]: and then tries to execute a null UPDATE just in order to get the new version number, which fails. It would work if we did a dummy update, for example assigning the PK to itself, as this would change the version number (xmin) returned by the server. Keeping the version number on the base table allows it to be shared by all polymorphic subclasses. Thanks in advance for your consideration, Chris Wilson. -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.