This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# sql/crud.py
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -241,7 +241,7 @@ def _get_crud_params(
|
||||
stmt_parameter_tuples = list(spd.items())
|
||||
spd_str_key = {_column_as_key(key) for key in spd}
|
||||
else:
|
||||
stmt_parameter_tuples = spd = spd_str_key = None
|
||||
stmt_parameter_tuples = spd_str_key = None
|
||||
|
||||
# if we have statement parameters - set defaults in the
|
||||
# compiled params
|
||||
@@ -332,6 +332,52 @@ def _get_crud_params(
|
||||
.difference(check_columns)
|
||||
)
|
||||
if check:
|
||||
|
||||
if dml.isupdate(compile_state):
|
||||
tables_mentioned = set(
|
||||
c.table
|
||||
for c, v in stmt_parameter_tuples
|
||||
if isinstance(c, ColumnClause) and c.table is not None
|
||||
).difference([compile_state.dml_table])
|
||||
|
||||
multi_not_in_from = tables_mentioned.difference(
|
||||
compile_state._extra_froms
|
||||
)
|
||||
|
||||
if tables_mentioned and (
|
||||
not compile_state.is_multitable
|
||||
or not compiler.render_table_with_column_in_update_from
|
||||
):
|
||||
if not compiler.render_table_with_column_in_update_from:
|
||||
preamble = (
|
||||
"Backend does not support additional "
|
||||
"tables in the SET clause"
|
||||
)
|
||||
else:
|
||||
preamble = (
|
||||
"Statement is not a multi-table UPDATE statement"
|
||||
)
|
||||
|
||||
raise exc.CompileError(
|
||||
f"{preamble}; cannot "
|
||||
f"""include columns from table(s) {
|
||||
", ".join(f"'{t.description}'"
|
||||
for t in tables_mentioned)
|
||||
} in SET clause"""
|
||||
)
|
||||
|
||||
elif multi_not_in_from:
|
||||
assert compiler.render_table_with_column_in_update_from
|
||||
raise exc.CompileError(
|
||||
f"Multi-table UPDATE statement does not include "
|
||||
"table(s) "
|
||||
f"""{
|
||||
", ".join(
|
||||
f"'{t.description}'" for
|
||||
t in multi_not_in_from)
|
||||
}"""
|
||||
)
|
||||
|
||||
raise exc.CompileError(
|
||||
"Unconsumed column names: %s"
|
||||
% (", ".join("%s" % (c,) for c in check))
|
||||
@@ -393,9 +439,9 @@ def _create_bind_param(
|
||||
process: Literal[True] = ...,
|
||||
required: bool = False,
|
||||
name: Optional[str] = None,
|
||||
force_anonymous: bool = False,
|
||||
**kw: Any,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
@@ -404,8 +450,7 @@ def _create_bind_param(
|
||||
col: ColumnElement[Any],
|
||||
value: Any,
|
||||
**kw: Any,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
|
||||
def _create_bind_param(
|
||||
@@ -415,10 +460,14 @@ def _create_bind_param(
|
||||
process: bool = True,
|
||||
required: bool = False,
|
||||
name: Optional[str] = None,
|
||||
force_anonymous: bool = False,
|
||||
**kw: Any,
|
||||
) -> Union[str, elements.BindParameter[Any]]:
|
||||
if name is None:
|
||||
if force_anonymous:
|
||||
name = None
|
||||
elif name is None:
|
||||
name = col.key
|
||||
|
||||
bindparam = elements.BindParameter(
|
||||
name, value, type_=col.type, required=required
|
||||
)
|
||||
@@ -488,7 +537,7 @@ def _key_getters_for_crud_column(
|
||||
)
|
||||
|
||||
def _column_as_key(
|
||||
key: Union[ColumnClause[Any], str]
|
||||
key: Union[ColumnClause[Any], str],
|
||||
) -> Union[str, Tuple[str, str]]:
|
||||
str_key = c_key_role(key)
|
||||
if hasattr(key, "table") and key.table in _et:
|
||||
@@ -834,6 +883,7 @@ def _append_param_parameter(
|
||||
):
|
||||
value = parameters.pop(col_key)
|
||||
|
||||
has_visiting_cte = kw.get("visiting_cte") is not None
|
||||
col_value = compiler.preparer.format_column(
|
||||
c, use_table=compile_state.include_table_with_column_exprs
|
||||
)
|
||||
@@ -859,11 +909,14 @@ def _append_param_parameter(
|
||||
c,
|
||||
value,
|
||||
required=value is REQUIRED,
|
||||
name=_col_bind_name(c)
|
||||
if not _compile_state_isinsert(compile_state)
|
||||
or not compile_state._has_multi_parameters
|
||||
else "%s_m0" % _col_bind_name(c),
|
||||
name=(
|
||||
_col_bind_name(c)
|
||||
if not _compile_state_isinsert(compile_state)
|
||||
or not compile_state._has_multi_parameters
|
||||
else "%s_m0" % _col_bind_name(c)
|
||||
),
|
||||
accumulate_bind_names=accumulated_bind_names,
|
||||
force_anonymous=has_visiting_cte,
|
||||
**kw,
|
||||
)
|
||||
elif value._is_bind_parameter:
|
||||
@@ -884,10 +937,12 @@ def _append_param_parameter(
|
||||
compiler,
|
||||
c,
|
||||
value,
|
||||
name=_col_bind_name(c)
|
||||
if not _compile_state_isinsert(compile_state)
|
||||
or not compile_state._has_multi_parameters
|
||||
else "%s_m0" % _col_bind_name(c),
|
||||
name=(
|
||||
_col_bind_name(c)
|
||||
if not _compile_state_isinsert(compile_state)
|
||||
or not compile_state._has_multi_parameters
|
||||
else "%s_m0" % _col_bind_name(c)
|
||||
),
|
||||
accumulate_bind_names=accumulated_bind_names,
|
||||
**kw,
|
||||
)
|
||||
@@ -1213,8 +1268,7 @@ def _create_insert_prefetch_bind_param(
|
||||
c: ColumnElement[Any],
|
||||
process: Literal[True] = ...,
|
||||
**kw: Any,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
@@ -1223,8 +1277,7 @@ def _create_insert_prefetch_bind_param(
|
||||
c: ColumnElement[Any],
|
||||
process: Literal[False],
|
||||
**kw: Any,
|
||||
) -> elements.BindParameter[Any]:
|
||||
...
|
||||
) -> elements.BindParameter[Any]: ...
|
||||
|
||||
|
||||
def _create_insert_prefetch_bind_param(
|
||||
@@ -1247,8 +1300,7 @@ def _create_update_prefetch_bind_param(
|
||||
c: ColumnElement[Any],
|
||||
process: Literal[True] = ...,
|
||||
**kw: Any,
|
||||
) -> str:
|
||||
...
|
||||
) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
@@ -1257,8 +1309,7 @@ def _create_update_prefetch_bind_param(
|
||||
c: ColumnElement[Any],
|
||||
process: Literal[False],
|
||||
**kw: Any,
|
||||
) -> elements.BindParameter[Any]:
|
||||
...
|
||||
) -> elements.BindParameter[Any]: ...
|
||||
|
||||
|
||||
def _create_update_prefetch_bind_param(
|
||||
@@ -1288,7 +1339,7 @@ class _multiparam_column(elements.ColumnElement[Any]):
|
||||
def compare(self, other, **kw):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _copy_internals(self, other, **kw):
|
||||
def _copy_internals(self, **kw):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __eq__(self, other):
|
||||
@@ -1364,9 +1415,28 @@ def _get_update_multitable_params(
|
||||
|
||||
affected_tables = set()
|
||||
for t in compile_state._extra_froms:
|
||||
# extra gymnastics to support the probably-shouldnt-have-supported
|
||||
# case of "UPDATE table AS alias SET table.foo = bar", but it's
|
||||
# supported
|
||||
we_shouldnt_be_here_if_columns_found = (
|
||||
not include_table
|
||||
and not compile_state.dml_table.is_derived_from(t)
|
||||
)
|
||||
|
||||
for c in t.c:
|
||||
if c in normalized_params:
|
||||
|
||||
if we_shouldnt_be_here_if_columns_found:
|
||||
raise exc.CompileError(
|
||||
"Backend does not support additional tables "
|
||||
"in the SET "
|
||||
"clause; cannot include columns from table(s) "
|
||||
f"'{t.description}' in "
|
||||
"SET clause"
|
||||
)
|
||||
|
||||
affected_tables.add(t)
|
||||
|
||||
check_columns[_getattr_col_key(c)] = c
|
||||
value = normalized_params[c]
|
||||
|
||||
@@ -1392,6 +1462,7 @@ def _get_update_multitable_params(
|
||||
value = compiler.process(value.self_group(), **kw)
|
||||
accumulated_bind_names = ()
|
||||
values.append((c, col_value, value, accumulated_bind_names))
|
||||
|
||||
# determine tables which are actually to be updated - process onupdate
|
||||
# and server_onupdate for these
|
||||
for t in affected_tables:
|
||||
@@ -1437,6 +1508,7 @@ def _extend_values_for_multiparams(
|
||||
values_0 = initial_values
|
||||
values = [initial_values]
|
||||
|
||||
has_visiting_cte = kw.get("visiting_cte") is not None
|
||||
mp = compile_state._multi_parameters
|
||||
assert mp is not None
|
||||
for i, row in enumerate(mp[1:]):
|
||||
@@ -1453,7 +1525,8 @@ def _extend_values_for_multiparams(
|
||||
compiler,
|
||||
col,
|
||||
row[key],
|
||||
name="%s_m%d" % (col.key, i + 1),
|
||||
name=("%s_m%d" % (col.key, i + 1)),
|
||||
force_anonymous=has_visiting_cte,
|
||||
**kw,
|
||||
)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user