Add "Move objects..” functionality to tablespace node. Fixes #1286

Additionally I have removed pre_9.1 sql's & align SQL line indentation as well.
pull/3/head
Murtuza Zabuawala 2016-06-14 14:53:48 +01:00 committed by Dave Page
parent 93706ee40b
commit 62c1369938
14 changed files with 267 additions and 194 deletions

View File

@ -7,6 +7,7 @@
#
##########################################################################
import json
import re
from flask import render_template, make_response, request, jsonify, current_app
from flask.ext.babel import gettext
from pgadmin.utils.ajax import make_json_response, \
@ -72,7 +73,8 @@ class TablespaceView(PGChildNodeView):
'dependency': [{'get': 'dependencies'}],
'dependent': [{'get': 'dependents'}],
'module.js': [{}, {}, {'get': 'module_js'}],
'vopts': [{}, {'get': 'variable_options'}]
'vopts': [{}, {'get': 'variable_options'}],
'move_objects': [{'put': 'move_objects'}]
})
def module_js(self):
@ -115,10 +117,8 @@ class TablespaceView(PGChildNodeView):
ver = self.manager.version
if ver >= 90200:
self.template_path = 'tablespaces/sql/9.2_plus'
elif 90100 >= ver < 90200:
self.template_path = 'tablespaces/sql/9.1_plus'
else:
self.template_path = 'tablespaces/sql/pre_9.1'
self.template_path = 'tablespaces/sql/9.1_plus'
current_app.logger.debug(
"Using the template path: %s", self.template_path
)
@ -471,7 +471,7 @@ class TablespaceView(PGChildNodeView):
"/".join([self.template_path, 'alter.sql']),
data=data, conn=self.conn
)
SQL = re.sub('\n{2,}', '\n\n', SQL)
return SQL
@check_precondition
@ -517,7 +517,7 @@ class TablespaceView(PGChildNodeView):
""".format(old_data['name'])
SQL = sql_header + SQL
SQL = re.sub('\n{2,}', '\n\n', SQL)
return ajax_response(response=SQL.strip('\n'))
@ -710,4 +710,38 @@ class TablespaceView(PGChildNodeView):
return dependents
@check_precondition
def move_objects(self, gid, sid, tsid):
"""
This function moves objects from current tablespace to another
Args:
gid: Server Group ID
sid: Server ID
tsid: Tablespace ID
"""
data = json.loads(request.form['data'])
try:
SQL = render_template("/".join(
[self.template_path, 'move_objects.sql']),
data=data, conn=self.conn
)
status, res = self.conn.execute_scalar(SQL.strip('\n'))
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info="Tablespace updated",
data={
'id': tsid,
'sid': sid,
'gid': gid
}
)
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=str(e))
TablespaceView.register_node_view(blueprint)

View File

@ -48,6 +48,13 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
category: 'create', priority: 4, label: '{{ _('Tablespace...') }}',
icon: 'wcTabIcon pg-icon-tablespace', data: {action: 'create'},
enable: 'can_create_tablespace'
},{
name: 'move_tablespace', node: 'tablespace', module: this,
applies: ['object', 'context'], callback: 'move_objects',
category: 'move_tablespace', priority: 5,
label: '{{ _('Move objects to...') }}',
icon: 'fa fa-exchange', data: {action: 'create'},
enable: 'can_create_tablespace'
}
]);
},
@ -57,6 +64,184 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
return server.connected && server.user.is_superuser;
},
callbacks: {
/* Move objects from one tablespace to another */
move_objects: function(args){
var input = args || {},
obj = this,
t = pgBrowser.tree,
i = input.item || t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined,
node = d && pgBrowser.Nodes[d._type],
url = obj.generate_url(i, 'move_objects', d, true);
if (!d)
return false;
// Object model
var objModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
new_tblspc: undefined,
obj_type: 'all',
user: undefined
},
schema: [{
id: 'tblspc', label: '{{ _('New tablespace') }}',
type: 'text', disabled: false, control: 'node-list-by-name',
node: 'tablespace', select2: {allowClear: false},
filter: function(o) {
return o && (o.label != d.label);
}
},{
id: 'obj_type', label: '{{ _('Object type') }}',
type: 'text', disabled: false, control: 'select2',
select2: { allowClear: false, width: "100%" },
options: [
{label: "All", value: 'all'},
{label: "Tables", value: 'tables'},
{label: "Indexes", value: 'indexes'},
{label: "Materialized views", value: 'materialized_views'},
]
},{
id: 'user', label: '{{ _('Object owner') }}',
type: 'text', disabled: false, control: 'node-list-by-name',
node: 'role', select2: {allowClear: false}
}],
validate: function() {
return null;
}
});
if(!alertify.move_objects_dlg) {
alertify.dialog('move_objects_dlg' ,function factory() {
return {
main: function() {
var title = '{{ _('Move objects to another tablespace') }} ';
this.set('title', title);
},
setup:function() {
return {
buttons: [{
text: '{{ _('Ok') }}', key: 27, className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button'
},{
text: '{{ _('Cancel') }}', key: 27, className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button'
}],
// Set options for dialog
options: {
//disable both padding and overflow control.
padding : !1,
overflow: !1,
model: 0,
resizable: true,
maximizable: true,
pinnable: false,
closableByDimmer: false
}
};
},
hooks: {
// Triggered when the dialog is closed
onclose: function() {
if (this.view) {
// clear our backform model/view
this.view.remove({data: true, internal: true, silent: true});
}
}
},
prepare: function() {
var self = this,
$container = $("<div class='move_objects'></div>");
//Disbale Okay button
this.__internal.buttons[0].element.disabled = true;
// Find current/selected node
var t = pgBrowser.tree,
i = t.selected(),
d = i && i.length == 1 ? t.itemData(i) : undefined,
node = d && pgBrowser.Nodes[d._type];
if (!d)
return;
// Create treeInfo
var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]);
// Instance of backbone model
var newModel = new objModel(
{}, {node_info: treeInfo}
),
fields = Backform.generateViewSchema(
treeInfo, newModel, 'create', node, treeInfo.server, true
);
var view = this.view = new Backform.Dialog({
el: $container, model: newModel, schema: fields
});
// Add our class to alertify
$(this.elements.body.childNodes[0]).addClass(
'alertify_tools_dialog_properties obj_properties'
);
// Render dialog
view.render();
this.elements.content.appendChild($container.get(0));
// Listen to model & if filename is provided then enable Backup button
this.view.model.on('change', function() {
if (!_.isUndefined(this.get('tblspc')) && this.get('tblspc') !== '') {
this.errorModel.clear();
self.__internal.buttons[0].element.disabled = false;
} else {
self.__internal.buttons[0].element.disabled = true;
this.errorModel.set('tblspc', '{{ _('Please select tablespace') }}')
}
});
},
// Callback functions when click on the buttons of the Alertify dialogs
callback: function(e) {
if (e.button.text === '{{ _('Ok') }}') {
var self = this,
args = this.view.model.toJSON();
args.old_tblspc = d.label;
e.cancel = true;
alertify.confirm('{{ _('Move objects...') }}',
'{{ _('Are you sure you wish to move objects ') }}'
+ '"' + args.old_tblspc + '"'
+ '{{ _(' to ') }}'
+ '"' + args.tblspc + '"?',
function() {
$.ajax({
url: url,
method:'PUT',
data:{'data': JSON.stringify(args) },
success: function(res) {
if (res.success) {
alertify.success(res.info);
self.close();
} else {
alertify.error(res.errormsg);
}
},
error: function(xhr, status, error) {
try {
var err = $.parseJSON(xhr.responseText);
if (err.success == 0) {
alertify.error(err.errormsg);
}
} catch (e) {}
}
});
},
function() {
// Do nothing as user cancel the operation
}
);
}
}
}
});
}
alertify.move_objects_dlg(true).resizeTo('40%','50%');;
}
},
model: pgBrowser.Node.Model.extend({
defaults: {
name: undefined,

View File

@ -0,0 +1,21 @@
{% if data.obj_type and data.tblspc and data.old_tblspc %}
{% if data.obj_type == 'all' or data.obj_type == 'tables' %}
ALTER TABLE ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% if data.obj_type == 'all' or data.obj_type == 'indexes' %}
ALTER INDEX ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% if data.obj_type == 'all' or data.obj_type == 'materialized_views' %}
ALTER MATERIALIZED VIEW ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% endif %}

View File

@ -0,0 +1,21 @@
{% if data.obj_type and data.tblspc and data.old_tblspc %}
{% if data.obj_type == 'all' or data.obj_type == 'tables' %}
ALTER TABLE ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% if data.obj_type == 'all' or data.obj_type == 'indexes' %}
ALTER INDEX ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% if data.obj_type == 'all' or data.obj_type == 'materialized_views' %}
ALTER MATERIALIZED VIEW ALL IN TABLESPACE {{ conn|qtIdent(data.old_tblspc) }}
{% if data.user %} OWNED BY {{ conn|qtIdent(data.user) }}{% endif %}
SET TABLESPACE {{ conn|qtIdent(data.tblspc) }};
{% endif %}
{% endif %}

View File

@ -1,28 +0,0 @@
{### SQL to fetch privileges for tablespace ###}
SELECT 'spcacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor,
array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
FROM
(SELECT
d.grantee, d.grantor, d.is_grantable,
CASE d.privilege_type
WHEN 'CREATE' THEN 'C'
ELSE 'UNKNOWN'
END AS privilege_type
FROM
(SELECT ts.spcacl
FROM pg_tablespace ts
{% if tsid %}
WHERE ts.oid={{ tsid|qtLiteral }}::OID
{% endif %}
) acl,
(SELECT (d).grantee AS grantee, (d).grantor AS grantor, (d).is_grantable
AS is_grantable, (d).privilege_type AS privilege_type FROM (SELECT
aclexplode(ts.spcacl) as d FROM pg_tablespace ts
{% if tsid %}
WHERE ts.oid={{ tsid|qtLiteral }}::OID
{% endif %}
) a) d
) d
LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
GROUP BY g.rolname, gt.rolname

View File

@ -1,41 +0,0 @@
{### SQL to alter tablespace ###}
{% import 'macros/security.macros' as SECLABEL %}
{% import 'macros/variable.macros' as VARIABLE %}
{% import 'macros/privilege.macros' as PRIVILEGE %}
{% if data %}
{### Owner on tablespace ###}
{% if data.spcuser %}
ALTER TABLESPACE {{ conn|qtIdent(data.name) }}
OWNER TO {{ conn|qtIdent(data.spcuser) }};
{% endif %}
{### Comments on tablespace ###}
{% if data.description %}
COMMENT ON TABLESPACE {{ conn|qtIdent(data.name) }}
IS {{ data.description|qtLiteral }};
{% endif %}
{### Security Labels on tablespace ###}
{% if data.seclabels and data.seclabels|length > 0 %}
{% for r in data.seclabels %}
{{ SECLABEL.APPLY(conn, 'TABLESPACE', data.name, r.provider, r.label) }}
{% endfor %}
{% endif %}
{### Variables on tablespace ###}
{% if data.spcoptions %}
{{ VARIABLE.SET(conn, 'TABLESPACE', data.name, data.spcoptions) }}
{% endif %}
{### ACL on tablespace ###}
{% if data.spcacl %}
{% for priv in data.spcacl %}
{{ PRIVILEGE.APPLY(conn, 'TABLESPACE', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
{% endfor %}
{% endif %}
{% endif %}
{# ======== The SQl Below will fetch id for given dataspace ======== #}
{% if tablespace %}
SELECT ts.oid FROM pg_tablespace ts WHERE spcname = {{tablespace|qtLiteral}};
{% endif %}

View File

@ -1,9 +0,0 @@
{### SQL to create tablespace object ###}
{% if data %}
CREATE TABLESPACE {{ conn|qtIdent(data.name) }}
{% if data.spcuser %}
OWNER {{ conn|qtIdent(data.spcuser) }}
{% endif %}
LOCATION {{ data.spclocation|qtLiteral }};
{% endif %}

View File

@ -1,2 +0,0 @@
{### SQL to delete tablespace object ###}
DROP TABLESPACE {{ conn|qtIdent(tsname) }};

View File

@ -1,20 +0,0 @@
{% if fetch_database %}
SELECT datname,
datallowconn AND pg_catalog.has_database_privilege(datname, 'CONNECT') AS datallowconn,
dattablespace
FROM pg_database db
ORDER BY datname
{% endif %}
{% if fetch_dependents %}
SELECT cl.relkind, COALESCE(cin.nspname, cln.nspname) as nspname,
COALESCE(ci.relname, cl.relname) as relname, cl.relname as indname
FROM pg_class cl
JOIN pg_namespace cln ON cl.relnamespace=cln.oid
LEFT OUTER JOIN pg_index ind ON ind.indexrelid=cl.oid
LEFT OUTER JOIN pg_class ci ON ind.indrelid=ci.oid
LEFT OUTER JOIN pg_namespace cin ON ci.relnamespace=cin.oid,
pg_database WHERE datname = current_database() AND
(cl.reltablespace = {{tsid}}::oid OR (cl.reltablespace=0 AND dattablespace = {{tsid}}::oid))
ORDER BY 1,2,3
{% endif %}

View File

@ -1,8 +0,0 @@
SELECT
ts.oid, spcname AS name, spcowner as owner
FROM pg_tablespace ts
{% if tsid %}
WHERE
ts.oid={{ tsid|qtLiteral }}::OID
{% endif %}
ORDER BY name;

View File

@ -1,12 +0,0 @@
{### SQL to fetch tablespace object properties ###}
SELECT
ts.oid, spcname AS name, spclocation, spcoptions,
pg_get_userbyid(spcowner) as spcuser,
array_to_string(spcacl::text[], ', ') as acl,
pg_catalog.shobj_description(oid, 'pg_tablespace') AS description
FROM
pg_tablespace ts
{% if tsid %}
WHERE ts.oid={{ tsid|qtLiteral }}::OID
{% endif %}
ORDER BY name

View File

@ -1,6 +0,0 @@
{### SQL to fetch tablespace object stats ###}
{% if tsid %}
SELECT pg_size_pretty(pg_tablespace_size({{ tsid|qtLiteral }}::OID)) AS size
{% else %}
SELECT ts.spcname as name, pg_size_pretty(pg_tablespace_size(ts.oid)) AS size FROM pg_catalog.pg_tablespace ts;
{% endif %}

View File

@ -1,58 +0,0 @@
{### SQL to update tablespace object ###}
{% import 'macros/variable.macros' as VARIABLE %}
{% import 'macros/privilege.macros' as PRIVILEGE %}
{% if data %}
{# ==== To update tablespace name ==== #}
{% if data.name != o_data.name %}
ALTER TABLESPACE {{ conn|qtIdent(o_data.name) }}
RENAME TO {{ conn|qtIdent(data.name) }};
{% endif %}
{# ==== To update tablespace user ==== #}
{% if data.spcuser and data.spcuser != o_data.spcuser %}
ALTER TABLESPACE {{ conn|qtIdent(data.name) }}
OWNER TO {{ conn|qtIdent(data.spcuser) }};
{% endif %}
{# ==== To update tablespace comments ==== #}
{% if data.description is defined and data.description != o_data.description %}
COMMENT ON TABLESPACE {{ conn|qtIdent(data.name) }}
IS {{ data.description|qtLiteral }};
{% endif %}
{# ==== To update tablespace variables ==== #}
{% if 'spcoptions' in data and data.spcoptions|length > 0 %}
{% set variables = data.spcoptions %}
{% if 'deleted' in variables and variables.deleted|length > 0 %}
{{ VARIABLE.UNSET(conn, 'TABLESPACE', data.name, variables.deleted) }}
{% endif %}
{% if 'added' in variables and variables.added|length > 0 %}
{{ VARIABLE.SET(conn, 'TABLESPACE', data.name, variables.added) }}
{% endif %}
{% if 'changed' in variables and variables.changed|length > 0 %}
{{ VARIABLE.SET(conn, 'TABLESPACE', data.name, variables.changed) }}
{% endif %}
{% endif %}
{# ==== To update tablespace privileges ==== #}
{# Change the privileges #}
{% if data.spcacl %}
{% if 'deleted' in data.spcacl %}
{% for priv in data.spcacl.deleted %}
{{ PRIVILEGE.RESETALL(conn, 'TABLESPACE', priv.grantee, data.name) }}
{% endfor %}
{% endif %}
{% if 'changed' in data.spcacl %}
{% for priv in data.spcacl.changed %}
{{ PRIVILEGE.RESETALL(conn, 'TABLESPACE', priv.grantee, data.name) }}
{{ PRIVILEGE.APPLY(conn, 'TABLESPACE', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
{% endfor %}
{% endif %}
{% if 'added' in data.spcacl %}
{% for priv in data.spcacl.added %}
{{ PRIVILEGE.APPLY(conn, 'TABLESPACE', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}

View File

@ -1,4 +0,0 @@
{### SQL to fetch tablespace object options ###}
SELECT name, vartype, min_val, max_val, enumvals
FROM pg_settings
WHERE name IN ('seq_page_cost', 'random_page_cost');