Skip to content
Closed
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
20 changes: 18 additions & 2 deletions web/pgadmin/tools/sqleditor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,11 @@ def panel(trans_id):
params=json.dumps(params))


# Execute options that can be changed from the Query Tool toolbar, and which
# are kept on the transaction object.
EXECUTE_OPTIONS = ('auto_commit', 'auto_rollback', 'server_cursor')


@blueprint.route(
'/initialize/sqleditor/<int:trans_id>/<int:sgid>/<int:sid>/'
'<did>',
Expand Down Expand Up @@ -496,6 +501,13 @@ def initialize_sqleditor(trans_id, sgid, sid, did=None):
'role': data['role'] if 'role' in data else None,
'password': data['password'] if 'password' in data else None
}
# The execute options currently chosen on the toolbar, sent when the
# transaction is re-created, so they are kept rather than reset to the
# preferences. Anything other than a boolean falls back to the
# preference in _init_sqleditor.
for option in EXECUTE_OPTIONS:
if isinstance(data.get(option), bool):
kwargs[option] = data[option]

is_error, errmsg, conn_id, version = _init_sqleditor(
trans_id, connect, sgid, sid, did, data['dbname'], **kwargs)
Expand Down Expand Up @@ -721,9 +733,13 @@ def update_sqleditor_connection(trans_id, sgid, sid, did):
'user': data['user'],
'role': data['role'] if 'role' in data else None,
'password': data['password'] if 'password' in data else None,
'auto_commit': getattr(trans_obj, 'auto_commit', None),
'auto_rollback': getattr(trans_obj, 'auto_rollback', None),
}
# Keep the execute options from the old transaction, but only
# booleans, as initialize_sqleditor() does; anything else falls
# back to the preferences.
for option in EXECUTE_OPTIONS:
value = getattr(trans_obj, option, None)
kwargs[option] = value if isinstance(value, bool) else None

