plugins/wb.admin/frontend/wb_admin_export.py (1,965 lines of code) (raw):
# Copyright (c) 2007, 2021, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
import re
import sys
import subprocess
import threading
import _thread
import time
import tempfile
import platform
import io
import grt
import wb_admin_export_options
import wb_common
from wb_common import to_unicode
from wb_server_management import local_run_cmd
from workbench.db_utils import QueryError, ConnectionTunnel, escape_sql_identifier
from collections import deque
from workbench.utils import Version
from workbench.log import log_warning, log_error, log_debug
from mforms import newBox, newButton, newPanel, newTextBox, newRadioButton, newLabel, newTreeView, newProgressBar, newTextEntry, newCheckBox, newScrollPanel, newTabView, newSelector
from mforms import Utilities, FileChooser
import mforms
from wb_admin_utils import weakcb, WbAdminTabBase, WbAdminValidationConnection
def local_quote_shell_token(s):
if sys.platform.lower() == "win32":
t = '"%s"' % s.replace('\\', '\\\\').replace('"', '\\"')
else:
t = '"%s"' % s.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$')
if t[:5] == '"\\\\\\\\':
t = t[3:]
t = '\"' + t
return t
def normalize_filename(s):
s = s.replace(":", "_").replace("/", "_").replace("\\", "_")
return s
def get_path_to_mysqldump():
"""get path to mysqldump from options"""
try:
path = grt.root.wb.options.options["mysqldump"]
if path:
if os.path.exists(path):
return path
if any(os.path.exists(os.path.join(p, path)) for p in os.getenv("PATH").split(os.pathsep)):
return path
if path != "mysqldump":
log_error("mysqldump path specified in configurations is invalid: %s" % path)
return None
except:
return None
if sys.platform == "darwin":
# if path is not specified, use bundled one
return mforms.App.get().get_executable_path("mysqldump")
elif sys.platform == "win32":
return mforms.App.get().get_executable_path("mysqldump.exe")
else:
# if path is not specified, use bundled one
path = mforms.App.get().get_executable_path("mysqldump")
if path:
return path
# just pick default
if any(os.path.exists(os.path.join(p,"mysqldump")) for p in os.getenv("PATH").split(os.pathsep)):
return "mysqldump"
return None
def get_mysqldump_version():
path = get_path_to_mysqldump()
if not path:
log_error("mysqldump command was not found, please install it or configure it in Edit -> Preferences -> Administration")
return None
output = io.StringIO()
rc = local_run_cmd('"%s" --version' % path, output_handler=output.write)
output = output.getvalue()
if rc or not output:
log_error("Error retrieving version from %s:\n%s (exit %s)"%(path, output, rc))
return None
regexp = r".*Ver ([\d.a-z]+).*"
if ("Distrib" in output):
regexp = r".*Distrib ([\d.a-z]+).*"
s = re.match(regexp, output)
if not s:
log_error("Could not parse version number from %s:\n%s"%(path, output))
return None
version_group = s.groups()[0]
major, minor, revision = [int(i) for i in version_group.split(".")[:3]]
return Version(major, minor, revision)
####################################################################################################
class DumpThread(threading.Thread):
class TaskData:
def __init__(self, title, table_count, extra_arguments, objec_names, tables_to_ignore, make_pipe = lambda:None):
"""description, object_count, pipe_factory, extra_args, objects
operations.append((title, len(tables), lambda schema=schema:self.dump_to_file([schema]), params, objects))"""
self.title = title
self.table_count = table_count
self.extra_arguments = extra_arguments
self.objec_names = objec_names
self.tables_to_ignore = tables_to_ignore
self.make_pipe = make_pipe
def __init__(self, command, operations, pwd, owner, log_queue):
self.owner = owner
self.pwd = pwd
self.logging_lock, self.log = log_queue
self.is_import = False
self.command = command
self.operations = operations
self.done = False
self.progress = 0
self.status_text = "Starting"
self.error_count = 0
self.process_handle = None
self.abort_requested = False
self.e = None
threading.Thread.__init__(self)
def process_db(self, respipe, extra_arguments, object_names, tables_to_ignore):
pwdfilename = None
tmpdir = None
try:
if '<' in self.command:
index = self.command.find('<')
params = [self.command[:index] + ' '.join(extra_arguments) + ' ' + self.command[index:]]
else:
params = [self.command] + extra_arguments
for arg in object_names:
params.append(local_quote_shell_token(arg))
strcmd = " ".join(params)
logstr = strcmd.partition("--password=")[0]
if platform.system() == 'Windows':
pwdfile = tempfile.NamedTemporaryFile(delete=False, suffix=".cnf")
pwdfilename = pwdfile.name
else:
tmpdir = tempfile.mkdtemp()
pwdfilename = os.path.join(tmpdir, 'extraparams.cnf')
os.mkfifo(pwdfilename)
logstr += "--defaults-file=\"" + pwdfilename + "\" "
logstr += strcmd.partition("--password=")[2]
log_debug("Executing command: %s\n" % logstr)
self.print_log_message("Running: " + logstr)
if platform.system() != 'Windows':
try:
p1 = subprocess.Popen(logstr,stdout=respipe,stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
except OSError as exc:
log_error("Error executing command %s\n%s\n" % (logstr, exc))
import traceback
traceback.print_exc()
self.print_log_message("Error executing task: %s" % exc)
pwdfile = open(pwdfilename, 'w')
else:
pwdfile = open(pwdfilename, 'w')
pwdfile.write('[client]\npassword="')
pwdfile.write(self.pwd.replace("\\", "\\\\"))
pwdfile.write('"')
# When there are tables to ignore they are added as ignored entries
# On the configuration file
if tables_to_ignore:
pwdfile.write('\n\n[mysqldump]\n')
for s, t in tables_to_ignore:
line = "ignore-table=%s.%s\n" % (s,t)
pwdfile.write(line)
pwdfile.close()
if platform.system() == 'Windows':
try:
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = subprocess.SW_HIDE
# Command line can contain object names in case of export and filename in case of import
# Object names must be in utf-8 but filename must be encoded in the filesystem encoding,
# which probably isn't utf-8 in windows.
log_debug("Executing command: %s\n" % logstr)
p1 = subprocess.Popen(logstr, stdout=respipe, stdin=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=info, shell=logstr[0] != '"', encoding='utf8')
except OSError as exc:
log_error("Error executing command %s\n%s\n" % (logstr, exc))
import traceback
traceback.print_exc()
self.print_log_message("Error executing task: %s" % exc)
p1 = None
# finally:
# pass
self.process_handle = p1
while p1 and p1.poll() == None and not self.abort_requested:
err = to_unicode(p1.stderr.read())
if err != "":
log_error("Error from task: %s\n" % err)
self.print_log_message(err)
if 'Access denied for user' in err:
self.e = wb_common.InvalidPasswordError('Wrong username/password!')
result = ""
except Exception as exc:
import traceback
traceback.print_exc()
log_error("Error executing task: %s\n" % exc)
self.print_log_message("Error executing task: %s" % exc)
finally:
pass
if pwdfilename:
os.remove(pwdfilename)
if platform.system() != 'Windows' and tmpdir:
os.rmdir(tmpdir)
err = to_unicode(p1.stderr.read())
if err != "":
result += err
exitcode = p1.poll()
if exitcode != 0:
log_warning("Task exited with code %s\n" % exitcode)
self.print_log_message("Operation failed with exitcode " + str(exitcode))
else:
log_debug("Task exited with code %s\n" % exitcode)
self.print_log_message("")
if result:
log_debug("Task output: %s\n" % result)
self.print_log_message(result)
return p1.poll()
def kill(self):
self.abort_requested = True
if self.process_handle:
if platform.system() == 'Windows':
cmd = "taskkill /F /T /PID %i" % self.process_handle.pid
log_debug("Killing task: %s\n" % cmd)
subprocess.Popen(cmd , shell=True)
else:
import signal
try:
log_debug("Sending SIGTERM to task %s\n" % self.process_handle.pid)
os.kill(self.process_handle.pid, signal.SIGTERM)
except OSError as exc:
log_error("Exception sending SIGTERM to task: %s\n" % exc)
self.print_log_message("kill task: %s" % str(exc))
def print_log_message(self,message):
if message:
self.logging_lock.acquire()
self.log.append(message)
self.logging_lock.release()
def run(self):
try:
self.progress = 0
tables_processed = 0.0
tables_total = 0.0
# for title, count, make_pipe, args, objs in self.operations:
for task in self.operations:
tables_total += task.table_count or 1
# for title, table_count, make_pipe, arguments, objects in self.operations:
for task in self.operations:
self.print_log_message(time.strftime('%X ') + to_unicode(task.title))
tables_processed += task.table_count or 1
pipe = task.make_pipe()
exitcode = self.process_db(pipe, task.extra_arguments, task.objec_names, task.tables_to_ignore)
if exitcode == 0:
if self.is_import:
self.status_text = "%i of %i imported." % (tables_processed, tables_total)
else:
self.status_text = "%i of %i exported." % (tables_processed, tables_total)
else:
self.owner.fail_callback()
self.error_count += 1
if self.abort_requested:
break
self.progress = float(tables_processed) / tables_total
# print self.progress
# Emulate slow dump
# import time
# time.sleep(1)
except Exception as exc:
import traceback
traceback.print_exc()
self.print_log_message("Error executing task %s" % exc )
# finally:
if not self.abort_requested:
self.progress = 1
self.done = True
class TableListModel(object):
def __init__(self):
self.tables_by_schema = {}
self.selected_schemas = set()
self.routines_placeholder = None
def get_full_selection(self):
result = []
for schema, (tables, selection) in list(self.tables_by_schema.items()):
for table in selection:
result.append((schema,table))
result.sort()
return result
def get_schema_names(self):
return list(self.tables_by_schema.keys())
def set_schema_selected(self, schema, flag):
tables = self.get_tables(schema)
selection = self.get_selection(schema)
if flag:
selection.update(set(tables))
self.selected_schemas.add(schema)
else:
selection.clear()
if schema in self.selected_schemas:
self.selected_schemas.remove(schema)
def set_tables_by_schema(self, tables_by_schema):
self.tables_by_schema = tables_by_schema
def set_routines_placeholder(self, placeholder):
self.routines_placeholder = placeholder
def get_tables(self, schema):
tables, selection = self.tables_by_schema[schema]
return tables
def is_view(self, schema, table):
return False
def list_icon_for_table(self, schema, table):
if (schema, table) == self.routines_placeholder:
return "db.RoutineGroup.16x16.png"
else:
return "db.Table.16x16.png"
def get_selection(self, schema):
selection = []
if schema in list(self.tables_by_schema.keys()):
tables, selection = self.tables_by_schema[schema]
return selection
def count_selected_tables(self):
count = 0
for tlist, selected in list(self.tables_by_schema.values()):
count += len(selected)
return count
def get_count(self):
return sum([len(item[1]) for item in list(self.tables_by_schema.values())])
def get_objects_to_dump(self, include_empty_schemas=False):
schemas_to_dump = []
#names = self.tables_by_schema.keys()
#names.sort()
schemas_with_selection = set([key for key, value in list(self.tables_by_schema.items()) if value[1]])
for schema in self.selected_schemas | schemas_with_selection:
#No tables selected for schema so skip it
if schema not in self.tables_by_schema.keys():
continue
tables, selection = self.tables_by_schema[schema]
if not selection and not include_empty_schemas:
continue
schemas_to_dump.append((schema, list(selection)))
return schemas_to_dump
def get_tables_to_ignore(self):
ignore_list = []
names = list(self.tables_by_schema.keys())
names.sort()
for schema in names:
#No tables selected for schema so skip it
if schema not in self.tables_by_schema.keys():
continue
tables, selection = self.tables_by_schema[schema]
if not selection or len(selection) == len(tables):
continue
for t in tables:
if not t in selection:
ignore_list.append((schema, t))
return ignore_list
####################################################################################################
class WbAdminSchemaListTab(mforms.Box):
def __init__(self, owner, server_profile, progress_tab, is_importing = False):
super(WbAdminSchemaListTab, self).__init__(False)
self.skip_data_check = False
self.suspend_layout()
progress_tab.operation_tab = self
self.owner = owner
self.progress_tab = progress_tab
self.is_importing = is_importing
self.dump_thread = None
self.bad_password_detected = False
self.server_profile = server_profile
self.out_pipe = None
self.schema_list = newTreeView(mforms.TreeFlatList)
self.schema_list.set_name("Schema List")
self.schema_list.set_min_size(-1, 150)
self.schema_list.add_column(mforms.CheckColumnType, is_importing and "Import" or "Export", 40, True)
self.schema_list.add_column(mforms.IconColumnType, "Schema", 300, False)
self.schema_list.set_cell_edited_callback(self.schema_list_edit)
self.schema_list.end_columns()
self.schema_list.set_allow_sorting(True)
self.table_list = newTreeView(mforms.TreeFlatList)
self.table_list.set_name("Table List")
self.table_list.set_min_size(-1, 150)
self.table_list.add_column(mforms.CheckColumnType, is_importing and "Import" or "Export", 40, True)
self.table_list.add_column(mforms.IconColumnType, "Schema Objects", 300, False)
self.table_list.end_columns()
self.table_list.set_allow_sorting(True)
self.table_list.set_cell_edited_callback(self.table_list_edit)
self.schema_list.add_changed_callback(self.schema_selected)
self.set_padding(8)
self.set_spacing(10)
box = newBox(True)
box.set_spacing(12)
optionspanel = newPanel(mforms.TitledBoxPanel)
if is_importing:
self.export_objects_panel = None
optionspanel.set_title("Import Options")
optionspanel.set_name("Import Options")
else:
self.export_objects_panel = newPanel(mforms.TitledBoxPanel)
self.export_objects_panel.set_title("Objects to Export")
self.export_objects_panel.set_name("Objects To Export")
optionspanel.set_title("Export Options")
optionspanel.set_name("Export Options")
optionsbox = newBox(False)
optionsbox.set_padding(8)
optionsbox.set_spacing(6)
self.file_btn = newButton()
self.file_btn.set_text("...")
self.file_btn.set_name("File Path Browse")
self.file_btn.enable_internal_padding(False)
self.file_btn.set_enabled(False)
self._radio_group = mforms.RadioButton.new_id()
if is_importing:
self.folderlabel = newLabel("Select the Dump Project Folder to import. You can do a selective restore.")
self.folderradio = newRadioButton(self._radio_group)
self.folderradio.set_name("Import From Dump Project Folder")
self.statlabel = newLabel("Press [Start Import] to start...")
self.statlabel.set_name("Press [Start Import] To Start")
self.statlabel.set_name("Press Start Info")
self.filelabel = newLabel("Select the SQL/dump file to import. Please note that the whole file will be imported.")
self.filelabel.set_name("Select File to Import Info")
self.single_transaction_check = None
self.include_schema_check = None
self.dump_triggers_check = None
#self.dump_view_check = None
self.dump_routines_check = None
self.dump_events_check = None
else:
self.filelabel = newLabel("All selected database objects will be exported into a single, self-contained file.")
self.folderlabel = newLabel("Each table will be exported into a separate file. This allows a selective restore, but may be slower.")
self.folderradio = newRadioButton(self._radio_group)
self.folderradio.set_name("Export To Dump Project Folder")
self.statlabel = newLabel("Press [Start Export] to start...")
self.statlabel.set_name("Press [Start Export] To Start")
self.single_transaction_check = newCheckBox()
self.single_transaction_check.set_name("Create Dump In A Single Transaction")
self.include_schema_check = newCheckBox()
self.include_schema_check.set_name("Include Create Schema")
self.dump_triggers_check = newCheckBox()
self.dump_triggers_check.set_name("Dump Triggers")
#self.dump_view_check = newCheckBox()
self.dump_routines_check = newCheckBox()
self.dump_routines_check.set_name("Dump Stored Procedures and Functions")
self.dump_events_check = newCheckBox()
self.dump_events_check.set_name("Dump Events")
self.filelabel.set_enabled(False)
self.filelabel.set_style(mforms.SmallStyle)
if is_importing:
self.fileradio = newRadioButton(self._radio_group)
self.fileradio.set_text("Import from Self-Contained File")
self.fileradio.set_name("Import From Self-Contained File")
else:
self.fileradio = newRadioButton(self._radio_group)
self.fileradio.set_text("Export to Self-Contained File")
self.fileradio.set_name("Export To Self-Contained File")
self.fileradio.set_size(260,-1)
self.fileradio.add_clicked_callback(self.set_save_option)
file_path = newBox(True)
file_path.set_spacing(4)
self.file_te = newTextEntry()
self.file_te.set_name("File Path Entry")
file_path.add(self.fileradio,False,True)
file_path.add(self.file_te, True, True)
file_path.add(self.file_btn, False, True)
self.folderradio.add_clicked_callback(self.set_save_option)
self.folderradio.set_active(True)
self.folderradio.set_size(260,-1)
self.folderlabel.set_style(mforms.SmallStyle)
folder_path = newBox(True)
folder_path.set_spacing(4)
self.folder_te = newTextEntry()
self.folder_te.set_name("Folder Path Entry")
self.folder_btn = newButton()
self.folder_btn.set_text("...")
self.folder_btn.set_name("Folder Path Browse")
self.folder_btn.enable_internal_padding(False)
self.folder_btn.add_clicked_callback(self.open_folder_chooser)
folder_path.add(self.folderradio, False,True)
folder_path.add(self.folder_te, True, True)
folder_path.add(self.folder_btn, False, True)
optionsbox.add(folder_path, False, True)
optionsbox.add(self.folderlabel, False, True)
if is_importing:
self.folder_load_btn = newButton()
self.folder_load_btn.set_text("Load Folder Contents")
self.folder_load_btn.set_name("Load Folder Contents")
self.folder_load_btn.add_clicked_callback(self.refresh_table_list)
tbox = newBox(True)
tbox.add(self.folder_load_btn, False, True)
optionsbox.add(tbox, False, True)
optionsbox.add(file_path, False, True)
optionsbox.add(self.filelabel, False, True)
if self.single_transaction_check or self.dump_routines_check:
export_objects_opts = mforms.newTable()
export_objects_opts.set_homogeneous(True)
export_objects_opts.set_padding(4)
export_objects_opts.set_row_count(1)
export_objects_opts.set_column_count(3)
export_objects_opts.set_row_spacing(2)
export_objects_opts.set_column_spacing(2)
self.export_objects_panel.add(export_objects_opts)
export_options = mforms.newTable()
export_options.set_name("Export Options")
export_options.set_homogeneous(True)
export_options.set_padding(4)
export_options.set_row_count(1)
export_options.set_column_count(2)
export_options.set_row_spacing(2)
export_options.set_column_spacing(2)
optionsbox.add(export_options, False, True)
if self.single_transaction_check:
export_options.add(self.single_transaction_check,0,1,0,1)
if self.include_schema_check:
export_options.add(self.include_schema_check,1,2,0,1)
if self.dump_routines_check:
export_objects_opts.add(self.dump_routines_check,0,1,0,1)
if self.dump_events_check:
export_objects_opts.add(self.dump_events_check,1,2,0,1)
if self.dump_triggers_check:
export_objects_opts.add(self.dump_triggers_check,2,3,0,1)
self.file_te.set_enabled(False)
optionspanel.add(optionsbox)
selectionpanel = newPanel(mforms.TitledBoxPanel)
if is_importing:
selectionpanel.set_title("Select Database Objects to Import (only available for Project Folders)")
selectionpanel.set_name("Database Objects To Import")
else:
selectionpanel.set_title("Tables to Export")
selectionpanel.set_name("Tables To Export")
selectionvbox = newBox(False)
selectionvbox.set_padding(8)
selectionvbox.set_spacing(8)
selectionbox = newBox(True)
selectionvbox.add(selectionbox, True, True)
selectionbox.set_spacing(12)
selectionbox.add(self.schema_list, True, True)
selectionbox.add(self.table_list, True, True)
selectionbbox = newBox(True)
selectionbbox.set_spacing(8)
if not is_importing:
self.refresh_button = newButton()
self.refresh_button.set_text("Refresh")
self.refresh_button.set_name("Refresh")
selectionbbox.add(self.refresh_button, False, True)
self.refresh_button.add_clicked_callback(self.refresh_table_list)
self.select_summary_label = newLabel("")
selectionbbox.add(self.select_summary_label, True, True)
self.select_all_views_btn = newButton()
self.select_all_views_btn.set_text("Select Views")
self.select_all_views_btn.set_name("Select Views")
self.select_all_views_btn.add_clicked_callback(self.select_all_views)
self.select_all_views_btn.set_enabled(False)
self.select_all_btn = newButton()
self.select_all_btn.set_text("Select Tables")
self.select_all_btn.set_name("Select Tables")
self.select_all_btn.add_clicked_callback(self.select_all_tables)
self.select_all_btn.set_enabled(False)
self.unselect_all_btn = newButton()
self.unselect_all_btn.set_text("Unselect All")
self.unselect_all_btn.set_name("Unselect All")
self.unselect_all_btn.add_clicked_callback(self.unselect_all_tables)
self.unselect_all_btn.set_enabled(False)
self.dump_type_selector = newSelector()
self.dump_type_selector.add_items(["Dump Structure and Data", "Dump Data Only", "Dump Structure Only"]);
self.dump_type_selector.set_name("Dump Selector")
selectionbbox.add_end(self.unselect_all_btn, False, True)
selectionbbox.add_end(self.select_all_btn, False, True)
selectionbbox.add_end(self.select_all_views_btn, False, True)
selectionbbox.add_end(self.dump_type_selector, False, True)
selectionvbox.add(selectionbbox, False, True)
selectionpanel.add(selectionvbox)
if is_importing:
self.add(optionspanel, False, True)
self.import_target_schema_panel = targetpanel = newPanel(mforms.TitledBoxPanel)
targetpanel.set_title("Default Schema to be Imported To")
targetpanel.set_name("Default Schema To Be Imported To")
hbox = newBox(True)
hbox.set_spacing(8)
hbox.add(newLabel("Default Target Schema:"), False, True)
self.import_target_schema = newSelector()
self.import_target_schema.set_name("Target Schema Selector")
hbox.add(self.import_target_schema, True, True)
b = newButton()
b.set_name("New")
b.set_text("New...")
b.add_clicked_callback(self.new_target_schema)
hbox.add(b, False, True)
help = newLabel("The default schema to import the dump into.\nNOTE: this is only used if the dump file doesn't contain its schema,\notherwise it is ignored.")
help.set_style(mforms.SmallHelpTextStyle)
hbox.add(help, False, True)
hbox.set_padding(12)
targetpanel.add(hbox)
self.add(targetpanel, False, True)
self.add(selectionpanel, True, True)
if not is_importing:
self.add(self.export_objects_panel, False, True)
self.add(optionspanel, False, True)
box = newBox(True)
self.add(box, False, True)
box.add(self.statlabel, True, True)
box.set_spacing(8)
box.set_padding(0)
self.export_button = newButton()
if is_importing:
self.export_button.set_enabled(False)
box.add_end(self.export_button, False, True)
self.export_button.add_clicked_callback(self.start)
if is_importing:
self.file_btn.add_clicked_callback(lambda: self.open_file_chooser(mforms.OpenFile))
self.folderradio.set_text("Import from Dump Project Folder")
self.export_button.set_text("Start Import")
self.export_button.set_name("Start Import")
else:
self.file_btn.add_clicked_callback(lambda: self.open_file_chooser(mforms.SaveFile))
self.single_transaction_check.set_text("Create Dump in a Single Transaction (self-contained file only)")
self.single_transaction_check.set_enabled(False)
self.single_transaction_check.add_clicked_callback(self.single_transaction_clicked)
self.include_schema_check.set_text("Include Create Schema")
self.dump_triggers_check.set_text("Dump Triggers")
#self.dump_view_check.set_text("Dump Views")
self.dump_routines_check.set_text("Dump Stored Procedures and Functions")
self.dump_events_check.set_text("Dump Events")
self.folderradio.set_text("Export to Dump Project Folder")
self.export_button.set_text("Start Export")
self.export_button.set_name("Start Export")
self.resume_layout()
def print_log_message(self, msg):
self.progress_tab.print_log_message(msg)
def get_default_dump_folder(self):
try:
path = grt.root.wb.options.options["dumpdirectory"] or os.path.join("~", "dumps")
except:
path = os.path.join("~", "dumps")
path = os.path.expanduser(path)
return os.path.normpath(path)
def schema_list_edit(self, node, col, data):
if col == 0:
node.set_bool(0, int(data) != 0)
schema = node.get_string(1)
self.table_list_model.set_schema_selected(schema, int(data))
self.schema_selected()
def table_list_edit(self, node, col, data):
node.set_bool(col, int(data) != 0)
self.update_table_selection()
if int(data):
# select the schema if its not
sel = self.schema_list.get_selected_node()
if sel and not sel.get_bool(0):
sel.set_bool(col, True)
self.schema_selected()
def update_table_selection(self):
if not self.get_selected_schema():
return
schema = self.get_selected_schema()
selection = self.table_list_model.get_selection(schema)
for r in range(self.table_list.count()):
node = self.table_list.node_at_row(r)
table_name = node.get_tag()
if node.get_bool(0):
selection.add(table_name)
else:
selection.discard(table_name)
self.select_summary_label.set_text("%i tables/views selected" % self.table_list_model.count_selected_tables())
def get_selected_schema(self):
sel = self.schema_list.get_selected_node()
if not sel:
return None
return sel.get_string(1)
def schema_selected(self):
sel = self.schema_list.get_selected_node()
self.table_list.freeze_refresh()
self.table_list.clear()
if not sel:
self.unselect_all_btn.set_enabled(False)
self.select_all_btn.set_enabled(False)
self.select_all_views_btn.set_enabled(False)
self.table_list.thaw_refresh()
return
schema = self.get_selected_schema()
tables = self.table_list_model.get_tables(schema)
selection = self.table_list_model.get_selection(schema)
for table in tables:
r = self.table_list.add_node()
r.set_bool(0, table in selection)
r.set_icon_path(1, self.table_list_model.list_icon_for_table(schema, table))
r.set_string(1, table)
r.set_tag(table)
self.table_list.thaw_refresh()
self.unselect_all_btn.set_enabled(True)
self.select_all_btn.set_enabled(True)
self.select_all_views_btn.set_enabled(True)
self.select_summary_label.set_text("%i tables selected" % self.table_list_model.count_selected_tables())
def select_all_views(self):
sel = self.schema_list.get_selected_node()
if not sel:
return
sel.set_bool(0, True)
schema = self.get_selected_schema()
for row in range(self.table_list.count()):
node = self.table_list.node_at_row(row)
table = node.get_string(1)
node.set_bool(0, self.table_list_model.is_view(schema, table))
self.update_table_selection()
def select_all_tables(self, exclude_views=True):
sel = self.schema_list.get_selected_node()
if not sel:
return
sel.set_bool(0, True)
schema = self.get_selected_schema()
for row in range(self.table_list.count()):
node = self.table_list.node_at_row(row)
table = node.get_string(1)
node.set_bool(0, not self.table_list_model.is_view(schema, table))
self.update_table_selection()
def unselect_all_tables(self):
for row in range(self.table_list.count()):
self.table_list.node_at_row(row).set_bool(0, False)
self.update_table_selection()
def set_save_option(self):
folder_selected = self.folderradio.get_active()
self.folder_te.set_enabled(folder_selected)
self.folder_btn.set_enabled(folder_selected)
self.folderlabel.set_enabled(folder_selected)
self.file_te.set_enabled(not folder_selected)
self.file_btn.set_enabled(not folder_selected)
self.filelabel.set_enabled(not folder_selected)
if self.is_importing:
if folder_selected:
count = self.table_list_model.get_count()
self.progress_tab.set_start_enabled(count > 0)
self.import_target_schema_panel.set_enabled(False)
else:
self.progress_tab.set_start_enabled(True)
self.import_target_schema_panel.set_enabled(True)
self.refresh_schema_list()
self.schema_list.set_enabled(folder_selected)
self.table_list.set_enabled(folder_selected)
else:
if folder_selected:
self.single_transaction_check.set_active(False)
self.single_transaction_check.set_enabled(False)
else:
self.single_transaction_check.set_enabled(True)
def refresh(self):
pass
def update_progress(self):
completed = True
progress = 0
progress_info = ""
if self.dump_thread != None:
if not self.dump_thread.done:
completed = False
progress = self.dump_thread.progress
self.progress_tab.flush_queued_logs()
progress_info = self.dump_thread.status_text
if isinstance(self.dump_thread.e, wb_common.InvalidPasswordError):
self.dump_thread = None
self.bad_password_detected = True
self.start()
return False
self.progress_tab.set_progress(progress, progress_info)
# Python 2.6 needed
# completed = self.dump_thread.is_alive()
if completed:
# print progress
self.close_pipe()
if self.dump_thread.abort_requested:
self.tasks_aborted()
else:
self.tasks_completed()
self.dump_thread = None
return not completed
def open_folder_chooser(self):
filechooser = FileChooser(mforms.OpenDirectory)
filechooser.set_directory(self.folder_te.get_string_value())
if filechooser.run_modal():
self.folder_te.set_value(filechooser.get_path())
if self.is_importing:
self.refresh_table_list()
def open_file_chooser(self, chooser_type=mforms.SaveFile):
filechooser = FileChooser(chooser_type)
filechooser.set_directory(os.path.dirname(self.file_te.get_string_value()))
filechooser.set_extensions("SQL Files (*.sql)|*.sql","sql");
if filechooser.run_modal():
filepath = filechooser.get_path()
self.file_te.set_value(filepath)
if self.is_importing:
self.refresh_table_list()
else:
if len(os.path.splitext(filepath)[1][1:]) == 0:
self.file_te.set_value("%s.sql" % filepath)
def get_mysql_password(self, reset_password=False):
parameterValues = self.server_profile.db_connection_params.parameterValues
username = parameterValues["userName"]
host = self.server_profile.db_connection_params.hostIdentifier
title = self.is_importing and "Import" or "Export"
if self.bad_password_detected:
title += ' (type the correct password)'
self.bad_password_detected = False
if not reset_password and not self.bad_password_detected:
pwd = self.owner.ctrl_be.get_mysql_password()
if pwd is None:
accepted, pwd = mforms.Utilities.find_or_ask_for_password(title, host, username, reset_password)
if not accepted:
return None
return pwd
def stop(self):
if self.dump_thread:
self.dump_thread.kill()
def failed(self, message):
self.progress_tab.did_fail(message)
def cancelled(self, message):
self.progress_tab.did_cancel(message)
####################################################################################################
## Import
####################################################################################################
class WbAdminImportTab(WbAdminSchemaListTab):
def __init__(self, owner, server_profile, progress_tab):
WbAdminSchemaListTab.__init__(self, owner, server_profile, progress_tab, True)
self.table_list_model = TableListModel()
self.export_button.set_text("Start Import")
self.tables_paths = {}
self.views_paths = {}
self._update_schema_list_tm = None
self._update_progress_tm = None
self.folder_te.set_value(self.get_default_dump_folder())
self.file_te.set_value(os.path.join(self.get_default_dump_folder(), "export.sql"))
def new_target_schema(self):
ret, name = Utilities.request_input("Create Schema", "Name of schema to create:", "newschema")
if ret:
if not self.owner.ctrl_be.is_sql_connected():
Utilities.show_error("Create Schema", "Cannot create schema because there is no connection to the DB server.", "OK", "", "")
return
name = name.replace("`", "``")
try:
self.print_log_message("Creating schema %s\n" % name)
self.owner.ctrl_be.exec_sql("CREATE DATABASE `%s`" % name, auto_reconnect=False)
except QueryError as err:
self.print_log_message("Error creating schema %s: %s\n" % (name, err))
Utilities.show_error("Create Schema", str(err), "OK", "", "")
if err.is_connection_error():
self.owner.ctrl_be.handle_sql_disconnection(err)
return
self.refresh_schema_list()
self.import_target_schema_selection = name
def _refresh_schema_list_thread(self):
self.schema_names = []
try:
result = self.owner.ctrl_be.exec_query("SHOW DATABASES", auto_reconnect=False)
while result.nextRow():
value = result.unicodeByName("Database")
if value == "information_schema":
continue
self.schema_names.append(value)
del result
self.schema_refresh_done = True
except QueryError as exc:
self.print_log_message("Error fetching schema list: %s" % str(exc))
if exc.is_connection_error():
if self.owner.ctrl_be.handle_sql_disconnection(exc):
self.schema_refresh_cancelled = "Error fetching schema list:\n%s\nReconnected successfully." % str(exc)
else:
self.schema_refresh_cancelled = "Error fetching schema list:\n%s\nCould not reconnect." % str(exc)
else:
self.schema_refresh_cancelled = "Error fetching schema list:\n%s" % str(exc)
except Exception as exc:
self.print_log_message("Error fetching schema list: %s" % str(exc) )
self.schema_refresh_cancelled = "Error fetching schema list:\n%s" % str(exc)
def refresh_schema_list(self):
if not self.owner.ctrl_be.is_sql_connected():
return
class SchemaRefreshThread(threading.Thread):
def __init__(self, owner):
self.owner = owner
threading.Thread.__init__(self)
def run(self):
self.owner._refresh_schema_list_thread()
self.schema_refresh_thread = SchemaRefreshThread(self)
self.schema_refresh_thread.start()
self.schema_refresh_done = False
self.schema_refresh_cancelled = None
self.import_target_schema_selection = self.import_target_schema.get_string_value()
self._update_schema_list_tm = Utilities.add_timeout(float(0.4), self._update_schema_list)
def _update_schema_list(self):
if self.schema_refresh_cancelled:
Utilities.show_error("Refresh Schema List", self.schema_refresh_cancelled, "OK", "", "")
self.schema_refresh_cancelled = None
self._update_schema_list_tm = None
return False
if not self.schema_refresh_done:
return True
self.import_target_schema.clear()
self.import_target_schema.add_items([""]+self.schema_names)
if self.import_target_schema_selection:
self.import_target_schema.set_value(self.import_target_schema_selection)
self._update_schema_list_tm = None
return False
def close(self):
if self._update_schema_list_tm:
Utilities.cancel_timeout(self._update_schema_list_tm)
if self._update_progress_tm:
Utilities.cancel_timeout(self._update_progress_tm)
def refresh_table_list(self):
def parse_name_from_single_table_dump(path):
import codecs
f = codecs.open(path, encoding="utf-8")
schema = None
table = None
is_view = False
has_default_schema = False
for line in f:
if line.startswith("-- Host:"):
schema = line.partition("Database: ")[-1].strip()
if table:
break
elif line.startswith("USE "):
has_default_schema = True
elif not table and line.startswith("-- Table structure for table"):
table = line.partition("-- Table structure for table")[-1].strip()
if table[0] == '`':
table = table[1:-1]
if schema:
break
elif not table and line.startswith("-- Dumping data for table"):
table = line.partition("-- Dumping data for table")[-1].strip()
if table[0] == '`':
table = table[1:-1]
if schema:
break
elif line.startswith( ("/*!50001 VIEW", "/*!50003 CREATE*/ /*!50020 DEFINER=","/*!50106 CREATE*/ /*!50117 DEFINER=") ):
is_view = True
table = "Views, routines, events etc"
if schema:
break
return schema, table, is_view, has_default_schema
self.folder_load_btn.set_enabled(False)
tables_by_schema = {}
# (schema, table) -> path
self.tables_paths = {}
self.views_paths = {}
self.needs_default_schema = {}
self.schema_list.freeze_refresh()
self.schema_list.clear()
try:
save_to_folder = not self.fileradio.get_active()
if save_to_folder:
self.progress_tab.set_start_enabled(False)
path = self.folder_te.get_string_value()
dirList=os.listdir(path)
for fname in dirList:
fullname = os.path.join(path, fname)
if os.path.isfile(fullname) and os.path.splitext(fullname)[1] == ".sql":
# open the backup file and look for schema and table name in it
schema, table, is_view, has_default_schema = parse_name_from_single_table_dump(fullname)
if not schema or not table:
self.progress_tab.print_log_message("%s does not contain schema/table information" % fullname)
continue
if schema in tables_by_schema:
tables, selection = tables_by_schema[schema]
if table in tables:
self.schema_list.thaw_refresh()
message = "The selected folder doesn't appear to be valid. Multiple definitions of the same object (%s.%s) have been found." % (schema, table)
Utilities.show_error("Open Dump Folder", message, "OK", "", "")
return
tables.append(table)
tables.sort()
selection.add(table) # select all by default
else:
tables_by_schema[schema] = ([table], set([table]))
if is_view:
self.views_paths[(schema, table)] = fullname
self.table_list_model.set_routines_placeholder((schema, table))
else:
self.tables_paths[(schema, table)] = fullname
self.needs_default_schema[(schema, table)] = not has_default_schema
if not tables_by_schema:
Utilities.show_message("Open Dump Folder", "There were no dump files in the selected folder.", "OK", "", "")
else:
names = list(tables_by_schema.keys())
names.sort()
for schema in names:
row = self.schema_list.add_node()
row.set_bool(0, True)
row.set_icon_path(1, "db.Schema.16x16.png")
row.set_string(1, schema)
self.progress_tab.set_start_enabled(True)
self.schema_list.thaw_refresh()
except Exception as exc:
import traceback
self.schema_list.thaw_refresh()
traceback.print_exc()
Utilities.show_error("Error Opening Dump", str(exc), "OK", "", "")
self.export_button.set_enabled(False)
self.failed(str(exc))
self.table_list_model.set_tables_by_schema(tables_by_schema)
self.folder_load_btn.set_enabled(True)
for schema in list(tables_by_schema.keys()):
self.table_list_model.set_schema_selected(schema, True)
def get_path_to_mysql(self):
# get path to mysql client from options
try:
path = grt.root.wb.options.options["mysqlclient"]
if path:
if os.path.exists(path):
return path
if any(os.path.exists(os.path.join(p,path)) for p in os.getenv("PATH").split(os.pathsep)):
return path
if path != "mysql":
return None
except:
return None
if sys.platform.lower() == "darwin":
# if path is not specified, use bundled one
return mforms.App.get().get_executable_path("mysql")
elif sys.platform.lower() == "win32":
return mforms.App.get().get_executable_path("mysql.exe")
else:
# if path is not specified, use bundled one
path = mforms.App.get().get_executable_path("mysql")
if path:
return path
# just pick default
if any(os.path.exists(os.path.join(p,"mysql")) for p in os.getenv("PATH").split(os.pathsep)):
return "mysql"
return None
def start(self):
self.progress_tab.set_start_enabled(False)
self.progress_tab.did_start()
self.progress_tab.set_status("Import is running...")
connection_params = self.server_profile.db_connection_params
tunnel = ConnectionTunnel(connection_params)
conn = connection_params.parameterValues
from_folder = not self.fileradio.get_active()
operations = []
extra_args = ""
if from_folder:
self.path = self.folder_te.get_string_value()
else:
self.path = self.file_te.get_string_value()
#self.path = self.folder_te.get_string_value() if from_folder else self.file_te.get_string_value()
if from_folder:
selection = self.table_list_model.get_full_selection()
# make sure routines, views and stuffs get imported last
if self.table_list_model.routines_placeholder in selection:
selection.remove(self.table_list_model.routines_placeholder)
selection.append(self.table_list_model.routines_placeholder)
for schema, table in selection:
logmsg = "Restoring %s (%s)" % (schema, table)
path = self.tables_paths.get((schema, table))
if self.needs_default_schema[(schema, table)]:
extra_args = ["--database=%s" % schema]
# description, object_count, extra_args, objects, pipe_factory
if path != None:
task = DumpThread.TaskData(logmsg, 1, extra_args, [path], None, lambda:None)
#operations.insert(0,task)
operations.append(task)
else:
path = self.views_paths.get((schema, table))
task = DumpThread.TaskData(logmsg, 1, extra_args, [path], None, lambda:None)
if path != None:
operations.append(task)
else:
if not os.path.exists(self.path):
Utilities.show_message("Dump file not found", "File %s doesn't exist" % self.path, "OK", "", "")
self.failed("Dump file not found: File %s doesn't exist" % self.path)
return
logmsg = "Restoring " + self.path
# description, object_count, pipe_factory, extra_args, objects
extra_args = []
task = DumpThread.TaskData(logmsg, 1, extra_args, [self.path], None, lambda:None)
operations.append(task)
# operations.append((logmsg, 1, lambda:None, [], [self.path]))
if connection_params.driver.name == "MysqlNativeSocket":
host_option = "--protocol="+("pipe" if sys.platform == "win32" else "socket")
if conn["socket"]:
port_option = "--socket=" + [conn["socket"]][0]
else:
port_option = ""
else:
if tunnel.port or conn["port"]:
port_option = "--port=" + str(tunnel.port or conn["port"])
else:
port_option = ""
if (tunnel.port and ["localhost"] or [conn["hostName"]])[0]:
host_option = "--host=" + (tunnel.port and ["localhost"] or [conn["hostName"]])[0]
else:
host_option = ""
params = [
"--password="
]
if conn.get("useSSL", 0):
if conn.get("sslCert", ""):
params.append("--ssl-cert=%s" % conn["sslCert"])
if conn.get("sslCA", ""):
params.append("--ssl-ca=%s" % conn["sslCA"])
if conn.get("sslKey", ""):
params.append("--ssl-key=%s" % conn["sslKey"])
if conn.get("sslCipher", ""):
params.append("--ssl-cipher=%s" % conn["sslCipher"])
params += [
host_option,
"--user=" + conn["userName"],
port_option,
"--default-character-set=utf8",
"--comments",
"<" # the rest of the params will be redirected from stdin
]
if not from_folder:
target_db = self.import_target_schema.get_string_value()
if target_db:
params.insert(-1, "--database=%s" % target_db)
params = [item for item in params if item]
if connection_params.driver.name != "MysqlNativeSocket":
params.insert(1, "--protocol=tcp")
if conn.get("OPT_ENABLE_CLEARTEXT_PLUGIN", ""):
params.insert(1, "--enable-cleartext-plugin")
cmd = self.get_path_to_mysql()
if cmd == None:
self.failed("mysql command was not found, please install it or configure it in Preferences -> Administrator")
return
# if cmd[0] != '"':
# cmd = '"' + cmd + '"'
#cmd += " " + (" ".join(params))
cmd = subprocess.list2cmdline([cmd] + params)
password = self.get_mysql_password(self.bad_password_detected)
if password is None:
self.cancelled("Password Input Cancelled")
return
self.dump_thread = DumpThread(cmd, operations, password, self, (self.progress_tab.logging_lock, self.progress_tab.log_queue))
self.dump_thread.is_import = True
self.dump_thread.start()
self._update_progress_tm = Utilities.add_timeout(float(0.4), self._update_progress)
def _update_progress(self):
r = self.update_progress()
if not r:
self._update_progress_tm = None
return r
def fail_callback(self):
pass
def close_pipe(self):
pass
def tasks_aborted(self):
self.cancelled(time.strftime('%X ') + "Aborted by User")
self.progress_tab.print_log_message("Restored database(s) maybe in an inconsistent state")
def tasks_completed(self):
logmsg = time.strftime('%X ') + "Import of %s has finished" % self.path
if self.dump_thread.error_count > 0:
self.progress_tab.set_status("Import Completed With %i Errors" % self.dump_thread.error_count)
logmsg += " with %i errors" % self.dump_thread.error_count
else:
self.progress_tab.set_status("Import Completed")
self.progress_tab.print_log_message(logmsg)
self.progress_tab.did_complete()
####################################################################################################
## Export
####################################################################################################
class WbAdminExportTab(WbAdminSchemaListTab):
class ExportTableListModel(TableListModel):
def __init__(self):
TableListModel.__init__(self)
self.views_by_schema = {}
self.schemasqls = {}
self.schemas_to_load = {}
self.schemas = []
self.load_schema_data = lambda:None
def reset(self):
self.tables_by_schema = {}
self.views_by_schema = {}
self.schemasqls = {}
def get_schema_names(self):
return self.schemas
def set_schema_data(self, schema, schematables_and_views, viewlist, dbsql):
self.tables_by_schema[schema] = schematables_and_views, set()
self.views_by_schema[schema] = set(viewlist)
self.schemasqls[schema] = dbsql
def get_schema_sql(self, schema):
return self.schemasqls[schema]
def is_view(self, schema, table):
return table in self.views_by_schema[schema]
def list_icon_for_table(self, schema, table):
return "db.View.16x16.png" if self.is_view(schema, table) else "db.Table.16x16.png"
def set_schema_list(self,schemas_to_load):
self.schemas = schemas_to_load
self.schemas_to_load = schemas_to_load
def get_tables(self, schema):
if schema in self.schemas_to_load:
# print "Loading: ",schema,self.load_schema_data(schema)
schema,schematables,viewlist,dbsql = self.load_schema_data(schema)
self.set_schema_data(schema,schematables,viewlist,dbsql)
self.schemas_to_load.remove(schema)
tables = []
if schema in list(self.tables_by_schema.keys()):
tables, selection = self.tables_by_schema[schema]
return tables
def validate_single_transaction(self, schemas_to_dump):
return True
class TableRefreshThread(threading.Thread):
def __init__(self, owner):
self.owner = owner
threading.Thread.__init__(self)
def run(self):
self.owner.refresh_table_list_thread()
def __init__(self, owner, server_profile, progress_tab):
self.table_list_model = self.ExportTableListModel()
WbAdminSchemaListTab.__init__(self, owner, server_profile, progress_tab, False)
self.schemasqls = {}
self._update_refresh_tm = None
self._update_progress_tm = None
defaultPath = os.path.join(self.get_default_dump_folder(), time.strftime("Dump%Y%m%d"))
# updates self.savefolder_path
self.folder_te.set_value(self.find_available_path(defaultPath))
# updates self.savefile_path
self.file_te.set_value(self.find_available_path(defaultPath + '.sql'))
self.table_list_model.load_schema_data = self.load_schema_tables
# self.filefolder_label.set_text("Save to")
# run mysqldump --help to get default option values
self.mysqldump_defaults = self.check_mysqldump_defaults()
self.ignore_internal_log_tables = True
self.internal_log_tables = ["apply_status", "general_log", "slow_log", "schema"]
self._compatibility_params = False
self.show_internal_schemas = False
def close(self):
if self._update_refresh_tm:
Utilities.cancel_timeout(self._update_refresh_tm)
self._update_refresh_tm = None
if self._update_progress_tm:
Utilities.cancel_timeout(self._update_progress_tm)
self._update_progress_tm = None
def load_schema_tables(self, schema):
schematables_and_views = []
viewlist = []
dbsql = ""
try:
self.refresh_state= "Retrieving tables data for schema " + schema
dbcreate = self.owner.ctrl_be.exec_query("SHOW CREATE DATABASE `"+escape_sql_identifier(schema)+"`")
tableset = self.owner.ctrl_be.exec_query("SHOW FULL TABLES FROM `"+escape_sql_identifier(schema)+"`")
dbcreate.nextRow()
dbsql = dbcreate.unicodeByName("Create Database")
parts = dbsql.partition("CREATE DATABASE ")
dbsql = "%s%s IF NOT EXISTS %s;\nUSE `%s`;\n" % (parts[0], parts[1], parts[2], escape_sql_identifier(schema))
while tableset.nextRow():
tabletype = tableset.unicodeByName("Table_type")
tablename = tableset.unicodeByName("Tables_in_"+schema)
if self.ignore_internal_log_tables and schema == "mysql" and tablename in self.internal_log_tables:
continue
if tabletype == "VIEW":
viewlist.append(tablename)
schematables_and_views.append(tablename)
del tableset
except Exception as exc:
import traceback
traceback.print_exc()
print("Error retrieving table list form schema '",schema,"'")
self.progress_tab.print_log_message("Error Fetching Table List From %s (%s)" % (schema, str(exc)) )
return schema,schematables_and_views,viewlist,dbsql
def refresh_table_list_thread(self):
self.table_list_model.reset()
try:
result = self.owner.ctrl_be.exec_query("SHOW DATABASES")
schema_names = []
while result.nextRow():
value = result.unicodeByName("Database")
if not self.show_internal_schemas and value in ["information_schema", "performance_schema", "mysql"]:
continue
schema_names.append(value)
del result
self.table_list_model.set_schema_list(schema_names)
schema_cntr = 1
if schema_names:
self.refresh_progress = float(schema_cntr) / len(schema_names)
for schema in schema_names:
# schematables,viewlist,dbsql = self.load_schema_tables(schema)
# finally:
schema_cntr += 1
self.refresh_progress = float(schema_cntr) / len(schema_names)
# self.table_list_model.set_schema_data(schema,(schematables, set()),viewlist,dbsql)
else:
self.refresh_progress = 1
except Exception as exc:
self.print_log_message("Error updating DB: %s" % str(exc) )
# finally:
self.refresh_completed = True
def refresh_table_list(self):
self.table_list_model.reset()
####self.refresh_progressbar.set_value(0)
if not self.owner.ctrl_be.is_sql_connected():
return
#self.hintlabel.set_text("Schema list update...")
self.refresh_state = "Retrieving schema list"
self.refresh_progress = 0
self.schema_list.clear()
self.refresh_button.set_enabled(False)
self.refresh_thread = self.TableRefreshThread(self)
# refresh_thread.run()
self.refresh_completed = False
self.refresh_thread.start()
if not self._update_refresh_tm:
self._update_refresh_tm = Utilities.add_timeout(float(0.4), self.update_refresh)
def update_refresh(self):
self.progress_tab.flush_queued_logs()
if self.refresh_progress > 1:
self.refresh_progress = float(1)
####self.refresh_progressbar.set_value(self.refresh_progress)
self.select_summary_label.set_text(self.refresh_state)
if not self.refresh_completed:
return True
####self.refresh_progressbar.set_value(0)
names = self.table_list_model.get_schema_names()
names.sort()
self.schema_list.freeze_refresh()
for schema in names:
r = self.schema_list.add_node()
r.set_icon_path(1, "db.Schema.16x16.png")
r.set_string(1, schema)
r.set_bool(0, False)
self.schema_list.thaw_refresh()
self.refresh_button.set_enabled(True)
self.select_summary_label.set_text("")
###self.hintlabel.set_text("Press [Start Export] to start...")
self._update_refresh_tm = None
return False
def find_available_path(self, currentPath, ext = None):
# remove the extension if exists
ext = os.path.splitext(currentPath)[1]
if len(ext) > 1:
currentPath = currentPath[:-len(ext)]
# remove old path resolution, if exists
suffixBegin = currentPath.find(' (')
if suffixBegin > 0:
suffixEnd = currentPath.find(')', suffixBegin)
if suffixEnd == -1:
suffixEnd = len(currentPath)
else:
suffixEnd += 1
currentPath = currentPath[:suffixBegin] + currentPath[suffixEnd:]
result = currentPath
counter = 0
while os.path.exists(result + ext):
counter += 1
result = "%s (%d)" % (currentPath, counter)
return result + ext
def check_mysqldump_version(self, about_to_run=False):
mysqldump_version = get_mysqldump_version()
if not mysqldump_version:
if about_to_run:
mforms.Utilities.show_error("Could not get mysqldump version", "Workbench was unable to get mysqldump version. Please verify the log for more information.", "OK", "", "")
else:
self.print_log_message("Workbench was unable to get mysqldump version")
return False
if str(mysqldump_version).split('.')[:2] != str(self.owner.ctrl_be.target_version).split('.')[:2]:
msg = "%s is version %s, but the MySQL Server to be dumped has version %s.\nBecause the version of mysqldump is not the same as the server, some features may not be backed up properly.\nIt is recommended you upgrade or downgrade your local MySQL client programs, including mysqldump, to a version equal to or newer than that of the target server.\nThe path to the dump tool must then be set in Preferences -> Administrator -> Path to mysqldump Tool:" % (get_path_to_mysqldump(), mysqldump_version, self.owner.ctrl_be.target_version)
if about_to_run:
if not mforms.Utilities.show_warning("mysqldump Version Mismatch", msg, "Continue Anyway", "Cancel", ""):
return False
else:
self.print_log_message(msg)
# When using mysqldump >=5.6 and a server < 5.6, an additional parameter needs to be added
# for backwards compatibility
if (mysqldump_version >= Version(5, 6) and self.owner.ctrl_be.target_version < Version(5, 6)):
self._compatibility_params = True
return True
def check_mysqldump_defaults(self):
defaults = {}
# check mysqldump default values
path = get_path_to_mysqldump()
if path:
output = []
local_run_cmd('"%s" --help' % path, output_handler= lambda line,l=output: l.append(line if isinstance(line, str) else line.decode("utf-8")))
ok = False
for line in ("\n".join(output)).split("\n"):
line = line.strip()
if line.startswith("-----------") and line.endswith("-----------"):
ok = True
continue
if ok:
t = line.split()
if len(t) == 2:
k, v = t
if v in ("TRUE", "FALSE"):
defaults[k] = v
return defaults
def validate_single_transaction(self, starting):
if self.single_transaction_check.get_active() and self.owner.get_lock_tables() == {'lock-tables': 'TRUE'}:
r = mforms.Utilities.show_warning("Export to Disk",
"Single transaction with --lock-tables is not supported.\n"
"Disable --lock-tables?",
"Disable", "Cancel", "")
if r == mforms.ResultOk:
self.owner.set_lock_tables(False)
return True
else:
return False
return True
def single_transaction_clicked(self):
self.validate_single_transaction(False)
def set_show_internal_schemas(self, show_internal_schemas):
self.show_internal_schemas = show_internal_schemas
class ViewDumpData(DumpThread.TaskData):
def __init__(self,schema,views,make_pipe):
title = "Dumping " + schema + " views"
DumpThread.TaskData.__init__(self, title, len(views), [], [schema] + views, None, make_pipe)
class TableDumpData(DumpThread.TaskData):
def __init__(self,schema,table,args, make_pipe):
title = "Dumping " + schema
title += " (%s)" % table
DumpThread.TaskData.__init__(self,title, 1, [] + args, [schema, table], None, make_pipe)
class TableDumpNoData(DumpThread.TaskData):
def __init__(self,schema,table,args, make_pipe):
title = "Dumping " + schema
title += " (%s)" % table
DumpThread.TaskData.__init__(self,title, 1, ["--no-data"] + args, [schema, table], None, make_pipe)
class ViewsRoutinesEventsDumpData(DumpThread.TaskData):
def __init__(self, schema, views, args, make_pipe):
title = "Dumping " + schema + " views and/or routines and/or events"
if not views:
extra_args = ["--no-create-info"]
else:
extra_args = []
DumpThread.TaskData.__init__(self,title, len(views), ["--skip-triggers", " --no-data" ," --no-create-db"] + extra_args + args, [schema] + views, None, make_pipe)
def dump_to_folder(self, schemaname, tablename, include_schema):
self.close_pipe()
path = os.path.join(self.path, normalize_filename(schemaname) + "_" + normalize_filename(tablename) + '.sql')
i = 0
# check if the path already exists (they could become duplicated because of normalization)
while os.path.exists(path):
path = os.path.join(self.path, normalize_filename(schemaname) + "_" + normalize_filename(tablename) + ('%i.sql'%i))
self.out_pipe = open(path,"w")
if include_schema:
data = self.table_list_model.get_schema_sql(schemaname)
self.out_pipe.write(data)
self.out_pipe.flush()
return self.out_pipe
def start(self):
save_to_folder = not self.fileradio.get_active()
if save_to_folder:
self.path = self.folder_te.get_string_value()
if os.path.exists(self.path):
if not mforms.Utilities.show_warning("Folder already exists", "You are about to overwrite the specified folder. Do you want to continue overwrite?", "Overwrite", "Cancel", ""):
return False
else:
self.path = self.file_te.get_string_value()
if os.path.exists(self.path):
if not mforms.Utilities.show_warning("File already exists", "You are about to overwrite the specified file. Do you want to continue overwrite?", "Overwrite", "Cancel", ""):
return False
self.progress_tab.set_start_enabled(False)
if not self.check_mysqldump_version(True):
self.progress_tab.set_start_enabled(True)
return
if not self.validate_single_transaction(True):
self.progress_tab.set_start_enabled(True)
return
connection_params = self.server_profile.db_connection_params
tunnel = ConnectionTunnel(connection_params)
conn = connection_params.parameterValues
single_transaction = self.single_transaction_check.get_active()
#dump_views = self.dump_view_check.get_active()
sel_index = self.dump_type_selector.get_selected_index()
skip_data = True if sel_index == 2 else False
skip_table_structure = True if sel_index == 1 else False
# For mysqldump >= 8.0.2 there is column-statistic arg that cause issue while executing on MySQL Server version <= 5.7.
# To avoid that we force add '--column-statistics=0' to mysqldump args.
skip_column_statistics = True if get_mysqldump_version() > Version(8, 0, 2) and self.owner.ctrl_be.target_version < Version(8, 0, 0) else False
dump_routines = self.dump_routines_check.get_active()
dump_events = self.dump_events_check.get_active()
dump_triggers = self.dump_triggers_check.get_active()
save_to_folder = not self.fileradio.get_active()
if save_to_folder:
self.path = self.folder_te.get_string_value()
else:
self.path = self.file_te.get_string_value()
# gather objects to dump
schemas_to_dump = self.table_list_model.get_objects_to_dump(include_empty_schemas=True)
tables_to_ignore = self.table_list_model.get_tables_to_ignore()
if len(schemas_to_dump) == 0:
self.progress_tab.print_log_message(time.strftime('%X ') + "Nothing to do, no schemas or tables selected." + "\n")
self.progress_tab.set_start_enabled(True)
return
# assemble list of operations/command calls to be performed
operations = []
if save_to_folder:
if not os.path.exists(self.path):
try:
os.makedirs(self.path, mode=0o700)
except:
Utilities.show_error('Error', 'Access to "%s" failed' % self.path, "OK", "", "")
self.export_button.set_enabled(True)
return
for schema, tables in schemas_to_dump:
views = []
for table in tables:
if self.table_list_model.is_view(schema, table):
views.append(table)
else:
title = "Dumping " + schema
title += " (%s)" % table
# description, object_count, pipe_factory, extra_args, objects
args = []
if not dump_triggers:
args.append('--skip-triggers')
if skip_table_structure:
args.append('--no-create-info')
if skip_column_statistics:
args.append('--skip-column-statistics')
include_schema = self.include_schema_check.get_active()
if skip_data:
task = self.TableDumpNoData(schema,table, args, lambda schema=schema,table=table:self.dump_to_folder(schema, table, include_schema))
else:
task = self.TableDumpData(schema,table, args, lambda schema=schema,table=table:self.dump_to_folder(schema, table, include_schema))
operations.append(task)
# dump everything non-tables to file for routines
#if views:
# task = self.ViewDumpData(schema, views, lambda schema=schema, table=table:self.dump_to_folder(schema, "routines"))
# operations.append(task)
#if dump_events:
# task = self.EventDumpData(schema, lambda schema=schema, table=table:self.dump_to_folder(schema, "routines"))
# operations.append(task)
if views or dump_routines or dump_events:
args = []
if dump_routines:
args.append("--routines")
if dump_events:
args.append("--events")
if skip_column_statistics:
args.append('--skip-column-statistics')
task = self.ViewsRoutinesEventsDumpData(schema, views, args, lambda schema=schema, table=None:self.dump_to_folder(schema, "routines", include_schema))
operations.append(task)
else: # single file
if not os.path.exists(os.path.dirname(self.path)):
try:
os.makedirs(os.path.dirname(self.path))
except:
Utilities.show_error('Error', 'Access to "%s" failed' % self.path, "OK", "", "")
self.export_button.set_enabled(True)
return
# if there is a single schema to dump or single-transaction is off we allow selecting the individual tables, otherwise we need to bulk dump the whole db
#if (len(schemas_to_dump) != 1 and not single_transaction) or (len(schemas_to_dump) == 1 not self.table_list_model.validate_single_transaction(schemas_to_dump)):
if len(schemas_to_dump) == 1 or (len(schemas_to_dump) > 1 and (not single_transaction or not self.table_list_model.validate_single_transaction(schemas_to_dump))):
for schema, tables in schemas_to_dump:
# don't list the tables explicitly if everything is dumped
# this is to workaround a problem with mysqldump where functions are dumped after
# tables if the table names are specified
# see bug #14359349
title = "Dumping " + schema
if set(tables) == set(self.table_list_model.get_tables(schema)):
title += " (all tables)"
else:
title += " (%s)" % ", ".join(tables)
objects = [schema]
if single_transaction:
params = ["--single-transaction=TRUE"]
else:
params = []
if dump_routines:
params.append("--routines")
if dump_events:
params.append("--events")
if skip_data or not tables:
params.append("--no-data")
if skip_column_statistics:
params.append('--column-statistics=0')
if not tables or skip_table_structure:
params.append("--no-create-info=TRUE")
if not tables or not dump_triggers:
params.append("--skip-triggers")
# description, object_count, pipe_factory, extra_args, objects
include_schema = self.include_schema_check.get_active()
task = DumpThread.TaskData(title, len(tables), params, objects, tables_to_ignore, lambda schema=schema:self.dump_to_file([schema], include_schema))
operations.append(task)
# operations.append((title, len(tables), lambda schema=schema:self.dump_to_file([schema]), params, objects))
else:
params = []
schema_names = [s[0] for s in schemas_to_dump]
count = sum([len(s[1]) for s in schemas_to_dump])
title = "Dumping " + ", ".join(schema_names)
if dump_routines:
params.append("--routines")
if dump_events:
params.append("--events")
if skip_data:
params.append("--no-data")
if skip_column_statistics:
params.append('--column-statistics=0')
if single_transaction:
params += ["--single-transaction=TRUE", "--databases"]
else:
params += ["--databases"]
# --databases includes CREATE DATABASE info, so it's not needed for dump_to_file()
# description, object_count, pipe_factory, extra_args, objects
include_schema = self.include_schema_check.get_active()
task = DumpThread.TaskData(title, count, params, schema_names, tables_to_ignore, lambda:self.dump_to_file([], include_schema))
operations.append(task)
# operations.append((title, count, lambda:self.dump_to_file([]), params, schema_names))
if connection_params.driver.name == "MysqlNativeSocket":
params = {
"protocol":"pipe" if sys.platform == "win32" else "socket",
"socket":([conn["socket"]])[0],
"default-character-set":"utf8",
"user":conn["userName"]
}
if not params["socket"]:
del params["socket"]
else:
params = {
"host":(tunnel.port and ["localhost"] or [conn["hostName"]])[0],
"port":(tunnel.port and [str(tunnel.port)] or [conn["port"]])[0],
"default-character-set":"utf8",
"user":conn["userName"]
}
params["protocol"] = "tcp"
if not params["port"]:
del params["port"]
if not params["host"]:
del params["host"]
options = {}
excludes = ['Show Internal Schemas']
for key, value in list(self.owner.get_export_options(self.mysqldump_defaults).items()):
if not key in excludes:
options[key] = value
params.update(options)
cmd = get_path_to_mysqldump()
if cmd == None:
self.failed("mysqldump command was not found, please install it or configure it in Edit -> Preferences -> Administration")
return
#if cmd[0] != '"':
# cmd = '"' + cmd + '"'
#cmd += " --password="
args = [cmd, "--password="]
if conn.get("useSSL", 0):
if conn.get("sslCert", ""):
args.append("--ssl-cert=%s" % conn["sslCert"])
if conn.get("sslCA", ""):
args.append("--ssl-ca=%s" % conn["sslCA"])
if conn.get("sslKey", ""):
args.append("--ssl-key=%s" % conn["sslKey"])
if conn.get("sslCipher", ""):
args.append("--ssl-cipher=%s" % conn["sslCipher"])
# Sets the compatibility parameters if needed
if self._compatibility_params:
args.append("--set-gtid-purged=OFF")
if conn.get("OPT_ENABLE_CLEARTEXT_PLUGIN", ""):
args.append("--enable-cleartext-plugin")
for paramname, paramvalue in list(params.items()):
args.append("--"+paramname+((paramvalue != None and ["="+str(paramvalue)] or [""])[0]))
cmd = subprocess.list2cmdline(args)
password = self.get_mysql_password(self.bad_password_detected)
if password is None:
self.cancelled("Password Input Cancelled")
return
self.progress_tab.did_start()
self.progress_tab.set_status("Export is running...")
self.dump_thread = DumpThread(cmd, operations, password, self, (self.progress_tab.logging_lock, self.progress_tab.log_queue))
self.dump_thread.is_import = False
self.dump_thread.start()
self._update_progress_tm = Utilities.add_timeout(float(0.4), self._update_progress)
def _update_progress(self):
r = self.update_progress()
if not r:
self._update_progress_tm = None
return r
def dump_to_file(self, schemanames, include_schema):
if self.out_pipe == None:
self.out_pipe = open(self.path, "w")
if include_schema:
for schema in schemanames:
self.out_pipe.write(self.table_list_model.get_schema_sql(schema))
self.out_pipe.flush()
return self.out_pipe
def fail_callback(self):
fname = self.out_pipe.name
self.close_pipe()
os.remove(fname)
def close_pipe(self):
if self.out_pipe != None:
self.out_pipe.close()
self.out_pipe = None
def tasks_aborted(self):
if self.path:
try:
os.rename(self.path, self.path+".cancelled")
self.progress_tab.print_log_message("Partial backup file renamed to %s.cancelled" % self.path)
except Exception as exc:
self.progress_tab.print_log_message("Error renaming partial backup file %s: %s" % (self.path, exc))
self.cancelled(time.strftime('%X ') + "Aborted by User")
def tasks_completed(self):
self.folder_te.set_value(self.find_available_path(self.folder_te.get_string_value()))
self.file_te.set_value(self.find_available_path(self.file_te.get_string_value()))
logmsg = time.strftime('%X ') + "Export of %s has finished" % self.path
if self.dump_thread.error_count > 0:
self.progress_tab.set_status("Export Completed With %i Errors" % self.dump_thread.error_count)
logmsg += " with %i errors" % self.dump_thread.error_count
else:
self.progress_tab.set_status("Export Completed")
self.progress_tab.print_log_message(logmsg)
self.progress_tab.did_complete()
####################################################################################################
## Options
####################################################################################################
class WbAdminExportOptionsTab(mforms.Box):
class Check_option_model:
def __init__(self,optname,checkbox,default):
self.optname = optname
self.checkbox = checkbox
self.default = default
def get_option(self, defaults):
is_bool_option = self.optname in defaults
value = self.checkbox.get_active() and "TRUE" or "FALSE"
if is_bool_option:
if defaults[self.optname] != value:
return {self.optname: value}
return {}
else:
if self.default == "TRUE" and not self.checkbox.get_active():
return {"skip-"+self.optname:"TRUE"}
else:
return {self.optname:(self.checkbox.get_active() and ["TRUE"] or ["FALSE"])[0]}
def set_option(self, value):
if value in ("TRUE", "FALSE"):
self.checkbox.set_active(value == "TRUE")
else:
self.checkbox.set_active(value)
self.checkbox.call_clicked_callback()
class Text_option_model:
def __init__(self, optname, textentry, default):
self.optname = optname
self.entry = textentry
self.default = default
def get_option(self, defaults):
if self.entry.get_string_value() == self.default:
return {}
return {self.optname:self.entry.get_string_value() or self.default}
def set_option(self, value):
self.entry.set_value(value)
def __init__(self, target_version, defaults_from_mysqldump):
mforms.Box.__init__(self, False)
self.set_managed()
self.set_release_on_add()
mysqldump_version = get_mysqldump_version()
self.options = {}
button_box = newBox(True)
button_box.set_padding(8)
button_box.set_spacing(12)
self.restore_defaults_button = newButton()
self.restore_defaults_button.set_text('Restore Defaults')
self.restore_defaults_button.add_clicked_callback(self.restore_default_options)
button_box.add_end(self.restore_defaults_button, False)
self.add_end(button_box, False)
outerbox = newBox(False)
outerbox.set_padding(8)
outerbox.set_spacing(12)
for groupname, options in reversed(list(wb_admin_export_options.export_options.items())):
box = newBox(False)
box.set_padding(8)
box.set_spacing(8)
panel = newPanel(mforms.TitledBoxPanel)
panel.set_title(groupname)
panel.set_name(groupname)
# print groupname
for optname, option_info in reversed(list(options.items())):
option_type = "BOOL"
if len(option_info) == 2:
(option, default) = option_info
elif len(option_info) == 4: # includes type and (min_version, max_version) tuple
(option, default, option_type, (min_version, max_version)) = option_info
if min_version and target_version:
if not target_version.is_supported_mysql_version_at_least(Version.fromstr(min_version)):
log_debug("Skip option %s because it's for version %s\n" % (optname, min_version))
continue
if max_version and target_version:
if target_version.is_supported_mysql_version_at_least(Version.fromstr(max_version)):
log_debug("Skip option %s because it's deprecated in version %s\n" % (optname, max_version))
continue
if min_version and mysqldump_version < min_version:
log_debug("Skip option %s because it's for mysqldump %s\n" % (optname, min_version))
continue
if max_version and mysqldump_version > max_version:
log_debug("Skip option %s because it's deprecated in mysqldump %s\n" % (optname, max_version))
continue
exlude = ['column-statistics']
# get the default value from mysqldump --help, if we don't have that data, use the stored default
if optname not in exlude or get_mysqldump_version() == target_version:
default = defaults_from_mysqldump.get(optname, default)
if option_type == "BOOL":
checkbox = newCheckBox()
checkbox.set_text("%s - %s"% (optname, option))
checkbox.set_name(optname)
checkbox.set_active(default == "TRUE")
box.add(checkbox, False, True)
self.options[optname] = self.Check_option_model(optname,checkbox,default)
else:
hbox = newBox(True)
hbox.set_spacing(4)
label = newLabel("%s - %s"% (optname, option))
hbox.add(label, False, True)
entry = newTextEntry()
entry.set_name(optname)
hbox.add(entry, True, True)
entry.set_value(default)
box.add(hbox, False, True)
self.options[optname] = self.Text_option_model(optname,entry,default)
if groupname == "Other":
max_allowed_packet_box = newBox(True)
self.max_allowed_packet_te = newTextEntry()
self.max_allowed_packet_te.set_value("1G")
self.max_allowed_packet_te.set_size(40, -1)
label = newLabel(" The maximum size of one packet or any generated/intermediate string. ")
max_allowed_packet_box.add(label,False,False)
max_allowed_packet_box.add(self.max_allowed_packet_te,False,True)
max_allowed_packet_box.add(newBox(True),True,True)
max_allowed_packet_box.add(newBox(True),True,True)
max_allowed_packet_box.add(newBox(True),True,True)
box.add(max_allowed_packet_box,False,True)
self.options['max_allowed_packet'] = self.Text_option_model('max_allowed_packet', self.max_allowed_packet_te, "1G")
panel.add(box)
outerbox.add(panel, False, True)
scrollpan = newScrollPanel(mforms.ScrollPanelNoFlags)
scrollpan.add(outerbox)
self.add(scrollpan, True, True)
def get_lock_tables(self):
return self.options["lock-tables"].get_option({'lock-tables': 'TRUE'})
def set_lock_tables(self, value):
return self.options["lock-tables"].set_option(value)
def get_options(self, defaults):
options = {}
for optname, getter in list(self.options.items()):
result = getter.get_option(defaults)
if result != None:
options.update(result)
#options.update({"max_allowed_packet":self.max_allowed_packet_te.get_string_value()})
return options
def set_options(self, values):
for k, v in list(values.items()):
if k in self.options:
self.options[k].set_option(v)
def restore_default_options(self):
for option in list(self.options.values()):
option.set_option(option.default)
def add_clicked_callback_to_checkbox(self, optname, callback_function):
opt = self.options[optname]
if opt:
opt.checkbox.add_clicked_callback(callback_function)
####################################################################################################
class WbAdminProgressTab(mforms.Box):
def __init__(self, owner_tab, is_export):
mforms.Box.__init__(self, False)
self.set_managed()
self.set_release_on_add()
self.set_spacing(12)
self.set_padding(8)
self.owner_tab = owner_tab
self.operation_tab = None
self.is_export = is_export
self.logging_lock = _thread.allocate_lock()
self.log_queue = deque([])
statusbox = newBox(False)
statusbox.set_spacing(2)
self.dump_progressbar = newProgressBar()
self.dump_progressbar.set_name("Progress")
self.statlabel = newLabel("")
statusbox.set_size(400, -1)
if is_export:
self.hintlabel = newLabel("Press [Start Export] to start...")
else:
self.hintlabel = newLabel("Press [Start Import] to start...")
self.hintlabel.set_name("Press to Start")
statusbox.add(self.hintlabel, False, True)
statusbox.add(self.dump_progressbar, False, True)
label = newLabel("Status:")
label.set_name("Status")
statusbox.add(label, False, True)
statusbox.add(self.statlabel, False, True)
self.progress_log = newTextBox(mforms.VerticalScrollBar)
self.progress_log.set_name("Log Entry")
self.progress_log.set_read_only(True)
self.add(statusbox, False, True)
label = newLabel("Log:")
label.set_name("Log")
self.add(label, False, True)
self.add(self.progress_log, True, True)
box = newBox(True)
self.add(box, False, True)
box.set_spacing(8)
box.set_padding(0)
self.export_button = newButton()
if is_export:
self.export_button.set_text("Start Export")
self.export_button.set_name("Start Export")
else:
self.export_button.set_text("Start Import")
self.export_button.set_name("Start Import")
self.export_button.set_enabled(False)
box.add_end(self.export_button, False, True)
self.export_button.add_clicked_callback(self.start)
self.stop_button = newButton()
self.stop_button.set_text("Stop")
self.stop_button.set_name("Stop")
self.stop_button.set_enabled(False)
self.stop_button.add_clicked_callback(self.stop)
box.add_end(self.stop_button, False, True)
def set_progress(self, progress, progress_text):
self.statlabel.set_text(progress_text)
self.dump_progressbar.set_value(progress)
def set_start_enabled(self, flag):
self.export_button.set_enabled(bool(flag))
self.operation_tab.export_button.set_enabled(bool(flag))
def set_status(self, text):
self.hintlabel.set_text(text)
self.operation_tab.statlabel.set_text(text)
def flush_queued_logs(self):
self.logging_lock.acquire()
while len(self.log_queue) > 0:
self.progress_log.append_text_and_scroll(self.log_queue.popleft()+"\n", True)
self.logging_lock.release()
def print_log_message(self, message):
if mforms.Utilities.in_main_thread():
self.progress_log.append_text_and_scroll(message+"\n", True)
else:
self.logging_lock.acquire()
self.log_queue.append(message+"\n")
self.logging_lock.release()
def start(self):
self.operation_tab.start()
def stop(self):
self.operation_tab.stop()
def did_start(self):
self.owner_tab.switch_to_progress()
self.set_start_enabled(False)
self.stop_button.set_enabled(True)
self.set_status("Export running...")
def did_complete(self):
self.set_start_enabled(True)
self.stop_button.set_enabled(False)
self.print_log_message("\n\n\n")
if self.is_export:
self.export_button.set_text("Export Again")
else:
self.export_button.set_text("Import Again")
def did_fail(self, message):
self.progress_log.append_text_and_scroll(message+"\n", True)
self.set_status("Operation Failed")
self.set_start_enabled(True)
self.stop_button.set_enabled(False)
def did_cancel(self, message):
self.progress_log.append_text_and_scroll(message+"\n", True)
self.set_status("Operation Cancelled")
self.dump_progressbar.set_value(0)
self.set_start_enabled(True)
self.stop_button.set_enabled(False)
def close(self):
pass
####################################################################################################
class WbAdminExport(WbAdminTabBase):
@classmethod
def wba_register(cls, admin_context):
admin_context.register_page(cls, "Management", "Data Export", "Data Export")
@classmethod
def identifier(cls):
return "admin_export"
def __init__(self, ctrl_be, instance_info, main_view):
WbAdminTabBase.__init__(self, ctrl_be, instance_info, main_view)
self.set_name("Administration - Data Export/Import")
self.showing_options = False
self.add_validation(WbAdminValidationConnection(ctrl_be))
self.advanced_options_btn = mforms.newButton()
self.advanced_options_btn.set_name("Toggle Advanced Options")
self.advanced_options_btn.set_text("Advanced Options...")
self.advanced_options_btn.add_clicked_callback(self.show_options)
self.set_header(self.create_standard_header("title_export.png", self.instance_info.name, "Data Export", self.advanced_options_btn))
self.export_tab = None
self.progress_tab = None
def switch_to_progress(self):
self.tabview.set_active_tab(1)
def show_options(self):
self.showing_options = not self.showing_options
if self.showing_options:
self.tabview.show(False)
self.options_tab.show(True)
self.advanced_options_btn.set_text("< Return")
else:
self.tabview.show(True)
self.options_tab.show(False)
self.advanced_options_btn.set_text("Advanced Options...")
def validation_failed_notification(self, failed_validation):
self.advanced_options_btn.show(False)
def validation_successful_notification(self):
self.advanced_options_btn.show(True)
def create_ui(self):
self.ui_box = mforms.newBox(False)
self.tabview = newTabView(False)
self.tabview.set_name("Import/Export Tab Panel")
self.ui_box.add(self.tabview, True, True)
self.tabview.show(False)
self.progress_tab = WbAdminProgressTab(self, True)
self.export_tab = WbAdminExportTab(self, self.instance_info, self.progress_tab)
self.tabview.add_page(self.export_tab, "Object Selection")
self.options_tab = WbAdminExportOptionsTab(self.ctrl_be.target_version, self.export_tab.mysqldump_defaults)
self.ui_box.add(self.options_tab, True, True)
self.options_tab.show(False)
self.options_tab.add_clicked_callback_to_checkbox("Show Internal Schemas", self.show_internal_schemas_changed)
self.tabview.add_page(self.progress_tab, "Export Progress")
self.tabview.show(True)
self.recall_options()
self.export_tab.refresh_table_list()
return self.ui_box
def shutdown(self): # called when admin tab is closed
self.remember_options()
if self.ui_created():
if self.export_tab:
self.export_tab.close()
if self.progress_tab:
self.progress_tab.close()
def get_lock_tables(self):
return self.options_tab.get_lock_tables()
def set_lock_tables(self, value):
return self.options_tab.set_lock_tables(value)
def get_export_options(self, defaults):
options = self.options_tab.get_options(defaults)
return options
def remember_options(self):
if self.ui_created() and self.export_tab:
dic = grt.root.wb.options.options
dic["wb.admin.export:exportType"] = self.export_tab.folderradio.get_active() and "folder" or "file"
dic["wb.admin.export:selectedFolder"] = self.export_tab.folder_te.get_string_value()
dic["wb.admin.export:selectedFile"] = self.export_tab.file_te.get_string_value()
dic["wb.admin.export:singleTransaction"] = self.export_tab.single_transaction_check.get_active()
dic["wb.admin.export:dumpRoutines"] = self.export_tab.dump_routines_check.get_active()
dic["wb.admin.export:dumpEvents"] = self.export_tab.dump_events_check.get_active()
dic["wb.admin.export:dumpTriggers"] = self.export_tab.dump_triggers_check.get_active()
dic["wb.admin.export:skipData"] = self.export_tab.dump_type_selector.get_selected_index()
for key, value in list(self.get_export_options({}).items()):
dic["wb.admin.export.option:"+key] = value
def recall_options(self):
dic = grt.root.wb.options.options
if "wb.admin.export:exportType" in dic:
if dic["wb.admin.export:exportType"] == "folder":
self.export_tab.folderradio.set_active(True)
else:
self.export_tab.fileradio.set_active(True)
self.export_tab.set_save_option()
# if dic.has_key("wb.admin.export:selectedFolder"):
# self.export_tab.folder_te.set_value(dic["wb.admin.export:selectedFolder"])
# if dic.has_key("wb.admin.export:selectedFile"):
# self.export_tab.file_te.set_value(dic["wb.admin.export:selectedFile"])
if "wb.admin.export:singleTransaction" in dic:
self.export_tab.single_transaction_check.set_active(dic["wb.admin.export:singleTransaction"] != 0)
if "wb.admin.export:dumpRoutines" in dic:
self.export_tab.dump_routines_check.set_active(dic["wb.admin.export:dumpRoutines"] != 0)
if "wb.admin.export:dumpEvents" in dic:
self.export_tab.dump_events_check.set_active(dic["wb.admin.export:dumpEvents"] != 0)
if "wb.admin.export:dumpTriggers" in dic:
self.export_tab.dump_triggers_check.set_active(dic["wb.admin.export:dumpTriggers"] != 0)
if "wb.admin.export:skipData" in dic:
self.export_tab.dump_type_selector.set_selected(dic["wb.admin.export:skipData"] != 0)
values = {}
for key in list(self.get_export_options({}).keys()):
if "wb.admin.export.option:"+key in dic:
values[key] = dic["wb.admin.export.option:"+key]
self.options_tab.set_options(values)
def show_internal_schemas_changed(self):
self.export_tab.set_show_internal_schemas(self.get_export_options({})['Show Internal Schemas'] == 'TRUE')
self.export_tab.refresh_table_list()
####################################################################################################
class WbAdminImport(WbAdminTabBase):
def __init__(self, ctrl_be, instance_info, main_view):
WbAdminTabBase.__init__(self, ctrl_be, instance_info, main_view)
self.set_name("Administration - Data Export/Import")
self.add_validation(WbAdminValidationConnection(ctrl_be))
self.set_standard_header("title_import.png", self.instance_info.name, "Data Import")
self.progress_tab = None
self.import_tab = None
self.tabview = None
@classmethod
def wba_register(cls, admin_context):
admin_context.register_page(cls, "Management", "Data Import/Restore", "Data Import and Export")
@classmethod
def identifier(cls):
return "admin_restore_data"
def shutdown(self):
if self.ui_created():
if self.import_tab:
self.import_tab.close()
if self.progress_tab:
self.progress_tab.close()
def switch_to_progress(self):
self.tabview.set_active_tab(1)
def create_ui(self):
self.tabview = newTabView(False)
self.tabview.show(False)
self.progress_tab = WbAdminProgressTab(self, False)
self.import_tab = WbAdminImportTab(self, self.instance_info, self.progress_tab)
self.tabview.add_page(self.import_tab, "Import from Disk")
self.tabview.add_page(self.progress_tab, "Import Progress")
self.tabview.show(True)
return self.tabview