Hello everybody, I am currently pulling my hair out because I have a solution that I think should work on the database side (albeit I don't like it), but I can't figure out how to do this with sqlalchemy. My current goal is to manage (long-lived) locks on copy-on-write hierarchical data that is stored in Oracle RDBMS.
Of course the code must guaranteee uniqueness. Funny thing is that I can have multiple locks on the same object as it can be shared due to the shallow copy nature of the application. The weird schema is something like this: CREATE TABLE item_locks ( lock_id VARCHAR(36) NOT NULL, context_id INTEGER NOT NULL, target_path VARCHAR(2048) NOT NULL, target_id INTEGER, expiration_time DATETIME, owner_id INTEGER NOT NULL, PRIMARY KEY (lock_id), CONSTRAINT item_locks_unique UNIQUE (target_path), FOREIGN KEY(id) REFERENCES taggable (id) ON DELETE CASCADE, FOREIGN KEY(target_id) REFERENCES taggable (id) ON DELETE CASCADE, FOREIGN KEY(owner_id) REFERENCES users (id) DEFERRABLE INITIALLY deferred ); CREATE INDEX ix_taggable_locks_target_id ON taggable_locks (target_id); Yes I know. My code is basically working fine but I ran into one limitation: target_path is actually a comma separated list of integers like "5,7,8" which identify the locked object (id 8, same as target_id) as seen from the context object (id 5, same as context_id). Given the copy on write nature I have to propagate the locks to the actual writable object though. context_id is always writable but the children are reused across different contexts. A COW operation will create a new instance that is also writable, so the lock may move from "5,7,8" to "9, 8" with id 9 being the new copy of the object with id 7. (For completeness: when the object is actually written by the lock holder, the lock would transform to something like "10", with context_id = target_id = 10 as well). What I forgot though: Usually the copy on write created objects are created within the ORM session which means that I actually got target_path = "None,8" when actually using the code in the intended setting. That's one thing I did not have in mind... :-( I can hack around this by code like this: from sqlalchemy import create_engine, Column, Integer, String, event from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base engine = create_engine('sqlite:///:memory:', echo=True) Base = declarative_base() class Person(Base): __tablename__ = "persons" id = Column(Integer, primary_key=True) name = Column(String) class Bundle(Base): __tablename__ = "bundle" id = Column(Integer, primary_key=True) member_ids = Column(String, unique=True) def __init__(self, members): self.members = members Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) @event.listens_for(Session, "after_flush") def receive_after_flush(session, flush_context): items = set(session.dirty) | set(session.new) for item in items: if isinstance(item, Bundle): session.execute(Bundle.__table__ .update() .values(member_ids=",".join(str(x.id) for x in item.members )) .where(Bundle.id == item.id)) session = Session() persons = [Person(name=name) for name in ["Martin", "Michael", "Fabian"]] for person in persons: session.add(person) session.commit() bundle = Bundle(persons[1:]) session.add(bundle) session.commit() This comes with its share of new problems (like updating bundle.members after the initial commit does not yield an update). This frustrates me because SQLAlchemy internally has all the knowledge for topological sorting of operations, but I can not figure out how to use the goodness to implement the schema that I would like to have. I have a faint hope that Mike will come up with a recipe how I can build my own relationship like property so that the code can just be changed to class Bundle(Base): # ... members = csv_relationship(Person, "id") BTW: For extra fun the lock propagation work on the target_path column in before_flush. Any hints how to mount this with SQLAlchemy greatly appreciated! Greetings, Torsten -- 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.