summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-04-15 00:13:48 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-04-15 00:13:48 -0400
commit1cf4a745d83acc51a562ca1d1289cf524fbee33c (patch)
treeb7d175573b73938c1e01cd20fb291d86a60e69a6 /lib
parent74a417a5996f829f301853eeed363e5389226107 (diff)
downloadsqlalchemy-1cf4a745d83acc51a562ca1d1289cf524fbee33c.tar.gz
- beef up the --reversetop test option to embed RandomSet throughout the ORM
- with m2m we have to go back to the previous approach of having both sides of the DP fire off, tracking each pair of objects. history may not be consistently present in one side or the other - this revealed a whole lot of issues with self-referential m2m, which are fixed
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/dependency.py55
-rw-r--r--lib/sqlalchemy/sql/expression.py3
-rw-r--r--lib/sqlalchemy/test/assertsql.py3
-rw-r--r--lib/sqlalchemy/test/config.py13
-rw-r--r--lib/sqlalchemy/test/noseplugin.py2
-rw-r--r--lib/sqlalchemy/topological.py4
6 files changed, 46 insertions, 34 deletions
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
index 624035c68..9f1b78f4a 100644
--- a/lib/sqlalchemy/orm/dependency.py
+++ b/lib/sqlalchemy/orm/dependency.py
@@ -101,12 +101,6 @@ class DependencyProcessor(object):
"""
- # assertions to ensure this method isn't being
- # called unnecessarily. can comment these out when
- # code is stable
- assert not self.post_update or not self._check_reverse(uow)
-
-
# locate and disable the aggregate processors
# for this dependency
@@ -776,11 +770,6 @@ class DetectKeySwitch(DependencyProcessor):
class ManyToManyDP(DependencyProcessor):
- def per_property_preprocessors(self, uow):
- if self._check_reverse(uow):
- return
- DependencyProcessor.per_property_preprocessors(self, uow)
-
def per_property_dependencies(self, uow, parent_saves,
child_saves,
parent_deletes,
@@ -860,11 +849,27 @@ class ManyToManyDP(DependencyProcessor):
child):
uowcommit.register_object(
attributes.instance_state(c), isdelete=True)
-
+
+ def _get_reversed_processed_set(self, uow):
+ if not self.prop._reverse_property:
+ return None
+
+ process_key = tuple(sorted(
+ [self.key] +
+ [p.key for p in self.prop._reverse_property]
+ ))
+ return uow.memo(
+ ('reverse_key', process_key),
+ set
+ )
+
def process_deletes(self, uowcommit, states):
secondary_delete = []
secondary_insert = []
secondary_update = []
+
+ processed = self._get_reversed_processed_set(uowcommit)
+
for state in states:
history = uowcommit.get_attribute_history(
state,
@@ -872,7 +877,9 @@ class ManyToManyDP(DependencyProcessor):
passive=self.passive_deletes)
if history:
for child in history.non_added():
- if child is None:
+ if child is None or \
+ (processed is not None and (state, child) in processed) or \
+ not uowcommit.session._contains_state(child):
continue
associationrow = {}
self._synchronize(
@@ -881,7 +888,10 @@ class ManyToManyDP(DependencyProcessor):
associationrow,
False, uowcommit)
secondary_delete.append(associationrow)
-
+
+ if processed is not None:
+ processed.update((c, state) for c in history.non_added())
+
self._run_crud(uowcommit, secondary_insert,
secondary_update, secondary_delete)
@@ -890,11 +900,14 @@ class ManyToManyDP(DependencyProcessor):
secondary_insert = []
secondary_update = []
+ processed = self._get_reversed_processed_set(uowcommit)
+
for state in states:
history = uowcommit.get_attribute_history(state, self.key)
if history:
for child in history.added:
- if child is None:
+ if child is None or \
+ (processed is not None and (state, child) in processed):
continue
associationrow = {}
self._synchronize(state,
@@ -903,7 +916,9 @@ class ManyToManyDP(DependencyProcessor):
False, uowcommit)
secondary_insert.append(associationrow)
for child in history.deleted:
- if child is None:
+ if child is None or \
+ (processed is not None and (state, child) in processed) or \
+ not uowcommit.session._contains_state(child):
continue
associationrow = {}
self._synchronize(state,
@@ -911,7 +926,10 @@ class ManyToManyDP(DependencyProcessor):
associationrow,
False, uowcommit)
secondary_delete.append(associationrow)
-
+
+ if processed is not None:
+ processed.update((c, state) for c in history.added + history.deleted)
+
if not self.passive_updates and \
self._pks_changed(uowcommit, state):
if not history:
@@ -935,13 +953,14 @@ class ManyToManyDP(DependencyProcessor):
secondary_update.append(associationrow)
+
self._run_crud(uowcommit, secondary_insert,
secondary_update, secondary_delete)
def _run_crud(self, uowcommit, secondary_insert,
secondary_update, secondary_delete):
connection = uowcommit.transaction.connection(self.mapper)
-
+
if secondary_delete:
associationrow = secondary_delete[0]
statement = self.secondary.delete(sql.and_(*[
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index fc6b5ad97..70e26cfcc 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -2898,6 +2898,9 @@ class Join(FromClause):
underlying :func:`select()` function.
"""
+ global sql_util
+ if not sql_util:
+ from sqlalchemy.sql import util as sql_util
if fold_equivalents:
collist = sql_util.folded_equivalents(self)
else:
diff --git a/lib/sqlalchemy/test/assertsql.py b/lib/sqlalchemy/test/assertsql.py
index d67de2355..81ef73a7c 100644
--- a/lib/sqlalchemy/test/assertsql.py
+++ b/lib/sqlalchemy/test/assertsql.py
@@ -173,7 +173,8 @@ class CompiledSQL(SQLMatchRule):
self._result = equivalent
if not self._result:
self._errmsg = "Testing for compiled statement %r partial params %r, " \
- "received %r with params %r" % (self.statement, all_params, _received_statement, all_received)
+ "received %r with params %r" % \
+ (self.statement, all_params, _received_statement, all_received)
class CountStatements(AssertRule):
diff --git a/lib/sqlalchemy/test/config.py b/lib/sqlalchemy/test/config.py
index efbe00fef..7d528a04b 100644
--- a/lib/sqlalchemy/test/config.py
+++ b/lib/sqlalchemy/test/config.py
@@ -1,6 +1,5 @@
import optparse, os, sys, re, ConfigParser, time, warnings
-
# 2to3
import StringIO
@@ -166,15 +165,9 @@ post_configure['table_options'] = _set_table_options
def _reverse_topological(options, file_config):
if options.reversetop:
- from sqlalchemy.orm import unitofwork
+ from sqlalchemy.orm import unitofwork, session, mapper, dependency
from sqlalchemy import topological
- class RevQueueDepSort(topological.QueueDependencySorter):
- def __init__(self, tuples, allitems):
- self.tuples = list(tuples)
- self.allitems = list(allitems)
- self.tuples.reverse()
- self.allitems.reverse()
- topological.QueueDependencySorter = RevQueueDepSort
- unitofwork.DependencySorter = RevQueueDepSort
+ from sqlalchemy.test.util import RandomSet
+ topological.set = unitofwork.set = session.set = mapper.set = dependency.set = RandomSet
post_configure['topological'] = _reverse_topological
diff --git a/lib/sqlalchemy/test/noseplugin.py b/lib/sqlalchemy/test/noseplugin.py
index 5e8e21e8f..6a3106e69 100644
--- a/lib/sqlalchemy/test/noseplugin.py
+++ b/lib/sqlalchemy/test/noseplugin.py
@@ -51,7 +51,7 @@ class NoseSQLAlchemy(Plugin):
callback=_engine_strategy,
help="Engine strategy (plain or threadlocal, defaults to plain)")
opt("--reversetop", action="store_true", dest="reversetop", default=False,
- help="Reverse the collection ordering for topological sorts (helps "
+ help="Use a random-ordering set implementation in the ORM (helps "
"reveal dependency issues)")
opt("--unhashable", action="store_true", dest="unhashable", default=False,
help="Disallow SQLAlchemy from performing a hash() on mapped test objects.")
diff --git a/lib/sqlalchemy/topological.py b/lib/sqlalchemy/topological.py
index 2b6eadd5d..6c3e90d98 100644
--- a/lib/sqlalchemy/topological.py
+++ b/lib/sqlalchemy/topological.py
@@ -9,10 +9,6 @@
from sqlalchemy.exc import CircularDependencyError
from sqlalchemy import util
-# this enables random orderings for iterated subsets
-# of non-dependent items.
-#from sqlalchemy.test.util import RandomSet as set
-
__all__ = ['sort', 'sort_as_subsets', 'find_cycles']
def sort_as_subsets(tuples, allitems):