On Jul 18, 2014, at 4:33 AM, Johannes Klug <klug.johan...@gmail.com> wrote:
> > results in the stacktrace: > > InvalidRequestError: Instance <Foo at 0x224f710> cannot be refreshed - it's > not persistent and does not contain a full primary key. so I hate to be a stickler here but that's not a stack trace, that's an error message. if we reproduce your full script we can see what is going on. First, always use echo=True to see what's happening. We can see that the statement does in fact work as planned: INSERT INTO foo (pk, bar) VALUES ((SELECT coalesce(max(foo.pk) + ?, ?) AS coalesce_1 however, the error then happens later on, with this partial trace: File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/unitofwork.py", line 389, in finalize_flush_changes self.session._register_newly_persistent(other) File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1408, in _register_newly_persistent instance_key = mapper._identity_key_from_state(state) File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/mapper.py", line 2285, in _identity_key_from_state for col in self.primary_key File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 580, in get value = callable_(state, passive) File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/state.py", line 423, in __call__ self.manager.deferred_scalar_loader(self, toload) File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/loading.py", line 597, in load_scalar_attributes "contain a full primary key." % state_str(state)) sqlalchemy.exc.InvalidRequestError: Instance <Foo at 0x10165d510> cannot be refreshed - it's not persistent and does not contain a full primary key. we can see the SQLAlchemy ORM, like all ORMs, needs to know the primary key of this newly inserted object - "identity key from state". SQLAlchemy is failing pretty ungracefully here. This could be improved. To understand the full picture here is pretty complicated because there are a lot of nooks and crannies to the reality here so I will try to bullet them out: 1. Typically, it's not possible to avoid a failure here. SQLite does not support RETURNING, so the only way we can get at the value when it is done inline like this is through cursor.lastrowid. Cursor.lastrowid typically only gives us the value that was generated using the database's sequencing mechanism. On MySQL, we would have to use an AUTOINCREMENT column; we get a zero back from lastrowid instead. 2. Apparently, based on a short test, recent pysqlite is giving us the actual value here in cursor.lastrowid. I've never seen a DBAPI do this before and SQLAlchemy would need additional enhancements to distinguish between databases where lastrowid only works for auto increment values, and lastrowid that actually gives you the value no matter where it came from. That's not available right now. 3. If we run this script as is on a database that supports RETURNING, the whole thing works just fine. Here's Postgresql: INSERT INTO foo (pk, bar) VALUES ((SELECT coalesce(max(foo.pk) + %(max_1)s, %(param_1)s) AS coalesce_1 FROM foo), %(bar)s) RETURNING foo.pk 4. So for now, for ORM use (which requires the PK, as opposed to Core where this isn't needed since there is no object to track), to use this pattern on a lastrowid backend the statement needs to be invoked outside of the INSERT. We can achieve this using ColumnDefault: class Foo(Base): __tablename__ = 'foo' pk = Column(Integer, primary_key=True) bar = Column(Integer) Foo.__table__.c.pk.default = ColumnDefault(sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)]).as_scalar()) Where we'll see: SELECT (SELECT coalesce(max(foo.pk) + ?, ?) AS coalesce_1 FROM foo) AS anon_1 (1, 1) INSERT INTO foo (pk, bar) VALUES (?, ?) (1, None) This will work on any backend. Or if the "per instance" system is needed, just run the statement: foo = Foo() foo.pk = session.scalar(sql.select([sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1)])) session.add(foo) session.commit() -- 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 http://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.