Ensure that when pasting a row in query tool grid, default value is used for autogenerated/serial columns. #5922

This commit is contained in:
Pravesh Sharma 2023-04-26 15:41:10 +05:30 committed by GitHub
parent 1d7d6561f6
commit 861c66d180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 10 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -178,7 +178,12 @@ Data Editing Options
| | | | | | | |
| | * Click *Copy with headers* to copy the highlighted content along with the header. | | | | * Click *Copy with headers* to copy the highlighted content along with the header. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+ +----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Paste* | Click the *Paste* icon to paste a previously copied row into a new row. | Accesskey + P | | *Paste* | Click the *Paste* icon to paste a previously copied row with or without serial/identity values: | Accesskey + P |
| | | |
| | * Click the *Paste* icon to paste a previously copied row into a new row. | |
| | | |
| | * Click the *Paste with SERIAL/IDENTITY values?* if you want to paste the copied column values | |
| | in the serial/identity columns. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+ +----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Delete* | Click the *Delete* icon to mark the selected rows for deletion. These marked rows get deleted | Accesskey + D | | *Delete* | Click the *Delete* icon to mark the selected rows for deletion. These marked rows get deleted | Accesskey + D |
| | when you click the *Save Data Changes* icon. | | | | when you click the *Save Data Changes* icon. | |

View File

@ -1,5 +1,5 @@
SELECT DISTINCT att.attname as name, att.attnum as OID, pg_catalog.format_type(ty.oid,NULL) AS datatype, SELECT DISTINCT att.attname as name, att.attnum as OID, pg_catalog.format_type(ty.oid,NULL) AS datatype,
att.attnotnull as not_null, att.atthasdef as has_default_val, des.description att.attnotnull as not_null, att.atthasdef as has_default_val, des.description, seq.seqtypid
FROM pg_catalog.pg_attribute att FROM pg_catalog.pg_attribute att
JOIN pg_catalog.pg_type ty ON ty.oid=atttypid JOIN pg_catalog.pg_type ty ON ty.oid=atttypid
JOIN pg_catalog.pg_namespace tn ON tn.oid=ty.typnamespace JOIN pg_catalog.pg_namespace tn ON tn.oid=ty.typnamespace
@ -11,6 +11,7 @@ FROM pg_catalog.pg_attribute att
LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=cs.relnamespace LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=cs.relnamespace
LEFT OUTER JOIN pg_catalog.pg_index pi ON pi.indrelid=att.attrelid AND indisprimary LEFT OUTER JOIN pg_catalog.pg_index pi ON pi.indrelid=att.attrelid AND indisprimary
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass) LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass)
LEFT OUTER JOIN pg_catalog.pg_sequence seq ON cs.oid=seq.seqrelid
WHERE WHERE
att.attrelid = {{ tid|qtLiteral(conn) }}::oid att.attrelid = {{ tid|qtLiteral(conn) }}::oid
{% if clid %} {% if clid %}

View File

