[Sqlalchemy-commits] [1498] sqlalchemy/branches/schema/test: added new cascade test, fixes to delete
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-05-24 18:42:26
|
<!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>[1498] sqlalchemy/branches/schema/test: added new cascade test, fixes to delete-orphan cascade rules (more probably needed)</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1498</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-05-24 13:42:09 -0500 (Wed, 24 May 2006)</dd> </dl> <h3>Log Message</h3> <pre>added new cascade test, fixes to delete-orphan cascade rules (more probably needed)</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemaexamplesadjacencytreebasic_treepy">sqlalchemy/branches/schema/examples/adjacencytree/basic_tree.py</a></li> <li><a href="#sqlalchemybranchesschemaexamplesadjacencytreebyroot_treepy">sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyattributespy">sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormdependencypy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyormunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyutilpy">sqlalchemy/branches/schema/lib/sqlalchemy/util.py</a></li> <li><a href="#sqlalchemybranchesschematestalltestspy">sqlalchemy/branches/schema/test/alltests.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemybranchesschematestcascadepy">sqlalchemy/branches/schema/test/cascade.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemaexamplesadjacencytreebasic_treepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/examples/adjacencytree/basic_tree.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/examples/adjacencytree/basic_tree.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/examples/adjacencytree/basic_tree.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -6,8 +6,6 @@ </span><span class="cx"> </span><span class="cx"> metadata = BoundMetaData('sqlite:///', echo=True) </span><span class="cx"> </span><del>-"""create the treenodes table. This is a basic adjacency list model table.""" - </del><span class="cx"> trees = Table('treenodes', metadata, </span><span class="cx"> Column('node_id', Integer, Sequence('treenode_id_seq',optional=False), primary_key=True), </span><span class="cx"> Column('parent_node_id', Integer, ForeignKey('treenodes.node_id'), nullable=True), </span><span class="lines">@@ -15,6 +13,7 @@ </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> class NodeList(util.OrderedDict): </span><ins>+ """subclasses OrderedDict to allow usage as a list-based property.""" </ins><span class="cx"> def append(self, node): </span><span class="cx"> self[node.name] = node </span><span class="cx"> def __iter__(self): </span><span class="lines">@@ -77,7 +76,7 @@ </span><span class="cx"> print "Flushing:" </span><span class="cx"> print "----------------------------" </span><span class="cx"> </span><del>-session = create_session(echo_uow=True) </del><ins>+session = create_session() </ins><span class="cx"> session.save(node) </span><span class="cx"> session.flush() </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemaexamplesadjacencytreebyroot_treepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/examples/adjacencytree/byroot_tree.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -2,10 +2,11 @@ </span><span class="cx"> import sqlalchemy.util as util </span><span class="cx"> import string, sys, time </span><span class="cx"> </span><del>-"""a more advanced example of basic_tree.py. illustrates MapperExtension objects which -add application-specific functionality to a Mapper object.""" </del><ins>+"""a more advanced example of basic_tree.py. treenodes can now reference their "root" node, and +introduces a new selection method which selects an entire tree of nodes at once, taking +advantage of a custom MapperExtension to assemble incoming nodes into their correct structure.""" </ins><span class="cx"> </span><del>-engine = create_engine('sqlite:///:memory:', echo = "debug") </del><ins>+engine = create_engine('sqlite:///:memory:', echo=True) </ins><span class="cx"> </span><span class="cx"> metadata = BoundMetaData(engine) </span><span class="cx"> </span><span class="lines">@@ -26,14 +27,10 @@ </span><span class="cx"> Column('data_id', Integer, primary_key=True), </span><span class="cx"> Column('value', String(100), nullable=False) </span><span class="cx"> ) </span><del>- </del><span class="cx"> </span><span class="cx"> </span><span class="cx"> class NodeList(util.OrderedDict): </span><del>- """extends an Ordered Dictionary, which is just a dictionary that iterates its keys and values - in the order they were inserted. Adds an "append" method, which appends a node to the - dictionary as though it were a list, and also within append automatically associates - the parent of a TreeNode with itself.""" </del><ins>+ """subclasses OrderedDict to allow usage as a list-based property.""" </ins><span class="cx"> def append(self, node): </span><span class="cx"> self[node.name] = node </span><span class="cx"> def __iter__(self): </span><span class="lines">@@ -120,18 +117,18 @@ </span><span class="cx"> print "Creating Tree Table:" </span><span class="cx"> print "----------------------------" </span><span class="cx"> </span><del>-treedata.create() -trees.create() </del><ins>+metadata.create_all() </ins><span class="cx"> </span><del>- </del><ins>+# the mapper is created with properties that specify "lazy=None" - this is because we are going +# to handle our own "eager load" of nodes based on root id </ins><span class="cx"> mapper(TreeNode, trees, properties=dict( </span><span class="cx"> id=trees.c.node_id, </span><span class="cx"> name=trees.c.node_name, </span><span class="cx"> parent_id=trees.c.parent_node_id, </span><span class="cx"> root_id=trees.c.root_node_id, </span><span class="cx"> root=relation(TreeNode, primaryjoin=trees.c.root_node_id==trees.c.node_id, foreignkey=trees.c.node_id, lazy=None, uselist=False), </span><del>- children=relation(TreeNode, primaryjoin=trees.c.parent_node_id==trees.c.node_id, lazy=None, uselist=True, cascade="delete,save-update"), - data=relation(mapper(TreeData, treedata, properties=dict(id=treedata.c.data_id)), cascade="delete,save-update", lazy=False) </del><ins>+ children=relation(TreeNode, primaryjoin=trees.c.parent_node_id==trees.c.node_id, lazy=None, uselist=True, cascade="delete,delete-orphan,save-update"), + data=relation(mapper(TreeData, treedata, properties=dict(id=treedata.c.data_id)), cascade="delete,delete-orphan,save-update", lazy=False) </ins><span class="cx"> </span><span class="cx"> ), extension = TreeLoader()) </span><span class="cx"> </span><span class="lines">@@ -166,10 +163,10 @@ </span><span class="cx"> </span><span class="cx"> print node.print_nodes() </span><span class="cx"> </span><del>-node.append('node4') -node.children['node4'].append('subnode3') -node.children['node4'].append('subnode4') -node.children['node4'].children['subnode3'].append('subsubnode1') </del><ins>+#node.append('node4') +#node.children['node4'].append('subnode3') +#node.children['node4'].append('subnode4') +#node.children['node4'].children['subnode3'].append('subsubnode1') </ins><span class="cx"> del node.children['node1'] </span><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="lines">@@ -184,6 +181,7 @@ </span><span class="cx"> print "Committing:" </span><span class="cx"> print "----------------------------" </span><span class="cx"> session.flush() </span><ins>+sys.exit() </ins><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span><span class="cx"> print "Tree After Save:" </span><span class="lines">@@ -199,6 +197,9 @@ </span><span class="cx"> print "----------------------------" </span><span class="cx"> </span><span class="cx"> session.clear() </span><ins>+ +# load some nodes. we do this based on "root id" which will load an entire sub-tree in one pass. +# the MapperExtension will assemble the incoming nodes into a tree structure. </ins><span class="cx"> t = session.query(TreeNode).select(TreeNode.c.root_id==nodeid, order_by=[TreeNode.c.id])[0] </span><span class="cx"> </span><span class="cx"> print "\n\n\n----------------------------" </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -131,8 +131,11 @@ </span><span class="cx"> if self.orig is ScalarAttribute.NONE: </span><span class="cx"> self.orig = orig </span><span class="cx"> self.obj.__dict__[self.key] = value </span><del>- if value is not None and self.trackparent: - self.sethasparent(value, True) </del><ins>+ if self.trackparent: + if value is not None: + self.sethasparent(value, True) + if orig is not None: + self.sethasparent(orig, False) </ins><span class="cx"> if self.extension is not None: </span><span class="cx"> self.extension.set(self.obj, value, orig) </span><span class="cx"> self.value_changed(orig, value) </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormdependencypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/dependency.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -117,7 +117,6 @@ </span><span class="cx"> if child is not None and self.post_update: </span><span class="cx"> uowcommit.register_object(child, postupdate=True) </span><span class="cx"> for child in childlist.deleted_items(): </span><del>- print "HI KEY", self.key, "OBJECT", obj, "CHILD", child </del><span class="cx"> if not self.cascade.delete_orphan: </span><span class="cx"> self._synchronize(obj, child, None, True) </span><span class="cx"> </span><span class="lines">@@ -137,9 +136,13 @@ </span><span class="cx"> for child in childlist.deleted_items(): </span><span class="cx"> if child is not None and childlist.hasparent(child) is False: </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><ins>+ for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) </ins><span class="cx"> for child in childlist.unchanged_items(): </span><span class="cx"> if child is not None: </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><ins>+ for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) </ins><span class="cx"> else: </span><span class="cx"> for obj in deplist: </span><span class="cx"> childlist = self.get_object_dependencies(obj, uowcommit, passive=False) </span><span class="lines">@@ -158,10 +161,11 @@ </span><span class="cx"> uowcommit.register_object(child) </span><span class="cx"> for child in childlist.deleted_items(): </span><span class="cx"> if not self.cascade.delete_orphan: </span><del>- print "PREPROCESS KEY", self.key, "OBJECT", obj, "CHILD", child </del><span class="cx"> uowcommit.register_object(child, isdelete=False) </span><span class="cx"> elif childlist.hasparent(child) is False: </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><ins>+ for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) </ins><span class="cx"> </span><span class="cx"> def _synchronize(self, obj, child, associationrow, clearkeys): </span><span class="cx"> source = obj </span><span class="lines">@@ -205,17 +209,25 @@ </span><span class="cx"> if self.post_update: </span><span class="cx"> return </span><span class="cx"> if delete: </span><del>- if self.cascade.delete_orphan: </del><ins>+ if self.cascade.delete: </ins><span class="cx"> for obj in deplist: </span><span class="cx"> childlist = self.get_object_dependencies(obj, uowcommit, passive=False) </span><span class="cx"> for child in childlist.deleted_items() + childlist.unchanged_items(): </span><span class="cx"> if child is not None and childlist.hasparent(child) is False: </span><span class="cx"> uowcommit.register_object(child, isdelete=True) </span><ins>+ for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) </ins><span class="cx"> else: </span><span class="cx"> for obj in deplist: </span><del>- childlist = self.get_object_dependencies(obj, uowcommit, passive=True) </del><span class="cx"> uowcommit.register_object(obj) </span><del>- </del><ins>+ if self.cascade.delete_orphan: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items(): + if childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + </ins><span class="cx"> def _synchronize(self, obj, child, associationrow, clearkeys): </span><span class="cx"> source = child </span><span class="cx"> dest = obj </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyormunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/lib/sqlalchemy/orm/unitofwork.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -351,13 +351,31 @@ </span><span class="cx"> targettask = self.get_task_by_mapper(mapperfrom) </span><span class="cx"> up = UOWDependencyProcessor(processor, targettask) </span><span class="cx"> task.dependencies.append(up) </span><del>- up.preexecute(self) </del><span class="cx"> self._mark_modified() </span><span class="cx"> </span><span class="cx"> def execute(self, echo=False): </span><ins>+ # tell all related mappers to set up dependency processors </ins><span class="cx"> for task in self.tasks.values(): </span><span class="cx"> task.mapper.register_dependencies(self) </span><span class="cx"> </span><ins>+ # pre-execute dependency processors. this process may + # result in new tasks and/or dependency processors being added, + # particularly with 'delete-orphan' cascade rules. + # keep running through the full list of tasks until no more + # new objects get processed. + while True: + ret = False + for task in self.tasks.values(): + for up in task.dependencies: + if up.preexecute(self): + ret = True + if not ret: + break + + # flip the execution flag on. in some test cases + # we like to check this flag against any new objects being added, since everything + # should be registered by now. there is a slight exception in the case of + # post_update requests; this should be fixed. </ins><span class="cx"> self.__is_executing = True </span><span class="cx"> </span><span class="cx"> head = self._sort_dependencies() </span><span class="lines">@@ -434,10 +452,28 @@ </span><span class="cx"> def __init__(self, processor, targettask): </span><span class="cx"> self.processor = processor </span><span class="cx"> self.targettask = targettask </span><ins>+ self.preprocessed = Set() </ins><span class="cx"> </span><span class="cx"> def preexecute(self, trans): </span><del>- self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements if elem.obj is not None], trans, delete=False) - self.processor.preprocess_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements if elem.obj is not None], trans, delete=True) </del><ins>+ ret = False + elements = [elem.obj for elem in self.targettask.tosave_elements if elem.obj is not None and elem.obj not in self.preprocessed] + if len(elements): + ret = True + todo = [] + for e in elements: + self.preprocessed.add(e) + todo.append(e) + self.processor.preprocess_dependencies(self.targettask, todo, trans, delete=False) + + elements = [elem.obj for elem in self.targettask.todelete_elements if elem.obj is not None and elem.obj not in self.preprocessed] + if len(elements): + ret = True + todo = [] + for e in elements: + self.preprocessed.add(e) + todo.append(e) + self.processor.preprocess_dependencies(self.targettask, todo, trans, delete=True) + return ret </ins><span class="cx"> </span><span class="cx"> def execute(self, trans, delete): </span><span class="cx"> if not delete: </span><span class="lines">@@ -468,7 +504,7 @@ </span><span class="cx"> </span><span class="cx"> def is_empty(self): </span><span class="cx"> return len(self.objects) == 0 and len(self.dependencies) == 0 and len(self.childtasks) == 0 </span><del>- </del><ins>+ </ins><span class="cx"> def append(self, obj, listonly = False, childtask = None, isdelete = False): </span><span class="cx"> """appends an object to this task, to be either saved or deleted depending on the </span><span class="cx"> 'isdelete' attribute of this UOWTask. 'listonly' indicates that the object should </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyutilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/util.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/util.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/lib/sqlalchemy/util.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -367,6 +367,7 @@ </span><span class="cx"> self.__delrecord(self.data[i]) </span><span class="cx"> del self.data[i] </span><span class="cx"> def __setslice__(self, i, j, other): </span><ins>+ print "HAS SETSLICE" </ins><span class="cx"> i = max(i, 0); j = max(j, 0) </span><span class="cx"> if isinstance(other, UserList.UserList): </span><span class="cx"> l = other.data </span><span class="lines">@@ -374,6 +375,7 @@ </span><span class="cx"> l = other </span><span class="cx"> else: </span><span class="cx"> l = list(other) </span><ins>+ [self.__delrecord(x) for x in self.data[i:]] </ins><span class="cx"> g = [a for a in l if self.__setrecord(a)] </span><span class="cx"> self.data[i:] = g </span><span class="cx"> def __delslice__(self, i, j): </span></span></pre></div> <a id="sqlalchemybranchesschematestalltestspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/alltests.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/alltests.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/test/alltests.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -42,6 +42,7 @@ </span><span class="cx"> # ORM persistence </span><span class="cx"> 'sessioncontext', </span><span class="cx"> 'objectstore', </span><ins>+ 'cascade', </ins><span class="cx"> 'relationships', </span><span class="cx"> </span><span class="cx"> # cyclical ORM persistence </span></span></pre></div> <a id="sqlalchemybranchesschematestcascadepy"></a> <div class="addfile"><h4>Added: sqlalchemy/branches/schema/test/cascade.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/cascade.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/test/cascade.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -0,0 +1,175 @@ </span><ins>+import testbase, tables +import unittest, sys, datetime + +from sqlalchemy.ext.sessioncontext import SessionContext +from sqlalchemy import * + +class O2MCascadeTest(testbase.AssertMixin): + def tearDown(self): + ctx.current.clear() + + def tearDownAll(self): + clear_mappers() + + def setUpAll(self): + global ctx, data + ctx = SessionContext(create_session) + tables.create() + mapper(tables.User, tables.users, properties = dict( + address = relation(mapper(tables.Address, tables.addresses), lazy = False, uselist = False, private = True), + orders = relation( + mapper(tables.Order, tables.orders, properties = dict ( + items = relation(mapper(tables.Item, tables.orderitems), lazy = False, uselist =True, private = True) + )), + lazy = True, uselist = True, private = True) + )) + + def setUp(self): + global data + data = [tables.User, + {'user_name' : 'ed', + 'address' : (tables.Address, {'email_address' : 'fo...@ba...'}), + 'orders' : (tables.Order, [ + {'description' : 'eds 1st order', 'items' : (tables.Item, [{'item_name' : 'eds o1 item'}, {'item_name' : 'eds other o1 item'}])}, + {'description' : 'eds 2nd order', 'items' : (tables.Item, [{'item_name' : 'eds o2 item'}, {'item_name' : 'eds other o2 item'}])} + ]) + }, + {'user_name' : 'jack', + 'address' : (tables.Address, {'email_address' : 'ja...@ja...'}), + 'orders' : (tables.Order, [ + {'description' : 'jacks 1st order', 'items' : (tables.Item, [{'item_name' : 'im a lumberjack'}, {'item_name' : 'and im ok'}])} + ]) + }, + {'user_name' : 'foo', + 'address' : (tables.Address, {'email_address': 'hi...@la...'}), + 'orders' : (tables.Order, [ + {'description' : 'foo order', 'items' : (tables.Item, [])}, + {'description' : 'foo order 2', 'items' : (tables.Item, [{'item_name' : 'hi'}])}, + {'description' : 'foo order three', 'items' : (tables.Item, [{'item_name' : 'there'}])} + ]) + } + ] + + for elem in data[1:]: + u = tables.User() + ctx.current.save(u) + u.user_name = elem['user_name'] + u.address = tables.Address() + u.address.email_address = elem['address'][1]['email_address'] + u.orders = [] + for order in elem['orders'][1]: + o = tables.Order() + o.isopen = None + o.description = order['description'] + u.orders.append(o) + o.items = [] + for item in order['items'][1]: + i = tables.Item() + i.item_name = item['item_name'] + o.items.append(i) + + ctx.current.flush() + ctx.current.clear() + + def tearDown(self): + tables.delete() + + def tearDownAll(self): + clear_mappers() + + def testdelete(self): + l = ctx.current.query(tables.User).select() + for u in l: + self.echo( repr(u.orders)) + self.assert_result(l, data[0], *data[1:]) + + self.echo("\n\n\n") + ids = (l[0].user_id, l[2].user_id) + ctx.current.delete(l[0]) + ctx.current.delete(l[2]) + + ctx.current.flush() + self.assert_(tables.orders.count(tables.orders.c.user_id.in_(*ids)).scalar() == 0) + self.assert_(tables.orderitems.count(tables.orders.c.user_id.in_(*ids) &(tables.orderitems.c.order_id==tables.orders.c.order_id)).scalar() == 0) + self.assert_(tables.addresses.count(tables.addresses.c.user_id.in_(*ids)).scalar() == 0) + self.assert_(tables.users.count(tables.users.c.user_id.in_(*ids)).scalar() == 0) + + + def testorphan(self): + l = ctx.current.query(tables.User).select() + jack = l[1] + jack.orders[:] = [] + + ids = [jack.user_id] + self.assert_(tables.orders.count(tables.orders.c.user_id.in_(*ids)).scalar() == 1) + self.assert_(tables.orderitems.count(tables.orders.c.user_id.in_(*ids) &(tables.orderitems.c.order_id==tables.orders.c.order_id)).scalar() == 2) + + ctx.current.flush() + + self.assert_(tables.orders.count(tables.orders.c.user_id.in_(*ids)).scalar() == 0) + self.assert_(tables.orderitems.count(tables.orders.c.user_id.in_(*ids) &(tables.orderitems.c.order_id==tables.orders.c.order_id)).scalar() == 0) + + +class M2OCascadeTest(testbase.AssertMixin): + def tearDown(self): + ctx.current.clear() + for t in metadata.table_iterator(reverse=True): + t.delete().execute() + + def tearDownAll(self): + clear_mappers() + + def setUpAll(self): + global ctx, data, metadata, User, Pref + ctx = SessionContext(create_session) + metadata = BoundMetaData(testbase.db) + prefs = Table('prefs', metadata, + Column('prefs_id', Integer, Sequence('prefs_id_seq', optional=True), primary_key=True), + Column('prefs_data', String(40))) + + users = Table('users', metadata, + Column('user_id', Integer, Sequence('user_id_seq', optional=True), primary_key = True), + Column('user_name', String(40)), + Column('pref_id', Integer, ForeignKey('prefs.prefs_id')) + ) + class User(object): + pass + class Pref(object): + pass + metadata.create_all() + mapper(User, users, properties = dict( + pref = relation(mapper(Pref, prefs), lazy=False, cascade="all, delete-orphan") + )) + + def setUp(self): + global data + data = [User, + {'user_name' : 'ed', + 'pref' : (Pref, {'prefs_data' : 'pref 1'}), + }, + {'user_name' : 'jack', + 'pref' : (Pref, {'prefs_data' : 'pref 2'}), + }, + {'user_name' : 'foo', + 'pref' : (Pref, {'prefs_data' : 'pref 3'}), + } + ] + + for elem in data[1:]: + u = User() + ctx.current.save(u) + u.user_name = elem['user_name'] + u.pref = Pref() + u.pref.prefs_data = elem['pref'][1]['prefs_data'] + + ctx.current.flush() + ctx.current.clear() + + def testorphan(self): + l = ctx.current.query(User).select() + jack = l[1] + jack.pref = None + ctx.current.flush() + +if __name__ == "__main__": + testbase.main() </ins></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1497 => 1498)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-05-24 16:35:30 UTC (rev 1497) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-05-24 18:42:09 UTC (rev 1498) </span><span class="lines">@@ -591,84 +591,7 @@ </span><span class="cx"> ctx.current.delete(u) </span><span class="cx"> ctx.current.flush() </span><span class="cx"> self.assert_(a.address_id is not None and a.user_id is None and not ctx.current.identity_map.has_key(u._instance_key) and ctx.current.identity_map.has_key(a._instance_key)) </span><del>- - def testcascadingdelete(self): - m = mapper(User, users, properties = dict( - address = relation(mapper(Address, addresses), lazy = False, uselist = False, private = True), - orders = relation( - mapper(Order, orders, properties = dict ( - items = relation(mapper(Item, orderitems), lazy = False, uselist =True, private = True) - )), - lazy = True, uselist = True, private = True) - )) - - data = [User, - {'user_name' : 'ed', - 'address' : (Address, {'email_address' : 'fo...@ba...'}), - 'orders' : (Order, [ - {'description' : 'eds 1st order', 'items' : (Item, [{'item_name' : 'eds o1 item'}, {'item_name' : 'eds other o1 item'}])}, - {'description' : 'eds 2nd order', 'items' : (Item, [{'item_name' : 'eds o2 item'}, {'item_name' : 'eds other o2 item'}])} - ]) - }, - {'user_name' : 'jack', - 'address' : (Address, {'email_address' : 'ja...@ja...'}), - 'orders' : (Order, [ - {'description' : 'jacks 1st order', 'items' : (Item, [{'item_name' : 'im a lumberjack'}, {'item_name' : 'and im ok'}])} - ]) - }, - {'user_name' : 'foo', - 'address' : (Address, {'email_address': 'hi...@la...'}), - 'orders' : (Order, [ - {'description' : 'foo order', 'items' : (Item, [])}, - {'description' : 'foo order 2', 'items' : (Item, [{'item_name' : 'hi'}])}, - {'description' : 'foo order three', 'items' : (Item, [{'item_name' : 'there'}])} - ]) - } - ] </del><span class="cx"> </span><del>- for elem in data[1:]: - u = User() - u.user_name = elem['user_name'] - u.address = Address() - u.address.email_address = elem['address'][1]['email_address'] - u.orders = [] - for order in elem['orders'][1]: - o = Order() - o.isopen = None - o.description = order['description'] - u.orders.append(o) - o.items = [] - for item in order['items'][1]: - i = Item() - i.item_name = item['item_name'] - o.items.append(i) - - ctx.current.flush() - ctx.current.clear() - - l = m.select() - for u in l: - self.echo( repr(u.orders)) - self.assert_result(l, data[0], *data[1:]) - - self.echo("\n\n\n") - ctx.current.delete(l[0], l[2]) - ctx.current.flush() - return - res = self.capture_exec(db, lambda: ctx.current.flush()) - state = None - - for line in res.split('\n'): - if line == "DELETE FROM items WHERE items.item_id = :item_id": - self.assert_(state is None or state == 'addresses') - elif line == "DELETE FROM orders WHERE orders.order_id = :order_id": - state = 'orders' - elif line == "DELETE FROM email_addresses WHERE email_addresses.address_id = :address_id": - if state is None: - state = 'addresses' - elif line == "DELETE FROM users WHERE users.user_id = :user_id": - self.assert_(state is not None) - </del><span class="cx"> def testbackwardsonetoone(self): </span><span class="cx"> # test 'backwards' </span><span class="cx"> # m = mapper(Address, addresses, properties = dict( </span></span></pre> </div> </div> </body> </html> |