mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added a mechanism to detect a corrupt/broken config database file. Fixes #6460
This commit is contained in:
parent
93ddc4a5ba
commit
7c88ee7cff
@ -21,6 +21,7 @@ Housekeeping
|
|||||||
************
|
************
|
||||||
|
|
||||||
| `Issue #6225 <https://redmine.postgresql.org/issues/6225>`_ - Updated Flask-Security-Too to the latest v4.
|
| `Issue #6225 <https://redmine.postgresql.org/issues/6225>`_ - Updated Flask-Security-Too to the latest v4.
|
||||||
|
| `Issue #6460 <https://redmine.postgresql.org/issues/6460>`_ - Added a mechanism to detect a corrupt/broken config database file.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
*********
|
*********
|
||||||
|
@ -14,6 +14,8 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import traceback
|
||||||
|
|
||||||
from types import MethodType
|
from types import MethodType
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
@ -38,8 +40,8 @@ from pgadmin.utils import PgAdminModule, driver, KeyManager
|
|||||||
from pgadmin.utils.preferences import Preferences
|
from pgadmin.utils.preferences import Preferences
|
||||||
from pgadmin.utils.session import create_session_interface, pga_unauthorised
|
from pgadmin.utils.session import create_session_interface, pga_unauthorised
|
||||||
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
|
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
|
||||||
from datetime import timedelta
|
from datetime import timedelta, datetime
|
||||||
from pgadmin.setup import get_version, set_version
|
from pgadmin.setup import get_version, set_version, check_db_tables
|
||||||
from pgadmin.utils.ajax import internal_server_error, make_json_response
|
from pgadmin.utils.ajax import internal_server_error, make_json_response
|
||||||
from pgadmin.utils.csrf import pgCSRFProtect
|
from pgadmin.utils.csrf import pgCSRFProtect
|
||||||
from pgadmin import authenticate
|
from pgadmin import authenticate
|
||||||
@ -349,6 +351,44 @@ def create_app(app_name=None):
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
# Upgrade the schema (if required)
|
# Upgrade the schema (if required)
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
def backup_db_file():
|
||||||
|
"""
|
||||||
|
Create a backup of the current database file
|
||||||
|
and create new database file with default settings.
|
||||||
|
"""
|
||||||
|
backup_file_name = "{0}.{1}".format(
|
||||||
|
SQLITE_PATH, datetime.now().strftime('%Y%m%d%H%M%S'))
|
||||||
|
os.rename(SQLITE_PATH, backup_file_name)
|
||||||
|
app.logger.error('Exception in database migration.')
|
||||||
|
app.logger.info('Creating new database file.')
|
||||||
|
try:
|
||||||
|
db_upgrade(app)
|
||||||
|
os.environ[
|
||||||
|
'CORRUPTED_DB_BACKUP_FILE'] = backup_file_name
|
||||||
|
app.logger.info('Database migration completed.')
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error('Database migration failed')
|
||||||
|
app.logger.error(traceback.format_exc())
|
||||||
|
raise RuntimeError('Migration failed')
|
||||||
|
|
||||||
|
def upgrade_db():
|
||||||
|
"""
|
||||||
|
Execute the migrations.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
db_upgrade(app)
|
||||||
|
os.environ['CORRUPTED_DB_BACKUP_FILE'] = ''
|
||||||
|
except Exception as e:
|
||||||
|
backup_db_file()
|
||||||
|
|
||||||
|
# check all tables are present in the db.
|
||||||
|
is_db_error, invalid_tb_names = check_db_tables()
|
||||||
|
if is_db_error:
|
||||||
|
app.logger.error(
|
||||||
|
'Table(s) {0} are missing in the'
|
||||||
|
' database'.format(invalid_tb_names))
|
||||||
|
backup_db_file()
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
# Run migration for the first time i.e. create database
|
# Run migration for the first time i.e. create database
|
||||||
from config import SQLITE_PATH
|
from config import SQLITE_PATH
|
||||||
@ -356,25 +396,34 @@ def create_app(app_name=None):
|
|||||||
|
|
||||||
# If version not available, user must have aborted. Tables are not
|
# If version not available, user must have aborted. Tables are not
|
||||||
# created and so its an empty db
|
# created and so its an empty db
|
||||||
if not os.path.exists(SQLITE_PATH) or get_version() == -1:
|
version = get_version()
|
||||||
|
if not os.path.exists(SQLITE_PATH) or version == -1:
|
||||||
# If running in cli mode then don't try to upgrade, just raise
|
# If running in cli mode then don't try to upgrade, just raise
|
||||||
# the exception
|
# the exception
|
||||||
if not cli_mode:
|
if not cli_mode:
|
||||||
db_upgrade(app)
|
upgrade_db()
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(SQLITE_PATH):
|
if not os.path.exists(SQLITE_PATH):
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
'SQLite database file "' + SQLITE_PATH +
|
'SQLite database file "' + SQLITE_PATH +
|
||||||
'" does not exists.')
|
'" does not exists.')
|
||||||
raise RuntimeError('Specified SQLite database file '
|
raise RuntimeError(
|
||||||
'is not valid.')
|
'The configuration database file is not valid.')
|
||||||
else:
|
else:
|
||||||
schema_version = get_version()
|
schema_version = get_version()
|
||||||
|
|
||||||
# Run migration if current schema version is greater than the
|
# Run migration if current schema version is greater than the
|
||||||
# schema version stored in version table
|
# schema version stored in version table
|
||||||
if CURRENT_SCHEMA_VERSION >= schema_version:
|
if CURRENT_SCHEMA_VERSION >= schema_version:
|
||||||
db_upgrade(app)
|
upgrade_db()
|
||||||
|
else:
|
||||||
|
# check all tables are present in the db.
|
||||||
|
is_db_error, invalid_tb_names = check_db_tables()
|
||||||
|
if is_db_error:
|
||||||
|
app.logger.error(
|
||||||
|
'Table(s) {0} are missing in the'
|
||||||
|
' database'.format(invalid_tb_names))
|
||||||
|
backup_db_file()
|
||||||
|
|
||||||
# Update schema version to the latest
|
# Update schema version to the latest
|
||||||
if CURRENT_SCHEMA_VERSION > schema_version:
|
if CURRENT_SCHEMA_VERSION > schema_version:
|
||||||
|
@ -342,6 +342,7 @@ class BrowserModule(PgAdminModule):
|
|||||||
list: a list of url endpoints exposed to the client.
|
list: a list of url endpoints exposed to the client.
|
||||||
"""
|
"""
|
||||||
return [BROWSER_INDEX, 'browser.nodes',
|
return [BROWSER_INDEX, 'browser.nodes',
|
||||||
|
'browser.check_corrupted_db_file',
|
||||||
'browser.check_master_password',
|
'browser.check_master_password',
|
||||||
'browser.set_master_password',
|
'browser.set_master_password',
|
||||||
'browser.reset_master_password',
|
'browser.reset_master_password',
|
||||||
@ -951,6 +952,19 @@ def form_master_password_response(existing=True, present=False, errmsg=None):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/check_corrupted_db_file",
|
||||||
|
endpoint="check_corrupted_db_file", methods=["GET"])
|
||||||
|
def check_corrupted_db_file():
|
||||||
|
"""
|
||||||
|
Get the corrupted database file path.
|
||||||
|
"""
|
||||||
|
file_location = os.environ['CORRUPTED_DB_BACKUP_FILE'] \
|
||||||
|
if 'CORRUPTED_DB_BACKUP_FILE' in os.environ else ''
|
||||||
|
# reset the corrupted db file path in env.
|
||||||
|
os.environ['CORRUPTED_DB_BACKUP_FILE'] = ''
|
||||||
|
return make_json_response(data=file_location)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/master_password", endpoint="check_master_password",
|
@blueprint.route("/master_password", endpoint="check_master_password",
|
||||||
methods=["GET"])
|
methods=["GET"])
|
||||||
def check_master_password():
|
def check_master_password():
|
||||||
|
@ -594,7 +594,7 @@ define('pgadmin.browser', [
|
|||||||
}, 300000);
|
}, 300000);
|
||||||
|
|
||||||
obj.set_master_password('');
|
obj.set_master_password('');
|
||||||
|
obj.check_corrupted_db_file();
|
||||||
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode, obj);
|
||||||
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode, obj);
|
||||||
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
|
obj.Events.on('pgadmin:browser:tree:refresh', obj.onRefreshTreeNode, obj);
|
||||||
@ -607,7 +607,35 @@ define('pgadmin.browser', [
|
|||||||
obj.register_to_activity_listener(document);
|
obj.register_to_activity_listener(document);
|
||||||
obj.start_inactivity_timeout_daemon();
|
obj.start_inactivity_timeout_daemon();
|
||||||
},
|
},
|
||||||
|
check_corrupted_db_file: function() {
|
||||||
|
$.ajax({
|
||||||
|
url: url_for('browser.check_corrupted_db_file'),
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
}).done((res)=> {
|
||||||
|
if(res.data.length > 0) {
|
||||||
|
|
||||||
|
Alertify.alert(
|
||||||
|
'Warning',
|
||||||
|
'pgAdmin detected unrecoverable corruption in it\'s SQLite configuration database. ' +
|
||||||
|
'The database has been backed up and recreated with default settings. '+
|
||||||
|
'It may be possible to recover data such as query history manually from '+
|
||||||
|
'the original/corrupt file using a tool such as DB Browser for SQLite if desired.'+
|
||||||
|
'<br><br>Original file: ' + res.data + '<br>Replacement file: ' +
|
||||||
|
res.data.substring(0, res.data.length - 14)
|
||||||
|
)
|
||||||
|
.set({'closable': true,
|
||||||
|
'onok': function() {
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}).fail(function(xhr, status, error) {
|
||||||
|
Alertify.alert(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
init_master_password: function() {
|
init_master_password: function() {
|
||||||
let self = this;
|
let self = this;
|
||||||
// Master password dialog
|
// Master password dialog
|
||||||
|
@ -11,3 +11,4 @@ from .user_info import user_info
|
|||||||
from .db_version import get_version, set_version
|
from .db_version import get_version, set_version
|
||||||
from .db_upgrade import db_upgrade
|
from .db_upgrade import db_upgrade
|
||||||
from .data_directory import create_app_data_directory
|
from .data_directory import create_app_data_directory
|
||||||
|
from .db_table_check import check_db_tables
|
||||||
|
32
web/pgadmin/setup/db_table_check.py
Normal file
32
web/pgadmin/setup/db_table_check.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
from pgadmin.model import Version
|
||||||
|
from pgadmin.model import db
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_table_names():
|
||||||
|
db_table_names = db.metadata.tables.keys() if db.metadata.tables else 0
|
||||||
|
return db_table_names
|
||||||
|
|
||||||
|
|
||||||
|
def check_db_tables():
|
||||||
|
is_error = False
|
||||||
|
invalid_tb_names = list()
|
||||||
|
db_table_names = get_db_table_names()
|
||||||
|
# check table is actually present in the db.
|
||||||
|
for table_name in db_table_names:
|
||||||
|
if not db.engine.dialect.has_table(db.engine, table_name):
|
||||||
|
invalid_tb_names.append(table_name)
|
||||||
|
is_error = True
|
||||||
|
|
||||||
|
if is_error:
|
||||||
|
return True, invalid_tb_names
|
||||||
|
else:
|
||||||
|
return False, invalid_tb_names
|
@ -16,7 +16,10 @@ def get_version():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
if version:
|
||||||
return version.value
|
return version.value
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def set_version(new_version):
|
def set_version(new_version):
|
||||||
|
Loading…
Reference in New Issue
Block a user