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
1 change: 1 addition & 0 deletions docs/en_US/release_notes_9_16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Bug fixes
| `Issue #6308 <https://github.com/pgadmin-org/pgadmin4/issues/6308>`_ - Fix the infinite loading spinner after an idle database connection is silently dropped, by detecting stale connections and offering a reconnect dialog.
| `Issue #9595 <https://github.com/pgadmin-org/pgadmin4/issues/9595>`_ - Fix missing ALTER ... SET DEFAULT statements for inherited columns in the generated table SQL/EDIT script.
| `Issue #9677 <https://github.com/pgadmin-org/pgadmin4/issues/9677>`_ - Fix the Unlogged table toggle in table properties not generating any ALTER TABLE ... SET LOGGED/UNLOGGED statement.
| `Issue #9744 <https://github.com/pgadmin-org/pgadmin4/issues/9744>`_ - Fix a View/Edit Data crash when the session contains a transaction object that is not filter-capable (e.g. left by the Query Tool or persisted by an older version), which could prevent the desktop application from loading after an upgrade.
| `Issue #9828 <https://github.com/pgadmin-org/pgadmin4/issues/9828>`_ - Fix tool calls failing against OpenAI-compatible providers that emit empty/null name, arguments, or id fields in streaming continuation deltas.
| `Issue #9875 <https://github.com/pgadmin-org/pgadmin4/issues/9875>`_ - Fixed an issue where EXPLAIN and EXPLAIN ANALYZE failed to execute when blank lines separated clauses in the SQL query.
| `Issue #9810 <https://github.com/pgadmin-org/pgadmin4/issues/9810>`_ - Use the ServerManager's passfile for the credential gate in connect() so the check matches the passfile actually used for the connection, and warn on conflicting passfile/passexec settings.
Expand Down
10 changes: 9 additions & 1 deletion web/pgadmin/tools/sqleditor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,15 @@ def initialize_viewdata(trans_id, cmd_type, obj_type, sgid, sid, did, obj_id):
if str(trans_id) in sql_grid_data:
old_trans_obj = pickle.loads(
sql_grid_data[str(trans_id)]['command_obj'])
if old_trans_obj.did == did and old_trans_obj.obj_id == obj_id:
# Only restore the filter/sorting when the previously stored object is
# a filter-capable (View/Edit Data) command. The same trans_id may
# have been used by a non-filter command such as the Query Tool, or by
# an incompatible object persisted by an older version - neither
# carries the _row_filter/_data_sorting attributes, and blindly
# accessing them raises an AttributeError that prevents the tool (and,
# in desktop mode, the application) from loading.
if isinstance(old_trans_obj, SQLFilter) and \
old_trans_obj.did == did and old_trans_obj.obj_id == obj_id:
command_obj.set_filter(old_trans_obj._row_filter)
command_obj.set_data_sorting(
dict(data_sorting=old_trans_obj._data_sorting), True)
Comment on lines 331 to 333
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################

import uuid
import pickle
import secrets
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from regression import parent_node_dict
from regression.python_test_utils import test_utils
from pgadmin.tools.sqleditor.command import QueryToolCommand


class TestViewDataRestoreStaleTransObj(BaseTestGenerator):
"""
Regression test for issue #9744.

When View/Edit Data initialises a transaction it tries to restore the
filter/sorting from any command object previously stored in the session
under the same trans_id. That stored object is not always a filter-capable
(View/Edit Data) command - the same trans_id may have been used by the
Query Tool, or the session may contain an incompatible object persisted by
an older version after an upgrade. In those cases the object has no
_row_filter/_data_sorting attributes, and blindly accessing them raised an
AttributeError that returned a 500 and, in desktop mode, prevented the
application from loading.

This test seeds the session with a pickled QueryToolCommand (which is what
a restored Query Tool transaction leaves in session['gridData']) under the
trans_id used to initialise View/Edit Data, and asserts the request
succeeds instead of crashing.
"""
scenarios = [
('Initialize View/Edit Data over a stale non-filter trans object',
dict())
]

def setUp(self):
self.server_id = self.server_information['server_id']
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.db_id = self.database_info["db_id"]

self.connection = test_utils.get_db_connection(
self.db_name,
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port']
)

db_con = database_utils.connect_database(self, test_utils.SERVER_GROUP,
self.server_id, self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to the database.")

def runTest(self):
self.table = "test_table_%s" % (str(uuid.uuid4())[1:8])
# Note: do not give the primary key an explicit constraint name -
# other tests in this package create tables in the same database, and
# a hard-coded constraint name would collide across the suite. Letting
# PostgreSQL auto-name it (<table>_pkey) keeps it unique.
table_sql = """Create Table %s(
id integer Not Null Primary Key
);""" % self.table
test_utils.create_table_with_query(self.server, self.db_name,
table_sql)

# Fetch the table OID
pg_cursor = self.connection.cursor()
pg_cursor.execute("""Select oid FROM pg_catalog.pg_class WHERE
relname = '%s' AND relkind IN ('r','s','t')""" % self.table)
table_id = pg_cursor.fetchall()[0][0]

trans_id = str(secrets.choice(range(1, 9999999)))

# Build a QueryToolCommand - the kind of object the Query Tool stores
# in session['gridData'] - and give it the same did/obj_id as the
# table so the restore guard in initialize_viewdata is reached. A
# QueryToolCommand has no _row_filter attribute, which is what
# previously triggered the AttributeError.
stale_obj = QueryToolCommand(
sgid=test_utils.SERVER_GROUP, sid=self.server_id, did=self.db_id)
stale_obj.obj_id = table_id

with self.tester.session_transaction() as sess:
grid_data = sess.get('gridData', {})
grid_data[trans_id] = {
'command_obj': pickle.dumps(stale_obj, -1)
}
sess['gridData'] = grid_data

url = '/sqleditor/initialize/viewdata/{0}/3/table/{1}/{2}/{3}/{4}' \
.format(trans_id, test_utils.SERVER_GROUP, self.server_id,
self.db_id, table_id)
response = self.tester.post(url)

# Before the fix this returned 500 with:
# 'QueryToolCommand' object has no attribute '_row_filter'
self.assertEqual(response.status_code, 200)

def tearDown(self):
self.connection.cursor().execute(
"DROP TABLE IF EXISTS %s" % self.table)
self.connection.commit()
self.connection.close()
database_utils.disconnect_database(self, self.server_id,
self.db_id)
Loading