Ah, crap, you're totally right. The modified datetime in my application is 
only in one of the tables in the joined table object, so my update really 
never made any sense.

I think perhaps a serializable transaction is our best bet here. This 
should not be a frequent or expensive operation, but it does need to make 
certain it's an update from the most recent record.

Just for the record, I did know the SET was the problem in the last 
example, it was just an example derived from the 2nd to show the intent. So 
I'm only like 90% an idiot.

Thanks a lot as always.

Douglas

On Thursday, 23 June 2016 15:25:19 UTC-4, Mike Bayer wrote:
>
>
>
> On 06/23/2016 02:42 PM, Douglas Russell wrote: 
> > Hi, 
> > 
> > I have a situation where I need to check a condition during an update. 
> > In my case it is a timestamp and the function of the condition is to 
> > ensure that there was no intermittent update by another between the type 
> > the object was edited and when it was saved. In this example though, I 
> > just use a string as datetimes are harder to read: 
> > 
> > Usually I do these updates in the ORM. Here is an example where I update 
> > the author name. 
> > 
> > | 
> > import sqlalchemy as sa 
> > from sqlalchemy.ext.declarative import declarative_base 
> > from sqlalchemy.orm import scoped_session, sessionmaker, relationship 
> > 
> > engine = sa.create_engine('sqlite:///:memory:', echo=False) 
> > session = scoped_session(sessionmaker(bind=engine)) 
> > Base = declarative_base() 
> > 
> > 
> > class Author(Base): 
> >     __tablename__ = 'authors' 
> >     id = sa.Column(sa.Integer, primary_key=True) 
> >     name = sa.Column(sa.String) 
> > 
> >     def __repr__(self): 
> >         return '<Author(name={self.name!r})>'.format(self=self) 
> > 
> > Base.metadata.create_all(engine) 
> > 
> > author = Author(name='Someone') 
> > session.add(author) 
> > session.commit() 
> > print author 
> > # <Author(name=u'Someone')> (Correct) 
> > 
> > # Update the author via ORM 
> > author.name = 'Someone else' 
> > session.commit() 
> > print author 
> > # <Author(name=u'Someone else')> (Correct) 
> > | 
> > 
> > Now lets say that I want to make sure that I only update the author name 
> > if the previous value was what I think it was. As far as I can tell, it 
> > is not possible to do this with the ORM. So using core: 
> > 
> > | 
> > import sqlalchemy as sa 
> > from sqlalchemy.ext.declarative import declarative_base 
> > from sqlalchemy.orm import scoped_session, sessionmaker, relationship 
> > 
> > engine = sa.create_engine('sqlite:///:memory:', echo=False) 
> > session = scoped_session(sessionmaker(bind=engine)) 
> > Base = declarative_base() 
> > 
> > 
> > class Author(Base): 
> >     __tablename__ = 'authors' 
> >     id = sa.Column(sa.Integer, primary_key=True) 
> >     name = sa.Column(sa.String) 
> > 
> >     def __repr__(self): 
> >         return '<Author(name={self.name!r})>'.format(self=self) 
> > 
> > Base.metadata.create_all(engine) 
> > 
> > author = Author(name='Someone') 
> > session.add(author) 
> > session.commit() 
> > print author 
> > 
> > # author.name = 'Someone else' 
> > # session.commit() 
> > # print author 
> > # <Author(name=u'Someone else')> (Correct) 
> > 
> > # Update the author via Core, get and display 
> > q = author.__table__.update().where( 
> >     sa.and_( 
> >         Author.id == author.id, 
> >         Author.name == 'Someone' 
> >     ) 
> > ).values( 
> >     name='Someone else' 
> > ) 
> > result = session.execute(q) 
> > session.commit() 
> > 
> > print 'Rowcount: ', result.rowcount 
> > print author 
>
> the ORM does ad-hoc UPDATE statements like this: 
>
> s.query(Author).filter(Author.id == author.id).filter(Author.name == 
> 'someone').update({"name": "Someone"}) 
>
>
> http://docs.sqlalchemy.org/en/latest/orm/query.html?highlight=query.update#sqlalchemy.orm.query.Query.update
>  
>
>
>
>
> > 
> > So, this works and doesn't look too bad for this very simple example, 
> > but this is going to get pretty messy for more complicated ORM objects. 
> > 
> > | 
> > import sqlalchemy as sa 
> > from sqlalchemy.ext.declarative import declarative_base 
> > from sqlalchemy.orm import scoped_session, sessionmaker, relationship 
> > 
> > engine = sa.create_engine('sqlite:///:memory:', echo=False) 
> > session = scoped_session(sessionmaker(bind=engine)) 
> > Base = declarative_base() 
> > 
> > class Author(Base): 
> >     __tablename__ = 'author' 
> > 
> >     type = sa.Column(sa.String(50)) 
> > 
> >     __mapper_args__ = { 
> >         'polymorphic_identity': 'author', 
> >         'polymorphic_on': type 
> >     } 
> > 
> >     id = sa.Column(sa.Integer, primary_key=True) 
> >     name = sa.Column(sa.String) 
> > 
> >     def __repr__(self): 
> >         return '<Author(name={self.name!r})>'.format(self=self) 
> > 
> > 
> > class AuthorDetail(Author): 
> >     __tablename__ = 'author_detail' 
> > 
> >     __mapper_args__ = { 
> >         'polymorphic_identity': 'author_detail', 
> >     } 
> > 
> >     id = sa.Column(sa.Integer, sa.ForeignKey('author.id'), 
> primary_key=True) 
> >     age = sa.Column(sa.Integer) 
> > 
> >     def __repr__(self): 
> >         return '<Author(name={self.name!r}, 
> > age={self.age})>'.format(self=self) 
> > 
> > Base.metadata.create_all(engine) 
> > 
> > author = AuthorDetail(name='Someone', age=20) 
> > session.add(author) 
> > session.commit() 
> > print author 
> > 
> > q = author.__table__.update().where( 
> >     sa.and_( 
> >         Author.id == author.id, 
> >         Author.name == 'Someone' 
> >     ) 
> > ).values( 
> >     name='Someone else', 
> >     age=30 
> > ) 
> > result = session.execute(q) 
> > session.commit() 
> > 
> > print 'Rowcount: ', result.rowcount 
> > print author 
> > 
> > | 
> > 
> > This will fail, because 'name' is not a column in the author_detail 
> > table. 
>
> that is true, you would need to emit a second UPDATE statement.  This is 
> how UPDATE works in SQL, unless you are using MySQL's "multi-table 
> update" feature (which we support).   I see that "name" is part of 
> "author" and "age" is part of "author_detail", and you're looking to 
> UPDATE them both at the same time.  SQL doesn't do that in one statement 
> (only mysql's special trick does, and you're illustrating SQLite). 
>
>
> At this point, I could write more sophisticated updates to handle 
> > this situation, but not having to handle creating those complicated 
> > statements (and updating them on any changes) was part of the attraction 
> > of using the ORM in the first place. 
>
> the ORM's strength is in handling the persistence state of your objects 
> as well as applying patterns for loading collections and related objects 
> efficiently.   It will freely UPDATE objects, including those mapped in 
> inheritance hierarchies across multiple tables, on a per-identity basis 
> in any way you have instructed; that is, load all the objects you want 
> to UPDATE using the filter criteria you describe, change their 
> attributes in memory, then flush.   I've never seen an ORM that would 
> emit multiple UPDATE statements against multiple tables in an 
> inheritance hierarchy based on a custom WHERE clause.   That would be 
> very complicated to implement and just as hard for the end-user to use 
> effectively. 
>
> However, in this case you are specifically going for a special 
> concurrency use case.   The fact that UPDATE works on only one table at 
> a time is not avoidable; whether or not the ORM can magically emit the 
> two UPDATE statements you need, you still aren't updating in isolation 
> unless you are using the right transaction isolation in conjunction with 
> the right kind of locking for that isolation. 
>
> If this is in fact SQLite, you get that for free, since SQLite locks the 
> whole database file during a transaction.  Otherwise, I'd advise doing a 
> SELECT..FOR UPDATE to lock the rows that you need, then emit normal 
> UPDATE statements as needed. 
>
> The "atomic update" thing depends highly on the kind of backend you're 
> using.   With SQLite, it's one thing, if you were for example using 
> MySQL Galera, then the SELECT..FOR UPDATE wouldn't be available to you 
> either. 
>
>
>
> > 
> > So I guess the question is... Is there a smart way to handle this 
> > requirement without having to abandon the ORM? Some kind of ability to 
> > modify the update generated by the ORM with the extra where clause (and 
> > then determine if the update where matched rows or not as I need to 
> > throw an error if the table is not updateable for this reason). 
>
> the WHERE clause isn't the problem here, it's the SET clause.  It can 
> only refer to one table at a time, and your example has two tables 
> receiving a SET at once. 
>
> > 
> > Cheers, 
> > 
> > Douglas 
> > 
> > -- 
> > 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+...@googlegroups.com <javascript:> 
> > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>. 
> > To post to this group, send email to sqlal...@googlegroups.com 
> <javascript:> 
> > <mailto:sqlal...@googlegroups.com <javascript:>>. 
> > Visit this group at https://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 https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to