diff options
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 26 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/result.py | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/loading.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 10 | ||||
-rw-r--r-- | test/dialect/mssql/test_compiler.py | 26 | ||||
-rw-r--r-- | test/orm/test_loading.py | 18 | ||||
-rw-r--r-- | test/sql/test_compiler.py | 23 | ||||
-rw-r--r-- | test/sql/test_resultset.py | 11 |
9 files changed, 110 insertions, 16 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 1e10b97d0..fa1be4c16 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -16,6 +16,32 @@ :start-line: 5 .. changelog:: + :version: 1.0.13 + + .. change:: + :tags: bug, orm + :tickets: 3658 + + Fixed regression appearing in the 1.0 series in ORM loading where the + exception raised for an expected column missing would incorrectly + be a ``NoneType`` error, rather than the expected + :class:`.NoSuchColumnError`. + + .. change:: + :tags: bug, mssql, oracle + :tickets: 3657 + + Fixed regression appearing in the 1.0 series which would cause the Oracle + and SQL Server dialects to incorrectly account for result set columns + when these dialects would wrap a SELECT in a subquery in order to + provide LIMIT/OFFSET behavior, and the original SELECT statement + referred to the same column multiple times, such as a column and + a label of that same column. This issue is related + to :ticket:`3658` in that when the error occurred, it would also + cause a ``NoneType`` error, rather than reporting that it couldn't + locate a column. + +.. changelog:: :version: 1.0.12 :released: February 15, 2016 diff --git a/lib/sqlalchemy/engine/result.py b/lib/sqlalchemy/engine/result.py index c069fcedf..f09b0b40b 100644 --- a/lib/sqlalchemy/engine/result.py +++ b/lib/sqlalchemy/engine/result.py @@ -561,11 +561,11 @@ class ResultMetaData(object): else: return self._key_fallback(key, False) is not None - def _getter(self, key): + def _getter(self, key, raiseerr=True): if key in self._keymap: processor, obj, index = self._keymap[key] else: - ret = self._key_fallback(key, False) + ret = self._key_fallback(key, raiseerr) if ret is None: return None processor, obj, index = ret @@ -640,13 +640,13 @@ class ResultProxy(object): context.engine._should_log_debug() self._init_metadata() - def _getter(self, key): + def _getter(self, key, raiseerr=True): try: getter = self._metadata._getter except AttributeError: return self._non_result(None) else: - return getter(key) + return getter(key, raiseerr) def _has_key(self, key): try: diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index b8bc205d4..d457f3c63 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -316,7 +316,7 @@ def _instance_processor( else: if adapter: col = adapter.columns[col] - getter = result._getter(col) + getter = result._getter(col, False) if getter: populators["quick"].append((prop.key, getter)) else: diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 7d816e626..370cb974b 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -174,7 +174,7 @@ class ColumnLoader(LoaderStrategy): for col in self.columns: if adapter: col = adapter.columns[col] - getter = result._getter(col) + getter = result._getter(col, False) if getter: populators["quick"].append((self.key, getter)) break diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index a2fc0fe68..b75dc1c07 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1638,15 +1638,11 @@ class SQLCompiler(Compiled): if populate_result_map and select_wraps_for is not None: # if this select is a compiler-generated wrapper, # rewrite the targeted columns in the result map - wrapped_inner_columns = set(select_wraps_for.inner_columns) + translate = dict( - (outer, inner.pop()) for outer, inner in [ - ( - outer, - outer.proxy_set.intersection(wrapped_inner_columns)) - for outer in select.inner_columns - ] if inner + zip(select.inner_columns, select_wraps_for.inner_columns) ) + self._result_columns = [ (key, name, tuple(translate.get(o, o) for o in obj), type_) for key, name, obj, type_ in self._result_columns diff --git a/test/dialect/mssql/test_compiler.py b/test/dialect/mssql/test_compiler.py index d91c79db2..b59ca4fd1 100644 --- a/test/dialect/mssql/test_compiler.py +++ b/test/dialect/mssql/test_compiler.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -from sqlalchemy.testing import eq_ +from sqlalchemy.testing import eq_, is_ from sqlalchemy import schema from sqlalchemy.sql import table, column from sqlalchemy.databases import mssql @@ -521,6 +521,30 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): assert t.c.x in set(c._create_result_map()['x'][1]) assert t.c.y in set(c._create_result_map()['y'][1]) + def test_limit_offset_w_ambiguous_cols(self): + t = table('t', column('x', Integer), column('y', Integer)) + + cols = [t.c.x, t.c.x.label('q'), t.c.x.label('p'), t.c.y] + s = select(cols).where(t.c.x == 5).order_by(t.c.y).limit(10).offset(20) + + self.assert_compile( + s, + "SELECT anon_1.x, anon_1.q, anon_1.p, anon_1.y " + "FROM (SELECT t.x AS x, t.x AS q, t.x AS p, t.y AS y, " + "ROW_NUMBER() OVER (ORDER BY t.y) AS mssql_rn " + "FROM t " + "WHERE t.x = :x_1) AS anon_1 " + "WHERE mssql_rn > :param_1 AND mssql_rn <= :param_2 + :param_1", + checkparams={'param_1': 20, 'param_2': 10, 'x_1': 5} + ) + c = s.compile(dialect=mssql.MSDialect()) + eq_(len(c._result_columns), 4) + + result_map = c._create_result_map() + + for col in cols: + is_(result_map[col.key][1][0], col) + def test_limit_offset_with_correlated_order_by(self): t1 = table('t1', column('x', Integer), column('y', Integer)) t2 = table('t2', column('x', Integer), column('y', Integer)) diff --git a/test/orm/test_loading.py b/test/orm/test_loading.py index f86477ec2..6f3f6a016 100644 --- a/test/orm/test_loading.py +++ b/test/orm/test_loading.py @@ -1,8 +1,11 @@ from . import _fixtures from sqlalchemy.orm import loading, Session, aliased -from sqlalchemy.testing.assertions import eq_, assert_raises +from sqlalchemy.testing.assertions import eq_, \ + assert_raises, assert_raises_message from sqlalchemy.util import KeyedTuple from sqlalchemy.testing import mock +from sqlalchemy import select +from sqlalchemy import exc # class GetFromIdentityTest(_fixtures.FixtureTest): # class LoadOnIdentTest(_fixtures.FixtureTest): # class InstanceProcessorTest(_fixture.FixtureTest): @@ -34,6 +37,19 @@ class InstancesTest(_fixtures.FixtureTest): ) assert cursor.close.called, "Cursor wasn't closed" + def test_row_proc_not_created(self): + User = self.classes.User + s = Session() + + q = s.query(User.id, User.name) + stmt = select([User.id]) + + assert_raises_message( + exc.NoSuchColumnError, + "Could not locate column in row for column 'users.name'", + q.from_statement(stmt).all + ) + class MergeResultTest(_fixtures.FixtureTest): run_setup_mappers = 'once' diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index f11a4e61a..1c5dc2340 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -3855,3 +3855,26 @@ class ResultMapTest(fixtures.TestBase): (table1.c.description, 'description', 'description'), table1.c.description.type)} ) + + def test_select_wraps_for_translate_ambiguity(self): + # test for issue #3657 + t = table('a', column('x'), column('y'), column('z')) + + l1, l2, l3 = t.c.z.label('a'), t.c.x.label('b'), t.c.x.label('c') + orig = [t.c.x, t.c.y, l1, l2, l3] + stmt = select(orig) + wrapped = stmt._generate() + wrapped = wrapped.column( + func.ROW_NUMBER().over(order_by=t.c.z)).alias() + + wrapped_again = select([c for c in wrapped.c]) + + compiled = wrapped_again.compile( + compile_kwargs={'select_wraps_for': stmt}) + + proxied = [obj[0] for (k, n, obj, type_) in compiled._result_columns] + for orig_obj, proxied_obj in zip( + orig, + proxied + ): + is_(orig_obj, proxied_obj) diff --git a/test/sql/test_resultset.py b/test/sql/test_resultset.py index bd2b8c0ae..aaeb82fa4 100644 --- a/test/sql/test_resultset.py +++ b/test/sql/test_resultset.py @@ -204,7 +204,8 @@ class ResultProxyTest(fixtures.TablesTest): lambda: result[0][addresses.c.address_id]) def test_column_error_printing(self): - row = testing.db.execute(select([1])).first() + result = testing.db.execute(select([1])) + row = result.first() class unprintable(object): @@ -222,6 +223,14 @@ class ResultProxyTest(fixtures.TablesTest): assert_raises_message( exc.NoSuchColumnError, msg % repl, + result._getter, accessor + ) + + is_(result._getter(accessor, False), None) + + assert_raises_message( + exc.NoSuchColumnError, + msg % repl, lambda: row[accessor] ) |