--- /dev/null
+.. change::
+ :tags: bug, engine
+ :tickets: 11306
+
+ Fixed issue in cursor handling which affected handling of duplicate
+ :class:`_sql.Column` or similar objcts in the columns clause of
+ :func:`_sql.select`, both in combination with arbitary :func:`_sql.text()`
+ clauses in the SELECT list, as well as when attempting to retrieve
+ :meth:`_engine.Result.mappings` for the object, which would lead to an
+ internal error.
+
+
**kwargs: Any,
) -> str:
if add_to_result_map is not None:
- add_to_result_map(func.name, func.name, (), func.type)
+ add_to_result_map(func.name, func.name, (func.name,), func.type)
disp = getattr(self, "visit_%s_func" % func.name.lower(), None)
objects: Tuple[Any, ...],
type_: TypeEngine[Any],
) -> None:
+
+ # note objects must be non-empty for cursor.py to handle the
+ # collection properly
+ assert objects
+
if keyname is None or keyname == "*":
self._ordered_columns = False
self._ad_hoc_textual = True
_add_to_result_map = add_to_result_map
def add_to_result_map(keyname, name, objects, type_):
- _add_to_result_map(keyname, name, (), type_)
+ _add_to_result_map(keyname, name, (keyname,), type_)
# if we redefined col_expr for type expressions, wrap the
# callable with one that adds the original column to the targets
eq_(row[6], "d3")
eq_(row[7], "d3")
+ @testing.requires.duplicate_names_in_cursor_description
+ @testing.combinations((None,), (0,), (1,), (2,), argnames="pos")
+ @testing.variation("texttype", ["literal", "text"])
+ def test_dupe_col_targeting(self, connection, pos, texttype):
+ """test #11306"""
+
+ keyed2 = self.tables.keyed2
+ col = keyed2.c.b
+ data_value = "b2"
+
+ cols = [col, col, col]
+ expected = [data_value, data_value, data_value]
+
+ if pos is not None:
+ if texttype.literal:
+ cols[pos] = literal_column("10")
+ elif texttype.text:
+ cols[pos] = text("10")
+ else:
+ texttype.fail()
+
+ expected[pos] = 10
+
+ stmt = select(*cols)
+
+ result = connection.execute(stmt)
+
+ if texttype.text and pos is not None:
+ # when using text(), the name of the col is taken from
+ # cursor.description directly since we don't know what's
+ # inside a text()
+ key_for_text_col = result.cursor.description[pos][0]
+ elif texttype.literal and pos is not None:
+ # for literal_column(), we use the text
+ key_for_text_col = "10"
+
+ eq_(result.all(), [tuple(expected)])
+
+ result = connection.execute(stmt).mappings()
+ if pos is None:
+ eq_(set(result.keys()), {"b", "b__1", "b__2"})
+ eq_(
+ result.all(),
+ [{"b": data_value, "b__1": data_value, "b__2": data_value}],
+ )
+
+ else:
+ eq_(set(result.keys()), {"b", "b__1", key_for_text_col})
+
+ eq_(
+ result.all(),
+ [{"b": data_value, "b__1": data_value, key_for_text_col: 10}],
+ )
+
def test_columnclause_schema_column_one(self, connection):
# originally addressed by [ticket:2932], however liberalized
# Column-targeting rules are deprecated