From a8551a85be78cfe06168060c6aa1e5485b693d48 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Wed, 10 Jun 2026 10:45:06 +0100 Subject: [PATCH] Add PostgreSQL 18 options to the Backup and Restore dialogs. Adds the new pg_dump/pg_dumpall/pg_restore options introduced in PostgreSQL 18, gated to servers >= 18: - Only statistics (--statistics-only) in Type of objects, mutually exclusive with the other Only * switches. - Row security policies (--no-policies), Data (--no-data) and Schema (--no-schema) in Do not save (both dialogs); Statistics (--no-statistics) in the Restore dialog. - Dump statistics (--statistics) and Sequence data (--sequence-data) in Backup Miscellaneous. The pg_dumpall -F/--format option and the pg_restore --exclude-database / -g/--globals-only options are not included: the non-text pg_dumpall feature was reverted before PostgreSQL 18.0 and those options do not exist in the shipped 18.x client utilities. Includes backend arg-builder wiring, unit-test scenarios and docs. Closes #9064 Closes #9065 Closes #9066 --- docs/en_US/backup_dialog.rst | 24 ++++ docs/en_US/backup_server_dialog.rst | 24 ++++ docs/en_US/release_notes_9_16.rst | 3 + docs/en_US/restore_dialog.rst | 20 +++ web/pgadmin/tools/backup/__init__.py | 7 + .../tools/backup/static/js/backup.ui.js | 78 +++++++++-- .../tests/test_backup_create_job_unit_test.py | 128 ++++++++++++++++++ web/pgadmin/tools/restore/__init__.py | 8 ++ .../tools/restore/static/js/restore.ui.js | 59 +++++++- .../test_restore_create_job_unit_test.py | 36 +++++ 10 files changed, 375 insertions(+), 12 deletions(-) diff --git a/docs/en_US/backup_dialog.rst b/docs/en_US/backup_dialog.rst index 02cc46ba788..dd7cf0ea0cb 100644 --- a/docs/en_US/backup_dialog.rst +++ b/docs/en_US/backup_dialog.rst @@ -100,6 +100,10 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg * Move the switch next to *Only schemas* to limit the back up to schema-level database objects. + * Move the switch next to *Only statistics* to the *Yes* position to limit + the back up to optimizer statistics. **Note:** This option is visible only + for database server greater than or equal to 18. + * Move the switch next to *Blobs* to the *No* position to exclude large objects in the backup. @@ -143,6 +147,18 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg Table access methods. **Note:** This option is visible only for database server greater than or equal to 15. + * Move the switch next to *Row security policies* to the *Yes* position to + exclude row security policies. **Note:** This option is visible only for + database server greater than or equal to 18. + + * Move the switch next to *Data* to the *Yes* position to exclude data from + the backup. **Note:** This option is visible only for database server + greater than or equal to 18. + + * Move the switch next to *Schema* to the *Yes* position to exclude schema + (the data definitions) from the backup. **Note:** This option is visible + only for database server greater than or equal to 18. + .. image:: images/backup_queries.png :alt: Queries option on backup dialog :align: center @@ -259,6 +275,14 @@ tab to provide other backup options. position to include a statement that will use a SET SESSION AUTHORIZATION command to determine object ownership (instead of an ALTER OWNER command). + * Move the switch next to *Dump statistics* to the *Yes* position to include + optimizer statistics in the backup. **Note:** This option is visible only + for database server greater than or equal to 18. + + * Move the switch next to *Sequence data* to the *Yes* position to include + sequence data that would normally be excluded. **Note:** This option is + visible only for database server greater than or equal to 18. + * Use the *Exclude schema* field to not dump schemas whose name matches pattern. diff --git a/docs/en_US/backup_server_dialog.rst b/docs/en_US/backup_server_dialog.rst index ed9da9a5e1c..0d088fcbdb2 100644 --- a/docs/en_US/backup_server_dialog.rst +++ b/docs/en_US/backup_server_dialog.rst @@ -45,6 +45,10 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg * Move the switch next to *Only roles* to limit the back up to roles only. + * Move the switch next to *Only statistics* to the *Yes* position to limit + the back up to optimizer statistics. **Note:** This option is visible only + for database server greater than or equal to 18. + .. image:: images/backup_server_do_not_save.png :alt: Do not save option on backup server dialog :align: center @@ -88,6 +92,18 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg Table access methods. **Note:** This option is visible only for database server greater than or equal to 15. + * Move the switch next to *Row security policies* to the *Yes* position to + exclude row security policies. **Note:** This option is visible only for + database server greater than or equal to 18. + + * Move the switch next to *Data* to the *Yes* position to exclude data from + the backup. **Note:** This option is visible only for database server + greater than or equal to 18. + + * Move the switch next to *Schema* to the *Yes* position to exclude schema + (the data definitions) from the backup. **Note:** This option is visible + only for database server greater than or equal to 18. + .. image:: images/backup_server_queries.png :alt: Queries option on backup server dialog :align: center @@ -171,6 +187,14 @@ tab to provide other backup options. position to include a statement that will use a SET SESSION AUTHORIZATION command to determine object ownership (instead of an ALTER OWNER command). + * Move the switch next to *Dump statistics* to the *Yes* position to include + optimizer statistics in the backup. **Note:** This option is visible only + for database server greater than or equal to 18. + + * Move the switch next to *Sequence data* to the *Yes* position to include + sequence data that would normally be excluded. **Note:** This option is + visible only for database server greater than or equal to 18. + * Use the *Exclude database* field to not dump databases whose name matches pattern. diff --git a/docs/en_US/release_notes_9_16.rst b/docs/en_US/release_notes_9_16.rst index 4f0740cdff0..83da9751f59 100644 --- a/docs/en_US/release_notes_9_16.rst +++ b/docs/en_US/release_notes_9_16.rst @@ -26,6 +26,9 @@ Bundled PostgreSQL Utilities New features ************ + | `Issue #9064 `_ - Add the new PostgreSQL 18 pg_dump options to the Backup dialog: Only statistics, Dump statistics, Sequence data, and Do not save Row security policies/Data/Schema. + | `Issue #9065 `_ - Add the new PostgreSQL 18 pg_dumpall options (Row security policies and Sequence data, among others) to the server Backup dialog. + | `Issue #9066 `_ - Add the new PostgreSQL 18 pg_restore options to the Restore dialog: Only statistics and Do not restore Row security policies/Data/Schema/Statistics. | `Issue #9626 `_ - Add support for the TOAST tuple target storage parameter in the Materialized View dialog. | `Issue #9646 `_ - Make the init container security context in the Helm chart configurable via containerSecurityContext, consistent with the main container. | `Issue #9699 `_ - Add support for closing a tab with a middle-click on its title. diff --git a/docs/en_US/restore_dialog.rst b/docs/en_US/restore_dialog.rst index 72d45d48ba2..9b0a657faad 100644 --- a/docs/en_US/restore_dialog.rst +++ b/docs/en_US/restore_dialog.rst @@ -77,6 +77,10 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg * Move the switch next to *Only schema* to limit the restoration to schema-level database objects. + * Move the switch next to *Only statistics* to the *Yes* position to limit + the restoration to optimizer statistics. **Note:** This option is visible + only for database server greater than or equal to 18. + .. image:: images/restore_do_not_save.png :alt: Restore dialog do not save section :align: center @@ -110,6 +114,22 @@ tab to provide options related to data or pgAdmin objects that correspond to *pg Table access methods. **Note:** This option is visible only for database server greater than or equal to 15. + * Move the switch next to *Row security policies* to the *Yes* position to + exclude row security policies. **Note:** This option is visible only for + database server greater than or equal to 18. + + * Move the switch next to *Data* to the *Yes* position to exclude data from + the restore. **Note:** This option is visible only for database server + greater than or equal to 18. + + * Move the switch next to *Schema* to the *Yes* position to exclude schema + (the data definitions) from the restore. **Note:** This option is visible + only for database server greater than or equal to 18. + + * Move the switch next to *Statistics* to the *Yes* position to exclude + optimizer statistics from the restore. **Note:** This option is visible + only for database server greater than or equal to 18. + .. image:: images/restore_queries.png :alt: Restore dialog queries section :align: center diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py index 8778b9dd459..33ed7296ddd 100644 --- a/web/pgadmin/tools/backup/__init__.py +++ b/web/pgadmin/tools/backup/__init__.py @@ -294,6 +294,8 @@ def set_value(key, param, default_value=None, assertion=True): data.get('only_tablespaces', None)) set_param('only_roles', '--roles-only', data.get('only_roles', None)) + set_param('only_statistics', '--statistics-only', + manager.version >= 180000) # Sections set_param('pre_data', '--section=pre-data') @@ -317,6 +319,9 @@ def set_value(key, param, default_value=None, assertion=True): set_param('dns_table_access_method', '--no-table-access-method', manager.version >= 150000) set_param('dns_no_role_passwords', '--no-role-passwords') + set_param('no_policies', '--no-policies', manager.version >= 180000) + set_param('no_data', '--no-data', manager.version >= 180000) + set_param('no_schema', '--no-schema', manager.version >= 180000) # Query Options set_param('use_insert_commands', '--inserts') @@ -353,6 +358,8 @@ def set_value(key, param, default_value=None, assertion=True): set_param('verbose', '--verbose') set_param('dqoute', '--quote-all-identifiers') set_param('use_set_session_auth', '--use-set-session-authorization') + set_param('statistics', '--statistics', manager.version >= 180000) + set_param('sequence_data', '--sequence-data', manager.version >= 180000) set_value('exclude_schema', '--exclude-schema') set_value('extra_float_digits', '--extra-float-digits', None, manager.version >= 120000) diff --git a/web/pgadmin/tools/backup/static/js/backup.ui.js b/web/pgadmin/tools/backup/static/js/backup.ui.js index 9460b5b6a82..061611c306a 100644 --- a/web/pgadmin/tools/backup/static/js/backup.ui.js +++ b/web/pgadmin/tools/backup/static/js/backup.ui.js @@ -98,14 +98,15 @@ export class TypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), deps: ['pre_data', 'data', 'post_data', 'only_schema', - 'only_tablespaces', 'only_roles'], + 'only_tablespaces', 'only_roles', 'only_statistics'], disabled: function(state) { return state.pre_data || state.data || state.post_data || state.only_schema || state.only_tablespaces || - state.only_roles; + state.only_roles || + state.only_statistics; }, inlineGroup: 'type_of_objects', }, { @@ -114,14 +115,15 @@ export class TypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), deps: ['pre_data', 'data', 'post_data', 'only_data', - 'only_tablespaces', 'only_roles'], + 'only_tablespaces', 'only_roles', 'only_statistics'], disabled: function(state) { return state.pre_data || state.data || state.post_data || state.only_data || state.only_tablespaces || - state.only_roles; + state.only_roles || + state.only_statistics; }, inlineGroup: 'type_of_objects', }, { @@ -130,14 +132,15 @@ export class TypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_schema', - 'only_roles'], + 'only_roles', 'only_statistics'], disabled: function(state) { return state.pre_data || state.data || state.post_data || state.only_data || state.only_schema || - state.only_roles; + state.only_roles || + state.only_statistics; }, visible: isVisibleForObjectBackup(obj?.top?.backupType), inlineGroup: 'type_of_objects', @@ -147,7 +150,7 @@ export class TypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_schema', - 'only_tablespaces'], + 'only_tablespaces', 'only_statistics'], inlineGroup: 'type_of_objects', disabled: function(state) { return state.pre_data || @@ -155,9 +158,28 @@ export class TypeObjSchema extends BaseUISchema { state.post_data || state.only_data || state.only_schema || - state.only_tablespaces; + state.only_tablespaces || + state.only_statistics; }, visible: isVisibleForObjectBackup(obj?.top?.backupType) + }, { + id: 'only_statistics', + label: gettext('Only statistics'), + type: 'switch', + group: gettext('Type of objects'), + min_version: 180000, + deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_schema', + 'only_tablespaces', 'only_roles'], + inlineGroup: 'type_of_objects', + disabled: function(state) { + return state.pre_data || + state.data || + state.post_data || + state.only_data || + state.only_schema || + state.only_tablespaces || + state.only_roles; + }, }, { id: 'blobs', label: gettext('Blobs'), @@ -282,6 +304,30 @@ export class SaveOptSchema extends BaseUISchema { group: gettext('Do not save'), inlineGroup: 'do_not_save', min_version: 150000 + }, { + id: 'no_policies', + label: gettext('Row security policies'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'do_not_save', + min_version: 180000 + }, { + id: 'no_data', + label: gettext('Data'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'do_not_save', + min_version: 180000 + }, { + id: 'no_schema', + label: gettext('Schema'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'do_not_save', + min_version: 180000 }]; } } @@ -384,6 +430,22 @@ export class MiscellaneousSchema extends BaseUISchema { disabled: false, group: gettext('Miscellaneous'), inlineGroup: 'miscellaneous', + }, { + id: 'statistics', + label: gettext('Dump statistics'), + type: 'switch', + disabled: false, + group: gettext('Miscellaneous'), + inlineGroup: 'miscellaneous', + min_version: 180000 + }, { + id: 'sequence_data', + label: gettext('Sequence data'), + type: 'switch', + disabled: false, + group: gettext('Miscellaneous'), + inlineGroup: 'miscellaneous', + min_version: 180000 }, { id: 'exclude_schema', label: gettext('Exclude schema'), 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 6e613f17970..c7998967f44 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 @@ -1301,6 +1301,134 @@ class BackupCreateJobTest(BaseTestGenerator): expected_cmd_opts=['--globals-only'], not_expected_cmd_opts=[], expected_exit_code=[0, None] + )), + ('When backup the object with option - Do not save Row security ' + 'policies, Data, Schema (>= v18)', + 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_policies=True, + no_data=True, + no_schema=True, + ), + url=BACKUP_OBJECT_URL, + expected_cmd_opts=['--no-policies', '--no-data', '--no-schema'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=180000, + message='Backup object with --no-policies, --no-data, ' + '--no-schema is not supported by EPAS/PG server less ' + 'than 18.0' + )), + ('When backup the object with option - statistics, sequence data ' + 'and only statistics (>= v18)', + 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', + only_statistics=True, + statistics=True, + sequence_data=True, + ), + url=BACKUP_OBJECT_URL, + expected_cmd_opts=['--statistics-only', '--statistics', + '--sequence-data'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=180000, + message='Backup object with --statistics-only, --statistics, ' + '--sequence-data is not supported by EPAS/PG server less ' + 'than 18.0' + )), + ('When backup the object with v18 options against an older server ' + '(< v18)', + 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_policies=True, + no_data=True, + no_schema=True, + only_statistics=True, + statistics=True, + sequence_data=True, + ), + url=BACKUP_OBJECT_URL, + expected_cmd_opts=[], + not_expected_cmd_opts=['--no-policies', '--no-data', + '--no-schema', '--statistics-only', + '--statistics', '--sequence-data'], + expected_exit_code=[0, None], + server_max_version=179999, + message='v18 backup options must not be emitted for EPAS/PG ' + 'server less than 18.0' + )), + ('When backup the server with v18 options (>= v18)', + 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_policies=True, + sequence_data=True, + ), + url=BACKUP_SERVER_URL, + expected_cmd_opts=['--no-policies', '--sequence-data'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=180000, + message='Backup server with --no-policies, --sequence-data is ' + 'not supported by EPAS/PG server less than 18.0' )) ] diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py index 0fce22a9d79..3bc439bf042 100644 --- a/web/pgadmin/tools/restore/__init__.py +++ b/web/pgadmin/tools/restore/__init__.py @@ -360,6 +360,14 @@ def get_restore_util_args(data, manager, server, driver, conn, filepath): set_param('exit_on_error', '--exit-on-error', data, args) set_value('exclude_schema', '--exclude-schema', data, args) + # PostgreSQL 18 and above options + if manager.version >= 180000: + set_param('only_statistics', '--statistics-only', data, args) + set_param('no_policies', '--no-policies', data, args) + set_param('no_data', '--no-data', data, args) + set_param('no_schema', '--no-schema', data, args) + set_param('no_statistics', '--no-statistics', data, args) + set_multiple('schemas', '--schema', data, args, driver, conn, False) set_multiple('tables', '--table', data, args, driver, conn, False) set_multiple('functions', '--function', data, args, driver, conn, diff --git a/web/pgadmin/tools/restore/static/js/restore.ui.js b/web/pgadmin/tools/restore/static/js/restore.ui.js index 34066a35db1..4c6cee3c0fa 100644 --- a/web/pgadmin/tools/restore/static/js/restore.ui.js +++ b/web/pgadmin/tools/restore/static/js/restore.ui.js @@ -99,7 +99,7 @@ export class RestoreTypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), inlineGroup: 'types_of_data', - deps: ['pre_data', 'data', 'post_data', 'only_schema'], + deps: ['pre_data', 'data', 'post_data', 'only_schema', 'only_statistics'], disabled: function(state) { if(obj.selectedNodeType == 'table') { state.only_data = true; @@ -108,7 +108,8 @@ export class RestoreTypeObjSchema extends BaseUISchema { (state.pre_data || state.data || state.post_data || - state.only_schema + state.only_schema || + state.only_statistics ); }, }, { @@ -117,7 +118,7 @@ export class RestoreTypeObjSchema extends BaseUISchema { type: 'switch', group: gettext('Type of objects'), inlineGroup: 'types_of_data', - deps: ['pre_data', 'data', 'post_data', 'only_data'], + deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_statistics'], disabled: function(state) { if(obj.selectedNodeType == 'index' || obj.selectedNodeType == 'function') { state.only_schema = true; @@ -126,7 +127,25 @@ export class RestoreTypeObjSchema extends BaseUISchema { (state.pre_data || state.data || state.post_data || - state.only_data + state.only_data || + state.only_statistics + ); + }, + }, { + id: 'only_statistics', + label: gettext('Only statistics'), + type: 'switch', + group: gettext('Type of objects'), + inlineGroup: 'types_of_data', + min_version: 180000, + deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_schema'], + disabled: function(state) { + return (obj.selectedNodeType !== 'database' && obj.selectedNodeType !== 'schema') || + (state.pre_data || + state.data || + state.post_data || + state.only_data || + state.only_schema ); }, }]; @@ -217,6 +236,38 @@ export class RestoreSaveOptSchema extends BaseUISchema { group: gettext('Do not save'), inlineGroup: 'save_options', min_version: 150000 + }, { + id: 'no_policies', + label: gettext('Row security policies'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'save_options', + min_version: 180000 + }, { + id: 'no_data', + label: gettext('Data'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'save_options', + min_version: 180000 + }, { + id: 'no_schema', + label: gettext('Schema'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'save_options', + min_version: 180000 + }, { + id: 'no_statistics', + label: gettext('Statistics'), + type: 'switch', + disabled: false, + group: gettext('Do not save'), + inlineGroup: 'save_options', + min_version: 180000 }]; } } 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 8df960e7be4..df4dcebc853 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 @@ -408,6 +408,42 @@ class RestoreCreateJobTest(BaseTestGenerator): not_expected_cmd_opts=[], expected_exit_code=[0, None] )), + ('When restore object with option - Do not restore Row security ' + 'policies, Data, Schema, Statistics and Only statistics (>= v18)', + 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, + schemas=[], + tables=[], + database='postgres', + no_policies=True, + no_data=True, + no_schema=True, + no_statistics=True, + only_statistics=True, + ), + url=RESTORE_JOB_URL, + expected_cmd='pg_restore', + expected_cmd_opts=['--no-policies', '--no-data', '--no-schema', + '--no-statistics', '--statistics-only'], + not_expected_cmd_opts=[], + expected_exit_code=[0, None], + server_min_version=180000, + message='Restore object with --no-policies, --no-data, ' + '--no-schema, --no-statistics, --statistics-only is not ' + 'supported by EPAS/PG server less than 18.0' + )), ] def setUp(self):