diff --git a/docs/en_US/restore_dialog.rst b/docs/en_US/restore_dialog.rst
index 9641e7ff2..3d88ae488 100644
--- a/docs/en_US/restore_dialog.rst
+++ b/docs/en_US/restore_dialog.rst
@@ -27,6 +27,8 @@ restore process:
* Select *Custom or tar* to restore from a custom archive file to create a
copy of the backed-up object.
+ * Select *Plain* to restore a plain SQL backup. When selecting this option
+ all the other options will not be applicable.
* Select *Directory* to restore from a compressed directory-format archive.
* Enter the complete path to the backup file in the *Filename* field.
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx
index e9edd6e16..5e2495da4 100644
--- a/web/pgadmin/static/js/SchemaView/FormView.jsx
+++ b/web/pgadmin/static/js/SchemaView/FormView.jsx
@@ -32,6 +32,7 @@ import {
} from './hooks';
import { registerView, View } from './registry';
import { createFieldControls, listenDepChanges } from './utils';
+import FormViewTab from './FormViewTab';
const ErrorMessageBox = () => {
const [key, setKey] = useState(0);
@@ -181,14 +182,8 @@ export default function FormView({
action={(ref) => ref?.updateIndicator()}
>{
finalGroups.map((tabGroup, idx) =>
-
)
}{hasSQLTab &&
diff --git a/web/pgadmin/static/js/SchemaView/FormViewTab.jsx b/web/pgadmin/static/js/SchemaView/FormViewTab.jsx
new file mode 100644
index 000000000..f63c1281c
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/FormViewTab.jsx
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2025, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { Tab } from '@mui/material';
+import React, { useContext, useState } from 'react';
+import { useFieldOptions, useSchemaStateSubscriber } from './hooks';
+import { SchemaStateContext } from './SchemaState';
+import PropTypes from 'prop-types';
+
+export default function FormViewTab({tabGroup, idx, tabValue, ...props}) {
+ const accessPath = [tabGroup.id];
+ const [refreshKey, setRefreshKey] = useState(0);
+ const subscriberManager = useSchemaStateSubscriber(setRefreshKey);
+ const schemaState = useContext(SchemaStateContext);
+ const options = useFieldOptions(accessPath, schemaState, subscriberManager);
+
+ return (
+
+ );
+};
+
+FormViewTab.muiName = Tab.muiName;
+
+FormViewTab.propTypes = {
+ tabGroup: PropTypes.object,
+ idx: PropTypes.number,
+ tabValue: PropTypes.number,
+};
diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx
index 8d437c685..40ce813ef 100644
--- a/web/pgadmin/static/js/Theme/index.jsx
+++ b/web/pgadmin/static/js/Theme/index.jsx
@@ -788,13 +788,17 @@ function getFinalTheme(baseTheme) {
MuiTab: {
styleOverrides: {
root: {
- '&.MuiTab-textColorPrimary':{
+ '&:not(.Mui-disabled).MuiTab-textColorPrimary':{
color: baseTheme.palette.text.primary,
},
'&.Mui-selected': {
color: baseTheme.otherVars.activeColor,
},
- }
+ },
+ icon: {
+ fontSize: '1rem',
+ marginRight: '2px',
+ },
}
},
MuiBackdrop: {
diff --git a/web/pgadmin/tools/restore/__init__.py b/web/pgadmin/tools/restore/__init__.py
index ed190158d..74c5b45da 100644
--- a/web/pgadmin/tools/restore/__init__.py
+++ b/web/pgadmin/tools/restore/__init__.py
@@ -141,18 +141,18 @@ def _get_create_req_data():
data = json.loads(request.data)
try:
- _file = filename_with_file_manager_path(data['file'])
+ filepath = filename_with_file_manager_path(data['file'])
except Exception as e:
return True, internal_server_error(errormsg=str(e)), data, None
- if _file is None:
+ if filepath is None:
return True, make_json_response(
status=410,
success=0,
errormsg=_("File could not be found.")
- ), data, _file
+ ), data, filepath
- return False, '', data, _file
+ return False, '', data, filepath
def _connect_server(sid):
@@ -263,15 +263,15 @@ def set_multiple(key, param, data, args, driver, conn, with_schema=True):
return False
-def _set_args_param_values(data, manager, server, driver, conn, _file):
+def get_restore_util_args(data, manager, server, driver, conn, filepath):
"""
- add args to the list.
+ return the args for the command
:param data: Data.
:param manager: Manager.
:param server: Server.
:param driver: Driver.
:param conn: Connection.
- :param _file: File.
+ :param filepath: File.
:return: args list.
"""
args = []
@@ -347,11 +347,56 @@ def _set_args_param_values(data, manager, server, driver, conn, _file):
False)
set_multiple('indexes', '--index', data, args, driver, conn, False)
- args.append(fs_short_path(_file))
+ args.append(fs_short_path(filepath))
return args
+def get_sql_util_args(data, manager, server, filepath):
+ """
+ return the args for the command
+ :param data: Data.
+ :param manager: Manager.
+ :param server: Server.
+ :param filepath: File.
+ :return: args list.
+ """
+ args = [
+ '--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, '--dbname',
+ data['database'],
+ '--file', fs_short_path(filepath)
+ ]
+
+ return args
+
+
+def use_restore_utility(data, manager, server, driver, conn, filepath):
+ utility = manager.utility('restore')
+ ret_val = does_utility_exist(utility)
+ if ret_val:
+ return ret_val, None, None
+
+ args = get_restore_util_args(data, manager, server, driver, conn, filepath)
+
+ return None, utility, args
+
+
+def use_sql_utility(data, manager, server, filepath):
+ utility = manager.utility('sql')
+ ret_val = does_utility_exist(utility)
+ if ret_val:
+ return ret_val, None, None
+
+ args = get_sql_util_args(data, manager, server, filepath)
+
+ return None, utility, args
+
+
@blueprint.route('/job/', methods=['POST'], endpoint='create_job')
@pga_login_required
def create_restore_job(sid):
@@ -364,7 +409,7 @@ def create_restore_job(sid):
Returns:
None
"""
- is_error, errmsg, data, _file = _get_create_req_data()
+ is_error, errmsg, data, filepath = _get_create_req_data()
if is_error:
return errmsg
@@ -372,16 +417,19 @@ def create_restore_job(sid):
if is_error:
return errmsg
- utility = manager.utility('restore')
- ret_val = does_utility_exist(utility)
- if ret_val:
+ if data['format'] == 'plain':
+ error_msg, utility, args = use_sql_utility(
+ data, manager, server, filepath)
+ else:
+ error_msg, utility, args = use_restore_utility(
+ data, manager, server, driver, conn, filepath)
+
+ if error_msg is not None:
return make_json_response(
success=0,
- errormsg=ret_val
+ errormsg=error_msg
)
- args = _set_args_param_values(data, manager, server, driver, conn, _file)
-
try:
p = BatchProcess(
desc=RestoreMessage(
diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js
index f62c29b51..c262e5b83 100644
--- a/web/pgadmin/tools/restore/static/js/restore.js
+++ b/web/pgadmin/tools/restore/static/js/restore.js
@@ -81,7 +81,8 @@ define('tools.restore', [
()=>getRestoreDisableOptionSchema({nodeInfo: treeNodeInfo}),
()=>getRestoreMiscellaneousSchema({nodeInfo: treeNodeInfo}),
{
- role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData)
+ role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+ nodeType: itemNodeData._type,
},
treeNodeInfo,
pgBrowser
diff --git a/web/pgadmin/tools/restore/static/js/restore.ui.js b/web/pgadmin/tools/restore/static/js/restore.ui.js
index 685a88360..f124967d3 100644
--- a/web/pgadmin/tools/restore/static/js/restore.ui.js
+++ b/web/pgadmin/tools/restore/static/js/restore.ui.js
@@ -343,12 +343,37 @@ export default class RestoreSchema extends BaseUISchema {
this.getRestoreMiscellaneousSchema = restoreMiscellaneousSchema;
this.treeNodeInfo = treeNodeInfo;
this.pgBrowser = pgBrowser;
+
+ this.formatOptions = this.getFormatOptions();
}
get idAttribute() {
return 'id';
}
+ isPlainFormat(state) {
+ return state.format == 'plain';
+ }
+
+ getFormatOptions() {
+ const options = [{
+ label: gettext('Custom or tar'),
+ value: 'custom',
+ }, {
+ label: gettext('Directory'),
+ value: 'directory',
+ }];
+
+ if(this.fieldOptions.nodeType == 'database') {
+ options.splice(1, 0, {
+ label: gettext('Plain'),
+ value: 'plain',
+ });
+ }
+
+ return options;
+ }
+
get baseFields() {
let obj = this;
return [{
@@ -356,14 +381,15 @@ export default class RestoreSchema extends BaseUISchema {
label: gettext('Format'),
disabled: false,type: 'select',
controlProps: { allowClear: false, width: '100%' },
- options: [{
- label: gettext('Custom or tar'),
- value: 'custom',
- },
- {
- label: gettext('Directory'),
- value: 'directory',
- }],
+ options: this.formatOptions,
+ depChange: (state) => {
+ if(state.format == 'plain') {
+ return {
+ no_of_jobs: undefined,
+ role: undefined,
+ };
+ }
+ }
}, {
id: 'file',
label: gettext('Filename'),
@@ -378,11 +404,12 @@ export default class RestoreSchema extends BaseUISchema {
};
},
disabled: false,
- deps: ['format']
+ deps: ['format'],
}, {
id: 'no_of_jobs',
label: gettext('Number of jobs'),
type: 'int',
+ disabled: obj.isPlainFormat,
}, {
id: 'role',
label: gettext('Role name'),
@@ -392,35 +419,45 @@ export default class RestoreSchema extends BaseUISchema {
controlProps: {
allowClear: false,
},
+ disabled: obj.isPlainFormat,
+ }, {
+ id: 'data-options', type: 'group', label: gettext('Data Options'),
+ disabled: function(state) {
+ return obj.isPlainFormat(state);
+ }, deps: ['format']
}, {
type: 'nested-fieldset',
label: gettext('Sections'),
- group: gettext('Data Options'),
+ group: 'data-options',
schema:obj.getSectionSchema(),
- visible: true
+ visible: true,
}, {
type: 'nested-fieldset',
label: gettext('Type of objects'),
- group: gettext('Data Options'),
+ group: 'data-options',
schema:obj.getRestoreTypeObjSchema(),
- visible: true
+ visible: true,
}, {
type: 'nested-fieldset',
label: gettext('Do not save'),
- group: gettext('Data Options'),
+ group: 'data-options',
schema:obj.getRestoreSaveOptSchema(),
- visible: true
+ visible: true,
+ }, {
+ id: 'query-options', type: 'group', label: gettext('Query Options'),
+ disabled: function(state) {
+ return obj.isPlainFormat(state);
+ }, deps: ['format']
}, {
id: 'include_create_database',
label: gettext('Include CREATE DATABASE statement'),
type: 'switch',
- disabled: false,
- group: gettext('Query Options')
+ group: 'query-options'
}, {
id: 'clean',
label: gettext('Clean before restore'),
type: 'switch',
- group: gettext('Query Options'),
+ group: 'query-options',
inlineGroup: 'clean',
disabled: function(state) {
if(obj.selectedNodeType === 'function' || obj.selectedNodeType === 'trigger_function') {
@@ -432,7 +469,7 @@ export default class RestoreSchema extends BaseUISchema {
id: 'if_exists',
label: gettext('Include IF EXISTS clause'),
type: 'switch',
- group: gettext('Query Options'),
+ group: 'query-options',
inlineGroup: 'clean',
deps: ['clean'],
disabled: function(state) {
@@ -447,29 +484,39 @@ export default class RestoreSchema extends BaseUISchema {
label: gettext('Single transaction'),
type: 'switch',
disabled: false,
- group: gettext('Query Options'),
+ group: 'query-options',
+ }, {
+ id: 'table-options', type: 'group', label: gettext('Table Options'),
+ disabled: function(state) {
+ return obj.isPlainFormat(state);
+ }, deps: ['format']
}, {
id: 'enable_row_security',
label: gettext('Enable row security'),
type: 'switch',
disabled: false,
- group: gettext('Table Options'),
+ group: 'table-options',
}, {
id: 'no_data_fail_table',
label: gettext('No data for failed tables'),
type: 'switch',
disabled: false,
- group: gettext('Table Options'),
+ group: 'table-options',
+ }, {
+ id: 'options', type: 'group', label: gettext('Options'),
+ disabled: function(state) {
+ return obj.isPlainFormat(state);
+ }, deps: ['format']
}, {
type: 'nested-fieldset',
label: gettext('Disable'),
- group: gettext('Options'),
+ group: 'options',
schema:obj.getRestoreDisableOptionSchema(),
visible: true
}, {
type: 'nested-fieldset',
label: gettext('Miscellaneous / Behavior'),
- group: gettext('Options'),
+ group: 'options',
schema:obj.getRestoreMiscellaneousSchema(),
visible: true
}];
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 f2b94fe7d..99bbdb620 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
@@ -24,6 +24,28 @@ RESTORE_JOB_URL = '/restore/job/{0}'
class RestoreCreateJobTest(BaseTestGenerator):
"""Test the RestoreCreateJob class"""
scenarios = [
+ ('When restore object with plain format',
+ 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='plain',
+ database='postgres'
+ ),
+ url=RESTORE_JOB_URL,
+ expected_cmd='psql',
+ expected_cmd_opts=['--file'],
+ not_expected_cmd_opts=[],
+ expected_exit_code=[0, None]
+ )),
('When restore object with default options',
dict(
class_params=dict(
@@ -46,6 +68,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
database='postgres'
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--verbose'],
not_expected_cmd_opts=[],
expected_exit_code=[0, None]
@@ -72,6 +95,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
database='postgres'
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--verbose', '--format=d'],
not_expected_cmd_opts=[],
expected_exit_code=[0, None]
@@ -103,6 +127,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
only_schema=True
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--verbose', '--jobs', '2',
'--section=pre-data', '--section=data',
'--section=post-data'],
@@ -136,6 +161,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
dns_owner=True
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--verbose', '--data-only'],
not_expected_cmd_opts=[],
# Below options should be enabled once we fix the issue #3368
@@ -167,6 +193,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
only_data=False
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--no-owner',
'--no-tablespaces',
'--no-privileges'],
@@ -200,6 +227,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
only_data=False
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--no-comments', '--no-publications',
'--no-subscriptions', '--no-security-labels'],
not_expected_cmd_opts=[],
@@ -231,6 +259,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
dns_table_access_method=True
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--no-table-access-method'],
not_expected_cmd_opts=[],
expected_exit_code=[0, None],
@@ -262,6 +291,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
if_exists=True,
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--create', '--clean',
'--single-transaction', '--if-exists'],
not_expected_cmd_opts=[],
@@ -291,6 +321,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
only_schema=False
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--enable-row-security',
'--no-data-for-failed-tables'],
not_expected_cmd_opts=[],
@@ -318,6 +349,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
only_schema=False
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--disable-triggers'],
not_expected_cmd_opts=[],
expected_exit_code=[0, None]
@@ -344,6 +376,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
exit_on_error=True,
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--exit-on-error',
'--use-set-session-authorization'],
not_expected_cmd_opts=[],
@@ -370,6 +403,7 @@ class RestoreCreateJobTest(BaseTestGenerator):
exclude_schema="sch*"
),
url=RESTORE_JOB_URL,
+ expected_cmd='pg_restore',
expected_cmd_opts=['--exclude-schema', 'sch*'],
not_expected_cmd_opts=[],
expected_exit_code=[0, None]
@@ -459,6 +493,11 @@ class RestoreCreateJobTest(BaseTestGenerator):
self.assertTrue(restore_message_mock.called)
self.assertTrue(batch_process_mock.called)
+ if self.expected_cmd:
+ self.assertIn(
+ self.expected_cmd,
+ batch_process_mock.call_args_list[0][1]['cmd']
+ )
if self.expected_cmd_opts:
for opt in self.expected_cmd_opts:
self.assertIn(
diff --git a/web/regression/javascript/schema_ui_files/restore.ui.spec.js b/web/regression/javascript/schema_ui_files/restore.ui.spec.js
index f9f93e9ba..1e824e910 100644
--- a/web/regression/javascript/schema_ui_files/restore.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/restore.ui.spec.js
@@ -23,6 +23,7 @@ describe('RestoreSchema', ()=>{
{
role: ()=>[],
encoding: ()=>[],
+ nodeType: '',
},
{server: {version: 11000}},
pgAdmin.pgBrowser