merge_result() may throw an exception with joined query results sets

Issue #2640 resolved
Former user created an issue

from sqlalchemy/orm/loading.py in 0.8 or sqlalchemy/orm/query.py in 0.7

A query like:

session.query(Table_a, Table_b).outerjoin(Table_a.relation_table_b)

can produce result sets like:

[Table_a instance1>, None), (<Class Table_a instance2>, None), (<Class Table_a instance2>, <Class Table_b instance1>)]((<Class)

The 'None' types cause an exception in merge_result() when it tries to evaluate {{{attributes.instance_state(newrowi)}}} and newrowi is None

Line 129-136 of loading.py:

            for row in iterator:
                newrow = list(row)
                for i in mapped_entities:
                    newrow[i](i) = session._merge(
                            attributes.instance_state(newrow[i](i)),
                            attributes.instance_dict(newrow[i](i)),
                            load=load, _recursive={})
                result.append(util.KeyedTuple(newrow, keys))

To fix this, I added a check:

            for row in iterator:
                newrow = list(row)
                for i in mapped_entities:
                    if newrow[i](i) is not None:
                        newrow[i](i) = session._merge(
                            attributes.instance_state(newrow[i](i)),
                            attributes.instance_dict(newrow[i](i)),
                            load=load, _recursive={})
                result.append(util.KeyedTuple(newrow, keys))

In any case, it seems like a design accident that merge_result() works at all with tuples of class instances instead of a single class instance or list of rows; so, it may require a little more work to generalize merge_results() without any unintended consequences

Comments (5)

  1. Mike Bayer repo owner

    Replying to psharfstein:

    The 'None' types cause an exception in merge_result() when it tries to evaluate {{{attributes.instance_state(newrowi)}}} and newrowi is None

    got that, OK

    In any case, it seems like a design accident that merge_result() works at all with tuples of class instances instead of a single class instance or list of rows; so, it may require a little more work to generalize merge_results() without any unintended consequences

    I'm not following this part. Do you mean, query.merge_result((some_object, some_other_object)) as opposed to query.merge_result([some_other_object)]((some_object,))? the "iterator" argument to merge_result() is always supposed to be a list of entities or list of rows, which are linked to the structure of that Query. Basically whatever comes out of Query.__iter__(), for that Query is what it supports.

  2. Former user Account Deleted

    Replying to zzzeek:

    In any case, it seems like a design accident that merge_result() works at all with tuples of class instances instead of a single class instance or list of rows; so, it may require a little more work to generalize merge_results() without any unintended consequences

    I'm not following this part. Do you mean, query.merge_result((some_object, some_other_object)) as opposed to query.merge_result([some_other_object)]((some_object,))? the "iterator" argument to merge_result() is always supposed to be a list of entities or list of rows, which are linked to the structure of that Query. Basically whatever comes out of Query.__iter__(), for that Query is what it supports.

    Got it- I'll stop attributing your really great design to some random accident... Thanks for the quick response.

  3. Log in to comment