Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions web/pgadmin/tools/sqleditor/utils/save_changed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ def save_changed_data(changed_data, columns_info, conn, command_obj,
list_of_sql[of_type] = []
for each_row in changed_data[of_type]:
data = changed_data[of_type][each_row]['data']

# Drop any column the client included that isn't a real
# editable column of the underlying table (e.g.
# `first_name || ' ' || last_name as the_name`). The
# frontend already marks such columns as non-editable
# (shown with a lock icon), but still includes them in
# the changed data. Without this guard the rendered
# UPDATE references a non-existent column and Postgres
# rejects it. Issue #10103.
data = {
k: v for k, v in data.items()
if k in columns_info and
columns_info[k].get('is_editable', True)
}

pk_escaped = {
pk: pk_val.replace('%', '%%') if hasattr(
pk_val, 'replace') else pk_val
Expand Down
84 changes: 84 additions & 0 deletions web/pgadmin/tools/sqleditor/utils/tests/test_save_changed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1040,3 +1040,87 @@ def _create_test_table(self):
"FROM {0};"
).format(self.test_table_name)
utils.create_table_with_query(self.server, self.db_name, create_sql)


class TestSaveUpdatedRowSkipsNonEditableColumn(TestSaveChangedData):
"""Regression test for issue #10103.

When a Query Tool result includes an expression or alias column
(e.g. ``first_name || ' ' || last_name AS the_name``), editing a
real column on an existing row must not include the alias in the
generated UPDATE statement. The alias is not a real column of the
underlying table, so pgAdmin already flags it as non-editable (shown
with a lock icon in the grid) but the update save flow used to send
it anyway, causing PostgreSQL to reject the statement with
``column "the_name" does not exist``.
"""

scenarios = [
('Update a real column while an aliased expression column is '
'present', dict(
save_payload={
"updated": {
"1": {
"err": False,
"data": {
"first_name": "Jane",
# The client includes the aliased expression
# column in the changed data even though it
# is marked non-editable. Sending it must
# not break the UPDATE.
"the_name": "Jane Doe"
},
"primary_keys": {"id": 1}
}
},
"added": {},
"staged_rows": {},
"deleted": {},
"updated_index": {"1": "1"},
"added_index": {},
"columns": [
{"name": "id", "pos": 0, "can_edit": True,
"type": "integer", "cell": "number",
"not_null": True, "has_default_val": False,
"is_array": False, "display_name": "id"},
{"name": "first_name", "pos": 1, "can_edit": True,
"type": "text", "cell": "string",
"not_null": False, "has_default_val": False,
"is_array": False, "display_name": "first_name"},
{"name": "last_name", "pos": 2, "can_edit": True,
"type": "text", "cell": "string",
"not_null": False, "has_default_val": False,
"is_array": False, "display_name": "last_name"},
{"name": "the_name", "pos": 3, "can_edit": False,
"type": "text", "cell": "string",
"not_null": False, "has_default_val": False,
"is_array": False, "display_name": "the_name"},
]
},
save_status=True,
check_sql='SELECT id, first_name, last_name '
'FROM %s WHERE id = 1',
check_result=[[1, "Jane", "Doe"]]
)),
]

def _create_test_table(self):
self.test_table_name = "test_for_save_data_alias_" + \
str(secrets.choice(range(1000, 9999)))
create_sql = """
DROP TABLE IF EXISTS "{0}";

CREATE TABLE "{0}"(
id INT PRIMARY KEY,
first_name TEXT,
last_name TEXT
);

INSERT INTO "{0}" VALUES (1, 'John', 'Doe');
""".format(self.test_table_name)
self.select_sql = (
"SELECT id, first_name, last_name, "
"first_name || ' ' || last_name AS the_name "
"FROM {0};"
).format(self.test_table_name)
utils.create_table_with_query(self.server, self.db_name, create_sql)
Loading