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. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *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 |
| | 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,
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
JOIN pg_catalog.pg_type ty ON ty.oid=atttypid
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_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_sequence seq ON cs.oid=seq.seqrelid
WHERE
att.attrelid = {{ tid|qtLiteral(conn) }}::oid
{% if clid %}

View File

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

View File

@ -53,6 +53,7 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
/* Menu button refs */
const copyMenuRef = React.useRef(null);
const pasetMenuRef = React.useRef(null);
const queryToolPref = queryToolCtx.preferences.sqleditor;
@ -72,8 +73,8 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
field_separator: queryToolPref.results_grid_field_separator,
});
let copiedRows = copyUtils.getCopiedRows();
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows, true);
}, [queryToolPref]);
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, copiedRows, true, checkedMenuItems['paste_with_serials']);
}, [queryToolPref, checkedMenuItems['paste_with_serials']]);
const copyData = ()=>{
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} />
<PgIconButton title={gettext('Paste')} icon={<PasteIcon />}
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 />}
accesskey={shortcut_key(queryToolPref.btn_delete_row)} disabled={buttonsDisabled['delete-rows'] || !canEdit} onClick={deleteRows} />
</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>
</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'] = \
rset['rows'][key]['has_default_val']
col_type['seqtypid'] = col['seqtypid'] = \
rset['rows'][key]['seqtypid']
else:
for row in rset['rows']:
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['has_default_val'] = row['has_default_val']
col_type['seqtypid'] = col['seqtypid'] = \
rset['rows'][key]['seqtypid']
break
else:
col_type['not_null'] = col['not_null'] = None
col_type['has_default_val'] = col['has_default_val'] = None
col_type['seqtypid'] = col['seqtypid'] = None
return column_types