.. include:: changelog_07.rst
: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
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
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:
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:
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
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
# -*- 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
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))
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):
)
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'
(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)
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):
(Column("q", Integer) + 12, r"q \+ :q_1"),
(unprintable(), "unprintable element.*"),
]:
+ assert_raises_message(
+ exc.NoSuchColumnError,
+ msg % repl,
+ result._getter, accessor
+ )
+
+ is_(result._getter(accessor, False), None)
+
assert_raises_message(
exc.NoSuchColumnError,
msg % repl,