diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-02-22 23:17:15 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-02-22 23:17:15 +0000 |
commit | bb6ec17708e56170bcccf7e32703d0ba37b160d2 (patch) | |
tree | 1da7768491154707db045c235f1ba6f2170a02e7 | |
parent | e203b4dd4e2ce24e18fbeb9db515b2bdbbb56bc5 (diff) | |
download | sqlalchemy-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-- | CHANGES | 16 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 4 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 13 | ||||
-rw-r--r-- | test/orm/attributes.py | 20 | ||||
-rw-r--r-- | test/orm/session.py | 15 |
7 files changed, 67 insertions, 10 deletions
@@ -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): |