Skip to content

Commit eeec3b9

Browse files
committed
fix: reject foreign-table Column keys in UPSERT SET + add missing tests
- Reject Column objects from non-target tables in ON CONFLICT DO UPDATE SET fallback branch with CompileError (prevents silent retargeting) - Add test_index_where_rejected_do_update (symmetric with do_nothing test) - Add test_foreign_table_column_key_rejected (compile-time guard)
1 parent d075f7d commit eeec3b9

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

src/sqlalchemy_excel/compiler.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -965,17 +965,25 @@ def visit_on_conflict_do_update(self, on_conflict: Any, **kw: Any) -> str:
965965
if set_parameters:
966966
from sqlalchemy import util as sa_util
967967

968-
table_name = getattr(
969-
getattr(self.current_executable, "table", None),
970-
"name",
971-
"<unknown>",
972-
)
968+
insert_table = getattr(self.current_executable, "table", None)
969+
insert_table_name = getattr(insert_table, "name", "<unknown>")
970+
971+
# Reject foreign-table Column keys that would silently
972+
# retarget the assignment to the INSERT table
973+
for k in list(set_parameters):
974+
if hasattr(k, "table") and hasattr(k.table, "name"):
975+
if k.table.name != insert_table_name:
976+
raise exc.CompileError(
977+
"Column '%s' from table '%s' cannot be used as "
978+
"an ON CONFLICT DO UPDATE SET target for table '%s'"
979+
% (k.key, k.table.name, insert_table_name)
980+
)
973981

974982
sa_util.warn(
975983
"Additional column names not matching "
976984
"any column keys in table '%s': %s"
977985
% (
978-
table_name,
986+
insert_table_name,
979987
(", ".join("'%s'" % c for c in set_parameters)),
980988
)
981989
)

tests/test_upsert.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,29 @@ def test_missing_index_elements_do_update(items_table):
256256

257257
with pytest.raises(ValueError, match="requires index_elements"):
258258
stmt.on_conflict_do_update(set_={"age": 1})
259+
260+
261+
def test_index_where_rejected_do_update(items_table):
262+
"""index_where must be rejected on do_update too, not just do_nothing."""
263+
stmt = insert(items_table).values(id=1, name="Alice", age=30, code="A")
264+
265+
with pytest.raises(ValueError, match="does not support index_where"):
266+
stmt.on_conflict_do_update(
267+
index_elements=["id"],
268+
set_={"age": 1},
269+
index_where=items_table.c.id > 0,
270+
)
271+
272+
273+
def test_foreign_table_column_key_rejected(items_table, composite_items_table):
274+
"""set_ keys from a different table must be rejected at compile time."""
275+
stmt = insert(items_table).values(id=1, name="Alice", age=30, code="A")
276+
upsert = stmt.on_conflict_do_update(
277+
index_elements=["id"],
278+
set_={composite_items_table.c.age: 99},
279+
)
280+
from sqlalchemy.exc import CompileError
281+
from sqlalchemy_excel.dialect import ExcelDialect
282+
283+
with pytest.raises(CompileError, match="cannot be used as an ON CONFLICT"):
284+
upsert.compile(dialect=ExcelDialect())

0 commit comments

Comments
 (0)