I want to create a flat forum, where threads are no separate table, with a composite primary key for posts.
So posts have two fields forming a natural key: thread_id and post_number, where the further is the ID of the thread they are part of, and the latter is their position in the thread. if you aren’t convinced, check below the line.
My problem is that i don’t know how to tell SQLAlchemy
when committing the addition of new
Postinstances with thread_idtid, look up how many posts with thread_idtidexist, and autoincrement from that number on.
Why do i think that schema is a good idea? because it’s natural and performant:
class Post(Base):
number = Column(Integer, primary_key=True, autoincrement=False, nullable=False)
thread_id = Column(Integer, primary_key=True, autoincrement=False, nullable=False)
title = Column(Text) #nullable for not-first posts
text = Column(Text, nullable=False)
...
PAGESIZE = 10
#test
tid = 5
page = 4
Entire Thread (query):
thread5 = session.query(Post).filter_by(thread_id=5)
Thread title:
title = thread5.filter_by(number=0).one().title
Thread page
page4 = thread5.filter(
Post.number >= (page * PAGESIZE),
Post.number < ((page+1) * PAGESIZE)).all()
#or
page4 = thread5.offset(page * PAGESIZE).limit(PAGESIZE).all()
Number of pages:
ceil(thread5.count() / PAGESIZE)
You can probably do this with an SQL expression as a default value (see the
defaultargument). Give it a callable like this:I’m not absolutely sure you can return an sql expression from a default callable–you may have to actually execute this query and return a scalar value inside the callback. (The cursor should be available from the
contextparameter.)However, I strongly recommend you do what @kindall says and just use another auto-incrementing sequence for the
numbercolumn. What you want to do is actually very tricky to get right even without SQLAlchemy. For example, if you are using an MVCC database you need to introduce special row-level locking so that the number of rows with a matchingthread_iddoes not change while you are running the transaction. How this is done is database-dependent. For example with MySQL InnoDB, you need to do something like this:If you didn’t use
FOR UPDATE, then conceivably another connection trying to insert a new post into the same thread at the same time could have gotten the same value fornumber.So rather than being performant, post inserts are actually quite slow (relatively speaking) because of the extra query and locking required.
All this is resolved by using a separate sequence and not worrying about post number incrementing only within a thread_id.