exactly one expression in its columns clause.
- added "helper exception" to select.type access, generic functions raise
the chance of this happening
- a slight behavioral change to attributes is, del'ing an attribute
does *not* cause the lazyloader of that attribute to fire off again;
the "del" makes the effective value of the attribute "None". To
re-trigger the "loader" for an attribute, use
session.expire(instance, [attrname]).
- fix ormtutorial for IS NULL
names. Generated bind params now have the form "<paramname>_<num>",
whereas before only the second bind of the same name would have this form.
+ - select().as_scalar() will raise an exception if the select does not have
+ exactly one expression in its columns clause.
+
- bindparam() objects themselves can be used as keys for execute(), i.e.
statement.execute({bind1:'foo', bind2:'bar'})
(i.e. a pickled object or other mutable item), the reason for
the copy-on-load change in the first place, retain the old
behavior.
+
+ - a slight behavioral change to attributes is, del'ing an attribute
+ does *not* cause the lazyloader of that attribute to fire off again;
+ the "del" makes the effective value of the attribute "None". To
+ re-trigger the "loader" for an attribute, use
+ session.expire(instance, [attrname]).
- query.filter(SomeClass.somechild == None), when comparing
a many-to-one property to None, properly generates "id IS NULL"
[5]
{stop}[]
- a comparison to `None` also generates a negated EXISTS clause:
+ a comparison to `None` also generates an IS NULL clause for a many-to-one relation:
{python}
{sql}>>> session.query(Address).filter(Address.user==None).all()
SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id
FROM addresses
- WHERE NOT (EXISTS (SELECT 1
- FROM users
- WHERE users.id = addresses.user_id)) ORDER BY addresses.oid
+ WHERE addresses.user_id IS NULL ORDER BY addresses.oid
[]
{stop}[]
try:
return state.dict[self.key]
except KeyError:
- callable_ = self._get_callable(state)
- if callable_ is not None:
- if passive:
- return PASSIVE_NORESULT
- value = callable_()
- if value is not ATTR_WAS_SET:
- return self.set_committed_value(state, value)
- else:
- if self.key not in state.dict:
- return self.get(state, passive=passive)
- return state.dict[self.key]
- else:
- # Return a new, empty value
- return self.initialize(state)
+ # if no history, check for lazy callables, etc.
+ if self.key not in state.committed_state:
+ callable_ = self._get_callable(state)
+ if callable_ is not None:
+ if passive:
+ return PASSIVE_NORESULT
+ value = callable_()
+ if value is not ATTR_WAS_SET:
+ return self.set_committed_value(state, value)
+ else:
+ if self.key not in state.dict:
+ return self.get(state, passive=passive)
+ return state.dict[self.key]
+
+ # Return a new, empty value
+ return self.initialize(state)
def append(self, state, value, initiator, passive=False):
self.set(state, value, initiator)
self.dict.pop(attr.impl.key, None)
self.callables[attr.impl.key] = self.__fire_trigger
self.expired_attributes.add(attr.impl.key)
+ self.committed_state = {}
else:
for key in attribute_names:
self.dict.pop(key, None)
+ self.committed_state.pop(key, None)
if not getattr(self.class_, key).impl.accepts_global_callable:
continue
docstring for more details.
"""
- def __init__(self):
- self.primary_key = False
- self.foreign_keys = []
+ primary_key = False
+ foreign_keys = []
def base_columns(self):
if hasattr(self, '_base_columns'):
self.oid_column = None
def _get_from_objects(self, **modifiers):
- # this could also be [self], at the moment it doesnt matter to the Select object
return []
def default_order_by(self):
"""
def __init__(self, elem):
- ColumnElement.__init__(self)
self.elem = elem
self.type = getattr(elem, 'type', None)
__visit_name__ = 'grouping'
def __init__(self, elem):
- super(_ScalarSelect, self).__init__(elem)
- self.type = list(elem.inner_columns)[0].type
+ self.elem = elem
+ cols = list(elem.inner_columns)
+ if len(cols) != 1:
+ raise exceptions.InvalidRequestError("Scalar select can only be created from a Select object that has exactly one column expression.")
+ self.type = cols[0].type
def _no_cols(self):
raise exceptions.InvalidRequestError("Scalar Select expression has no columns; use this object directly within a column-level expression.")
froms = property(_get_display_froms, doc="""Return a list of all FromClause elements which will be applied to the FROM clause of the resulting statement.""")
+ def type(self):
+ raise exceptions.InvalidRequestError("Select objects don't have a type. Call as_scalar() on this Select object to return a 'scalar' version of this Select.")
+ type = property(type)
+
def locate_all_froms(self):
"""return a Set of all FromClause elements referenced by this Select.
f = Foo()
f.bars.append(bar2)
self.assertEquals(attributes.get_history(f._state, 'bars'), ([bar2], [], []))
-
+
def test_scalar_via_lazyload(self):
+ class Foo(fixtures.Base):
+ pass
+
+ lazy_load = None
+ def lazyload(instance):
+ def load():
+ return lazy_load
+ return load
+
+ attributes.register_class(Foo)
+ attributes.register_attribute(Foo, 'bar', uselist=False, callable_=lazyload, useobject=False)
+ lazy_load = "hi"
+
+ # with scalar non-object, the lazy callable is only executed on gets, not history
+ # operations
+
+ f = Foo()
+ self.assertEquals(f.bar, "hi")
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([], ["hi"], []))
+
+ f = Foo()
+ f.bar = None
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([None], [], []))
+
+ f = Foo()
+ f.bar = "there"
+ self.assertEquals(attributes.get_history(f._state, 'bar'), (["there"], [], []))
+ f.bar = "hi"
+ self.assertEquals(attributes.get_history(f._state, 'bar'), (["hi"], [], []))
+
+ f = Foo()
+ self.assertEquals(f.bar, "hi")
+ del f.bar
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([], [], ["hi"]))
+ assert f.bar is None
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([None], [], ["hi"]))
+
+ def test_scalar_object_via_lazyload(self):
class Foo(fixtures.Base):
pass
class Bar(fixtures.Base):
bar1, bar2 = [Bar(id=1), Bar(id=2)]
lazy_load = bar1
+ # with scalar object, the lazy callable is only executed on gets and history
+ # operations
+
f = Foo()
self.assertEquals(attributes.get_history(f._state, 'bar'), ([], [bar1], []))
f.bar = bar1
self.assertEquals(attributes.get_history(f._state, 'bar'), ([], [bar1], []))
+ f = Foo()
+ self.assertEquals(f.bar, bar1)
+ del f.bar
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([None], [], [bar1]))
+ assert f.bar is None
+ self.assertEquals(attributes.get_history(f._state, 'bar'), ([None], [], [bar1]))
+
if __name__ == "__main__":
testbase.main()
assert hasattr(table1.select(), 'c')
assert not hasattr(table1.c.myid.self_group(), 'columns')
assert hasattr(table1.select().self_group(), 'columns')
- assert not hasattr(table1.select().as_scalar().self_group(), 'columns')
+ assert not hasattr(select([table1.c.myid]).as_scalar().self_group(), 'columns')
assert not hasattr(table1.c.myid, 'columns')
assert not hasattr(table1.c.myid, 'c')
assert not hasattr(table1.select().c.myid, 'c')
def test_scalar_select(self):
+ try:
+ s = select([table1.c.myid, table1.c.name]).as_scalar()
+ assert False
+ except exceptions.InvalidRequestError, err:
+ assert str(err) == "Scalar select can only be created from a Select object that has exactly one column expression.", str(err)
+
+ try:
+ # generic function which will look at the type of expression
+ func.coalesce(select([table1.c.myid]))
+ assert False
+ except exceptions.InvalidRequestError, err:
+ assert str(err) == "Select objects don't have a type. Call as_scalar() on this Select object to return a 'scalar' version of this Select.", str(err)
+
s = select([table1.c.myid], scalar=True, correlate=False)
self.assert_compile(select([table1, s]), "SELECT mytable.myid, mytable.name, mytable.description, (SELECT mytable.myid FROM mytable) AS anon_1 FROM mytable")