mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-24 13:47:39 -05:00
Added support to drop databases using the 'WITH (FORCE)' option. #6367
This commit is contained in:
1 parent
eef295f9d8
commit
557f33c4f9
15 files changed
+167
-87
No files matched your search
@@ -343,36 +343,6 @@ def register_browser_preferences(self):
|
||||
fields=fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'grid_menu_drop_multiple',
|
||||
gettext('Delete/Drop multiple objects'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
'shift': True,
|
||||
'control': False,
|
||||
'key': {'key_code': 77, 'char': 'm'}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'grid_menu_drop_cascade_multiple',
|
||||
gettext('Drop Cascade multiple objects'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
'shift': True,
|
||||
'control': False,
|
||||
'key': {'key_code': 85, 'char': 'u'}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'context_menu',
|
||||
|
||||
@@ -195,7 +195,9 @@ class DatabaseView(PGChildNodeView):
|
||||
],
|
||||
'vopts': [
|
||||
{}, {'get': 'variable_options'}
|
||||
]
|
||||
],
|
||||
'delete': [{'delete': 'delete'},
|
||||
{'delete': 'delete'}]
|
||||
})
|
||||
|
||||
def check_precondition(action=None):
|
||||
@@ -1002,7 +1004,8 @@ class DatabaseView(PGChildNodeView):
|
||||
|
||||
sql = render_template(
|
||||
"/".join([self.template_path, self._DELETE_SQL]),
|
||||
datname=res, conn=self.conn
|
||||
datname=res, conn=self.conn,
|
||||
with_force=self.cmd == 'delete'
|
||||
)
|
||||
|
||||
status, msg = default_conn.execute_scalar(sql)
|
||||
|
||||
@@ -22,6 +22,14 @@ define('pgadmin.node.database', [
|
||||
'pgadmin.authenticate.kerberos', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, pgAdmin, pgBrowser, Kerberos) {
|
||||
|
||||
function canDeleteWithForce(itemNodeData, item) {
|
||||
let treeData = pgBrowser.tree.getTreeNodeHierarchy(item),
|
||||
server = treeData['server'],
|
||||
canDisconnect = !_.isUndefined(itemNodeData?.canDisconn) ? itemNodeData.canDisconn : true;
|
||||
|
||||
return (canDisconnect && server && server.version >= 130000);
|
||||
}
|
||||
|
||||
if (!pgBrowser.Nodes['coll-database']) {
|
||||
pgBrowser.Nodes['coll-database'] =
|
||||
pgBrowser.Collection.extend({
|
||||
@@ -33,6 +41,7 @@ define('pgadmin.node.database', [
|
||||
canDrop: true,
|
||||
selectParentNodeOnDelete: true,
|
||||
canDropCascade: false,
|
||||
canDropForce: canDeleteWithForce,
|
||||
statsPrettifyFields: [gettext('Size'), gettext('Size of temporary files')],
|
||||
});
|
||||
}
|
||||
@@ -90,9 +99,14 @@ define('pgadmin.node.database', [
|
||||
data_disabled: gettext('Selected database is already connected.'),
|
||||
},
|
||||
},{
|
||||
name: 'delete_database_force', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'delete_database_force',
|
||||
category: 'delete', priority: 2, label: gettext('Delete (Force)'),
|
||||
enable : canDeleteWithForce,
|
||||
}, {
|
||||
name: 'disconnect_database', node: 'database', module: this,
|
||||
applies: ['object', 'context'], callback: 'disconnect_database',
|
||||
category: 'drop', priority: 5, label: gettext('Disconnect from database'),
|
||||
category: 'disconnect', priority: 5, label: gettext('Disconnect from database'),
|
||||
enable : 'is_connected',data: {
|
||||
data_disabled: gettext('Selected database is already disconnected.'),
|
||||
},
|
||||
@@ -123,7 +137,6 @@ define('pgadmin.node.database', [
|
||||
// If server is less than 10 then do not allow 'create' menu
|
||||
return server && server.version >= 100000;
|
||||
},
|
||||
|
||||
is_not_connected: function(node) {
|
||||
return (node && !node.connected && node.allowConn);
|
||||
},
|
||||
@@ -310,6 +323,10 @@ define('pgadmin.node.database', [
|
||||
if (!d.allowConn) return;
|
||||
pgBrowser.Node.callbacks.refresh.apply(this, arguments);
|
||||
},
|
||||
|
||||
delete_database_force: function(args, item) {
|
||||
pgBrowser.Node.callbacks.delete_obj.apply(this, [{'url': 'delete'}, item]);
|
||||
}
|
||||
},
|
||||
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||
let c_types = ()=>getNodeAjaxOptions('get_ctypes', this, treeNodeInfo, itemNodeData, {
|
||||
@@ -437,7 +454,6 @@ define('pgadmin.node.database', [
|
||||
} else {
|
||||
Notify.success(res.info);
|
||||
}
|
||||
// obj.trigger('connected', obj, _item, _data);
|
||||
pgBrowser.Events.trigger(
|
||||
'pgadmin:database:connected', _item, _data
|
||||
);
|
||||
|
||||
+1
-1
@@ -4,5 +4,5 @@ SELECT db.datname as name FROM pg_catalog.pg_database as db WHERE db.oid = {{did
|
||||
{% endif %}
|
||||
{# Using name from above query we will drop the database #}
|
||||
{% if datname %}
|
||||
DROP DATABASE IF EXISTS {{ conn|qtIdent(datname) }};
|
||||
DROP DATABASE IF EXISTS {{ conn|qtIdent(datname) }}{% if with_force %} WITH (FORCE){%endif%};
|
||||
{% endif %}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
{# We need database name before we execute drop #}
|
||||
{% if db_ids %}
|
||||
SELECT db.datname as name FROM pg_catalog.pg_database as db WHERE db.oid in {{db_ids}};
|
||||
{% endif %}
|
||||
{# Using name from above query we will drop the database #}
|
||||
{% if dbname_array %}
|
||||
{% for db in dbname_array %}
|
||||
DROP DATABASE {{ conn|qtIdent(db.name) }};
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import uuid
|
||||
import json
|
||||
|
||||
from pgadmin.utils import server_utils
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression import parent_node_dict
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
|
||||
|
||||
class DatabaseMultipleDeleteForceTestCase(BaseTestGenerator):
|
||||
""" This class will delete the multiple database with force option under
|
||||
last added server. """
|
||||
scenarios = [
|
||||
# Fetching default URL for database node.
|
||||
('Delete with Force URL', dict(url='/browser/database/delete/'))
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
if self.server_information['server_version'] < 130000:
|
||||
message = "Delete with Force are not supported by PG < 130000."
|
||||
self.skipTest(message)
|
||||
|
||||
self.db_names = ["db_delete_%s" % str(uuid.uuid4())[1:8],
|
||||
"db_delete_%s" % str(uuid.uuid4())[1:8]]
|
||||
|
||||
self.db_ids = [utils.create_database(self.server, self.db_names[0]),
|
||||
utils.create_database(self.server, self.db_names[1])]
|
||||
|
||||
self.server_id = parent_node_dict["server"][-1]["server_id"]
|
||||
|
||||
def runTest(self):
|
||||
""" This function will delete the databases."""
|
||||
server_response = server_utils.connect_server(self, self.server_id)
|
||||
if server_response["data"]["connected"]:
|
||||
data = {'ids': self.db_ids}
|
||||
response = self.tester.delete(
|
||||
self.url + str(utils.SERVER_GROUP) + '/' +
|
||||
str(self.server_id) + '/',
|
||||
follow_redirects=True,
|
||||
data=json.dumps(data),
|
||||
content_type='html/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
else:
|
||||
raise Exception("Could not connect to server to delete the "
|
||||
"database.")
|
||||
|
||||
def tearDown(self):
|
||||
"""This function drop the added databases"""
|
||||
connection = utils.get_db_connection(self.server['db'],
|
||||
self.server['username'],
|
||||
self.server['db_password'],
|
||||
self.server['host'],
|
||||
self.server['port'],
|
||||
self.server['sslmode'])
|
||||
utils.drop_database_multiple(connection, self.db_names)
|
||||
@@ -39,8 +39,6 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
'sub_menu_refresh': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'sub_menu_refresh').value),
|
||||
'context_menu': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'context_menu').value),
|
||||
'direct_debugging': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'direct_debugging').value),
|
||||
'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value),
|
||||
'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value),
|
||||
'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value),
|
||||
'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value),
|
||||
|
||||
@@ -62,8 +60,6 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
'bindSubMenuRefresh': {'shortcuts': this.keyboardShortcut.sub_menu_refresh, 'bindElem': '#tree'}, // Sub menu - Refresh object,
|
||||
'bindContextMenu': {'shortcuts': this.keyboardShortcut.context_menu}, // Sub menu - Open context menu,
|
||||
'bindDirectDebugging': {'shortcuts': this.keyboardShortcut.direct_debugging}, // Sub menu - Direct Debugging
|
||||
'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple
|
||||
'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple
|
||||
'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
|
||||
'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ define('pgadmin.browser.node', [
|
||||
applies: ['object', 'context'],
|
||||
callback: 'delete_obj',
|
||||
priority: self.dropPriority,
|
||||
label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'),
|
||||
label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete'),
|
||||
data: {
|
||||
'url': 'drop',
|
||||
data_disabled: gettext('The selected tree node does not support this option.'),
|
||||
@@ -162,8 +162,8 @@ define('pgadmin.browser.node', [
|
||||
module: self,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'delete_obj',
|
||||
priority: 3,
|
||||
label: gettext('Drop Cascade'),
|
||||
priority: 2,
|
||||
label: gettext('Delete (Cascade)'),
|
||||
data: {
|
||||
'url': 'delete',
|
||||
},
|
||||
@@ -657,11 +657,14 @@ define('pgadmin.browser.node', [
|
||||
|
||||
let msg, title;
|
||||
|
||||
if (input.url == 'delete') {
|
||||
if (input.url == 'delete' && d._type === 'database') {
|
||||
msg = gettext('Delete database with the force option will attempt to terminate all existing connections to the "%s" database. Are you sure you want to proceed?', d.label);
|
||||
title = gettext('Delete FORCE %s?', obj.label);
|
||||
|
||||
msg = gettext('Are you sure you want to drop %s "%s" and all the objects that depend on it?',
|
||||
} else if (input.url == 'delete') {
|
||||
msg = gettext('Are you sure you want to delete %s "%s" and all the objects that depend on it?',
|
||||
obj.label.toLowerCase(), d.label);
|
||||
title = gettext('DROP CASCADE %s?', obj.label);
|
||||
title = gettext('Delete CASCADE %s?', obj.label);
|
||||
|
||||
if (!(_.isFunction(obj.canDropCascade) ?
|
||||
obj.canDropCascade.apply(obj, [d, i]) : obj.canDropCascade)) {
|
||||
@@ -676,8 +679,8 @@ define('pgadmin.browser.node', [
|
||||
msg = gettext('Are you sure you want to remove %s "%s"?', obj.label.toLowerCase(), d.label);
|
||||
title = gettext('Remove %s?', obj.label);
|
||||
} else {
|
||||
msg = gettext('Are you sure you want to drop %s "%s"?', obj.label.toLowerCase(), d.label);
|
||||
title = gettext('Drop %s?', obj.label);
|
||||
msg = gettext('Are you sure you want to delete %s "%s"?', obj.label.toLowerCase(), d.label);
|
||||
title = gettext('Delete %s?', obj.label);
|
||||
}
|
||||
|
||||
if (!(_.isFunction(obj.canDrop) ?
|
||||
|
||||
Reference in new issue
Block a user