-
Notifications
You must be signed in to change notification settings - Fork 828
Added support to download binary data from result grid. #4011 #9645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |||||||||||||||||||||||||||||||||||
| import secrets | ||||||||||||||||||||||||||||||||||||
| from urllib.parse import unquote | ||||||||||||||||||||||||||||||||||||
| from threading import Lock | ||||||||||||||||||||||||||||||||||||
| from io import BytesIO | ||||||||||||||||||||||||||||||||||||
| import threading | ||||||||||||||||||||||||||||||||||||
| import math | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -23,7 +24,8 @@ | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| from config import PG_DEFAULT_DRIVER, ALLOW_SAVE_PASSWORD | ||||||||||||||||||||||||||||||||||||
| from werkzeug.user_agent import UserAgent | ||||||||||||||||||||||||||||||||||||
| from flask import Response, url_for, render_template, session, current_app | ||||||||||||||||||||||||||||||||||||
| from flask import Response, url_for, render_template, session, current_app, \ | ||||||||||||||||||||||||||||||||||||
| send_file | ||||||||||||||||||||||||||||||||||||
| from flask import request | ||||||||||||||||||||||||||||||||||||
| from flask_babel import gettext | ||||||||||||||||||||||||||||||||||||
| from pgadmin.tools.sqleditor.utils.query_tool_connection_check \ | ||||||||||||||||||||||||||||||||||||
|
|
@@ -70,6 +72,8 @@ | |||||||||||||||||||||||||||||||||||
| from pgadmin.browser.server_groups.servers.utils import \ | ||||||||||||||||||||||||||||||||||||
| convert_connection_parameter, get_db_disp_restriction | ||||||||||||||||||||||||||||||||||||
| from pgadmin.misc.workspaces import check_and_delete_adhoc_server | ||||||||||||||||||||||||||||||||||||
| from pgadmin.utils.driver.psycopg3.typecast import \ | ||||||||||||||||||||||||||||||||||||
| register_binary_data_typecasters | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| MODULE_NAME = 'sqleditor' | ||||||||||||||||||||||||||||||||||||
| TRANSACTION_STATUS_CHECK_FAILED = gettext("Transaction status check failed.") | ||||||||||||||||||||||||||||||||||||
|
|
@@ -147,6 +151,7 @@ def get_exposed_url_endpoints(self): | |||||||||||||||||||||||||||||||||||
| 'sqleditor.server_cursor', | ||||||||||||||||||||||||||||||||||||
| 'sqleditor.nlq_chat_stream', | ||||||||||||||||||||||||||||||||||||
| 'sqleditor.explain_analyze_stream', | ||||||||||||||||||||||||||||||||||||
| 'sqleditor.download_binary_data', | ||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def on_logout(self): | ||||||||||||||||||||||||||||||||||||
|
|
@@ -2182,6 +2187,82 @@ def start_query_download_tool(trans_id): | |||||||||||||||||||||||||||||||||||
| return internal_server_error(errormsg=err_msg) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @blueprint.route( | ||||||||||||||||||||||||||||||||||||
| '/download_binary_data/<int:trans_id>', | ||||||||||||||||||||||||||||||||||||
| methods=["POST"], endpoint='download_binary_data' | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| @pga_login_required | ||||||||||||||||||||||||||||||||||||
| def download_binary_data(trans_id): | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| This method is used to download binary data. | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| (status, error_msg, conn, trans_obj, | ||||||||||||||||||||||||||||||||||||
| session_obj) = check_transaction_status(trans_id) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if isinstance(error_msg, Response): | ||||||||||||||||||||||||||||||||||||
| return error_msg | ||||||||||||||||||||||||||||||||||||
| if error_msg == ERROR_MSG_TRANS_ID_NOT_FOUND: | ||||||||||||||||||||||||||||||||||||
| return make_json_response( | ||||||||||||||||||||||||||||||||||||
| success=0, | ||||||||||||||||||||||||||||||||||||
| errormsg=error_msg, | ||||||||||||||||||||||||||||||||||||
| info='DATAGRID_TRANSACTION_REQUIRED', | ||||||||||||||||||||||||||||||||||||
| status=404 | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if not status or conn is None or trans_obj is None or \ | ||||||||||||||||||||||||||||||||||||
| session_obj is None: | ||||||||||||||||||||||||||||||||||||
| return internal_server_error( | ||||||||||||||||||||||||||||||||||||
| errormsg=TRANSACTION_STATUS_CHECK_FAILED | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| cur = conn._Connection__async_cursor | ||||||||||||||||||||||||||||||||||||
| if cur is None: | ||||||||||||||||||||||||||||||||||||
| return internal_server_error( | ||||||||||||||||||||||||||||||||||||
| errormsg=gettext('No active result cursor.') | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| register_binary_data_typecasters(cur) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| data = request.values if request.values else request.get_json(silent=True) | ||||||||||||||||||||||||||||||||||||
| if data is None: | ||||||||||||||||||||||||||||||||||||
| return make_json_response( | ||||||||||||||||||||||||||||||||||||
| status=410, | ||||||||||||||||||||||||||||||||||||
| success=0, | ||||||||||||||||||||||||||||||||||||
| errormsg=gettext( | ||||||||||||||||||||||||||||||||||||
| "Could not find the required parameter (query)." | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2226
to
+2234
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading error message — copy-pasted from another endpoint. The error text ✏️ Suggested fix errormsg=gettext(
- "Could not find the required parameter (query)."
+ "Could not find the required parameters (rowpos, colpos)."
)🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| col_pos = data['colpos'] | ||||||||||||||||||||||||||||||||||||
| cur.scroll(int(data['rowpos'])) | ||||||||||||||||||||||||||||||||||||
| binary_data = cur.fetchone() | ||||||||||||||||||||||||||||||||||||
| binary_data = binary_data[col_pos] | ||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+2235
to
+2238
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Unvalidated duplicate code executes before the Lines 2235–2238 scroll the cursor and fetch a row without any validation or error handling, then lines 2240–2256 repeat the same logic properly inside a
Remove these four lines entirely. 🐛 Proposed fix register_binary_data_typecasters(cur)
data = request.values if request.values else request.get_json(silent=True)
if data is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
"Could not find the required parameter (query)."
)
)
- col_pos = data['colpos']
- cur.scroll(int(data['rowpos']))
- binary_data = cur.fetchone()
- binary_data = binary_data[col_pos]
-
try:
row_pos = int(data['rowpos'])🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||
| row_pos = int(data['rowpos']) | ||||||||||||||||||||||||||||||||||||
| col_pos = int(data['colpos']) | ||||||||||||||||||||||||||||||||||||
| if row_pos < 0 or col_pos < 0: | ||||||||||||||||||||||||||||||||||||
| raise ValueError | ||||||||||||||||||||||||||||||||||||
| cur.scroll(row_pos) | ||||||||||||||||||||||||||||||||||||
| row = cur.fetchone() | ||||||||||||||||||||||||||||||||||||
| if row is None or col_pos >= len(row): | ||||||||||||||||||||||||||||||||||||
| return internal_server_error( | ||||||||||||||||||||||||||||||||||||
| errormsg=gettext('Requested cell is out of range.') | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| binary_data = row[col_pos] | ||||||||||||||||||||||||||||||||||||
| except (ValueError, IndexError, TypeError) as e: | ||||||||||||||||||||||||||||||||||||
| current_app.logger.error(e) | ||||||||||||||||||||||||||||||||||||
| return internal_server_error( | ||||||||||||||||||||||||||||||||||||
| errormsg='Invalid row/column position.' | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return send_file( | ||||||||||||||||||||||||||||||||||||
| BytesIO(binary_data), | ||||||||||||||||||||||||||||||||||||
| as_attachment=True, | ||||||||||||||||||||||||||||||||||||
| download_name='binary_data', | ||||||||||||||||||||||||||||||||||||
| mimetype='application/octet-stream' | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2258
to
+2263
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle If the selected cell contains a SQL 🛡️ Proposed fix+ if binary_data is None:
+ return bad_request(
+ errormsg=gettext('The selected cell contains NULL.')
+ )
+
return send_file(
BytesIO(binary_data),
as_attachment=True,
download_name='binary_data',
mimetype='application/octet-stream'
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For null value download is not allowed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If To properly implement the "no NULL downloads" requirement, I recommend adding an explicit check before calling if binary_data is None:
return bad_request(
errormsg=gettext('The selected cell contains NULL.')
)
return send_file(
BytesIO(binary_data),
as_attachment=True,
download_name='binary_data',
mimetype='application/octet-stream'
)This way, users will receive a clear, informative error message when they attempt to download a NULL value, which better reflects your stated intent. |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @blueprint.route( | ||||||||||||||||||||||||||||||||||||
| '/status/<int:trans_id>', | ||||||||||||||||||||||||||||||||||||
| methods=["GET"], | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -6,12 +6,18 @@ | |||
| // This software is released under the PostgreSQL Licence | ||||
| // | ||||
| ////////////////////////////////////////////////////////////// | ||||
| import { useContext } from 'react'; | ||||
| import { styled } from '@mui/material/styles'; | ||||
| import _ from 'lodash'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import gettext from 'sources/gettext'; | ||||
| import CustomPropTypes from '../../../../../../static/js/custom_prop_types'; | ||||
| import usePreferences from '../../../../../../preferences/static/js/store'; | ||||
|
|
||||
| import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded'; | ||||
| import { PgIconButton } from '../../../../../../static/js/components/Buttons'; | ||||
| import { QUERY_TOOL_EVENTS } from '../QueryToolConstants'; | ||||
| import { QueryToolEventsContext } from '../QueryToolComponent'; | ||||
| import { DataGridExtrasContext } from './index'; | ||||
|
|
||||
| const StyledNullAndDefaultFormatter = styled(NullAndDefaultFormatter)(({theme}) => ({ | ||||
| '& .Formatters-disabledCell': { | ||||
|
|
@@ -70,10 +76,22 @@ | |||
|
|
||||
| export function BinaryFormatter({row, column}) { | ||||
| let value = row[column.key]; | ||||
| const eventBus = useContext(QueryToolEventsContext); | ||||
| const dataGridExtras = useContext(DataGridExtrasContext); | ||||
| const downloadBinaryData = usePreferences().getPreferences('misc', 'enable_binary_data_download').value; | ||||
|
|
||||
| // Use clientPK as the absolute row position | ||||
| // rowKeyGetter returns the clientPK value which is a sequential counter (0, 1, 2, ...) | ||||
| // that persists across pagination and represents the 0-based absolute position in the result set | ||||
| const absoluteRowPos = parseInt(dataGridExtras?.rowKeyGetter?.(row) ?? 0); | ||||
|
Comment on lines
+83
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n "client_primary_key|clientPK|__temp_PK" web/pgadmin/tools/sqleditor/static/js/components -C 3 | head -120Repository: pgadmin-org/pgadmin4 Length of output: 12195 🏁 Script executed: # Find the rowKeyGetter function definition
rg -n "rowKeyGetter" web/pgadmin/tools/sqleditor/static/js -C 5 | head -100Repository: pgadmin-org/pgadmin4 Length of output: 10843 🏁 Script executed: # Check the ResultSet component to see how rows are initialized
fd -t f "ResultSet" web/pgadmin/tools/sqleditor/static/js/components -name "*.jsx" -o -name "*.js"Repository: pgadmin-org/pgadmin4 Length of output: 294 🏁 Script executed: rg -n "resetClientPKIndex" web/pgadmin/tools/sqleditor/static/js -C 4Repository: pgadmin-org/pgadmin4 Length of output: 3108 🏁 Script executed: # Check pagination or data fetch logic
rg -n "pagination|offset|limit" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx | head -30Repository: pgadmin-org/pgadmin4 Length of output: 689 🏁 Script executed: rg -n "offset|rows_from|rows_to|pagination" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx -B 2 -A 2 | grep -E "(getData|fetch|concat|append|setRows)" -A 3 -B 3Repository: pgadmin-org/pgadmin4 Length of output: 392 🏁 Script executed: # Look for how new pagination results are appended to existing data
rg -n "setRows|setDataOutputQuery" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx -A 5 | head -50Repository: pgadmin-org/pgadmin4 Length of output: 2139 🏁 Script executed: sed -n '1130,1160p' web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsxRepository: pgadmin-org/pgadmin4 Length of output: 1235 🏁 Script executed: # Also check how getData is called and what newRows contains
rg -n "getData|newRows" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx -B 3 -A 3 | grep -E "getData|newRows" -A 5 -B 5 | head -60Repository: pgadmin-org/pgadmin4 Length of output: 1605 🏁 Script executed: # Check processRows implementation and its parameters
sed -n '705,725p' web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsxRepository: pgadmin-org/pgadmin4 Length of output: 931 🏁 Script executed: # Check how getWindowRows API is called and what parameters it receives
rg -n "getWindowRows" web/pgadmin/tools/sqleditor/static/js -B 3 -A 8Repository: pgadmin-org/pgadmin4 Length of output: 2642 🏁 Script executed: # Look for the complete flow - check if clientPKLastIndex is ever reset during pagination
rg -n "clientPKLastIndex|resetClientPKIndex|fetchWindow" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx | head -20Repository: pgadmin-org/pgadmin4 Length of output: 496 🏁 Script executed: # Check the Formatters.jsx file to see the full context of how absoluteRowPos is used
cat -n web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Formatters.jsx | sed -n '80,100p'Repository: pgadmin-org/pgadmin4 Length of output: 1203 🏁 Script executed: # Search for any reset or offset handling in processRows or pagination logic
rg -n "clientPKLastIndex|fromRownum|offset|pagination" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx | grep -E "fromRownum|offset|startRowNum" -A 2 -B 2Repository: pgadmin-org/pgadmin4 Length of output: 662 🏁 Script executed: # Check if there's any mechanism to account for row offset when assigning clientPK
sed -n '700,730p' web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsxRepository: pgadmin-org/pgadmin4 Length of output: 1425 🏁 Script executed: # Search for TRIGGER_SAVE_BINARY_DATA event handler and backend cursor scroll usage
rg -n "TRIGGER_SAVE_BINARY_DATA" web/pgadmin/tools/sqleditor -C 5Repository: pgadmin-org/pgadmin4 Length of output: 4033 🏁 Script executed: # Check the Python backend for cur.scroll usage
find web/pgadmin/tools/sqleditor -name "*.py" -type f | head -10Repository: pgadmin-org/pgadmin4 Length of output: 613 🏁 Script executed: # Find the saveBinaryResultsToFile implementation in the backend
rg -n "saveBinaryResultsToFile|save_binary_results" web/pgadmin/tools/sqleditor -C 5Repository: pgadmin-org/pgadmin4 Length of output: 2551 🏁 Script executed: # Search for the backend API endpoint that handles binary data download
find web/pgadmin/tools/sqleditor -name "*.py" -exec rg -l "binary.*download|scroll" {} \;Repository: pgadmin-org/pgadmin4 Length of output: 103 🏁 Script executed: # Check the backend endpoint implementation
sed -n '1,100p' web/pgadmin/tools/sqleditor/__init__.py | grep -A 20 "download_binary_data"Repository: pgadmin-org/pgadmin4 Length of output: 46 🏁 Script executed: # Search for the download_binary_data endpoint definition
rg -n "download_binary_data" web/pgadmin/tools/sqleditor/__init__.py -A 15Repository: pgadmin-org/pgadmin4 Length of output: 1327 🏁 Script executed: # Get more of the download_binary_data implementation
sed -n '2195,2250p' web/pgadmin/tools/sqleditor/__init__.pyRepository: pgadmin-org/pgadmin4 Length of output: 1775 🏁 Script executed: # Check how fromRownum/toRownum are passed and if there's offset calculation
sed -n '496,510p' web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsxRepository: pgadmin-org/pgadmin4 Length of output: 742 🏁 Script executed: # Check if there's any caching or prevention of re-fetching pages
rg -n "fetchWindow|getWindowRows" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx -B 2 -A 8 | head -50Repository: pgadmin-org/pgadmin4 Length of output: 1757 🏁 Script executed: # Verify the issue - check if processRows receives fromRownum parameter
rg -n "processRows" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx -B 1 -A 1 | grep -E "processRows|fromRownum"Repository: pgadmin-org/pgadmin4 Length of output: 358 🏁 Script executed: # Check the processRows method signature
sed -n '680,720p' web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsxRepository: pgadmin-org/pgadmin4 Length of output: 1599 Fix clientPK calculation to account for pagination offset, and add radix to parseInt. The For example: after fetching page 2 (rows 100–199 with clientPK 100–199), re-fetching page 1 (rows 0–99) would assign clientPK 200–299, causing downloads from page 1 to fetch from the wrong rows. Additionally, - const absoluteRowPos = parseInt(dataGridExtras?.rowKeyGetter?.(row) ?? 0);
+ const absoluteRowPos = parseInt(dataGridExtras?.rowKeyGetter?.(row) ?? 0, 10);🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clientPK does not holds the actual value but the key which points to actual value There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain
🏁 Script executed: Length of output: 2665 🏁 Script executed: Length of output: 232 🏁 Script executed: Length of output: 1849 🏁 Script executed: Length of output: 355 🏁 Script executed: Length of output: 1337 🏁 Script executed: Length of output: 867 🏁 Script executed: Length of output: 1265 🏁 Script executed: Length of output: 182 🏁 Script executed: Length of output: 931 🏁 Script executed: Length of output: 503 🏁 Script executed: Length of output: 616 🏁 Script executed: Length of output: 817 🏁 Script executed: Length of output: 1944 🏁 Script executed: Length of output: 319 🏁 Script executed: Length of output: 700 🏁 Script executed: Length of output: 1092 🏁 Script executed: Length of output: 645 🏁 Script executed: Length of output: 2973 🏁 Script executed: Length of output: 831 🏁 Script executed: Length of output: 660 🏁 Script executed: Length of output: 1185
Let me explain the issue: Current Behavior:
The Problem:
The Solution:
// In fetchWindow around line 1147
const newRows = rsu.current.processRows(res.data.data.result, columns, {
startIndex: res.data.data.pagination.rows_from
});
// In processRows around line 717
rowObj[this.clientPK] = (options.startIndex || 0) + recIdx;
const absoluteRowPos = rowIdx + (dataGridExtras?.startRowNum ?? 0);Also, please add the radix parameter to -const absoluteRowPos = parseInt(dataGridExtras?.rowKeyGetter?.(row) ?? 0);
+const absoluteRowPos = parseInt(dataGridExtras?.rowKeyGetter?.(row) ?? 0, 10);Does this clarify the concern? Would you like me to create a plan to fix this issue? 🧬 Code Graph Analysis Results[file: web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Formatters.jsx] Summary: BinaryFormatter uses clientPK-derived absolute row position via dataGridExtras.rowKeyGetter.
[file: web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx] Summary: DataGridExtrasContext provides a rowKeyGetter function and is used to map a row to its absolute position (clientPK).
|
||||
| console.log(absoluteRowPos) | ||||
|
Check failure on line 87 in web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Formatters.jsx
|
||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove debug This leftover debug statement causes ESLint failures ( 🐛 Proposed fix- console.log(absoluteRowPos)📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Check Javascript style[error] 87-87: no-console: Unexpected console statement. Only these console methods are allowed: warn, error, trace [error] 87-87: semi: Missing semicolon 🪛 GitHub Actions: Run feature tests on PostgreSQL[error] 87-87: ESLint: Unexpected console statement. Only these console methods are allowed: warn, error, trace (no-console) [error] 87-87: ESLint: Missing semicolon (semi) 🪛 GitHub Actions: Run Javascript tests[error] 87-87: no-console: Unexpected console statement. Only these methods are allowed: warn, error, trace. 🪛 GitHub Check: run-javascript-tests (macos-latest)[failure] 87-87: [failure] 87-87: 🪛 GitHub Check: run-javascript-tests (ubuntu-22.04)[failure] 87-87: [failure] 87-87: 🪛 GitHub Check: run-javascript-tests (windows-latest)[failure] 87-87: [failure] 87-87: 🤖 Prompt for AI Agents |
||||
|
|
||||
| return ( | ||||
| <StyledNullAndDefaultFormatter value={value} column={column}> | ||||
| <span className='Formatters-disabledCell'>[{value}]</span> | ||||
| <span className='Formatters-disabledCell'>[{value}]</span> | ||||
| {downloadBinaryData && | ||||
| <PgIconButton size="xs" title={gettext('Download binary data')} icon={<GetAppRoundedIcon />} | ||||
| onClick={()=>eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_BINARY_DATA, absoluteRowPos, column.pos)}/>} | ||||
| </StyledNullAndDefaultFormatter> | ||||
| ); | ||||
| } | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 109
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 1378
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 157
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 739
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 1740
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 2469
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 104
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
# Get the full typecast.py file to understand ByteaDataLoader and ByteaBinaryDataLoader wc -l web/pgadmin/utils/driver/psycopg3/typecast.pyRepository: pgadmin-org/pgadmin4
Length of output: 113
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 7820
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 1118
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 688
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 348
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 2273
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 46
🏁 Script executed:
Repository: pgadmin-org/pgadmin4
Length of output: 1859
register_binary_data_typecasterspermanently alters the shared cursor's type adapters, potentially breaking subsequent grid operations.register_binary_data_typecasters(cur)modifies the adapter registry onconn._Connection__async_cursorby registeringByteaBinaryDataLoaderfor OIDs 17 and 1001 (bytea types). This cursor is reused for all subsequent fetch operations within the same query execution. After this call, if the user fetches additional rows or pages, bytea columns will return raw binary data instead of the hex-encoded strings the grid expects, breaking the display.Consider either: (a) saving and restoring the original adapters after the binary fetch, or (b) using a separate cursor for the binary download.
🤖 Prompt for AI Agents