@ -530,6 +530,8 @@ export class ResultSetUtils {
'not_null': c.not_null, 'not_null': c.not_null,
'has_default_val': c.has_default_val, 'has_default_val': c.has_default_val,
'is_array': arrayBracketIdx > -1 && arrayBracketIdx + 2 == columnTypeInternal.length, 'is_array': arrayBracketIdx > -1 && arrayBracketIdx + 2 == columnTypeInternal.length,
'seqtypid': c.seqtypid,
'isPK': isPK
}; };
} }
@ -559,14 +561,19 @@ export class ResultSetUtils {
return columns; return columns;
} }
processClipboardVal(columnVal, col, rawCopiedVal) { processClipboardVal(columnVal, col, rawCopiedVal, pasteSerials) {
if(columnVal === '') { if(columnVal === '' ) {
if(col.has_default_val) { if(col.has_default_val) {
// if column has default value
columnVal = undefined; columnVal = undefined;
} else if(rawCopiedVal === null) { } else if(rawCopiedVal === null) {
columnVal = null; columnVal = null;
} }
} else if (col.has_default_val && col.seqtypid && !pasteSerials) {
// if column has default value and is serial type
columnVal = undefined;
} }
if(col.cell === 'boolean') { if(col.cell === 'boolean') {
if(columnVal == 'true') { if(columnVal == 'true') {
columnVal = true; columnVal = true;
@ -581,7 +588,7 @@ export class ResultSetUtils {
return columnVal; return columnVal;
} }
processRows(result, columns, fromClipboard=false) { processRows(result, columns, fromClipboard=false, pasteSerials=false) {
let retVal = []; let retVal = [];
if(!_.isArray(result) || !_.size(result)) { if(!_.isArray(result) || !_.size(result)) {
return retVal; return retVal;
@ -598,7 +605,7 @@ export class ResultSetUtils {
let columnVal = rec[col.pos]; let columnVal = rec[col.pos];
/* If the source is clipboard, then it needs some extra handling */ /* If the source is clipboard, then it needs some extra handling */
if(fromClipboard) { if(fromClipboard) {
columnVal = this.processClipboardVal(columnVal, col, copiedRowsObjects[recIdx]?.[col.key]); columnVal = this.processClipboardVal(columnVal, col, copiedRowsObjects[recIdx]?.[col.key], pasteSerials);
} }
rowObj[col.key] = columnVal; rowObj[col.key] = columnVal;
} }
@ -1207,14 +1214,14 @@ export function ResultSet() {
}, [selectedRows, queryData, dataChangeStore, rows]); }, [selectedRows, queryData, dataChangeStore, rows]);
useEffect(()=>{ useEffect(()=>{
const triggerAddRows = (_rows, fromClipboard)=>{ const triggerAddRows = (_rows, fromClipboard, pasteSerials)=>{
let insPosn = 0; let insPosn = 0;
if(selectedRows.size > 0) { if(selectedRows.size > 0) {
let selectedRowsSorted = Array.from(selectedRows); let selectedRowsSorted = Array.from(selectedRows);
selectedRowsSorted.sort(); selectedRowsSorted.sort();
insPosn = _.findIndex(rows, (r)=>rowKeyGetter(r)==selectedRowsSorted[selectedRowsSorted.length-1])+1; insPosn = _.findIndex(rows, (r)=>rowKeyGetter(r)==selectedRowsSorted[selectedRowsSorted.length-1])+1;
} }
let newRows = rsu.current.processRows(_rows, columns, fromClipboard); let newRows = rsu.current.processRows(_rows, columns, fromClipboard, pasteSerials);
setRows((prev)=>[ setRows((prev)=>[
...prev.slice(0, insPosn), ...prev.slice(0, insPosn),
...newRows, ...newRows,

View File

@ -53,6 +53,7 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
const [checkedMenuItems, setCheckedMenuItems] = React.useState({}); const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
/* Menu button refs */ /* Menu button refs */
const copyMenuRef = React.useRef(null); const copyMenuRef = React.useRef(null);
const pasetMenuRef = React.useRef(null);
const queryToolPref = queryToolCtx.preferences.sqleditor; const queryToolPref = queryToolCtx.preferences.sqleditor;
@ -72,8 +73,8 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
field_separator: queryToolPref.results_grid_field_separator, field_separator: queryToolPref.results_grid_field_separator,
}); });
let copiedRows = copyUtils.getCopiedRows(); let copiedRows = copyUtils.getCopiedRows();
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows, true); eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows, true, checkedMenuItems['paste_with_serials']);
}, [queryToolPref]); }, [queryToolPref, checkedMenuItems['paste_with_serials']]);
const copyData = ()=>{ const copyData = ()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.COPY_DATA, checkedMenuItems['copy_with_headers']); eventBus.fireEvent(QUERY_TOOL_EVENTS.COPY_DATA, checkedMenuItems['copy_with_headers']);
}; };
@ -163,6 +164,8 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
name="menu-copyheader" ref={copyMenuRef} onClick={openMenu} /> name="menu-copyheader" ref={copyMenuRef} onClick={openMenu} />
<PgIconButton title={gettext('Paste')} icon={<PasteIcon />} <PgIconButton title={gettext('Paste')} icon={<PasteIcon />}
accesskey={shortcut_key(queryToolPref.btn_paste_row)} disabled={!canEdit} onClick={pasteRows} /> accesskey={shortcut_key(queryToolPref.btn_paste_row)} disabled={!canEdit} onClick={pasteRows} />
<PgIconButton title={gettext('Paste options')} icon={<KeyboardArrowDownIcon />} splitButton
name="menu-pasteoptions" ref={pasetMenuRef} onClick={openMenu} />
<PgIconButton title={gettext('Delete')} icon={<DeleteRoundedIcon />} <PgIconButton title={gettext('Delete')} icon={<DeleteRoundedIcon />}
accesskey={shortcut_key(queryToolPref.btn_delete_row)} disabled={buttonsDisabled['delete-rows'] || !canEdit} onClick={deleteRows} /> accesskey={shortcut_key(queryToolPref.btn_delete_row)} disabled={buttonsDisabled['delete-rows'] || !canEdit} onClick={deleteRows} />
</PgButtonGroup> </PgButtonGroup>
@ -188,6 +191,14 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
> >
<PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>{gettext('Copy with headers')}</PgMenuItem> <PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>{gettext('Copy with headers')}</PgMenuItem>
</PgMenu> </PgMenu>
<PgMenu
anchorRef={pasetMenuRef}
open={menuOpenId=='menu-pasteoptions'}
onClose={handleMenuClose}
label={gettext('Paste Options Menu')}
>
<PgMenuItem hasCheck value="paste_with_serials" checked={checkedMenuItems['paste_with_serials']} onClick={checkMenuClick}>{gettext('Paste with SERIAL/IDENTITY values?')}</PgMenuItem>
</PgMenu>
</> </>
); );
} }

View File

@ -49,6 +49,9 @@ def get_columns_types(is_query_tool, columns_info, table_oid, conn, has_oids):
col['has_default_val'] = \ col['has_default_val'] = \
rset['rows'][key]['has_default_val'] rset['rows'][key]['has_default_val']
col_type['seqtypid'] = col['seqtypid'] = \
rset['rows'][key]['seqtypid']
else: else:
for row in rset['rows']: for row in rset['rows']:
if row['oid'] == col['table_column']: if row['oid'] == col['table_column']:
@ -56,10 +59,14 @@ def get_columns_types(is_query_tool, columns_info, table_oid, conn, has_oids):
col_type['has_default_val'] = \ col_type['has_default_val'] = \
col['has_default_val'] = row['has_default_val'] col['has_default_val'] = row['has_default_val']
col_type['seqtypid'] = col['seqtypid'] = \
rset['rows'][key]['seqtypid']
break break
else: else:
col_type['not_null'] = col['not_null'] = None col_type['not_null'] = col['not_null'] = None
col_type['has_default_val'] = col['has_default_val'] = None col_type['has_default_val'] = col['has_default_val'] = None
col_type['seqtypid'] = col['seqtypid'] = None
return column_types return column_types