summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-02-22 23:17:15 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-02-22 23:17:15 +0000
commitbb6ec17708e56170bcccf7e32703d0ba37b160d2 (patch)
tree1da7768491154707db045c235f1ba6f2170a02e7
parente203b4dd4e2ce24e18fbeb9db515b2bdbbb56bc5 (diff)
downloadsqlalchemy-bb6ec17708e56170bcccf7e32703d0ba37b160d2.tar.gz
- the value of a bindparam() can be a callable, in which
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
-rw-r--r--CHANGES16
-rw-r--r--lib/sqlalchemy/orm/attributes.py5
-rw-r--r--lib/sqlalchemy/orm/strategies.py4
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py4
-rw-r--r--lib/sqlalchemy/sql/compiler.py13
-rw-r--r--test/orm/attributes.py20
-rw-r--r--test/orm/session.py15
7 files changed, 67 insertions, 10 deletions
diff --git a/CHANGES b/CHANGES
index c9030a6ba..78112999c 100644
--- 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
------
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 5cc9cb309..298a7f511 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -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)
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index bdc8ab9a9..81a5895f4 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -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):
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index e60b22043..48b6ea4cb 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -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()
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py
index 8d8cfa38f..c5ff974e5 100644
--- a/lib/sqlalchemy/sql/compiler.py
+++ b/lib/sqlalchemy/sql/compiler.py
@@ -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)
diff --git a/test/orm/attributes.py b/test/orm/attributes.py
index 27860517c..bfb4d0d77 100644
--- a/test/orm/attributes.py
+++ b/test/orm/attributes.py
@@ -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
diff --git a/test/orm/session.py b/test/orm/session.py
index a1c850073..1d6742d8a 100644
--- a/test/orm/session.py
+++ b/test/orm/session.py
@@ -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):