I took the SQL into mysql directly to see what may be going wrong, it looks like the generated
SELECT thing.id AS thing_id, thing.type_id AS thing_type_id, thing.txt AS thing_txt, (SELECT type.id FROM type WHERE type.id = thing.type_id) AS _sa_polymorphic_on, thing.stuff AS thing_stuff FROM thing WHERE thing.id = 1 AND (SELECT type.id FROM type WHERE type.id = thing.type_id) IN ('stuff'); returns no rows, the final row of that query should be ... (SELECT type.type FROM type ... which returns one row. I feel like I misconfigured something to generate the incorrect SQL. On Wednesday, February 1, 2017 at 8:14:06 AM UTC-6, Shane Carey wrote: > > I am having a strange issue with merging a polymorphic relationship, I've > managed to reduce my problem to this code: > > from sqlalchemy import * > from sqlalchemy import select, and_, event, inspect > from sqlalchemy.orm import * > from sqlalchemy.ext.declarative import * > > Base = declarative_base() > > class Type(Base): > __tablename__ = 'type' > > id = Column(Integer, primary_key=True) > type = Column(String(8), unique=True) > > class Thing(Base): > __tablename__ = 'thing' > > id = Column(Integer, primary_key=True) > type_id = Column(Integer, ForeignKey(Type.id), nullable=False) > txt = Column(String(8)) > > type = relationship(Type) > tags = relationship('Tag', backref='thing') > > __mapper_args__ = { > 'polymorphic_on': select([Type.id]).where(Type.id == type_id).as_scalar(), > 'with_polymorphic': '*' > } > > @event.listens_for(Thing, 'init', propagate=True) > def set_identity(instance, *args, **kwargs): > mapper = object_mapper(instance) > instance.type_id = select([Type.id]).where(Type.type == > mapper.polymorphic_identity) > > class Stuff(Thing): > stuff = Column(String(8)) > > __mapper_args__ = { > 'polymorphic_identity': 'stuff' > } > > class Junk(Thing): > junk = Column(String(8)) > > __mapper_args__ = { > 'polymorphic_identity': 'junk' > } > > class Tag(Base): > __tablename__ = 'tag' > > id = Column(Integer, primary_key=True) > thing_id = Column(Integer, ForeignKey(Thing.id), nullable=False) > txt = Column(String(8)) > > e = create_engine('mysql+pymysql://username:password@localhost/test', > echo=True) > > Base.metadata.drop_all(e) > Base.metadata.create_all(e) > > s = Session(e) > > s.add_all([Type(type='stuff'), Type(type='junk')]) > > thing = Stuff(id=1, txt='thing1', stuff='stuff1') > tag = Tag(id=1, txt='tag1', thing=thing) > > s.merge(tag) > > s.commit() > > thing = Stuff(id=1, txt='thing2', stuff='stuff2') > tag = Tag(id=1, txt='tag2', thing=thing) > > s.merge(tag) > > s.commit() > > This gives me the error > > sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, > "Duplicate entry '1' for key 'PRIMARY'") [SQL: 'INSERT INTO thing (id, > type_id, txt, stuff) VALUES (%(id)s, (SELECT type.id \nFROM type \nWHERE > type.type = %(type_1)s), %(txt)s, %(stuff)s)'] [parameters: {'txt': > 'thing2', 'id': 1, 'type_1': 'stuff', 'stuff': 'stuff2'}] > > And the full output is > > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES > LIKE 'sql_mode' > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine SELECT > DATABASE() > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine show collation > where `Charset` = 'utf8' and `Collation` = 'utf8_bin' > 2017-02-01 08:07:29,401 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine SELECT > CAST('test plain returns' AS CHAR(60)) AS anon_1 > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine SELECT > CAST('test unicode returns' AS CHAR(60)) AS anon_1 > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine SELECT > CAST('test collated returns' AS CHAR CHARACTER SET utf8) COLLATE utf8_bin > AS anon_1 > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `tag` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `type` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `thing` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `tag` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `type` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine DESCRIBE `thing` > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine ROLLBACK > 2017-02-01 08:07:29,417 INFO sqlalchemy.engine.base.Engine > CREATE TABLE type ( > id INTEGER NOT NULL AUTO_INCREMENT, > type VARCHAR(8), > PRIMARY KEY (id), > UNIQUE (type) > ) > > > 2017-02-01 08:07:29,433 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,479 INFO sqlalchemy.engine.base.Engine COMMIT > 2017-02-01 08:07:29,479 INFO sqlalchemy.engine.base.Engine > CREATE TABLE thing ( > id INTEGER NOT NULL AUTO_INCREMENT, > type_id INTEGER NOT NULL, > txt VARCHAR(8), > stuff VARCHAR(8), > junk VARCHAR(8), > PRIMARY KEY (id), > FOREIGN KEY(type_id) REFERENCES type (id) > ) > > > 2017-02-01 08:07:29,479 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,512 INFO sqlalchemy.engine.base.Engine COMMIT > 2017-02-01 08:07:29,513 INFO sqlalchemy.engine.base.Engine > CREATE TABLE tag ( > id INTEGER NOT NULL AUTO_INCREMENT, > thing_id INTEGER NOT NULL, > txt VARCHAR(8), > PRIMARY KEY (id), > FOREIGN KEY(thing_id) REFERENCES thing (id) > ) > > > 2017-02-01 08:07:29,513 INFO sqlalchemy.engine.base.Engine {} > 2017-02-01 08:07:29,575 INFO sqlalchemy.engine.base.Engine COMMIT > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine INSERT INTO > type (type) VALUES (%(type)s) > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine {'type': > 'stuff'} > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine INSERT INTO > type (type) VALUES (%(type)s) > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine {'type': 'junk'} > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine SELECT tag.id > AS tag_id, tag.thing_id AS tag_thing_id, tag.txt AS tag_txt > FROM tag > WHERE tag.id = %(param_1)s > 2017-02-01 08:07:29,591 INFO sqlalchemy.engine.base.Engine {'param_1': 1} > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine SELECT thing.id > AS thing_id, thing.type_id AS thing_type_id, thing.txt AS thing_txt, > (SELECT type.id > FROM type > WHERE type.id = thing.type_id) AS _sa_polymorphic_on, thing.stuff AS > thing_stuff > FROM thing > WHERE thing.id = %(param_1)s AND (SELECT type.id > FROM type > WHERE type.id = thing.type_id) IN (%(param_2)s) > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine {'param_1': 1, > 'param_2': 'stuff'} > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine INSERT INTO > thing (id, type_id, txt, stuff) VALUES (%(id)s, (SELECT type.id > FROM type > WHERE type.type = %(type_1)s), %(txt)s, %(stuff)s) > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine {'txt': > 'thing1', 'id': 1, 'type_1': 'stuff', 'stuff': 'stuff1'} > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine INSERT INTO tag > (id, thing_id, txt) VALUES (%(id)s, %(thing_id)s, %(txt)s) > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine {'id': 1, > 'txt': 'tag1', 'thing_id': 1} > 2017-02-01 08:07:29,606 INFO sqlalchemy.engine.base.Engine COMMIT > 2017-02-01 08:07:29,622 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) > 2017-02-01 08:07:29,622 INFO sqlalchemy.engine.base.Engine SELECT tag.id > AS tag_id, tag.thing_id AS tag_thing_id, tag.txt AS tag_txt > FROM tag > WHERE tag.id = %(param_1)s > 2017-02-01 08:07:29,622 INFO sqlalchemy.engine.base.Engine {'param_1': 1} > 2017-02-01 08:07:29,622 INFO sqlalchemy.engine.base.Engine SELECT thing.id > AS thing_id, thing.type_id AS thing_type_id, thing.txt AS thing_txt, > (SELECT type.id > FROM type > WHERE type.id = thing.type_id) AS _sa_polymorphic_on, thing.stuff AS > thing_stuff > FROM thing > WHERE thing.id = %(param_1)s AND (SELECT type.id > FROM type > WHERE type.id = thing.type_id) IN (%(param_2)s) > 2017-02-01 08:07:29,622 INFO sqlalchemy.engine.base.Engine {'param_1': 1, > 'param_2': 'stuff'} > 2017-02-01 08:07:29,637 INFO sqlalchemy.engine.base.Engine INSERT INTO > thing (id, type_id, txt, stuff) VALUES (%(id)s, (SELECT type.id > FROM type > WHERE type.type = %(type_1)s), %(txt)s, %(stuff)s) > 2017-02-01 08:07:29,637 INFO sqlalchemy.engine.base.Engine {'txt': > 'thing2', 'id': 1, 'type_1': 'stuff', 'stuff': 'stuff2'} > 2017-02-01 08:07:29,637 INFO sqlalchemy.engine.base.Engine ROLLBACK > C:\Python35\lib\site-packages\pymysql\cursors.py:166: Warning: (1292, > "Truncated incorrect DOUBLE value: 'stuff'") > result = self._query(query) > Traceback (most recent call last): > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 1182, in _execute_context > context) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\default.py", line > 462, in do_execute > cursor.execute(statement, parameters) > File "C:\Python35\lib\site-packages\pymysql\cursors.py", line 166, in > execute > result = self._query(query) > File "C:\Python35\lib\site-packages\pymysql\cursors.py", line 322, in > _query > conn.query(q) > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 835, > in query > self._affected_rows = self._read_query_result(unbuffered=unbuffered) > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 1019, > in _read_query_result > result.read() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 1302, > in read > first_packet = self.connection._read_packet() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 981, > in _read_packet > packet.check_error() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 393, > in check_error > err.raise_mysql_exception(self._data) > File "C:\Python35\lib\site-packages\pymysql\err.py", line 107, in > raise_mysql_exception > raise errorclass(errno, errval) > pymysql.err.IntegrityError: (1062, "Duplicate entry '1' for key 'PRIMARY'") > > The above exception was the direct cause of the following exception: > > Traceback (most recent call last): > File "sqlalchemy_casc_backref_test.py", line 76, in <module> > s.commit() > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 874, in commit > self.transaction.commit() > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 461, in commit > self._prepare_impl() > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 441, in _prepare_impl > self.session.flush() > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 2137, in flush > self._flush(objects) > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 2257, in _flush > transaction.rollback(_capture_exception=True) > File "C:\Python35\lib\site-packages\sqlalchemy\util\langhelpers.py", > line 60, in __exit__ > compat.reraise(exc_type, exc_value, exc_tb) > File "C:\Python35\lib\site-packages\sqlalchemy\util\compat.py", line > 186, in reraise > raise value > File "C:\Python35\lib\site-packages\sqlalchemy\orm\session.py", line > 2221, in _flush > flush_context.execute() > File "C:\Python35\lib\site-packages\sqlalchemy\orm\unitofwork.py", line > 389, in execute > rec.execute(self) > File "C:\Python35\lib\site-packages\sqlalchemy\orm\unitofwork.py", line > 548, in execute > uow > File "C:\Python35\lib\site-packages\sqlalchemy\orm\persistence.py", line > 176, in save_obj > mapper, table, insert) > File "C:\Python35\lib\site-packages\sqlalchemy\orm\persistence.py", line > 827, in _emit_insert_statements > params) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 945, in execute > return meth(self, multiparams, params) > File "C:\Python35\lib\site-packages\sqlalchemy\sql\elements.py", line > 263, in _execute_on_connection > return connection._execute_clauseelement(self, multiparams, params) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 1053, in _execute_clauseelement > compiled_sql, distilled_params > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 1189, in _execute_context > context) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 1393, in _handle_dbapi_exception > exc_info > File "C:\Python35\lib\site-packages\sqlalchemy\util\compat.py", line > 202, in raise_from_cause > reraise(type(exception), exception, tb=exc_tb, cause=cause) > File "C:\Python35\lib\site-packages\sqlalchemy\util\compat.py", line > 185, in reraise > raise value.with_traceback(tb) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\base.py", line > 1182, in _execute_context > context) > File "C:\Python35\lib\site-packages\sqlalchemy\engine\default.py", line > 462, in do_execute > cursor.execute(statement, parameters) > File "C:\Python35\lib\site-packages\pymysql\cursors.py", line 166, in > execute > result = self._query(query) > File "C:\Python35\lib\site-packages\pymysql\cursors.py", line 322, in > _query > conn.query(q) > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 835, > in query > self._affected_rows = self._read_query_result(unbuffered=unbuffered) > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 1019, > in _read_query_result > result.read() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 1302, > in read > first_packet = self.connection._read_packet() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 981, > in _read_packet > packet.check_error() > File "C:\Python35\lib\site-packages\pymysql\connections.py", line 393, > in check_error > err.raise_mysql_exception(self._data) > File "C:\Python35\lib\site-packages\pymysql\err.py", line 107, in > raise_mysql_exception > raise errorclass(errno, errval) > sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, > "Duplicate entry '1' for key 'PRIMARY'") [SQL: 'INSERT INTO thing (id, > type_id, txt, stuff) VALUES (%(id)s, (SELECT type.id \nFROM type \nWHERE > type.type = %(type_1)s), %(txt)s, %(stuff)s)'] [parameters: {'txt': > 'thing2', 'id': 1, 'type_1': 'stuff', 'stuff': 'stuff2'}] > > Why is the second merge generating an INSERT statement? I can see where > merge actually selects the appropriate Thing with id = 1 from the database, > but it still generates the INSERT rather than update. If I remove all > polymorphism from the experiment, this does not occur. > > > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- 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.