1
0
mirror of synced 2026-10-23 07:08:55 +03:00

Add the PostgreSQL 18 options to the Backup, Backup Server and Restore dialogs. #9064, #9066 (#10060)

Add --statistics-only, --no-policies, --no-data, --no-schema,
--no-statistics, --statistics and --sequence-data where the utilities
support them, gated on server version 18, and suppress combinations that
pg_dump, pg_dumpall and pg_restore would reject. The pg_dumpall --format
and pg_restore --exclude-database/--globals-only options were reverted
before PostgreSQL 18.0 and so are not included.
This commit is contained in:
Dave Page
2026-10-12 17:16:24 +01:00
parent d5bd079593
commit fad01ed776
11 changed files with 713 additions and 12 deletions

View File

@@ -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,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 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.
* Move the switch next to *Statistics* to the *Yes* position to exclude
optimizer statistics. **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 +279,15 @@ 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. It cannot be combined with *Only
data*, *Only schemas* or *Do not save* *Statistics*. **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.

View File

@@ -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,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 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.
* Move the switch next to *Statistics* to the *Yes* position to exclude
optimizer statistics. **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 +191,15 @@ 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. It cannot be combined with *Only
data*, *Only schemas* or *Do not save* *Statistics*. **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.

View File

@@ -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

View File

@@ -294,6 +294,14 @@ def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
data.get('only_tablespaces', None))
set_param('only_roles', '--roles-only',
data.get('only_roles', None))
# pg_dump rejects --statistics-only alongside --data-only or
# --schema-only, so suppress it the same way --schema-only is suppressed
# above. The dialog disables the switches, but a request can still arrive
# with both set.
set_param('only_statistics', '--statistics-only',
manager.version >= 180000 and
not data.get('only_data', None) and
not data.get('only_schema', None))
# Sections
set_param('pre_data', '--section=pre-data')
@@ -317,6 +325,17 @@ def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
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)
# Each of these conflicts with the matching --*-only option.
set_param('no_data', '--no-data',
manager.version >= 180000 and
not data.get('only_data', None))
set_param('no_schema', '--no-schema',
manager.version >= 180000 and
not data.get('only_schema', None))
set_param('no_statistics', '--no-statistics',
manager.version >= 180000 and
not data.get('only_statistics', None))
# Query Options
set_param('use_insert_commands', '--inserts')
@@ -353,6 +372,15 @@ def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
set_param('verbose', '--verbose')
set_param('dqoute', '--quote-all-identifiers')
set_param('use_set_session_auth', '--use-set-session-authorization')
# pg_dump rejects --statistics alongside --data-only, --schema-only or
# --no-statistics. Statistics are not dumped by default, so dropping
# --statistics gives the result the other option asked for.
set_param('statistics', '--statistics',
manager.version >= 180000 and
not data.get('only_data', None) and
not data.get('only_schema', None) and
not data.get('no_statistics', None))
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)

View File

@@ -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,49 @@ 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
}, {
// pg_dump rejects each of these alongside the matching *Only* switch
// on the Data/Objects tab, so they follow each other's state.
id: 'no_data',
label: gettext('Data'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'do_not_save',
min_version: 180000,
deps: ['only_data'],
disabled: function(state) {
return state.only_data;
},
}, {
id: 'no_schema',
label: gettext('Schema'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'do_not_save',
min_version: 180000,
deps: ['only_schema'],
disabled: function(state) {
return state.only_schema;
},
}, {
id: 'no_statistics',
label: gettext('Statistics'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'do_not_save',
min_version: 180000,
deps: ['only_statistics', 'statistics'],
disabled: function(state) {
return state.only_statistics || state.statistics;
},
}];
}
}
@@ -384,6 +449,25 @@ export class MiscellaneousSchema extends BaseUISchema {
disabled: false,
group: gettext('Miscellaneous'),
inlineGroup: 'miscellaneous',
}, {
id: 'statistics',
label: gettext('Dump statistics'),
type: 'switch',
deps: ['only_data', 'only_schema', 'no_statistics'],
disabled: function(state) {
return state.only_data || state.only_schema || state.no_statistics;
},
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'),

View File

@@ -1362,6 +1362,134 @@ class BackupCreateJobTest(BaseTestGenerator):
expected_env={
'PGDATABASE': 'host=127.0.0.1 port=5433 dbname=postgres'},
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'
))
]

View File

