diff --git a/docs/en_US/backup_dialog.rst b/docs/en_US/backup_dialog.rst index ab2e43bcf..8eb5cd465 100644 --- a/docs/en_US/backup_dialog.rst +++ b/docs/en_US/backup_dialog.rst @@ -58,13 +58,15 @@ Click the *Dump options* tab to continue. Use the box fields in the *Dump option * Move switches in the **Do not save** field box to select the objects that will not be included in the backup. - * Move the switch next to *Owner* to the *Yes* position to include commands that set object ownership. + * Move the switch next to *Owner* to the *Yes* position to exclude commands that set object ownership. - * Move the switch next to *Privilege* to the *Yes* position to include commands that create access privileges. + * Move the switch next to *Privilege* to the *Yes* position to exclude commands that create access privileges. - * Move the switch next to *Tablespace* to the *Yes* position to include tablespaces. + * Move the switch next to *Tablespace* to the *Yes* position to exclude tablespaces. - * Move the switch next to *Unlogged table data* to the *Yes* position to include the contents of unlogged tables. + * Move the switch next to *Unlogged table data* to the *Yes* position to exclude the contents of unlogged tables. + + * Move the switch next to *Comments* to the *Yes* position to exclude commands that set the comments. **Note:** This option is visible only for database server greater than or equal to 11. .. image:: images/backup_queries.png :alt: Queries option on backup dialog @@ -79,6 +81,8 @@ Click the *Dump options* tab to continue. Use the box fields in the *Dump option * Move the switch next to *Include DROP DATABASE statement* to the *Yes* position to include a command in the backup that will drop any existing database object with the same name before recreating the object during a backup. + * Move the switch next to *Load Via Partition Root* to the *Yes* position, so when dumping a COPY or INSERT statement for a partitioned table, target the root of the partitioning hierarchy which contains it rather than the partition itself. **Note:** This option is visible only for database server greater than or equal to 11. + .. image:: images/backup_disable.png :alt: Disable option on backup dialog diff --git a/docs/en_US/backup_server_dialog.rst b/docs/en_US/backup_server_dialog.rst index 786f9104f..234ecfd78 100644 --- a/docs/en_US/backup_server_dialog.rst +++ b/docs/en_US/backup_server_dialog.rst @@ -12,12 +12,66 @@ Use the *Backup Server* dialog to create a plain-text script that will recreate Use the fields in the *General* tab to specify the following: * Enter the name of the backup file in the *Filename* field. Optionally, select the *Browser* icon (ellipsis) to the right to navigate into a directory and select a file that will contain the archive. +* Use the *Encoding* drop-down listbox to select the character encoding method that should be used for the archive. **Note:** This option is visible only for database server greater than or equal to 11. * Use the drop-down listbox next to *Role name* to specify a role with connection privileges on the selected server. The role will be used for authentication during the backup. -Move switches in the *Miscellaneous* box to specify the type of statements that should be included in the backup. +.. image:: images/backup_server_objects.png + :alt: Type of objects option on backup server dialog -* Move the *Verbose messages* switch to the *No* position to exclude status messages from the backup. The default is *Yes*. -* Move the *Force double quote on identifiers* switch to the *Yes* position to name identifiers without changing case. The default is *No*. +* Move switches in the **Type of objects** field box to specify details about the type of objects that will be backed up. + + * Move the switch next to *Only data* to the *Yes* position to limit the back up to data. + + * Move the switch next to *Only schema* to limit the back up to schema-level database objects. + +.. image:: images/backup_server_do_not_save.png + :alt: Do not save option on backup server dialog + +* Move switches in the **Do not save** field box to select the objects that will not be included in the backup. + + * Move the switch next to *Owner* to the *Yes* position to exclude commands that set object ownership. + + * Move the switch next to *Privilege* to the *Yes* position to exclude commands that create access privileges. + + * Move the switch next to *Tablespace* to the *Yes* position to exclude tablespaces. + + * Move the switch next to *Unlogged table data* to the *Yes* position to exclude the contents of unlogged tables. + + * Move the switch next to *Comments* to the *Yes* position to exclude commands that set the comments. **Note:** This option is visible only for database server greater than or equal to 11. + +.. image:: images/backup_server_queries.png + :alt: Queries option on backup server dialog + +* Move switches in the **Queries** field box to specify the type of statements that should be included in the backup. + + * Move the switch next to *Use Column Inserts* to the *Yes* position to dump the data in the form of INSERT statements and include explicit column names. Please note: this may make restoration from backup slow. + + * Move the switch next to *Use Insert commands* to the *Yes* position to dump the data in the form of INSERT statements rather than using a COPY command. Please note: this may make restoration from backup slow. + + * Move the switch next to *Include DROP DATABASE statement* to the *Yes* position to include a command in the backup that will drop any existing database object with the same name before recreating the object during a backup. + + +.. image:: images/backup_server_disable.png + :alt: Disable option on backup server dialog + +* Move switches in the **Disable** field box to specify the type of statements that should be excluded from the backup. + + * Move the switch next to *Trigger* (active when creating a data-only backup) to the *Yes* position to include commands that will disable triggers on the target table while the data is being loaded. + + * Move the switch next to *$ quoting* to the *Yes* position to enable dollar quoting within function bodies; if disabled, the function body will be quoted using SQL standard string syntax. + +.. image:: images/backup_server_miscellaneous.png + :alt: Miscellaneous option on backup server dialog + +* Move switches in the **Miscellaneous** field box to specify miscellaneous backup options. + + * Move the switch next to *With OIDs* to the *Yes* position to include object identifiers as part of the table data for each table. + + * Move the switch next to *Verbose messages* to the *No* position to instruct *pg_dump* to exclude verbose messages. + + * Move the switch next to *Force double quotes on identifiers* to the *Yes* position to force the quoting of all identifiers. + + * Move the switch next to *Use SET SESSION AUTHORIZATION* to the *Yes* position to include a statement that will use a SET SESSION AUTHORIZATION command to determine object ownership (instead of an ALTER OWNER command). Click the *Backup* button to build and execute a command based on your selections; click the *Cancel* button to exit without saving work. diff --git a/docs/en_US/images/backup_do_not_save.png b/docs/en_US/images/backup_do_not_save.png index bc1d73a4f..3f829113a 100644 Binary files a/docs/en_US/images/backup_do_not_save.png and b/docs/en_US/images/backup_do_not_save.png differ diff --git a/docs/en_US/images/backup_queries.png b/docs/en_US/images/backup_queries.png index 08618535d..ab012f2ed 100644 Binary files a/docs/en_US/images/backup_queries.png and b/docs/en_US/images/backup_queries.png differ diff --git a/docs/en_US/images/backup_server_disable.png b/docs/en_US/images/backup_server_disable.png new file mode 100644 index 000000000..69bc984db Binary files /dev/null and b/docs/en_US/images/backup_server_disable.png differ diff --git a/docs/en_US/images/backup_server_do_not_save.png b/docs/en_US/images/backup_server_do_not_save.png new file mode 100644 index 000000000..660d5cdff Binary files /dev/null and b/docs/en_US/images/backup_server_do_not_save.png differ diff --git a/docs/en_US/images/backup_server_general.png b/docs/en_US/images/backup_server_general.png index e1ba1eaff..2cab2df06 100644 Binary files a/docs/en_US/images/backup_server_general.png and b/docs/en_US/images/backup_server_general.png differ diff --git a/docs/en_US/images/backup_server_miscellaneous.png b/docs/en_US/images/backup_server_miscellaneous.png new file mode 100644 index 000000000..d3cf1f71a Binary files /dev/null and b/docs/en_US/images/backup_server_miscellaneous.png differ diff --git a/docs/en_US/images/backup_server_objects.png b/docs/en_US/images/backup_server_objects.png new file mode 100644 index 000000000..64553aa53 Binary files /dev/null and b/docs/en_US/images/backup_server_objects.png differ diff --git a/docs/en_US/images/backup_server_queries.png b/docs/en_US/images/backup_server_queries.png new file mode 100644 index 000000000..17a7a762c Binary files /dev/null and b/docs/en_US/images/backup_server_queries.png differ diff --git a/docs/en_US/images/restore_do_not_save.png b/docs/en_US/images/restore_do_not_save.png index 272d80d6c..647b1531d 100644 Binary files a/docs/en_US/images/restore_do_not_save.png and b/docs/en_US/images/restore_do_not_save.png differ diff --git a/docs/en_US/release_notes_3_3.rst b/docs/en_US/release_notes_3_3.rst index 4b2be3027..02fc7dee0 100644 --- a/docs/en_US/release_notes_3_3.rst +++ b/docs/en_US/release_notes_3_3.rst @@ -10,6 +10,7 @@ This release contains a number of features and fixes reported since the release Features ******** +| `Feature #3503 `_ - Added new backup/restore options for PostgreSQL 11. Added dump options for 'pg_dumpall'. | `Feature #3553 `_ - Add a Spanish translation. Bug fixes @@ -17,6 +18,7 @@ Bug fixes | `Bug #3136 `_ - Stabilise feature tests for continuous running on CI systems. | `Bug #3325 `_ - Fix sort/filter dialog issue where it incorrectly requires ASC/DESC. +| `Bug #3347 `_ - Ensure backup should work with '--data-only' and '--schema-only' for any format. | `Bug #3407 `_ - Fix keyboard shortcuts layout in the preferences panel. | `Bug #3461 `_ - Ensure that refreshing a node also updates the Property list. | `Bug #3528 `_ - Handle connection errors properly in the query tool. diff --git a/docs/en_US/restore_dialog.rst b/docs/en_US/restore_dialog.rst index babe0ac76..245cccdcb 100644 --- a/docs/en_US/restore_dialog.rst +++ b/docs/en_US/restore_dialog.rst @@ -49,6 +49,8 @@ Click the *Restore options* tab to continue. Use the fields on the *Restore opti * Move the switch next to *Owner* to the *Yes* position to exclude commands that set object ownership. * Move the switch next to *Privilege* to the *Yes* position to exclude commands that create access privileges. * Move the switch next to *Tablespace* to the *Yes* position to exclude tablespaces. + * Move the switch next to *Comments* to the *Yes* position to exclude commands that set the comments. **Note:** This option is visible only for database server greater than or equal to 11. + .. image:: images/restore_queries.png :alt: Restore dialog queries section diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py index 3fae9a5e1..bebe62aa3 100644 --- a/web/pgadmin/tools/backup/__init__.py +++ b/web/pgadmin/tools/backup/__init__.py @@ -259,117 +259,6 @@ def filename_with_file_manager_path(_file, create_file=True): @blueprint.route( '/job/', methods=['POST'], endpoint='create_server_job' ) -@login_required -def create_backup_job(sid): - """ - Args: - sid: Server ID - - Creates a new job for backup task (Backup Server/Globals) - - Returns: - None - """ - if request.form: - # Convert ImmutableDict to dict - data = dict(request.form) - data = json.loads(data['data'][0], encoding='utf-8') - else: - data = json.loads(request.data, encoding='utf-8') - - try: - backup_file = filename_with_file_manager_path(data['file']) - except Exception as e: - return bad_request(errormsg=str(e)) - - # Fetch the server details like hostname, port, roles etc - server = Server.query.filter_by( - id=sid, user_id=current_user.id - ).first() - - if server is None: - return make_json_response( - success=0, - errormsg=_("Could not find the specified server.") - ) - - # To fetch MetaData for the server - from pgadmin.utils.driver import get_driver - driver = get_driver(PG_DEFAULT_DRIVER) - manager = driver.connection_manager(server.id) - conn = manager.connection() - connected = conn.connected() - - if not connected: - return make_json_response( - success=0, - errormsg=_("Please connect to the server first.") - ) - - utility = manager.utility('backup_server') - - args = [ - '--file', - backup_file, - '--host', - manager.local_bind_host if manager.use_ssh_tunnel else server.host, - '--port', - str(manager.local_bind_port) if manager.use_ssh_tunnel - else str(server.port), - '--username', - server.username, - '--no-password', - '--database', - server.maintenance_db - ] - - if 'role' in data and data['role']: - args.append('--role') - args.append(data['role']) - if 'verbose' in data and data['verbose']: - args.append('--verbose') - if 'dqoute' in data and data['dqoute']: - args.append('--quote-all-identifiers') - if data['type'] == 'globals': - args.append('--globals-only') - - try: - p = BatchProcess( - desc=BackupMessage( - BACKUP.SERVER if data['type'] != 'globals' else BACKUP.GLOBALS, - sid, - data['file'].encode('utf-8') if hasattr( - data['file'], 'encode' - ) else data['file'], - *args - ), - cmd=utility, args=args - ) - manager.export_password_env(p.id) - # Check for connection timeout and if it is greater than 0 then - # set the environment variable PGCONNECT_TIMEOUT. - if manager.connect_timeout > 0: - env = dict() - env['PGCONNECT_TIMEOUT'] = str(manager.connect_timeout) - p.set_env_variables(server, env=env) - else: - p.set_env_variables(server) - - p.start() - jid = p.id - except Exception as e: - current_app.logger.exception(e) - return make_json_response( - status=410, - success=0, - errormsg=str(e) - ) - # Return response - return make_json_response( - data={'job_id': jid, 'success': 1} - ) - - @blueprint.route( '/job//object', methods=['POST'], endpoint='create_object_job' ) @@ -392,12 +281,12 @@ def create_backup_objects_job(sid): else: data = json.loads(request.data, encoding='utf-8') - # Remove ratio from data in case of empty string - if 'ratio' in data and data['ratio'] == '': - data.pop("ratio") + backup_obj_type = 'objects' + if 'type' in data: + backup_obj_type = data['type'] try: - if data['format'] == 'directory': + if 'format' in data and data['format'] == 'directory': backup_file = filename_with_file_manager_path(data['file'], False) else: backup_file = filename_with_file_manager_path(data['file']) @@ -428,7 +317,9 @@ def create_backup_objects_job(sid): errormsg=_("Please connect to the server first.") ) - utility = manager.utility('backup') + utility = manager.utility('backup') if backup_obj_type == 'objects' \ + else manager.utility('backup_server') + args = [ '--file', backup_file, @@ -442,48 +333,53 @@ def create_backup_objects_job(sid): '--no-password' ] + if backup_obj_type != 'objects': + args.append('--database') + args.append(server.maintenance_db) + + if backup_obj_type == 'globals': + args.append('--globals-only') + def set_param(key, param): if key in data and data[key]: args.append(param) - def set_value(key, param, value): - if key in data: - if value: - if value is True and data[key]: - args.append(param) - args.append(data[key]) - else: - args.append(param) - args.append(value) + def set_value(key, param, default_value=None): + if key in data and data[key] is not None and data[key] != '': + args.append(param) + args.append(data[key]) + elif default_value is not None: + args.append(param) + args.append(default_value) set_param('verbose', '--verbose') set_param('dqoute', '--quote-all-identifiers') - set_value('role', '--role', True) - if data['format'] is not None: + set_value('role', '--role') + + if backup_obj_type == 'objects' and \ + 'format' in data and data['format'] is not None: if data['format'] == 'custom': args.extend(['--format=c']) - set_param('blobs', '--blobs') - set_value('ratio', '--compress', True) - + set_value('ratio', '--compress') elif data['format'] == 'tar': args.extend(['--format=t']) - set_param('blobs', '--blobs') - elif data['format'] == 'plain': args.extend(['--format=p']) - if 'only_data' in data and data['only_data']: - args.append('--data-only') - set_param('disable_trigger', '--disable-triggers') - else: - set_param('only_schema', '--schema-only') - set_param('dns_owner', '--no-owner') - set_param('include_create_database', '--create') - set_param('include_drop_database', '--clean') elif data['format'] == 'directory': args.extend(['--format=d']) + if 'only_data' in data and data['only_data']: + set_param('only_data', '--data-only') + if 'format' in data and data['format'] == 'plain': + set_param('disable_trigger', '--disable-triggers') + elif 'only_schema' in data and data['only_schema']: + set_param('only_schema', '--schema-only') + + set_param('dns_owner', '--no-owner') + set_param('include_create_database', '--create') + set_param('include_drop_database', '--clean') set_param('pre_data', '--section=pre-data') set_param('data', '--section=data') set_param('post_data', '--section=post-data') @@ -496,31 +392,51 @@ def create_backup_objects_job(sid): set_param('with_oids', '--oids') set_param('use_set_session_auth', '--use-set-session-authorization') - set_value('encoding', '--encoding', True) - set_value('no_of_jobs', '--jobs', True) + if manager.version >= 110000: + set_param('no_comments', '--no-comments') + set_param('load_via_partition_root', '--load-via-partition-root') - for s in data['schemas']: - args.extend(['--schema', s]) + set_value('encoding', '--encoding') + set_value('no_of_jobs', '--jobs') - for s, t in data['tables']: - args.extend([ - '--table', driver.qtIdent(conn, s, t) - ]) + if 'schemas' in data: + for s in data['schemas']: + args.extend(['--schema', s]) - args.append(data['database']) + if 'tables' in data: + for s, t in data['tables']: + args.extend([ + '--table', driver.qtIdent(conn, s, t) + ]) try: - p = BatchProcess( - desc=BackupMessage( - BACKUP.OBJECT, sid, - data['file'].encode('utf-8') if hasattr( - data['file'], 'encode' - ) else data['file'], - *args, - database=data['database'] - ), - cmd=utility, args=args - ) + if backup_obj_type == 'objects': + args.append(data['database']) + p = BatchProcess( + desc=BackupMessage( + BACKUP.OBJECT, sid, + data['file'].encode('utf-8') if hasattr( + data['file'], 'encode' + ) else data['file'], + *args, + database=data['database'] + ), + cmd=utility, args=args + ) + else: + p = BatchProcess( + desc=BackupMessage( + BACKUP.SERVER if backup_obj_type != 'globals' + else BACKUP.GLOBALS, + sid, + data['file'].encode('utf-8') if hasattr( + data['file'], 'encode' + ) else data['file'], + *args + ), + cmd=utility, args=args + ) + manager.export_password_env(p.id) # Check for connection timeout and if it is greater than 0 then # set the environment variable PGCONNECT_TIMEOUT. diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js index 79a1c909d..56b258d21 100644 --- a/web/pgadmin/tools/backup/static/js/backup.js +++ b/web/pgadmin/tools/backup/static/js/backup.js @@ -62,7 +62,7 @@ define([ dqoute: false, verbose: true, type: undefined, - /* global, server */ + /* global */ }, schema: [{ id: 'file', @@ -97,22 +97,11 @@ define([ disabled: false, group: gettext('Miscellaneous'), }], - }, { - id: 'server_note', - label: gettext('Note'), - text: gettext('The backup format will be PLAIN'), - type: 'note', - visible: function(m) { - return m.get('type') === 'server'; - }, }, { id: 'globals_note', label: gettext('Note'), text: gettext('Only objects global to the entire database will be backed up in PLAIN format'), type: 'note', - visible: function(m) { - return m.get('type') === 'globals'; - }, }, {}], validate: function() { // TODO: HOW TO VALIDATE ??? @@ -183,6 +172,13 @@ define([ value: 'directory', }, ], + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') { + setTimeout(function() { m.set('format', 'plain'); }, 10); + return false; + } + return true; + }, }, { id: 'ratio', label: gettext('Compression ratio'), @@ -190,6 +186,11 @@ define([ min: 0, max: 9, disabled: false, + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') + return false; + return true; + }, }, { id: 'encoding', label: gettext('Encoding'), @@ -198,6 +199,15 @@ define([ node: 'database', control: 'node-ajax-options', url: 'get_encodings', + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') { + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + return d.version >= 110000; + } + return true; + }, }, { id: 'no_of_jobs', label: gettext('Number of jobs'), @@ -206,6 +216,11 @@ define([ disabled: function(m) { return !(m.get('format') === 'Directory'); }, + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') + return false; + return true; + }, }, { id: 'role', label: gettext('Role name'), @@ -214,6 +229,14 @@ define([ select2: { allowClear: false, }, + }, { + id: 'server_note', + label: gettext('Note'), + text: gettext('The backup format will be PLAIN'), + type: 'note', + visible: function(m) { + return m.get('type') === 'server'; + }, }, { type: 'nested', control: 'fieldset', @@ -250,6 +273,11 @@ define([ m.get('only_schema'); }, }], + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') + return false; + return true; + }, }, { type: 'nested', control: 'fieldset', @@ -285,6 +313,13 @@ define([ control: Backform.CustomSwitchControl, disabled: false, group: gettext('Type of objects'), + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') { + setTimeout(function() { m.set('blobs', false); }, 10); + return false; + } + return true; + }, }], }, { type: 'nested', @@ -315,6 +350,20 @@ define([ control: Backform.CustomSwitchControl, disabled: false, group: gettext('Do not save'), + }, { + id: 'no_comments', + label: gettext('Comments'), + control: Backform.CustomSwitchControl, + disabled: false, + group: gettext('Do not save'), + visible: function() { + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + s = pgBrowser.Nodes[d._type].getTreeNodeHierarchy(i)['server']; + + return s.version >= 110000; + }, }], }, { type: 'nested', @@ -339,12 +388,41 @@ define([ control: Backform.CustomSwitchControl, disabled: false, group: gettext('Queries'), + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') + return false; + return true; + }, }, { id: 'include_drop_database', label: gettext('Include DROP DATABASE statement'), control: Backform.CustomSwitchControl, + group: gettext('Queries'), + deps: ['only_data'], + disabled: function(m) { + if (m.get('only_data')) { + setTimeout(function() { m.set('include_drop_database', false); }, 10); + return true; + } + return false; + }, + }, { + id: 'load_via_partition_root', + label: gettext('Load Via Partition Root'), + control: Backform.CustomSwitchControl, disabled: false, group: gettext('Queries'), + visible: function(m) { + if (!_.isUndefined(m.get('type')) && m.get('type') === 'server') + return false; + + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + s = pgBrowser.Nodes[d._type].getTreeNodeHierarchy(i)['server']; + + return s.version >= 110000; + }, }], }, { type: 'nested', @@ -376,8 +454,15 @@ define([ id: 'with_oids', label: gettext('With OID(s)'), control: Backform.CustomSwitchControl, - disabled: false, + deps: ['use_column_inserts', 'use_insert_commands'], group: gettext('Miscellaneous'), + disabled: function(m) { + if (m.get('use_column_inserts') || m.get('use_insert_commands')) { + setTimeout(function() { m.set('with_oids', false); }, 10); + return true; + } + return false; + }, }, { id: 'verbose', label: gettext('Verbose messages'), @@ -483,33 +568,23 @@ define([ return this; }, start_backup_global: function(action, item) { - var params = { - 'globals': true, - }; - this.start_backup_global_server.apply( - this, [action, item, params] - ); - }, - start_backup_server: function(action, item) { - var params = { - 'server': true, - }; - this.start_backup_global_server.apply( - this, [action, item, params] - ); - }, - - // Callback to draw Backup Dialog for globals/server - start_backup_global_server: function(action, treeItem, params) { let dialog = new globalBackupDialog.BackupDialog( pgBrowser, $, alertify, BackupModel ); - dialog.draw(action, treeItem, params); + dialog.draw(action, item, {'globals': true}); + }, + start_backup_server: function(action, item) { + let dialog = new globalBackupDialog.BackupDialog( + pgBrowser, + $, + alertify, + BackupObjectModel + ); + dialog.draw(action, item, {'server': true}); }, - // Callback to draw Backup Dialog for objects backup_objects: function(action, treeItem) { let dialog = new globalBackupDialog.BackupDialog( diff --git a/web/pgadmin/tools/backup/tests/test_backup_create_job_unit_test.py b/web/pgadmin/tools/backup/tests/test_backup_create_job_unit_test.py index dc2f7e9bd..5d2063fcf 100644 --- a/web/pgadmin/tools/backup/tests/test_backup_create_job_unit_test.py +++ b/web/pgadmin/tools/backup/tests/test_backup_create_job_unit_test.py @@ -156,7 +156,7 @@ class BackupCreateJobTest(BaseTestGenerator): ), url='/backup/job/{0}/object', expected_cmd_opts=['--verbose', '--format=p', '--data-only'], - not_expected_cmd_opts=['--schema-only', '--no-owner'], + not_expected_cmd_opts=['--schema-only'], expected_exit_code=[0, None] )), ('When backup the object with option only_schema', @@ -240,6 +240,34 @@ class BackupCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When backup the object with option - Do not save comments,', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + schemas=[], + tables=[], + database='postgres', + no_comments=True, + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--no-comments'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=110000, + message='Backup object with --no-comments are not supported ' + 'by EPAS/PG server less than 11.0' + )), ('When backup the object with option - all queries', dict( class_params=dict( @@ -269,6 +297,34 @@ class BackupCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When backup the object with option - load via partition root', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + load_via_partition_root=True, + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--load-via-partition-root'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=110000, + message='Backup object with --load-via-partition-root are not ' + 'supported by EPAS/PG server less than 11.0' + )), ('When backup the object with option - all queries and format custom', dict( class_params=dict( @@ -293,9 +349,9 @@ class BackupCreateJobTest(BaseTestGenerator): include_drop_database=True ), url='/backup/job/{0}/object', - expected_cmd_opts=['--inserts', - '--column-inserts'], - not_expected_cmd_opts=['--create', '--clean'], + expected_cmd_opts=['--inserts', '--clean', + '--column-inserts', '--create'], + not_expected_cmd_opts=[], expected_exit_code=[0, None] )), ('When backup the object with option - miscellaneous', @@ -355,6 +411,7 @@ class BackupCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When backup the server', dict( class_params=dict( @@ -377,6 +434,186 @@ class BackupCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When backup the server with option only_data', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + verbose=True, + only_data=True, + only_schema=False + ), + url='/backup/job/{0}', + expected_cmd_opts=['--verbose', '--data-only'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None] + )), + ('When backup the server with option only_schema', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + format='plain', + verbose=True, + only_data=False, + only_schema=True + ), + url='/backup/job/{0}', + expected_cmd_opts=['--verbose', '--schema-only'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None] + )), + ('When backup the server with option - Do not save privilege,' + ' tablespace, unlogged table data', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + format='plain', + verbose=True, + dns_privilege=True, + dns_unlogged_tbl_data=True, + dns_tablespace=True + ), + url='/backup/job/{0}', + expected_cmd_opts=['--no-privileges', + '--no-tablespaces', + '--no-unlogged-table-data'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None] + )), + ('When backup the server with option - Do not save comments,', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + format='plain', + verbose=True, + no_comments=True, + ), + url='/backup/job/{0}', + expected_cmd_opts=['--no-comments'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=110000, + message='Backup server with --no-comments are not supported ' + 'by EPAS/PG server less than 11.0' + )), + ('When backup the server with option - all queries', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + format='plain', + verbose=True, + use_column_inserts=True, + use_insert_commands=True, + include_drop_database=True + ), + url='/backup/job/{0}', + expected_cmd_opts=['--clean', '--inserts', + '--column-inserts'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None] + )), + ('When backup the server with option - miscellaneous', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + type='server', + verbose=True, + disable_quoting=True, + use_set_session_auth=True, + with_oids=True, + dqoute=True + ), + url='/backup/job/{0}', + expected_cmd_opts=['--verbose', '--quote-all-identifiers', + '--disable-dollar-quoting', '--oids', + '--use-set-session-authorization'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None] + )), + ('When backup the server with encoding', + dict( + class_params=dict( + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + username='postgres' + ), + params=dict( + file='test_backup_server_file', + dqoute=False, + verbose=True, + type='server', + encoding='UTF-8' + ), + url='/backup/job/{0}', + expected_cmd_opts=['--encoding'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=110000, + message='Backup server with encoding are not supported ' + 'by EPAS/PG server less than 11.0' + )), ('When backup globals', dict( class_params=dict( @@ -454,6 +691,11 @@ class BackupCreateJobTest(BaseTestGenerator): db_owner = server_response['data']['user']['name'] self.data = database_utils.get_db_data(db_owner) + if hasattr(self, 'server_min_version') and \ + server_response["data"]["version"] < \ + self.server_min_version: + self.skipTest(self.message) + url = self.url.format(self.server_id) # Create the backup job diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py index 81e480a7e..ae7477393 100644 --- a/web/pgadmin/tools/restore/__init__.py +++ b/web/pgadmin/tools/restore/__init__.py @@ -243,17 +243,13 @@ def create_restore_job(sid): return True return False - def set_value(key, param, value): - if key in data: - if value: - if value is True and data[key]: - args.append(param) - args.append(data[key]) - else: - args.append(param) - args.append(value) - return True - return False + def set_value(key, param, default_value=None): + if key in data and data[key] is not None and data[key] != '': + args.append(param) + args.append(data[key]) + elif default_value is not None: + args.append(param) + args.append(default_value) def set_multiple(key, param, with_schema=True): if key in data: @@ -293,8 +289,8 @@ def create_restore_job(sid): '--username', server.username, '--no-password' ]) - set_value('role', '--role', True) - set_value('database', '--dbname', True) + set_value('role', '--role') + set_value('database', '--dbname') if data['format'] == 'directory': args.extend(['--format=d']) @@ -318,7 +314,10 @@ def create_restore_job(sid): set_param('use_set_session_auth', '--use-set-session-authorization') set_param('exit_on_error', '--exit-on-error') - set_value('no_of_jobs', '--jobs', True) + if manager.version >= 110000: + set_param('no_comments', '--no-comments') + + set_value('no_of_jobs', '--jobs') set_param('verbose', '--verbose') set_multiple('schemas', '--schema', False) diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js index ce60c6d40..2f301764e 100644 --- a/web/pgadmin/tools/restore/static/js/restore.js +++ b/web/pgadmin/tools/restore/static/js/restore.js @@ -240,6 +240,20 @@ commonUtils, menuUtils, supportedNodes, restoreDialog control: Backform.CustomSwitchControl, disabled: false, group: gettext('Do not save'), + }, { + id: 'no_comments', + label: gettext('Comments'), + control: Backform.CustomSwitchControl, + disabled: false, + group: gettext('Do not save'), + visible: function() { + var t = pgBrowser.tree, + i = t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined, + s = pgBrowser.Nodes[d._type].getTreeNodeHierarchy(i)['server']; + + return s.version >= 110000; + }, }], }, { type: 'nested', diff --git a/web/pgadmin/tools/restore/static/js/restore.js.rej b/web/pgadmin/tools/restore/static/js/restore.js.rej deleted file mode 100644 index 43577f427..000000000 --- a/web/pgadmin/tools/restore/static/js/restore.js.rej +++ /dev/null @@ -1,322 +0,0 @@ -diff a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js (rejected hunks) -@@ -391,318 +344,8 @@ commonUtils - }, - // Callback to draw Backup Dialog for objects - restore_objects: function(action, treeItem) { -- -- var i = treeItem || pgBrowser.tree.selected(), -- server_data = null; -- -- while (i) { -- var node_data = pgBrowser.tree.itemData(i); -- if (node_data._type == 'server') { -- server_data = node_data; -- break; -- } -- -- if (pgBrowser.tree.hasParent(i)) { -- i = $(pgBrowser.tree.parent(i)); -- } else { -- alertify.alert( -- gettext('Restore Error'), -- gettext('Please select server or child node from tree.') -- ); -- break; -- } -- } -- -- if (!server_data) { -- return; -- } -- -- var module = 'paths', -- preference_name = 'pg_bin_dir', -- msg = gettext('Please configure the PostgreSQL Binary Path in the Preferences dialog.'); -- -- if ((server_data.type && server_data.type == 'ppas') || -- server_data.server_type == 'ppas') { -- preference_name = 'ppas_bin_dir'; -- msg = gettext('Please configure the EDB Advanced Server Binary Path in the Preferences dialog.'); -- } -- -- var preference = pgBrowser.get_preference(module, preference_name); -- -- if (preference) { -- if (!preference.value) { -- alertify.alert(gettext('Configuration required'), msg); -- return; -- } -- } else { -- alertify.alert( -- gettext('Restore Error'), -- S(gettext('Failed to load preference %s of module %s')).sprintf(preference_name, module).value() -- ); -- return; -- } -- -- var title = S(gettext('Restore (%s: %s)')), -- tree = pgBrowser.tree, -- item = treeItem || tree.selected(), -- data = item && item.length == 1 && tree.itemData(item), -- node = data && data._type && pgBrowser.Nodes[data._type]; -- -- if (!node) -- return; -- -- var treeInfo = node.getTreeNodeHierarchy.apply(node, [item]); -- -- if (treeInfo.database._label.indexOf('=') >= 0) { -- alertify.alert( -- gettext('Restore error'), -- gettext('Restore job creation failed. '+ -- 'Databases with = symbols in the name cannot be restored using this utility.') -- ); -- return; -- } -- -- title = title.sprintf(node.label, data.label).value(); -- -- if (!alertify.pg_restore) { -- // Create Dialog title on the fly with node details -- alertify.dialog('pg_restore', function factory() { -- return { -- main: function(title, item, data, node) { -- this.set('title', title); -- this.setting('pg_node', node); -- this.setting('pg_item', item); -- this.setting('pg_item_data', data); -- }, -- build: function() { -- alertify.pgDialogBuild.apply(this); -- }, -- setup: function() { -- return { -- buttons: [{ -- text: '', -- className: 'btn btn-default pull-left fa fa-lg fa-info', -- attrs: { -- name: 'object_help', -- type: 'button', -- url: 'backup.html', -- label: gettext('Restore'), -- }, -- }, { -- text: '', -- key: 112, -- className: 'btn btn-default pull-left fa fa-lg fa-question', -- attrs: { -- name: 'dialog_help', -- type: 'button', -- label: gettext('Restore'), -- url: url_for('help.static', { -- 'filename': 'restore_dialog.html', -- }), -- }, -- }, { -- text: gettext('Restore'), -- key: 13, -- className: 'btn btn-primary fa fa-upload pg-alertify-button', -- restore: true, -- 'data-btn-name': 'restore', -- }, { -- text: gettext('Cancel'), -- key: 27, -- className: 'btn btn-danger fa fa-lg fa-times pg-alertify-button', -- restore: false, -- 'data-btn-name': 'cancel', -- }], -- // Set options for dialog -- options: { -- title: title, -- //disable both padding and overflow control. -- padding: !1, -- overflow: !1, -- model: 0, -- resizable: true, -- maximizable: true, -- pinnable: false, -- closableByDimmer: false, -- modal: false, -- }, -- }; -- }, -- hooks: { -- // triggered when the dialog is closed -- onclose: function() { -- if (this.view) { -- this.view.remove({ -- data: true, -- internal: true, -- silent: true, -- }); -- } -- }, -- }, -- settings: { -- pg_node: null, -- pg_item: null, -- pg_item_data: null, -- }, -- prepare: function() { -- -- var self = this; -- // Disable Backup button until user provides Filename -- this.__internal.buttons[2].element.disabled = true; -- var $container = $('
'); -- 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; -- -- var treeInfo = node.getTreeNodeHierarchy.apply(node, [i]); -- -- var newModel = new RestoreObjectModel({ -- node_data: node, -- }, { -- 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, -- }); -- -- $(this.elements.body.childNodes[0]).addClass( -- 'alertify_tools_dialog_properties obj_properties' -- ); -- -- view.render(); -- -- this.elements.content.appendChild($container.get(0)); -- -- view.$el.attr('tabindex', -1); -- // var dialogTabNavigator = pgBrowser.keyboardNavigation.getDialogTabNavigator(view); -- pgBrowser.keyboardNavigation.getDialogTabNavigator(view); -- var container = view.$el.find('.tab-content:first > .tab-pane.active:first'); -- commonUtils.findAndSetFocus(container); -- -- // Listen to model & if filename is provided then enable Backup button -- this.view.model.on('change', function() { -- if (!_.isUndefined(this.get('file')) && this.get('file') !== '') { -- this.errorModel.clear(); -- self.__internal.buttons[2].element.disabled = false; -- } else { -- self.__internal.buttons[2].element.disabled = true; -- this.errorModel.set('file', gettext('Please provide filename')); -- } -- }); -- -- }, -- // Callback functions when click on the buttons of the Alertify dialogs -- callback: function(e) { -- // Fetch current server id -- var t = pgBrowser.tree, -- i = this.settings['pg_item'] || t.selected(), -- d = this.settings['pg_item_data'] || ( -- i && i.length == 1 ? t.itemData(i) : undefined -- ), -- node = this.settings['pg_node'] || ( -- d && pgBrowser.Nodes[d._type] -- ); -- -- if (e.button.element.name == 'dialog_help' || e.button.element.name == 'object_help') { -- e.cancel = true; -- pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'), -- node, i, e.button.element.getAttribute('label')); -- return; -- } -- -- if (e.button['data-btn-name'] === 'restore') { -- if (!d) -- return; -- -- var info = node.getTreeNodeHierarchy.apply(node, [i]), -- m = this.view.model; -- // Set current node info into model -- m.set('database', info.database._label); -- if (!m.get('custom')) { -- switch (d._type) { -- case 'schema': -- m.set('schemas', [d._label]); -- break; -- case 'table': -- m.set('schemas', [info.schema._label]); -- m.set('tables', [d._label]); -- break; -- case 'function': -- m.set('schemas', [info.schema._label]); -- m.set('functions', [d._label]); -- break; -- case 'index': -- m.set('schemas', [info.schema._label]); -- m.set('indexes', [d._label]); -- break; -- case 'trigger': -- m.set('schemas', [info.schema._label]); -- m.set('triggers', [d._label]); -- break; -- case 'trigger_func': -- m.set('schemas', [info.schema._label]); -- m.set('trigger_funcs', [d._label]); -- break; -- } -- } else { -- // TODO:: -- // When we will implement the object selection in the -- // import dialog, we will need to select the objects from -- // the tree selection tab. -- } -- -- var self = this, -- baseUrl = url_for('restore.create_job', { -- 'sid': info.server._id, -- }), -- args = this.view.model.toJSON(); -- -- $.ajax({ -- url: baseUrl, -- method: 'POST', -- data: { -- 'data': JSON.stringify(args), -- }, -- success: function(res) { -- if (res.success) { -- alertify.success( -- gettext('Restore job created.'), 5 -- ); -- pgBrowser.Events.trigger('pgadmin-bgprocess:created', self); -- } else { -- console.warn(res); -- } -- }, -- error: function(xhr) { -- try { -- var err = $.parseJSON(xhr.responseText); -- alertify.alert( -- gettext('Restore failed.'), -- err.errormsg -- ); -- } catch (e) { -- console.warn(e.stack || e); -- } -- }, -- }); -- } -- }, -- }; -- }); -- } -- -- alertify.pg_restore(title, item, data, node).resizeTo('65%', '60%'); -+ let dialog = new restoreDialog.RestoreDialog(pgBrowser, $, alertify, RestoreObjectModel); -+ dialog.draw(action, treeItem); - }, - }; - return pgBrowser.Restore; diff --git a/web/pgadmin/tools/restore/tests/test_restore_create_job_unit_test.py b/web/pgadmin/tools/restore/tests/test_restore_create_job_unit_test.py index 23a1cf04e..752f45744 100644 --- a/web/pgadmin/tools/restore/tests/test_restore_create_job_unit_test.py +++ b/web/pgadmin/tools/restore/tests/test_restore_create_job_unit_test.py @@ -174,6 +174,36 @@ class RestoreCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When restore object with option - Do not save comments', + dict( + class_params=dict( + sid=1, + name='test_restore_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_restore', + username='postgres' + ), + params=dict( + file='test_restore_file', + format='custom', + verbose=True, + custom=False, + schemas=[], + tables=[], + database='postgres', + no_comments=True, + only_data=False + ), + url='/restore/job/{0}', + expected_cmd_opts=['--no-comments'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=110000, + message='Restore object with --no-comments are not supported ' + 'by EPAS/PG server less than 11.0' + )), ('When restore object with option - Queries', dict( class_params=dict( @@ -315,6 +345,11 @@ class RestoreCreateJobTest(BaseTestGenerator): self.data = database_utils.get_db_data(db_owner) self.db_name = self.data['name'] + if hasattr(self, 'server_min_version') and \ + server_response["data"]["version"] < \ + self.server_min_version: + self.skipTest(self.message) + url = self.url.format(self.server_id) # Create the restore job