[Sqlalchemy-commits] [1984] sqlalchemy/trunk/test/orm: a simplification to syncrule generation, wh
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-10-14 06:58:51
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1984] sqlalchemy/trunk/test/orm: a simplification to syncrule generation, which also allows more flexible configuration</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1984</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-10-14 02:57:12 -0500 (Sat, 14 Oct 2006)</dd> </dl> <h3>Log Message</h3> <pre>a simplification to syncrule generation, which also allows more flexible configuration of which columns are to be involved in the synchronization via foreignkey property. foreignkey param is a little more important now and should have its role clarified particularly for self-referential mappers.</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyormdependencypy">sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormmapperpy">sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyormsyncpy">sqlalchemy/trunk/lib/sqlalchemy/orm/sync.py</a></li> <li><a href="#sqlalchemytrunktestormeagertest3py">sqlalchemy/trunk/test/orm/eagertest3.py</a></li> <li><a href="#sqlalchemytrunktestormrelationshipspy">sqlalchemy/trunk/test/orm/relationships.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyormdependencypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py (1983 => 1984)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py 2006-10-14 03:41:36 UTC (rev 1983) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/dependency.py 2006-10-14 07:57:12 UTC (rev 1984) </span><span class="lines">@@ -35,6 +35,7 @@ </span><span class="cx"> self.direction = prop.direction </span><span class="cx"> self.is_backref = prop.is_backref </span><span class="cx"> self.post_update = prop.post_update </span><ins>+ self.foreignkey = prop.foreignkey </ins><span class="cx"> self.key = prop.key </span><span class="cx"> </span><span class="cx"> self._compile_synchronizers() </span><span class="lines">@@ -84,15 +85,12 @@ </span><span class="cx"> </span><span class="cx"> The list of rules is used within commits by the _synchronize() method when dependent </span><span class="cx"> objects are processed.""" </span><del>- parent_tables = util.Set(self.parent.tables + [self.parent.mapped_table]) - target_tables = util.Set(self.mapper.tables + [self.mapper.mapped_table]) - </del><span class="cx"> self.syncrules = sync.ClauseSynchronizer(self.parent, self.mapper, self.direction) </span><span class="cx"> if self.direction == sync.MANYTOMANY: </span><del>- self.syncrules.compile(self.prop.primaryjoin, parent_tables, [self.secondary], False) - self.syncrules.compile(self.prop.secondaryjoin, target_tables, [self.secondary], True) </del><ins>+ self.syncrules.compile(self.prop.primaryjoin, issecondary=False) + self.syncrules.compile(self.prop.secondaryjoin, issecondary=True) </ins><span class="cx"> else: </span><del>- self.syncrules.compile(self.prop.primaryjoin, parent_tables, target_tables) </del><ins>+ self.syncrules.compile(self.prop.primaryjoin, foreignkey=self.foreignkey) </ins><span class="cx"> </span><span class="cx"> def get_object_dependencies(self, obj, uowcommit, passive = True): </span><span class="cx"> """returns the list of objects that are dependent on the given object, as according to the relationship </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py (1983 => 1984)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2006-10-14 03:41:36 UTC (rev 1983) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/mapper.py 2006-10-14 07:57:12 UTC (rev 1984) </span><span class="lines">@@ -271,7 +271,7 @@ </span><span class="cx"> # stricter set of tables to create "sync rules" by,based on the immediate </span><span class="cx"> # inherited table, rather than all inherited tables </span><span class="cx"> self._synchronizer = sync.ClauseSynchronizer(self, self, sync.ONETOMANY) </span><del>- self._synchronizer.compile(self.mapped_table.onclause, util.Set([self.inherits.local_table]), sqlutil.TableFinder(self.local_table)) </del><ins>+ self._synchronizer.compile(self.mapped_table.onclause) </ins><span class="cx"> else: </span><span class="cx"> self._synchronizer = None </span><span class="cx"> self.mapped_table = self.local_table </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyormsyncpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/orm/sync.py (1983 => 1984)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/orm/sync.py 2006-10-14 03:41:36 UTC (rev 1983) +++ sqlalchemy/trunk/lib/sqlalchemy/orm/sync.py 2006-10-14 07:57:12 UTC (rev 1984) </span><span class="lines">@@ -33,51 +33,57 @@ </span><span class="cx"> self.direction = direction </span><span class="cx"> self.syncrules = [] </span><span class="cx"> </span><del>- def compile(self, sqlclause, source_tables, target_tables, issecondary=None): - def check_for_table(binary, list1, list2): - #print "check for table", str(binary), [str(c) for c in l] - if binary.left.table in list1 and binary.right.table in list2: - return (binary.left, binary.right) - elif binary.right.table in list1 and binary.left.table in list2: - return (binary.right, binary.left) - else: - return (None, None) - </del><ins>+ def compile(self, sqlclause, issecondary=None, foreignkey=None): </ins><span class="cx"> def compile_binary(binary): </span><del>- """assembles a SyncRule given a single binary condition""" </del><ins>+ """assemble a SyncRule given a single binary condition""" </ins><span class="cx"> if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column): </span><span class="cx"> return </span><span class="cx"> </span><del>- if binary.left.table == binary.right.table: - # self-cyclical relation - if binary.left.primary_key: - source = binary.left - dest = binary.right - elif binary.right.primary_key: - source = binary.right - dest = binary.left </del><ins>+ source_column = None + dest_column = None + if foreignkey is not None: + # for self-referential relationships, + # the best we can do right now is figure out which side + # is the primary key + # TODO: need some better way for this + if binary.left.table == binary.right.table: + if binary.left.primary_key: + source_column = binary.left + dest_column = binary.right + elif binary.right.primary_key: + source_column = binary.right + dest_column = binary.left + else: + raise ArgumentError("Can't locate a primary key column in self-referential equality clause '%s'" % str(binary)) + # for other relationships we are more flexible + # and go off the 'foreignkey' property + elif binary.left in foreignkey: + dest_column = binary.left + source_column = binary.right + elif binary.right in foreignkey: + dest_column = binary.right + source_column = binary.left </ins><span class="cx"> else: </span><del>- raise ArgumentError("Cant determine direction for relationship %s = %s" % (binary.left.table.fullname, binary.right.table.fullname)) </del><ins>+ return + else: + if binary.left in [f.column for f in binary.right.foreign_keys]: + dest_column = binary.right + source_column = binary.left + elif binary.right in [f.column for f in binary.left.foreign_keys]: + dest_column = binary.left + source_column = binary.right + + if source_column and dest_column: </ins><span class="cx"> if self.direction == ONETOMANY: </span><del>- self.syncrules.append(SyncRule(self.parent_mapper, source, dest, dest_mapper=self.child_mapper)) </del><ins>+ self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper)) </ins><span class="cx"> elif self.direction == MANYTOONE: </span><del>- self.syncrules.append(SyncRule(self.child_mapper, source, dest, dest_mapper=self.parent_mapper)) </del><ins>+ self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, dest_mapper=self.parent_mapper)) </ins><span class="cx"> else: </span><del>- raise AssertionError("assert failed") - else: - (pt, tt) = check_for_table(binary, source_tables, target_tables) - #print "OK", binary, [t.name for t in source_tables], [t.name for t in target_tables] - if pt and tt: - if self.direction == ONETOMANY: - self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper)) - elif self.direction == MANYTOONE: - self.syncrules.append(SyncRule(self.child_mapper, tt, pt, dest_mapper=self.parent_mapper)) </del><ins>+ if not issecondary: + self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper, issecondary=issecondary)) </ins><span class="cx"> else: </span><del>- if not issecondary: - self.syncrules.append(SyncRule(self.parent_mapper, pt, tt, dest_mapper=self.child_mapper, issecondary=issecondary)) - else: - self.syncrules.append(SyncRule(self.child_mapper, pt, tt, dest_mapper=self.parent_mapper, issecondary=issecondary)) - </del><ins>+ self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, dest_mapper=self.parent_mapper, issecondary=issecondary)) + </ins><span class="cx"> rules_added = len(self.syncrules) </span><span class="cx"> processor = BinaryVisitor(compile_binary) </span><span class="cx"> sqlclause.accept_visitor(processor) </span><span class="lines">@@ -131,7 +137,8 @@ </span><span class="cx"> dest[self.dest_column.key] = value </span><span class="cx"> else: </span><span class="cx"> if clearkeys and self.dest_primary_key(): </span><del>- return </del><ins>+ raise exceptions.AssertionError("Dependency rule tried to blank-out a primary key column") + </ins><span class="cx"> if logging.is_debug_enabled(self.logger): </span><span class="cx"> self.logger.debug("execute() instances: %s(%s)->%s(%s) ('%s')" % (mapperutil.instance_str(source), str(self.source_column), mapperutil.instance_str(dest), str(self.dest_column), value)) </span><span class="cx"> self.dest_mapper._setattrbycolumn(dest, self.dest_column, value) </span></span></pre></div> <a id="sqlalchemytrunktestormeagertest3py"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/eagertest3.py (1983 => 1984)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/eagertest3.py 2006-10-14 03:41:36 UTC (rev 1983) +++ sqlalchemy/trunk/test/orm/eagertest3.py 2006-10-14 07:57:12 UTC (rev 1984) </span><span class="lines">@@ -42,7 +42,9 @@ </span><span class="cx"> mapper(Test,tests,properties={ </span><span class="cx"> 'owner':relation(Owner,backref='tests'), </span><span class="cx"> 'category':relation(Category), </span><del>- 'owner_option': relation(Option,primaryjoin=and_(tests.c.id==options.c.test_id,tests.c.owner_id==options.c.owner_id),uselist=False ) </del><ins>+ 'owner_option': relation(Option,primaryjoin=and_(tests.c.id==options.c.test_id,tests.c.owner_id==options.c.owner_id), + foreignkey=[options.c.test_id, options.c.owner_id], + uselist=False ) </ins><span class="cx"> }) </span><span class="cx"> </span><span class="cx"> s=create_session() </span></span></pre></div> <a id="sqlalchemytrunktestormrelationshipspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/orm/relationships.py (1983 => 1984)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/orm/relationships.py 2006-10-14 03:41:36 UTC (rev 1983) +++ sqlalchemy/trunk/test/orm/relationships.py 2006-10-14 07:57:12 UTC (rev 1984) </span><span class="lines">@@ -217,7 +217,142 @@ </span><span class="cx"> assert sess.query(Employee).get([c1.company_id, 3]).reports_to.name == 'emp1' </span><span class="cx"> assert sess.query(Employee).get([c2.company_id, 3]).reports_to.name == 'emp5' </span><span class="cx"> </span><ins>+class RelationTest3(testbase.PersistTest): + def setUpAll(self): + global jobs, pageversions, pages, metadata, Job, Page, PageVersion, PageComment + import datetime + metadata = BoundMetaData(testbase.db) + jobs = Table("jobs", metadata, + Column("jobno", Unicode(15), primary_key=True), + Column("created", DateTime, nullable=False, default=datetime.datetime.now), + Column("deleted", Boolean, nullable=False, default=False)) + pageversions = Table("pageversions", metadata, + Column("jobno", Unicode(15), primary_key=True), + Column("pagename", Unicode(30), primary_key=True), + Column("version", Integer, primary_key=True, default=1), + Column("created", DateTime, nullable=False, default=datetime.datetime.now), + Column("md5sum", String(32)), + Column("width", Integer, nullable=False, default=0), + Column("height", Integer, nullable=False, default=0), + ForeignKeyConstraint(["jobno", "pagename"], ["pages.jobno", "pages.pagename"]) + ) + pages = Table("pages", metadata, + Column("jobno", Unicode(15), ForeignKey("jobs.jobno"), primary_key=True), + Column("pagename", Unicode(30), primary_key=True), + Column("created", DateTime, nullable=False, default=datetime.datetime.now), + Column("deleted", Boolean, nullable=False, default=False), + Column("current_version", Integer)) + pagecomments = Table("pagecomments", metadata, + Column("jobno", Unicode(15), primary_key=True), + Column("pagename", Unicode(30), primary_key=True), + Column("comment_id", Integer, primary_key=True), + Column("content", Unicode), + ForeignKeyConstraint(["jobno", "pagename"], ["pages.jobno", "pages.pagename"]) + ) + + metadata.create_all() + class Job(object): + def __init__(self, jobno=None): + self.jobno = jobno + def create_page(self, pagename, *args, **kwargs): + return Page(job=self, pagename=pagename, *args, **kwargs) + class PageVersion(object): + def __init__(self, page=None, version=None): + self.page = page + self.version = version + class Page(object): + def __init__(self, job=None, pagename=None): + self.job = job + self.pagename = pagename + self.currentversion = PageVersion(self, 1) + def __repr__(self): + return "Page jobno:%s pagename:%s %s" % (self.jobno, self.pagename, getattr(self, '_instance_key', None)) + def add_version(self): + self.currentversion = PageVersion(self, self.currentversion.version+1) + comment = self.add_comment() + comment.closeable = False + comment.content = u'some content' + return self.currentversion + def add_comment(self): + nextnum = max([-1] + [c.comment_id for c in self.comments]) + 1 + newcomment = PageComment() + newcomment.comment_id = nextnum + self.comments.append(newcomment) + newcomment.created_version = self.currentversion.version + return newcomment + class PageComment(object): + pass + mapper(Job, jobs) + mapper(PageVersion, pageversions) + mapper(Page, pages, properties={ + 'job': relation(Job, backref=backref('pages', cascade="all, delete-orphan", order_by=pages.c.pagename)), + 'currentversion': relation(PageVersion, + foreignkey=pages.c.current_version, + primaryjoin=and_(pages.c.jobno==pageversions.c.jobno, + pages.c.pagename==pageversions.c.pagename, + pages.c.current_version==pageversions.c.version), + post_update=True), + 'versions': relation(PageVersion, cascade="all, delete-orphan", + primaryjoin=and_(pages.c.jobno==pageversions.c.jobno, + pages.c.pagename==pageversions.c.pagename), + order_by=pageversions.c.version, + backref=backref('page', lazy=False, + primaryjoin=and_(pages.c.jobno==pageversions.c.jobno, + pages.c.pagename==pageversions.c.pagename))) + }) + mapper(PageComment, pagecomments, properties={ + 'page': relation(Page, primaryjoin=and_(pages.c.jobno==pagecomments.c.jobno, + pages.c.pagename==pagecomments.c.pagename), + backref=backref("comments", cascade="all, delete-orphan", + primaryjoin=and_(pages.c.jobno==pagecomments.c.jobno, + pages.c.pagename==pagecomments.c.pagename), + order_by=pagecomments.c.comment_id)) + }) + + + def tearDownAll(self): + clear_mappers() + metadata.drop_all() + + def testbasic(self): + """test the combination of complicated join conditions with post_update""" + j1 = Job('somejob') + j1.create_page('page1') + j1.create_page('page2') + j1.create_page('page3') + + j2 = Job('somejob2') + j2.create_page('page1') + j2.create_page('page2') + j2.create_page('page3') + + j2.pages[0].add_version() + j2.pages[0].add_version() + j2.pages[1].add_version() + print j2.pages + print j2.pages[0].versions + print j2.pages[1].versions + s = create_session() + + s.save(j1) + s.save(j2) + s.flush() + + s.clear() + j = s.query(Job).get_by(jobno='somejob') + oldp = list(j.pages) + j.pages = [] + + s.flush() + + s.clear() + j = s.query(Job).get_by(jobno='somejob2') + j.pages[1].current_version = 12 + s.delete(j) + s.flush() </ins><span class="cx"> </span><span class="cx"> </span><ins>+ + </ins><span class="cx"> if __name__ == "__main__": </span><span class="cx"> testbase.main() </span></span></pre> </div> </div> </body> </html> |