Hopefully I'll be able to add this in 1.0 using the patch illustrated at https://bitbucket.org/zzzeek/sqlalchemy/issue/3133/support-dbs-that-magically-get-sql.
On Jul 18, 2014, at 11:54 AM, Michael Bayer <mike...@zzzcomputing.com> wrote: > > 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. -- 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.