diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py index 571afd7ef01..80ba1787bd6 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/__init__.py @@ -1035,7 +1035,7 @@ def _update_arguments_for_get_sql(data, old_data): :return: """ if 'arguments' in data and len(data['arguments']) > 0: - for arg in data['arguments']['changed']: + for arg in data['arguments'].get('changed', []): for old_arg in old_data['arguments']: if arg['argid'] == old_arg['argid']: old_arg.update(arg) @@ -1194,6 +1194,21 @@ def _get_sql_for_edit_mode(self, data, parallel_dict, all_ids_dict, data[arg]) > 0) or arg in data: data['change_func'] = True + # PostgreSQL cannot add an input argument to an existing + # function/procedure via CREATE OR REPLACE: a changed argument + # list is a different signature, so PostgreSQL creates a new, + # separate overloaded routine instead of replacing this one. + # Reject such edits explicitly, rather than silently leaving an + # orphaned routine behind. + if 'arguments' in data and isinstance(data['arguments'], dict) \ + and data['arguments'].get('added'): + return False, gettext( + "Adding a new argument to an existing function/procedure " + "is not supported, as PostgreSQL would create a separate, " + "overloaded routine rather than replacing this one. " + "Please create a new function/procedure instead." + ), '' + # If Function Definition/Arguments are changed then merge old # Arguments with changed ones for Create/Replace Function # SQL statement diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js index c6f1b649441..1e57e9d92f8 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js @@ -304,16 +304,16 @@ export default class FunctionSchema extends BaseUISchema { }, { id: 'arguments', label: gettext('Arguments'), cell: 'string', - group: gettext('Definition'), type: 'collection', canAdd: function(){ - return obj.isNew(); - }, + group: gettext('Definition'), type: 'collection', canDelete: true, mode: ['create', 'edit'], columns: ['argtype', 'argmode', 'argname', 'argdefval'], schema : new DefaultArgumentSchema(this.node_info, this.fieldOptions.getTypes), disabled: obj.inCatalog(), - canDeleteRow: function() { - return obj.isNew(); - }, + // Existing (already saved) arguments cannot be removed here, as + // PostgreSQL has no way to drop an argument from a function via + // CREATE OR REPLACE. Only rows added in the current session (not + // yet saved) can be deleted. + canDeleteRow: (state) => (this.isNew(state)), },{ id: 'prosrc', label: gettext('Code'), cell: 'text', type: 'sql', mode: ['properties', 'create', 'edit'], diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get_msql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get_msql.py index d574f7d46a8..8902052b23f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get_msql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/tests/test_function_get_msql.py @@ -142,6 +142,46 @@ class FunctionGetmsqlTestCase(BaseTestGenerator): } ) ), + ( + 'Fetch Function msql with newly added argument is rejected', + dict( + url='/browser/function/msql/', + is_positive_test=True, + mocking_required=False, + with_function_id=True, + is_mock_local_function=False, + test_data={ + "name": "Test Function", + "funcowner": "", + "pronamespace": 2200, + "prorettypename": "character varying", + "lanname": "sql", + "prosrc": "select '1'", + "probin": "$libdir/", + "variables": [], + "seclabels": [], + "acl": [], + # PostgreSQL cannot add an argument to an existing + # function via CREATE OR REPLACE (it would create a + # separate, overloaded routine instead), so this must + # be rejected with a clear error rather than silently + # producing SQL that orphans a routine. + "arguments": json.dumps({ + "added": [{ + "argname": "new_arg", + "argtype": "integer", + "argmode": "IN", + "argdefval": "1" + }] + }) + }, + mock_data={}, + expected_data={ + "status_code": 500, + "check_errormsg": "not supported" + } + ), + ), ( 'Fetch Function msql fetch properties not found', dict( @@ -222,5 +262,11 @@ def _get_sql(self, **kwargs): self.assertEqual(response.status_code, self.expected_data['status_code']) + if 'check_string' in self.expected_data: + self.assertIn(self.expected_data['check_string'], + response.json['data']) + if 'check_errormsg' in self.expected_data: + self.assertIn(self.expected_data['check_errormsg'], + response.json['errormsg']) # Disconnect the database database_utils.disconnect_database(self, self.server_id, self.db_id)