- mapper sets its "primary_key" attribute to be the ultimately decided primary_key column collection post-compilation
- added compare() method to MapperProperty, defines a comparison operation of the columns represented by the property to some value
- all the above combines into today's controversial feature: saying query.select_by(somerelationname=someinstance) will create the join of the primary key columns represented by "somerelationname"'s mapper to the actual primary key in "someinstance".
- docs for the above
- Firebird fix to autoload multifield foreign keys [ticket:409]
- Firebird NUMERIC type properly handles a type without precision [ticket:409]
- orm:
+ - poked the first hole in the can of worms: saying query.select_by(somerelationname=someinstance)
+ will create the join of the primary key columns represented by "somerelationname"'s mapper to the
+ actual primary key in "someinstance".
- added a mutex to the mapper compilation step. ive been reluctant to add any kind
of threading anything to SA but this is one spot that its its really needed since mappers
are typically "global", and while their state does not change during normal operation, the
Note that the `select_by` method, while it primarily uses keyword arguments, also can accomodate `ClauseElement` objects positionally; recall that a `ClauseElement` is genearated when producing a comparison off of a `Column` expression, such as `users.c.name=='ed'`. When using `ClauseElements` with `select_by`, these clauses are passed directly to the generated SQL and are **not** used to further locate join criterion. If criterion is being constructed with these kinds of expressions, consider using the `select()` method which is better designed to accomodate these expressions.
+As of SA 0.3.4, `select_by()` and related functions can compare not only column-based attributes to column-based values, but also relations to object instances:
+
+ {python}
+ # get an instance of Address
+ someaddress = session.query(Address).get_by(street='123 Green Street')
+
+ # look for User instances which have the
+ # "someaddress" instance in their "addresses" collection
+ l = session.query(User).select_by(addresses=someaddress)
+
+Where above, the comparison denoted by `addresses=someaddress` is constructed by comparing all the primary key columns in the `Address` mapper to each corresponding primary key value in the `someaddress` entity. In other words, its equivalent to saying `select_by(address_id=someaddress.address_id)` ([Alpha API][alpha_api]).
+
#### Generating Join Criterion Using join\_to, join\_via {@name=jointo}
Feature Status: [Alpha API][alpha_api]
def merge(self, session, source, dest):
"""merges the attribute represented by this MapperProperty from source to destination object"""
raise NotImplementedError()
+ def compare(self, value):
+ """returns a compare operation for the columns represented by this MapperProperty to the given value,
+ which may be a column value or an instance."""
+ raise NotImplementedError()
class StrategizedProperty(MapperProperty):
"""a MapperProperty which uses selectable strategies to affect loading behavior.
if len(self.pks_by_table[self.mapped_table]) == 0:
raise exceptions.ArgumentError("Could not assemble any primary key columns for mapped table '%s'" % (self.mapped_table.name))
-
+ self.primary_key = self.pks_by_table[self.mapped_table]
def _compile_properties(self):
"""inspects the properties dictionary sent to the Mapper's constructor as well as the mapped_table, and creates
prop = self._getpropbycolumn(column, raiseerror)
if prop is None:
return NO_ATTRIBUTE
- #self.__log_debug("get column attribute '%s' from instance %s" % (column.key, mapperutil.instance_str(obj)))
+ #print "get column attribute '%s' from instance %s" % (column.key, mapperutil.instance_str(obj))
return prop.getattr(obj)
def set_attr_by_column(self, obj, column, value):
else:
return strategies.ColumnLoader(self)
def getattr(self, object):
- return getattr(object, self.key, None)
+ return getattr(object, self.key)
def setattr(self, object, value):
setattr(object, self.key, value)
def get_history(self, obj, passive=False):
return sessionlib.attribute_manager.get_history(obj, self.key, passive=passive)
def merge(self, session, source, dest):
setattr(dest, self.key, getattr(source, self.key, None))
+ def compare(self, value):
+ return self.columns[0] == value
ColumnProperty.logger = logging.class_logger(ColumnProperty)
else:
self.backref = backref
self.is_backref = is_backref
+
+ def compare(self, value):
+ return sql.and_(*[x==y for (x, y) in zip(self.mapper.primary_key, self.mapper.primary_key_from_instance(value))])
private = property(lambda s:s.cascade.delete_orphan)
for key, value in params.iteritems():
(keys, prop) = self._locate_prop(key)
- c = (prop.columns[0]==value) & self.join_via(keys)
+ c = prop.compare(value) & self.join_via(keys)
if clause is None:
clause = c
else:
overrides the __eq__() method to produce SQL clauses between sets of
correlated columns."""
+ def __init__(self, *cols):
+ super(ColumnCollection, self).__init__()
+ [self.add(c) for c in cols]
def add(self, column):
"""add a column to this collection.
}))
})
- q = create_session().query(m)
+ sess = create_session()
+ q = sess.query(m)
l = q.select((orderitems.c.item_name=='item 4') & q.join_via(['orders', 'items']))
self.assert_result(l, User, user_result[0])
l = q.select((orderitems.c.item_name=='item 4') & q.join_to('items'))
self.assert_result(l, User, user_result[0])
+
+ # test comparing to an object instance
+ item = sess.query(Item).get_by(item_name='item 4')
+ l = q.select_by(items=item)
+ self.assert_result(l, User, user_result[0])
+ try:
+ # this should raise AttributeError
+ l = q.select_by(items=5)
+ assert False
+ except AttributeError:
+ assert True
+
def testcustomjoin(self):
"""test that the from_obj parameter to query.select() can be used
to totally replace the FROM parameters of the generated query."""