@@ -67,6 +67,9 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas
validate_database_sql
)
site = get_site(context)
frappe.init(site=site)
force = context.force or force
decompressed_file_name = extract_sql_from_archive(sql_file_path)
@@ -85,9 +88,6 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas
# check if valid SQL file
validate_database_sql(decompressed_file_name, _raise=not force)
site = get_site(context)
frappe.init(site=site)
# dont allow downgrading to older versions of frappe without force
if not force and is_downgrade(decompressed_file_name, verbose=True):
warn_message = (
@@ -474,7 +474,7 @@ def remove_from_installed_apps(context, app):
@click.command('uninstall-app')
@click.argument('app')
@click.option('--yes', '-y', help='To bypass confirmation prompt for uninstalling the app', is_flag=True, default=False, multiple=True )
@click.option('--yes', '-y', help='To bypass confirmation prompt for uninstalling the app', is_flag=True, default=False)
@click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
@click.option('--no-backup', help='Do not backup the site', is_flag=True, default=False)
@click.option('--force', help='Force remove app from site', is_flag=True, default=False)
@@ -738,6 +738,131 @@ def build_search_index(context):
finally:
frappe.destroy()
@click.command('trim-database')
@click.option('--dry-run', is_flag=True, default=False, help='Show what would be deleted')
@click.option('--format', '-f', default='text', type=click.Choice(['json', 'text']), help='Output format')
@click.option('--no-backup', is_flag=True, default=False, help='Do not backup the site')
@pass_context
def trim_database(context, dry_run, format, no_backup):
if not context.sites:
raise SiteNotSpecifiedError
from frappe.utils.backups import scheduled_backup
ALL_DATA = {}
for site in context.sites:
frappe.init(site=site)
frappe.connect()
TABLES_TO_DROP = []
STANDARD_TABLES = get_standard_tables()
information_schema = frappe.qb.Schema("information_schema")
table_name = frappe.qb.Field("table_name").as_("name")
queried_result = frappe.qb.from_(
information_schema.tables
).select(table_name).where(
information_schema.tables.table_schema == frappe.conf.db_name
).run()
database_tables = [x[0] for x in queried_result]
doctype_tables = frappe.get_all("DocType", pluck="name")
for x in database_tables:
doctype = x.lstrip("tab")
if not (doctype in doctype_tables or x.startswith("__") or x in STANDARD_TABLES):
TABLES_TO_DROP.append(x)
if not TABLES_TO_DROP:
if format == "text":
click.secho(f"No ghost tables found in {frappe.local.site}...Great!", fg="green")
else:
if not (no_backup or dry_run):
if format == "text":
print(f"Backing Up Tables: {', '.join(TABLES_TO_DROP)}")
odb = scheduled_backup(
ignore_conf=False,
include_doctypes=",".join(x.lstrip("tab") for x in TABLES_TO_DROP),
ignore_files=True,
force=True,
)
if format == "text":
odb.print_summary()
print("\nTrimming Database")
for table in TABLES_TO_DROP:
if format == "text":
print(f"* Dropping Table '{table}'...")
if not dry_run:
frappe.db.sql_ddl(f"drop table `{table}`")
ALL_DATA[frappe.local.site] = TABLES_TO_DROP
frappe.destroy()
if format == "json":
import json
print(json.dumps(ALL_DATA, indent=1))
def get_standard_tables():
import re
tables = []
sql_file = os.path.join(
"..", "apps", "frappe", "frappe", "database", frappe.conf.db_type, f'framework_{frappe.conf.db_type}.sql'
)
content = open(sql_file).read().splitlines()
for line in content:
table_found = re.search(r"""CREATE TABLE ("|`)(.*)?("|`) \(""", line)
if table_found:
tables.append(table_found.group(2))
return tables
@click.command('trim-tables')
@click.option('--dry-run', is_flag=True, default=False, help='Show what would be deleted')
@click.option('--format', '-f', default='table', type=click.Choice(['json', 'table']), help='Output format')
@click.option('--no-backup', is_flag=True, default=False, help='Do not backup the site')
@pass_context
def trim_tables(context, dry_run, format, no_backup):
if not context.sites:
raise SiteNotSpecifiedError
from frappe.model.meta import trim_tables
from frappe.utils.backups import scheduled_backup
for site in context.sites:
frappe.init(site=site)
frappe.connect()
if not (no_backup or dry_run):
click.secho(f"Taking backup for {frappe.local.site}", fg="green")
odb = scheduled_backup(ignore_files=False, force=True)
odb.print_summary()
try:
trimmed_data = trim_tables(dry_run=dry_run, quiet=format == 'json')
if format == 'table' and not dry_run:
click.secho(f"The following data have been removed from {frappe.local.site}", fg='green')
handle_data(trimmed_data, format=format)
finally:
frappe.destroy()
def handle_data(data: dict, format='json'):
if format == 'json':
import json
print(json.dumps({frappe.local.site: data}, indent=1, sort_keys=True))
else:
from frappe.utils.commands import render_table
data = [["DocType", "Fields"]] + [[table, ", ".join(columns)] for table, columns in data.items()]
render_table(data)
commands = [
add_system_manager,
backup,
@@ -766,5 +891,7 @@ commands = [
add_to_hosts,
start_ngrok,
build_search_index,
partial_restore
partial_restore,
trim_tables,
trim_database,
]