is_error, errmsg, conn_id, version = _init_sqleditor(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
new_trans_id, connect, sgid, sid, did, data['database_name'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ const FIXED_PREF = {
},
};

// The body of the request that initialises the transaction. A Query Tool
// sends the execute options currently chosen on the toolbar, so that they
// are kept rather than reset to the preferences when the transaction is
// re-created; only boolean values are sent, and the server uses the
// preference for any that are missing.
export function getInitialiseRequestData(params, connParams, toolbarOptions) {
if(params.is_query_tool) {
return {
..._.pickBy(_.pick(toolbarOptions ?? {}, ['auto_commit', 'auto_rollback', 'server_cursor']), _.isBoolean),
...connParams,
};
}
return {sql_filter: params.sql_filter, server_cursor: params.server_cursor, ...connParams};
}

export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedNodeInfo, qtPanelDocker, qtPanelId, eventBusObj}) {
const containerRef = React.useRef(null);
const preferencesStore = usePreferences();
Expand Down Expand Up @@ -397,9 +412,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
password: password,
dbname: selectedConn.database_name
};
api.post(baseUrl, qtState.params.is_query_tool ?
{...requestParams} :
{sql_filter: qtState.params.sql_filter, server_cursor: qtState.params.server_cursor, ...requestParams})
api.post(baseUrl, getInitialiseRequestData(qtState.params, requestParams, toolbarOptionsRef.current))
.then(()=>{
setQtStatePartial({
connected: true,
Expand Down
118 changes: 118 additions & 0 deletions web/pgadmin/tools/sqleditor/tests/test_execute_options_reinit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################

import json
import pickle
import secrets

from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils

# The preference defaults, which the freshly created test database uses.
PREFERENCE_OPTIONS = {
'auto_commit': True,
'auto_rollback': False,
'server_cursor': False,
}
CHANGED_OPTIONS = {
'auto_commit': False,
'auto_rollback': True,
'server_cursor': True,
}


class TestExecuteOptionsReinit(BaseTestGenerator):
"""
The execute options chosen on the Query Tool toolbar must be kept when
the transaction is re-created, rather than reset to the preferences
(issue #10598).
"""
scenarios = [
('Initialise with the toolbar execute options', dict(
change_connection=False, init_options=CHANGED_OPTIONS,
expected=CHANGED_OPTIONS)),
('Initialise with some of the toolbar execute options', dict(
change_connection=False,
init_options={'server_cursor': True},
expected=dict(PREFERENCE_OPTIONS, server_cursor=True))),
('Initialise without execute options uses the preferences', dict(
change_connection=False, init_options={},
expected=PREFERENCE_OPTIONS)),
('Initialise ignores execute options that are not booleans', dict(
change_connection=False,
init_options={'auto_commit': 'false', 'auto_rollback': None,
'server_cursor': 1},
expected=PREFERENCE_OPTIONS)),
('Changing the connection keeps the execute options', dict(
change_connection=True, init_options={},
toolbar_options=CHANGED_OPTIONS, expected=CHANGED_OPTIONS)),
('Changing the connection ignores execute options that are not '
'booleans', dict(
change_connection=True, init_options={},
toolbar_options={'auto_commit': 'false', 'auto_rollback': 1,
'server_cursor': 1},
expected=PREFERENCE_OPTIONS)),
]

def setUp(self):
database_info = parent_node_dict["database"][-1]
self.db_name = database_info["db_name"]
self.server_id = database_info["server_id"]
self.db_id = database_info["db_id"]
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to the database.")

self.trans_id = str(secrets.choice(range(1, 9999999)))
url = '/sqleditor/initialize/sqleditor/{0}/{1}/{2}/{3}'.format(
self.trans_id, utils.SERVER_GROUP, self.server_id, self.db_id)
response = self.tester.post(url, data=json.dumps(dict(
self.init_options, dbname=self.db_name)))
self.assertEqual(response.status_code, 200)

def runTest(self):
if self.change_connection:
# Change the options from the toolbar, then change the
# connection, which creates a new transaction.
for option, value in self.toolbar_options.items():
response = self.tester.post(
'/sqleditor/{0}/{1}'.format(option, self.trans_id),
data=json.dumps(value))
self.assertEqual(response.status_code, 200)

url = ('/sqleditor/initialize/sqleditor/update_connection/'
'{0}/{1}/{2}/{3}').format(self.trans_id,
utils.SERVER_GROUP,
self.server_id, self.db_id)
response = self.tester.post(url, data=json.dumps({
'user': self.server['username'],
'role': None,
'password': self.server['db_password'],
'database_name': self.db_name,
}), content_type='html/json')
self.assertEqual(response.status_code, 200)
self.trans_id = json.loads(
response.data.decode('utf-8'))['data']['trans_id']

with self.tester.session_transaction() as sess:
command_obj = pickle.loads(
sess['gridData'][str(self.trans_id)]['command_obj'])

for option, value in self.expected.items():
self.assertIs(getattr(command_obj, option), value, option)

def tearDown(self):
self.tester.delete('/sqleditor/close/{0}'.format(self.trans_id))
database_utils.disconnect_database(self, self.server_id, self.db_id)
68 changes: 68 additions & 0 deletions web/regression/javascript/sqleditor/QueryToolInitialise.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

// QueryToolComponent.jsx pulls in editors and viewers that this test never
// renders, and whose ESM-only dependencies Jest cannot load.
jest.mock('../../../pgadmin/static/js/components/ReactCodeMirror', () => ({
__esModule: true,
default: ({ value }) => value,
}));
jest.mock('../../../pgadmin/static/js/components/JsonEditor', () => ({
__esModule: true,
default: () => null,
}));
jest.mock('../../../pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer', () => ({
GeometryViewer: () => null,
}));
jest.mock('../../../pgadmin/tools/sqleditor/static/js/components/sections/GraphVisualiser', () => ({
GraphVisualiser: () => null,
}));

import { getInitialiseRequestData } from '../../../pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx';

const connParams = {
user: 'postgres',
role: null,
password: '',
dbname: 'postgres',
};

describe('getInitialiseRequestData', () => {
it('sends the toolbar execute options for a Query Tool', () => {
expect(getInitialiseRequestData({is_query_tool: true}, connParams, {
auto_commit: false,
auto_rollback: true,
server_cursor: true,
explain_verbose: true,
open_in_new_tab: false,
})).toEqual({
...connParams,
auto_commit: false,
auto_rollback: true,
server_cursor: true,
});
});

it('leaves out execute options that are not known yet', () => {
expect(getInitialiseRequestData({is_query_tool: true}, connParams, null))
.toEqual(connParams);
expect(getInitialiseRequestData({is_query_tool: true}, connParams, {}))
.toEqual(connParams);
expect(getInitialiseRequestData({is_query_tool: true}, connParams, {
auto_commit: undefined, server_cursor: false,
})).toEqual({...connParams, server_cursor: false});
});

it('does not send the toolbar execute options for View/Edit Data', () => {
expect(getInitialiseRequestData({
is_query_tool: false, sql_filter: 'id > 1', server_cursor: true,
}, connParams, {auto_commit: false, auto_rollback: true, server_cursor: false}))
.toEqual({...connParams, sql_filter: 'id > 1', server_cursor: true});
});
});
Loading