@@ -0,0 +1,138 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Unit tests for the PostgreSQL 18 pg_dump options.
These call _get_args_params_values() with a mocked manager rather than going
through the API against a live server, so the version-gated behaviour is
covered whatever the test server happens to be. The existing scenarios in
test_backup_create_job_unit_test.py can only assert the v18 options where a
v18 server is available to test against, and silently skip otherwise.
Each of --statistics-only, --no-data, --no-schema and --no-statistics
conflicts with one of the --*-only options, and --statistics conflicts with
--data-only, --schema-only and --no-statistics, verified against pg_dump 18:
"options -s/--schema-only and --statistics-only cannot be used together" and
so on. The utility rejects the whole command, so pgAdmin must not emit both
even if a request arrives with both set.
"""
from unittest.mock import MagicMock, patch
from pgadmin.tools.backup import _get_args_params_values
from pgadmin.utils.route import BaseTestGenerator
V18 = 180000
V17 = 170000
ALL_V18_OPTS = ['--statistics-only', '--no-policies', '--no-data',
'--no-schema', '--no-statistics', '--statistics',
'--sequence-data']
class BackupPG18ArgsTestCase(BaseTestGenerator):
"""The v18 options must be emitted only when they are usable."""
scenarios = [
('Do not save options are emitted on v18', dict(
version=V18,
data=dict(no_policies=True, no_data=True, no_schema=True,
no_statistics=True),
expected=['--no-policies', '--no-data', '--no-schema',
'--no-statistics'],
not_expected=['--statistics-only'],
)),
('Statistics and sequence data are emitted on v18', dict(
version=V18,
data=dict(statistics=True, sequence_data=True),
expected=['--statistics', '--sequence-data'],
not_expected=['--statistics-only'],
)),
('Only statistics is emitted on v18', dict(
version=V18,
data=dict(only_statistics=True),
expected=['--statistics-only'],
not_expected=['--data-only', '--schema-only'],
)),
('No v18 option is emitted on v17', dict(
version=V17,
data=dict(no_policies=True, no_data=True, no_schema=True,
no_statistics=True, only_statistics=True,
statistics=True, sequence_data=True),
expected=[],
not_expected=ALL_V18_OPTS,
)),
('Only data wins over only statistics and no data', dict(
version=V18,
data=dict(only_data=True, only_statistics=True, no_data=True),
expected=['--data-only'],
not_expected=['--statistics-only', '--no-data'],
)),
('Only schema wins over only statistics and no schema', dict(
version=V18,
data=dict(only_schema=True, only_statistics=True, no_schema=True),
expected=['--schema-only'],
not_expected=['--statistics-only', '--no-schema'],
)),
('Only statistics wins over no statistics', dict(
version=V18,
data=dict(only_statistics=True, no_statistics=True),
expected=['--statistics-only'],
not_expected=['--no-statistics'],
)),
('Only data wins over statistics', dict(
version=V18,
data=dict(only_data=True, statistics=True),
expected=['--data-only'],
not_expected=['--statistics'],
)),
('Only schema wins over statistics', dict(
version=V18,
data=dict(only_schema=True, statistics=True),
expected=['--schema-only'],
not_expected=['--statistics'],
)),
('No statistics wins over statistics', dict(
version=V18,
data=dict(no_statistics=True, statistics=True),
expected=['--no-statistics'],
not_expected=['--statistics'],
)),
('Statistics may be combined with only statistics', dict(
version=V18,
data=dict(only_statistics=True, statistics=True),
expected=['--statistics-only', '--statistics'],
not_expected=[],
)),
]
def setUp(self):
# A pure argument-marshalling test: no server connection needed.
pass
def _args(self):
manager = MagicMock(version=self.version, user='postgres',
use_ssh_tunnel=0)
server = MagicMock(host='localhost', port=5432,
maintenance_db='postgres')
data = dict(format='custom', schemas=[], tables=[], **self.data)
with patch('pgadmin.utils.driver.get_driver',
return_value=MagicMock()):
return _get_args_params_values(
data, MagicMock(), 'objects', 'test_backup_file', server,
manager)
def runTest(self):
args = self._args()
for opt in self.expected:
self.assertIn(opt, args)
for opt in self.not_expected:
self.assertNotIn(opt, args)

View File

@@ -371,6 +371,25 @@ 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:
# pg_restore refuses --statistics-only alongside --data-only or
# --schema-only, and each --no-* option below alongside the
# matching --*-only, so drop the narrower option rather than
# letting the utility reject the whole command. The dialog
# disables these switches, but a request can still arrive with
# both set.
if not data.get('only_data', None) and \
not data.get('only_schema', None):
set_param('only_statistics', '--statistics-only', data, args)
set_param('no_policies', '--no-policies', data, args)
if not data.get('only_data', None):
set_param('no_data', '--no-data', data, args)
if not data.get('only_schema', None):
set_param('no_schema', '--no-schema', data, args)
if not data.get('only_statistics', None):
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,

View File

@@ -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,49 @@ 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
}, {
// pg_restore rejects each of these alongside the matching *Only*
// switch on the Data/Objects tab, so they follow each other's state.
id: 'no_data',
label: gettext('Data'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'save_options',
min_version: 180000,
deps: ['only_data'],
disabled: function(state) {
return state.only_data;
},
}, {
id: 'no_schema',
label: gettext('Schema'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'save_options',
min_version: 180000,
deps: ['only_schema'],
disabled: function(state) {
return state.only_schema;
},
}, {
id: 'no_statistics',
label: gettext('Statistics'),
type: 'switch',
group: gettext('Do not save'),
inlineGroup: 'save_options',
min_version: 180000,
deps: ['only_statistics'],
disabled: function(state) {
return state.only_statistics;
},
}];
}
}

View File

@@ -490,6 +490,73 @@ class RestoreCreateJobTest(BaseTestGenerator):
# connect to a database named after the login role.
expected_status_code=400,
)),
('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,
),
url=RESTORE_JOB_URL,
expected_cmd='pg_restore',
expected_cmd_opts=['--no-policies', '--no-data', '--no-schema',
'--no-statistics'],
not_expected_cmd_opts=['--statistics-only'],
expected_exit_code=[0, None],
server_min_version=180000,
message='Restore object with --no-policies, --no-data, '
'--no-schema, --no-statistics is not '
'supported by EPAS/PG server less than 18.0'
)),
# Separate from the scenario above because pg_restore rejects
# --statistics-only alongside --no-statistics.
('When restore object with option - 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',
only_statistics=True,
),
url=RESTORE_JOB_URL,
expected_cmd='pg_restore',
expected_cmd_opts=['--statistics-only'],
not_expected_cmd_opts=['--no-statistics', '--data-only',
'--schema-only'],
expected_exit_code=[0, None],
server_min_version=180000,
message='Restore object with --statistics-only is not '
'supported by EPAS/PG server less than 18.0'
)),
]
def setUp(self):

View File

@@ -0,0 +1,97 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Unit tests for the PostgreSQL 18 pg_restore options.
These call get_restore_util_args() with a mocked manager rather than going
through the API against a live server, so the version-gated behaviour is
covered whatever the test server happens to be.
pg_restore 18 rejects --statistics-only alongside -a/--data-only or
-s/--schema-only, --no-data alongside --data-only, --no-schema alongside
--schema-only and --no-statistics alongside --statistics-only, so pgAdmin
must not emit both halves of any of those pairs.
"""
from unittest.mock import MagicMock
from pgadmin.tools.restore import get_restore_util_args
from pgadmin.utils.route import BaseTestGenerator
V18 = 180000
V17 = 170000
ALL_V18_OPTS = ['--statistics-only', '--no-policies', '--no-data',
'--no-schema', '--no-statistics']
class RestorePG18ArgsTestCase(BaseTestGenerator):
"""The v18 options must be emitted only when they are usable."""
scenarios = [
('Do not restore options are emitted on v18', dict(
version=V18,
data=dict(no_policies=True, no_data=True, no_schema=True,
no_statistics=True),
expected=['--no-policies', '--no-data', '--no-schema',
'--no-statistics'],
not_expected=['--statistics-only'],
)),
('Only statistics is emitted on v18', dict(
version=V18,
data=dict(only_statistics=True),
expected=['--statistics-only'],
not_expected=['--data-only', '--schema-only'],
)),
('No v18 option is emitted on v17', dict(
version=V17,
data=dict(no_policies=True, no_data=True, no_schema=True,
no_statistics=True, only_statistics=True),
expected=[],
not_expected=ALL_V18_OPTS,
)),
('Only data wins over only statistics and no data', dict(
version=V18,
data=dict(only_data=True, only_statistics=True, no_data=True),
expected=['--data-only'],
not_expected=['--statistics-only', '--no-data'],
)),
('Only schema wins over only statistics and no schema', dict(
version=V18,
data=dict(only_schema=True, only_statistics=True, no_schema=True),
expected=['--schema-only'],
not_expected=['--statistics-only', '--no-schema'],
)),
('Only statistics wins over no statistics', dict(
version=V18,
data=dict(only_statistics=True, no_statistics=True),
expected=['--statistics-only'],
not_expected=['--no-statistics'],
)),
]
def setUp(self):
# A pure argument-marshalling test: no server connection needed.
pass
def _args(self):
manager = MagicMock(version=self.version, use_ssh_tunnel=0)
server = MagicMock(host='localhost', port=5432, username='postgres')
data = dict(format='custom', **self.data)
return get_restore_util_args(data, manager, server, MagicMock(),
MagicMock(), 'test_restore_file')
def runTest(self):
args = self._args()
for opt in self.expected:
self.assertIn(opt, args)
for opt in self.not_expected:
self.assertNotIn(opt, args)