]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- the value of a bindparam() can be a callable, in which
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 22 Feb 2008 23:17:15 +0000 (23:17 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 22 Feb 2008 23:17:15 +0000 (23:17 +0000)
case it's evaluated at statement execution time to
get the value.
- expressions used in filter(), filter_by() and others,
when they make usage of a clause generated from a
relation using the identity of a child object
(e.g. filter(Parent.child==<somechild>)), evaluate
the actual primary key value of <somechild> at
execution time so that the autoflush step of the
Query can complete, thereby populating the PK value
of <somechild> in the case that <somechild> was
pending.
- cleanup of attributes.get_committed_value() to never return
the NO_VALUE value; evaluates to None

CHANGES
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/unitofwork.py
lib/sqlalchemy/sql/compiler.py
test/orm/attributes.py
test/orm/session.py

diff --git a/CHANGES b/CHANGES
index c9030a6ba8c37e004dea7a83e6d58b1d67a5b15c..78112999c1e8451ab9c3f2dfe89ad2c73dcbc955 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,9 @@ CHANGES
 - sql
     - can again create aliases of selects against textual
       FROM clauses, [ticket:975]
+    - the value of a bindparam() can be a callable, in which 
+      case it's evaluated at statement execution time to
+      get the value.
       
 - orm
     - any(), has(), contains(), attribute level == and != now
@@ -33,10 +36,15 @@ CHANGES
 
       - preventive code against a potential lost-reference 
         bug in flush()
-        
-0.4.4
-------
-
+      - expressions used in filter(), filter_by() and others,
+        when they make usage of a clause generated from a
+        relation using the identity of a child object
+        (e.g. filter(Parent.child==<somechild>)), evaluate
+        the actual primary key value of <somechild> at 
+        execution time so that the autoflush step of the
+        Query can complete, thereby populating the PK value
+        of <somechild> in the case that <somechild> was
+        pending.
 
 0.4.3
 ------
index 5cc9cb309121a9907af549502fa399b7a9414eed..298a7f51193e848c4d47dfc35662fa987c35c8d7 100644 (file)
@@ -246,7 +246,10 @@ class AttributeImpl(object):
         """return the unchanged value of this attribute"""
 
         if self.key in state.committed_state:
-            return state.committed_state.get(self.key)
+            if state.committed_state[self.key] is NO_VALUE:
+                return None
+            else:
+                return state.committed_state.get(self.key)
         else:
             return self.get(state)
 
index bdc8ab9a99ed752fa1841a1c7419bd68778c42bb..81a5895f4985b7a1ca67a088b42b39345fb76807 100644 (file)
@@ -294,7 +294,9 @@ class LazyLoader(AbstractRelationLoader):
             mapper = reverse_direction and self.parent_property.mapper or self.parent_property.parent
             if bindparam.key in bind_to_col:
                 # use the "committed" (database) version to get query column values
-                bindparam.value = mapper._get_committed_attr_by_column(instance, bind_to_col[bindparam.key])
+                # also its a deferred value; so that when used by Query, the committed value is used
+                # after an autoflush occurs
+                bindparam.value = lambda: mapper._get_committed_attr_by_column(instance, bind_to_col[bindparam.key])
         return visitors.traverse(criterion, clone=True, visit_bindparam=visit_bindparam)
     
     def _lazy_none_clause(self, reverse_direction=False):
index e60b220439021a4c59080d4c339fcf604fe7f1db..48b6ea4cb33a0a72b1460061dbe1933441a941a0 100644 (file)
@@ -136,8 +136,8 @@ class UnitOfWork(object):
             delattr(state, 'insert_order')
         
         o = state.obj()
-        # prevent against last minute dereferences of "dirty"
-        # objects TODO: identify a code path where state.obj() is None
+        # prevent against last minute dereferences of the object
+        # TODO: identify a code path where state.obj() is None
         if o is not None:
             self.identity_map[state.dict['_instance_key']] = o
             state.commit_all()
index 8d8cfa38faa0e2c6dc120ede77686d0e31220161..c5ff974e52de7f3fb70ada1b0ffb3466d26a3625 100644 (file)
@@ -213,10 +213,19 @@ class DefaultCompiler(engine.Compiled):
                         pd[name] = params[paramname]
                         break
                 else:
-                    pd[name] = bindparam.value
+                    if callable(bindparam.value):
+                        pd[name] = bindparam.value()
+                    else:
+                        pd[name] = bindparam.value
             return pd
         else:
-            return dict([(self.bind_names[bindparam], bindparam.value) for bindparam in self.bind_names])
+            pd = {}
+            for bindparam in self.bind_names:
+                if callable(bindparam.value):
+                    pd[self.bind_names[bindparam]] = bindparam.value()
+                else:
+                    pd[self.bind_names[bindparam]] = bindparam.value
+            return pd
 
     params = property(construct_params)
 
index 27860517c114bb9c33abbb00c696e90351eaf589..bfb4d0d7701fbcc3feb31289a3a97aa467aa063e 100644 (file)
@@ -597,6 +597,26 @@ class DeferredBackrefTest(TestBase):
         lazy_load = (p1, p2, p3) = [Post("post 1"), Post("post 2"), Post("post 3")]
 
 class HistoryTest(TestBase):
+    def test_get_committed_value(self):
+        class Foo(fixtures.Base):
+            pass
+
+        attributes.register_class(Foo)
+        attributes.register_attribute(Foo, 'someattr', uselist=False, useobject=False)
+
+        f = Foo()
+        self.assertEquals(Foo.someattr.impl.get_committed_value(f._state), None)
+
+        f.someattr = 3
+        self.assertEquals(Foo.someattr.impl.get_committed_value(f._state), None)
+
+        f = Foo()
+        f.someattr = 3
+        self.assertEquals(Foo.someattr.impl.get_committed_value(f._state), None)
+        
+        f._state.commit(['someattr'])
+        self.assertEquals(Foo.someattr.impl.get_committed_value(f._state), 3)
+
     def test_scalar(self):
         class Foo(fixtures.Base):
             pass
index a1c850073e965e6c85956860a855e28ecfdb7351..1d6742d8ad2bd2d87989834005eeec7e5c933282 100644 (file)
@@ -140,6 +140,21 @@ class SessionTest(TestBase, AssertsExecutionResults):
         assert testing.db.connect().execute("select count(1) from users").scalar() == 1
         sess.close()
 
+    def test_autoflush_expressions(self):
+        class User(fixtures.Base):
+            pass
+        class Address(fixtures.Base):
+            pass
+        mapper(User, users, properties={
+            'addresses':relation(Address, backref="user")
+        })
+        mapper(Address, addresses)
+        
+        sess = create_session(autoflush=True, transactional=True)
+        u = User(user_name='ed', addresses=[Address(email_address='foo')])
+        sess.save(u)
+        self.assertEquals(sess.query(Address).filter(Address.user==u).one(), Address(email_address='foo'))
+        
     @testing.unsupported('sqlite', 'mssql') # TEMP: test causes mssql to hang
     @engines.close_open_connections
     def test_autoflush_unbound(self):