mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-09 23:15:58 -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 #6460 <https://redmine.postgresql.org/issues/6460>`_ - Added a mechanism to detect a corrupt/broken config database file.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
@ -14,6 +14,8 @@ import os
|
||||
import sys
|
||||
import re
|
||||
import ipaddress
|
||||
import traceback
|
||||
|
||||
from types import MethodType
|
||||
from collections import defaultdict
|
||||
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.session import create_session_interface, pga_unauthorised
|
||||
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
|
||||
from datetime import timedelta
|
||||
from pgadmin.setup import get_version, set_version
|
||||
from datetime import timedelta, datetime
|
||||
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.csrf import pgCSRFProtect
|
||||
from pgadmin import authenticate
|
||||
@ -349,6 +351,44 @@ def create_app(app_name=None):
|
||||
##########################################################################
|
||||
# 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():
|
||||
# Run migration for the first time i.e. create database
|
||||
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
|
||||
# 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
|
||||
# the exception
|
||||
if not cli_mode:
|
||||
db_upgrade(app)
|
||||
upgrade_db()
|
||||
else:
|
||||
if not os.path.exists(SQLITE_PATH):
|
||||
raise FileNotFoundError(
|
||||
'SQLite database file "' + SQLITE_PATH +
|
||||
'" does not exists.')
|
||||
raise RuntimeError('Specified SQLite database file '
|
||||
'is not valid.')
|
||||
raise RuntimeError(
|
||||
'The configuration database file is not valid.')
|
||||
else:
|
||||
schema_version = get_version()
|
||||
|
||||
# Run migration if current schema version is greater than the
|
||||
# schema version stored in version table
|
||||
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
|
||||
if CURRENT_SCHEMA_VERSION > schema_version:
|
||||
|
@ -342,6 +342,7 @@ class BrowserModule(PgAdminModule):
|
||||
list: a list of url endpoints exposed to the client.
|
||||
"""
|
||||
return [BROWSER_INDEX, 'browser.nodes',
|
||||
'browser.check_corrupted_db_file',
|
||||
'browser.check_master_password',
|
||||
'browser.set_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",
|
||||
methods=["GET"])
|
||||
def check_master_password():
|
||||
|
@ -594,7 +594,7 @@ define('pgadmin.browser', [
|
||||
}, 300000);
|
||||
|
||||
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:update', obj.onUpdateTreeNode, 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.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() {
|
||||
let self = this;
|
||||
// Master password dialog
|
||||
|
@ -11,3 +11,4 @@ from .user_info import user_info
|
||||
from .db_version import get_version, set_version
|
||||
from .db_upgrade import db_upgrade
|
||||
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:
|
||||
return -1
|
||||
|
||||
return version.value
|
||||
if version:
|
||||
return version.value
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
def set_version(new_version):
|
||||
|
Loading…
Reference in New Issue
Block a user