diff --git a/docs/en_US/images/query_data_pagination.png b/docs/en_US/images/query_data_pagination.png index 5fa94e22348..8561d117af1 100644 Binary files a/docs/en_US/images/query_data_pagination.png and b/docs/en_US/images/query_data_pagination.png differ diff --git a/docs/en_US/query_tool_toolbar.rst b/docs/en_US/query_tool_toolbar.rst index 8e27f95d241..f30cc1128ba 100644 --- a/docs/en_US/query_tool_toolbar.rst +++ b/docs/en_US/query_tool_toolbar.rst @@ -241,6 +241,8 @@ Pagination Options +----------------------+---------------------------------------------------------------------------------------------------+----------------+ | *Edit Range* | Click to open the from and to rows range inputs to allow setting them. | | +----------------------+---------------------------------------------------------------------------------------------------+----------------+ + | *Show Entire Range* | Click to get all the rows and set the from and to rows range | | + +----------------------+---------------------------------------------------------------------------------------------------+----------------+ | *Page No* | Enter the page no you want to jump to out of total shown next to this input | | +----------------------+---------------------------------------------------------------------------------------------------+----------------+ | *First Page* | Click to go to the first page. | | diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 7a8af4303a1..c74b6eeb81d 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -286,3 +286,10 @@ export function useBeforeUnload({ enabled, isNewTab, beforeClose, closePanel }) return {forceClose}; } + +export function useLatestFunc(fn) { + const fnRef = useRef(fn); + fnRef.current = fn; + + return useCallback(((...args) => fnRef.current(...args)), []); +} diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx index 2386da374fe..93fac5c9fb6 100644 --- a/web/pgadmin/static/js/helpers/ModalProvider.jsx +++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx @@ -86,19 +86,14 @@ function alert(title, text, onOkClick, okLabel = gettext('OK')) { function confirm(title, text, onOkClick, onCancelClick, okLabel = gettext('Yes'), cancelLabel = gettext('No'), okIcon = 'default', modalId=null) { // bind the modal provider before calling this.showModal(title, (closeModal) => { - const onCancelClickClose = () => { - onCancelClick?.(); - closeModal(); - }; - const onOkClickClose = () => { onOkClick?.(); closeModal(); }; return ( - + ); - }, {id: modalId}); + }, {id: modalId, onClose: onCancelClick}); } function confirmDelete(title, text, onDeleteClick, onCancelClick, deleteLabel = gettext('Delete'), cancelLabel = gettext('Cancel')) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx index f91533c85a0..19275df2207 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx @@ -31,6 +31,7 @@ import { GraphVisualiser } from './GraphVisualiser'; import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider'; import pgAdmin from 'sources/pgadmin'; import { connectServer, connectServerModal } from '../connectServer'; +import { useLatestFunc } from '../../../../../../static/js/custom_hooks'; const StyledBox = styled(Box)(({theme}) => ({ display: 'flex', @@ -846,7 +847,7 @@ export function ResultSet() { const modalId = MODAL_DIALOGS.QT_CONFIRMATIONS; // We'll use this track if any changes were saved. // It will help to decide whether results refresh is required or not on page change. - const pageDataDirty = useRef(false); + const pageDataOutOfSync = useRef(false); const selectedCell = useRef([]); const selectedRange = useRef(null); @@ -873,9 +874,10 @@ export function ResultSet() { // To use setLoaderText to the ResultSetUtils. rsu.current.setLoaderText = setLoaderText; - const isDataChanged = ()=>{ - return Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted)); - }; + const isDataChangedRef = useRef(false); + useEffect(()=>{ + isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted)); + }, [dataChangeStore]); const fireRowsColsCellChanged = ()=>{ eventBus.fireEvent(QUERY_TOOL_EVENTS.SELECTED_ROWS_COLS_CELL_CHANGED, selectedRows.size, selectedColumns.size, selectedRange.current, selectedCell.current?.length); @@ -948,7 +950,7 @@ export function ResultSet() { }); }; - if(isDataChanged() && !refreshData) { + if(isDataChangedRef.current && !refreshData) { queryToolCtx.modal.confirm( gettext('Unsaved changes'), gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'), @@ -1093,6 +1095,7 @@ export function ResultSet() { setLoaderText(gettext('Fetching rows...')); try { res = await rsu.current.getWindowRows(fromRownum, toRownum); + resetSelectionAndChanges(); const newRows = rsu.current.processRows(res.data.data.result, columns); setRows([...newRows]); setQueryData((prev)=>({ @@ -1119,18 +1122,33 @@ export function ResultSet() { useEffect(()=>{ let deregExecEnd; const deregFetch = eventBus.registerListener(QUERY_TOOL_EVENTS.FETCH_WINDOW, (...args)=>{ - if(pageDataDirty.current) { - deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{ - if(!success) return; - pageDataDirty.current = false; + const impl = ()=> { + if(pageDataOutOfSync.current) { + deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{ + if(!success) return; + pageDataOutOfSync.current = false; + fetchWindow(...args); + }, true); + eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true}); + // executionStartCallback(rsu.current.query, {refreshData: true}); + } else { + pageDataOutOfSync.current = false; fetchWindow(...args); - }, true); - executionStartCallback(rsu.current.query, {refreshData: true}); + } + }; + + if(isDataChangedRef.current) { + queryToolCtx.modal.confirm( + gettext('Unsaved changes'), + gettext('The data has been modified, but not saved. Are you sure you wish to discard the changes?'), + impl, + function() { + /* Do nothing */ + } + ); } else { - pageDataDirty.current = false; - fetchWindow(...args); + impl(); } - resetSelectionAndChanges(); }); return ()=>{ deregFetch(); @@ -1144,7 +1162,7 @@ export function ResultSet() { const warnSaveDataClose = ()=>{ // No changes. - if(!isDataChanged() || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) { + if(!isDataChangedRef.current || !queryToolCtx.preferences?.sqleditor.prompt_save_data_changes) { eventBus.fireEvent(QUERY_TOOL_EVENTS.WARN_SAVE_TEXT_CLOSE); return; } @@ -1172,7 +1190,7 @@ export function ResultSet() { }; }, [dataChangeStore]); - const triggerSaveData = async ()=>{ + const triggerSaveData = useLatestFunc(async ()=>{ if(!_.size(dataChangeStore.updated) && !_.size(dataChangeStore.added) && !_.size(dataChangeStore.deleted)) { return; } @@ -1210,7 +1228,7 @@ export function ResultSet() { } catch {/* History errors should not bother others */} if(!respData.data.status) { - pageDataDirty.current = false; + pageDataOutOfSync.current = false; eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, respData.data.result); pgAdmin.Browser.notifier.error(respData.data.result, 20000); // If the transaction is not idle, notify the user that previous queries are not rolled back, @@ -1223,7 +1241,7 @@ export function ResultSet() { return; } - pageDataDirty.current = true; + pageDataOutOfSync.current = true; if(_.size(dataChangeStore.added)) { // Update the rows in a grid after addition respData.data.query_results.forEach((qr)=>{ @@ -1258,18 +1276,19 @@ export function ResultSet() { resetSelectionAndChanges(); eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_CONNECTION_STATUS, respData.data.transaction_status); eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, ''); + setLoaderText(null); pgAdmin.Browser.notifier.success(gettext('Data saved successfully.')); if(respData.data.transaction_status > CONNECTION_STATUS.TRANSACTION_STATUS_IDLE) { pgAdmin.Browser.notifier.info(gettext('Auto-commit is off. You still need to commit changes to the database.')); } } catch (error) { - pageDataDirty.current = false; + pageDataOutOfSync.current = false; eventBus.fireEvent(QUERY_TOOL_EVENTS.HANDLE_API_ERROR, error, { checkTransaction: true, }); + setLoaderText(null); } - setLoaderText(null); - }; + }); useEffect(()=>{ eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData); diff --git a/web/pgadmin/utils/driver/psycopg3/server_manager.py b/web/pgadmin/utils/driver/psycopg3/server_manager.py index 6952d7a2c6d..76cee8b8446 100644 --- a/web/pgadmin/utils/driver/psycopg3/server_manager.py +++ b/web/pgadmin/utils/driver/psycopg3/server_manager.py @@ -690,7 +690,8 @@ def create_connection_string(self, database, user, password=None): if key == 'hostaddr' and self.use_ssh_tunnel: continue - # Convert boolean connection parameters to integer for libpq compatibility + # Convert boolean connection parameters to integer for + # libpq compatibility if key in ('sslcompression', 'sslsni'): value = 1 if value else 0