mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
2057 lines
84 KiB
ObjectPascal
2057 lines
84 KiB
ObjectPascal
unit tabletools;
|
|
|
|
|
|
// -------------------------------------
|
|
// Table-diagnostics
|
|
// -------------------------------------
|
|
|
|
|
|
interface
|
|
|
|
uses
|
|
Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.Buttons, Vcl.Dialogs, Vcl.StdActns,
|
|
VirtualTrees, VirtualTrees.Header, Vcl.ExtCtrls, Vcl.Graphics, SynRegExpr, System.Math, System.Generics.Collections, extra_controls,
|
|
dbconnection, apphelpers, Vcl.Menus, gnugettext, System.DateUtils, System.Zip, System.UITypes, System.StrUtils, Winapi.Messages,
|
|
SynEdit, SynMemo, Vcl.ClipBrd, generic_types;
|
|
|
|
type
|
|
TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit);
|
|
TfrmTableTools = class(TExtForm)
|
|
btnCloseOrCancel: TButton;
|
|
pnlTop: TPanel;
|
|
TreeObjects: TVirtualStringTree;
|
|
spltHorizontally: TSplitter;
|
|
pnlRight: TPanel;
|
|
ResultGrid: TVirtualStringTree;
|
|
tabsTools: TPageControl;
|
|
tabMaintenance: TTabSheet;
|
|
comboOperation: TComboBox;
|
|
lblOperation: TLabel;
|
|
chkQuick: TCheckBox;
|
|
chkFast: TCheckBox;
|
|
chkMedium: TCheckBox;
|
|
chkExtended: TCheckBox;
|
|
chkChanged: TCheckBox;
|
|
chkUseFrm: TCheckBox;
|
|
lblOptions: TLabel;
|
|
btnHelpMaintenance: TButton;
|
|
tabFind: TTabSheet;
|
|
lblFindText: TLabel;
|
|
comboDataTypes: TComboBox;
|
|
lblDataTypes: TLabel;
|
|
tabSQLexport: TTabSheet;
|
|
chkExportDatabasesCreate: TCheckBox;
|
|
chkExportDatabasesDrop: TCheckBox;
|
|
chkExportTablesDrop: TCheckBox;
|
|
chkExportTablesCreate: TCheckBox;
|
|
lblExportData: TLabel;
|
|
comboExportData: TComboBox;
|
|
lblExportOutputType: TLabel;
|
|
comboExportOutputType: TComboBox;
|
|
comboExportOutputTarget: TComboBox;
|
|
lblExportDatabases: TLabel;
|
|
lblExportTables: TLabel;
|
|
lblExportOutputTarget: TLabel;
|
|
btnExecute: TButton;
|
|
btnExportOutputTargetSelect: TButton;
|
|
tabBulkTableEdit: TTabSheet;
|
|
chkBulkTableEditDatabase: TCheckBox;
|
|
comboBulkTableEditDatabase: TComboBox;
|
|
chkBulkTableEditResetAutoinc: TCheckBox;
|
|
chkBulkTableEditCollation: TCheckBox;
|
|
comboBulkTableEditCollation: TComboBox;
|
|
chkBulkTableEditEngine: TCheckBox;
|
|
comboBulkTableEditEngine: TComboBox;
|
|
chkBulkTableEditCharset: TCheckBox;
|
|
comboBulkTableEditCharset: TComboBox;
|
|
btnSeeResults: TButton;
|
|
chkCaseSensitive: TCheckBox;
|
|
lblCheckedSize: TLabel;
|
|
popupTree: TPopupMenu;
|
|
menuCheckAll: TMenuItem;
|
|
menuCheckByType: TMenuItem;
|
|
menuCheckNone: TMenuItem;
|
|
chkForUpgrade: TCheckBox;
|
|
lblInsertSize: TLabel;
|
|
editInsertSize: TEdit;
|
|
updownInsertSize: TUpDown;
|
|
lblInsertSizeUnit: TLabel;
|
|
btnExportOptions: TButton;
|
|
popupExportOptions: TPopupMenu;
|
|
menuExportAddComments: TMenuItem;
|
|
menuExportRemoveAutoIncrement: TMenuItem;
|
|
comboMatchType: TComboBox;
|
|
lblMatchType: TLabel;
|
|
menuExportRemoveDefiner: TMenuItem;
|
|
tabsTextType: TPageControl;
|
|
tabSimpleText: TTabSheet;
|
|
tabSQL: TTabSheet;
|
|
memoFindText: TMemo;
|
|
SynMemoFindText: TSynMemo;
|
|
menuCopyMysqldumpCommand: TMenuItem;
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormShow(Sender: TObject);
|
|
procedure btnHelpMaintenanceClick(Sender: TObject);
|
|
function GetCheckedObjects(DBNode: PVirtualNode): TDBObjectList;
|
|
procedure TreeObjectsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType; var CellText: String);
|
|
procedure TreeObjectsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
|
|
var InitialStates: TVirtualNodeInitStates);
|
|
procedure TreeObjectsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind;
|
|
Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex);
|
|
procedure TreeObjectsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal);
|
|
procedure Execute(Sender: TObject);
|
|
procedure ResultGridInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
|
|
var InitialStates: TVirtualNodeInitStates);
|
|
procedure ResultGridGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
|
|
procedure ResultGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType; var CellText: String);
|
|
procedure TreeObjectsChecked(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
procedure FillTargetDatabases;
|
|
procedure ResultGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
|
|
procedure ResultGridCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode;
|
|
Column: TColumnIndex; var Result: Integer);
|
|
procedure ResultGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType);
|
|
procedure ValidateControls(Sender: TObject);
|
|
procedure SaveSettings(Sender: TObject);
|
|
procedure chkExportOptionClick(Sender: TObject);
|
|
procedure btnExportOutputTargetSelectClick(Sender: TObject);
|
|
procedure comboExportOutputTargetChange(Sender: TObject);
|
|
procedure comboExportOutputTypeChange(Sender: TObject);
|
|
procedure FormClose(Sender: TObject; var Action: TCloseAction);
|
|
procedure TreeObjectsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType);
|
|
procedure chkBulkTableEditCheckComboClick(Sender: TObject);
|
|
procedure TreeObjectsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
procedure TreeObjectsChecking(Sender: TBaseVirtualTree; Node: PVirtualNode; var NewState: TCheckState;
|
|
var Allowed: Boolean);
|
|
procedure btnSeeResultsClick(Sender: TObject);
|
|
procedure TreeObjectsGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
|
|
procedure btnCloseOrCancelClick(Sender: TObject);
|
|
procedure TreeObjectsBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
|
|
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
|
|
var ContentRect: TRect);
|
|
procedure CheckAllClick(Sender: TObject);
|
|
procedure TreeObjectsExpanded(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
procedure btnExportOptionsClick(Sender: TObject);
|
|
procedure menuCopyMysqldumpCommandClick(Sender: TObject);
|
|
const
|
|
StatusMsg = '%s %s ...';
|
|
private
|
|
{ Private declarations }
|
|
FResults: TObjectList<TStringList>;
|
|
FToolMode: TToolMode;
|
|
FSecondExportPass: Boolean; // Set to True after everything is exported and final VIEWs need to be exported again
|
|
FCancelled: Boolean;
|
|
ExportStream: TStream;
|
|
FExportFileName: String;
|
|
ExportStreamStartOfQueryPos: Int64;
|
|
ExportLastDatabase: String;
|
|
FTargetConnection: TDBConnection;
|
|
FLastOutputSelectedIndex: Integer;
|
|
FModifiedDbs: TStringList;
|
|
FHeaderCreated: Boolean;
|
|
FFindSeeResultSQL: TStringList;
|
|
ToFile, ToDir, ToClipboard, ToDb, ToServer: Boolean;
|
|
FObjectSizes, FObjectSizesDone, FObjectSizesDoneExact: Int64;
|
|
FStartTimeAll: Cardinal;
|
|
procedure WMNCLBUTTONDOWN(var Msg: TWMNCLButtonDown) ; message WM_NCLBUTTONDOWN;
|
|
procedure WMNCLBUTTONUP(var Msg: TWMNCLButtonUp) ; message WM_NCLBUTTONUP;
|
|
procedure SetToolMode(Value: TToolMode);
|
|
procedure Output(SQL: String; IsEndOfQuery, ForFile, ForDir, ForDb, ForServer: Boolean);
|
|
procedure AddResults(SQL: String; Connection: TDBConnection);
|
|
procedure AddNotes(Col1, Col2, Col3, Col4: String); overload;
|
|
procedure AddNotes(DBObject: TDBObject; Msg1, Msg2: String); overload;
|
|
procedure SetupResultGrid(Results: TDBQuery=nil);
|
|
procedure UpdateResultGrid;
|
|
procedure DoMaintenance(DBObj: TDBObject);
|
|
procedure DoFind(DBObj: TDBObject);
|
|
procedure DoExport(DBObj: TDBObject);
|
|
procedure DoBulkTableEdit(DBObj: TDBObject);
|
|
public
|
|
{ Public declarations }
|
|
PreSelectObjects: TDBObjectList;
|
|
property ToolMode: TToolMode read FToolMode write SetToolMode;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses main, dbstructures;
|
|
|
|
const
|
|
STRSKIPPED: String = 'Skipped - ';
|
|
EXPORT_FILE_FOOTER =
|
|
'/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, ''system'') */;'+CRLF+
|
|
'/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '''') */;'+CRLF+
|
|
'/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;'+CRLF+
|
|
'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'+CRLF+
|
|
'/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;'+CRLF;
|
|
|
|
var
|
|
OUTPUT_FILE,
|
|
OUTPUT_FILE_COMPRESSED,
|
|
OUTPUT_CLIPBOARD,
|
|
OUTPUT_DIR,
|
|
OUTPUT_DB,
|
|
OUTPUT_SERVER,
|
|
DATA_NO,
|
|
DATA_REPLACE,
|
|
DATA_INSERT,
|
|
DATA_INSERTNEW,
|
|
DATA_UPDATE : String;
|
|
|
|
{$R *.DFM}
|
|
|
|
|
|
procedure TfrmTableTools.WMNCLBUTTONDOWN(var Msg: TWMNCLButtonDown) ;
|
|
begin
|
|
if Msg.HitTest = HTHELP then
|
|
Msg.Result := 0 // "eat" the message
|
|
else
|
|
inherited;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.WMNCLBUTTONUP(var Msg: TWMNCLButtonUp) ;
|
|
begin
|
|
if Msg.HitTest = HTHELP then begin
|
|
Msg.Result := 0;
|
|
if tabsTools.ActivePage = tabSQLexport then
|
|
Help(Self, 'sqlexport')
|
|
else
|
|
ErrorDialog(_('No help available for this tab.'));
|
|
end else
|
|
inherited;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.FormCreate(Sender: TObject);
|
|
var
|
|
i: Integer;
|
|
dtc: TDBDatatypeCategoryIndex;
|
|
SessionPaths: TStringList;
|
|
MenuItem: TMenuItem;
|
|
dt: TListNodeType;
|
|
Obj: TDBObject;
|
|
begin
|
|
HasSizeGrip := True;
|
|
OUTPUT_FILE := _('Single .sql file');
|
|
OUTPUT_FILE_COMPRESSED := _('ZIP compressed .sql file');
|
|
OUTPUT_CLIPBOARD := _('Clipboard');
|
|
OUTPUT_DIR := _('Directory - one file per object in database subdirectories');
|
|
OUTPUT_DB := _('Database');
|
|
OUTPUT_SERVER := _('Server')+': ';
|
|
// Todo: sanitize misleading names
|
|
DATA_NO := _('No data');
|
|
DATA_REPLACE := _('Delete + insert (truncate existing data)');
|
|
DATA_INSERT := _('Insert');
|
|
DATA_INSERTNEW := _('Insert ignore (do not update existing)');
|
|
DATA_UPDATE := _('Replace existing data');
|
|
|
|
// Find text tab
|
|
memoFindText.Text := AppSettings.ReadString(asTableToolsFindText);
|
|
SynMemoFindText.Text := AppSettings.ReadString(asTableToolsFindSQL);
|
|
tabsTextType.ActivePageIndex := AppSettings.ReadInt(asTableToolsFindTextTab);
|
|
comboDatatypes.Items.Add(_('All data types'));
|
|
for dtc:=Low(DatatypeCategories) to High(DatatypeCategories) do
|
|
comboDatatypes.Items.Add(DatatypeCategories[dtc].Name);
|
|
comboDatatypes.ItemIndex := AppSettings.ReadInt(asTableToolsDatatype);
|
|
chkCaseSensitive.Checked := AppSettings.ReadBool(asTableToolsFindCaseSensitive);
|
|
comboMatchType.ItemIndex := AppSettings.ReadInt(asTableToolsFindMatchType);
|
|
|
|
// SQL export tab
|
|
chkExportDatabasesCreate.Checked := AppSettings.ReadBool(asExportSQLCreateDatabases);
|
|
chkExportTablesCreate.Checked := AppSettings.ReadBool(asExportSQLCreateTables);
|
|
comboExportData.Items.Text := DATA_NO+CRLF +DATA_REPLACE+CRLF +DATA_INSERT+CRLF +DATA_INSERTNEW+CRLF +DATA_UPDATE;
|
|
comboExportData.ItemIndex := AppSettings.ReadInt(asExportSQLDataHow);
|
|
updownInsertSize.Position := AppSettings.ReadInt(asExportSQLDataInsertSize);
|
|
menuExportAddComments.Checked := AppSettings.ReadBool(asExportSQLAddComments);
|
|
menuExportRemoveAutoIncrement.Checked := AppSettings.ReadBool(asExportSQLRemoveAutoIncrement);
|
|
menuExportRemoveDefiner.Checked := AppSettings.ReadBool(asExportSQLRemoveDefiner);
|
|
// Add hardcoded output options and session names from registry
|
|
comboExportOutputType.Items.Text :=
|
|
OUTPUT_FILE + CRLF +
|
|
OUTPUT_FILE_COMPRESSED + CRLF +
|
|
OUTPUT_DIR + CRLF +
|
|
OUTPUT_CLIPBOARD + CRLF +
|
|
OUTPUT_DB;
|
|
SessionPaths := TStringList.Create;
|
|
AppSettings.GetSessionPaths('', SessionPaths);
|
|
for i:=0 to SessionPaths.Count-1 do begin
|
|
if SessionPaths[i] <> Mainform.ActiveConnection.Parameters.SessionPath then
|
|
comboExportOutputType.Items.Add(OUTPUT_SERVER+SessionPaths[i]);
|
|
end;
|
|
SessionPaths.Free;
|
|
comboExportOutputTarget.Text := '';
|
|
|
|
// Various
|
|
FixVT(TreeObjects);
|
|
FixVT(ResultGrid);
|
|
FResults := TObjectList<TStringList>.Create;
|
|
PreSelectObjects := TDBObjectList.Create(False);
|
|
FModifiedDbs := TStringList.Create;
|
|
FFindSeeResultSQL := TStringList.Create;
|
|
|
|
// Popup menu
|
|
Obj := TDBObject.Create(nil);
|
|
for dt:=lntTable to lntEvent do begin
|
|
Obj.NodeType := dt;
|
|
MenuItem := TMenuItem.Create(menuCheckByType);
|
|
MenuItem.Caption := _(Obj.ObjType+'s');
|
|
MenuItem.ImageIndex := Obj.ImageIndex;
|
|
MenuItem.OnClick := CheckAllClick;
|
|
MenuItem.Tag := Integer(dt);
|
|
menuCheckByType.Add(MenuItem);
|
|
end;
|
|
Obj.Free;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.FormShow(Sender: TObject);
|
|
var
|
|
Node, FirstChecked: PVirtualNode;
|
|
idx: Integer;
|
|
DBObj: TDBObject;
|
|
begin
|
|
// Restore GUI setup
|
|
Width := AppSettings.ReadIntDpiAware(asTableToolsWindowWidth, Self);
|
|
Height := AppSettings.ReadIntDpiAware(asTableToolsWindowHeight, Self);
|
|
TreeObjects.Width := AppSettings.ReadIntDpiAware(asTableToolsTreeWidth, Self);
|
|
|
|
// When this form is displayed the second time, databases may be deleted or filtered.
|
|
// Also, checked nodes must be unchecked and unchecked nodes may need to be checked.
|
|
TreeObjects.Clear;
|
|
TreeObjects.RootNodeCount := Mainform.DBtree.RootNodeCount;
|
|
|
|
FObjectSizes := 0;
|
|
|
|
// Init all objects in active database, so the tree does not just check the db node
|
|
// if we want the first child only. See issue #2267.
|
|
Node := MainForm.FindDBNode(TreeObjects, MainForm.ActiveConnection, MainForm.ActiveDatabase);
|
|
Node := TreeObjects.GetFirstChild(Node);
|
|
while Assigned(Node) do
|
|
Node := TreeObjects.GetNextSibling(Node);
|
|
for DBObj in PreSelectObjects do begin
|
|
Node := MainForm.FindDBObjectNode(TreeObjects, DBObj);
|
|
if Assigned(Node) then
|
|
TreeObjects.CheckState[Node] := csCheckedNormal;
|
|
end;
|
|
|
|
FirstChecked := TreeObjects.GetFirstChecked;
|
|
if Assigned(FirstChecked) then
|
|
SelectNode(TreeObjects, FirstChecked);
|
|
// CHECKSUM available since MySQL 4.1.1
|
|
idx := comboOperation.ItemIndex;
|
|
if idx = -1 then idx := 0;
|
|
comboOperation.Items.CommaText := 'Check,Analyze,Checksum,Optimize,Repair';
|
|
if Mainform.ActiveConnection.ServerVersionInt < 40101 then
|
|
comboOperation.Items.Text := StringReplace(comboOperation.Items.Text, 'Checksum', 'Checksum ('+_(SUnsupported)+')', [rfReplaceAll]);
|
|
comboOperation.ItemIndex := idx;
|
|
comboOperation.OnChange(Sender);
|
|
|
|
// Restore output option. Avoid server preselection to avoid unwanted connects.
|
|
// See issue #3411
|
|
idx := AppSettings.ReadInt(asExportSQLOutput);
|
|
if (idx = -1)
|
|
or (idx >= comboExportOutputType.Items.Count)
|
|
or StartsStr(OUTPUT_SERVER, comboExportOutputType.Items[idx])
|
|
then idx := 0;
|
|
comboExportOutputType.ItemIndex := idx;
|
|
comboExportOutputType.OnChange(Sender);
|
|
|
|
comboBulkTableEditDatabase.Items.Text := Mainform.ActiveConnection.AllDatabases.Text;
|
|
if comboBulkTableEditDatabase.Items.Count > 0 then
|
|
comboBulkTableEditDatabase.ItemIndex := 0;
|
|
|
|
comboBulkTableEditEngine.Items := MainForm.ActiveConnection.TableEngines;
|
|
if comboBulkTableEditEngine.Items.Count > 0 then
|
|
comboBulkTableEditEngine.ItemIndex := comboBulkTableEditEngine.Items.IndexOf(MainForm.ActiveConnection.TableEngineDefault);
|
|
|
|
comboBulkTableEditCollation.Items := MainForm.ActiveConnection.CollationList;
|
|
if comboBulkTableEditCollation.Items.Count > 0 then
|
|
comboBulkTableEditCollation.ItemIndex := 0;
|
|
|
|
comboBulkTableEditCharset.Items := MainForm.ActiveConnection.CharsetList;
|
|
if comboBulkTableEditCharset.Items.Count > 0 then
|
|
comboBulkTableEditCharset.ItemIndex := 0;
|
|
|
|
MainForm.SetupSynEditors(Self);
|
|
MainForm.SynCompletionProposal.AddEditor(SynMemoFindText);
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.menuCopyMysqldumpCommandClick(Sender: TObject);
|
|
var
|
|
BinPath, ConnectionArguments, FullCommand: String;
|
|
Arguments, DatabaseNames: TStringList;
|
|
Conn: TDBConnection;
|
|
SessionNode, DBNode: PVirtualNode;
|
|
DBObj: PDBObject;
|
|
begin
|
|
// Copy command line for use with mysqldump
|
|
Screen.Cursor := crHourGlass;
|
|
Conn := MainForm.ActiveConnection;
|
|
|
|
BinPath := AppSettings.ReadString(asMySQLBinaries);
|
|
if (not BinPath.IsEmpty) and (BinPath[Length(BinPath)] <> DirSep) then
|
|
BinPath := BinPath + DirSep;
|
|
BinPath := BinPath + IfThen(IsWine, 'mysqldump', 'mysqldump.exe');
|
|
|
|
ConnectionArguments := Conn.Parameters.GetExternalCliArguments(nil, nbUnset);
|
|
|
|
Arguments := TStringList.Create;
|
|
|
|
if chkExportDatabasesDrop.Checked then
|
|
Arguments.Add('--add-drop-database');
|
|
if not chkExportDatabasesCreate.Checked then
|
|
Arguments.Add('--no-create-db');
|
|
if chkExportTablesDrop.Checked then
|
|
Arguments.Add('--add-drop-table');
|
|
if not chkExportTablesCreate.Checked then
|
|
Arguments.Add('--no-create-info');
|
|
// Data output. No support for delete+insert - will just use inserts.
|
|
if comboExportData.Text = DATA_NO then
|
|
Arguments.Add('--no-data')
|
|
else if comboExportData.Text = DATA_UPDATE then
|
|
Arguments.Add('--replace')
|
|
else if comboExportData.Text = DATA_INSERTNEW then
|
|
Arguments.Add('--insert-ignore');
|
|
// Output file. No support for server, database and clipboard
|
|
if comboExportOutputType.Text = OUTPUT_FILE then
|
|
Arguments.Add('--result-file="'+comboExportOutputTarget.Text+'"')
|
|
else if comboExportOutputType.Text = OUTPUT_DIR then
|
|
Arguments.Add('--tab="'+comboExportOutputTarget.Text+'"');
|
|
|
|
// Use checked database names
|
|
DatabaseNames := TStringList.Create;
|
|
SessionNode := TreeObjects.GetFirstChild(nil);
|
|
while Assigned(SessionNode) do begin
|
|
DBNode := TreeObjects.GetFirstChild(SessionNode);
|
|
while Assigned(DBNode) do begin
|
|
if not (DBNode.CheckState in [csUncheckedNormal, csUncheckedPressed]) then begin
|
|
DBObj := TreeObjects.GetNodeData(DBNode);
|
|
DatabaseNames.Add(DBObj.Database);
|
|
// Todo: loop through tables?
|
|
// CheckedObjects := GetCheckedObjects(DBNode);
|
|
end;
|
|
DBNode := TreeObjects.GetNextSibling(DBNode);
|
|
end;
|
|
SessionNode := TreeObjects.GetNextSibling(SessionNode);
|
|
end;
|
|
// --databases or --all-databases can't be used with --tab
|
|
if comboExportOutputType.Text <> OUTPUT_DIR then
|
|
Arguments.Add('--databases ' + Implode(' ', DatabaseNames))
|
|
else
|
|
Arguments.Add(Implode(' ', DatabaseNames));
|
|
DatabaseNames.Free;
|
|
|
|
FullCommand := BinPath + ConnectionArguments + ' ' + Implode(' ', Arguments);
|
|
Arguments.Free;
|
|
|
|
Clipboard.AsText := FullCommand;
|
|
Screen.Cursor := crDefault;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.FormClose(Sender: TObject; var Action: TCloseAction);
|
|
begin
|
|
// Auto close temorary connection
|
|
if Assigned(FTargetConnection) then
|
|
FreeAndNil(FTargetConnection);
|
|
// Save GUI setup
|
|
AppSettings.WriteIntDpiAware(asTableToolsWindowWidth, Self, Width);
|
|
AppSettings.WriteIntDpiAware(asTableToolsWindowHeight, Self, Height);
|
|
AppSettings.WriteIntDpiAware(asTableToolsTreeWidth, Self, TreeObjects.Width);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.SaveSettings(Sender: TObject);
|
|
var
|
|
i: Integer;
|
|
Items: TStringList;
|
|
begin
|
|
case ToolMode of
|
|
tmFind: begin
|
|
AppSettings.WriteInt(asTableToolsFindTextTab, tabsTextType.ActivePageIndex);
|
|
AppSettings.WriteString(asTableToolsFindText, memoFindText.Text);
|
|
AppSettings.WriteString(asTableToolsFindSQL, SynMemoFindText.Text);
|
|
AppSettings.WriteInt(asTableToolsDatatype, comboDatatypes.ItemIndex);
|
|
AppSettings.WriteBool(asTableToolsFindCaseSensitive, chkCaseSensitive.Checked);
|
|
AppSettings.WriteInt(asTableToolsFindMatchType, comboMatchType.ItemIndex);
|
|
end;
|
|
|
|
tmSQLExport: begin
|
|
AppSettings.WriteBool(asExportSQLCreateDatabases, chkExportDatabasesCreate.Checked);
|
|
AppSettings.WriteBool(asExportSQLCreateTables, chkExportTablesCreate.Checked);
|
|
AppSettings.WriteInt(asExportSQLDataHow, comboExportData.ItemIndex);
|
|
if comboExportData.ItemIndex > 0 then
|
|
AppSettings.WriteInt(asExportSQLDataInsertSize, updownInsertSize.Position);
|
|
AppSettings.WriteBool(asExportSQLAddComments, menuExportAddComments.Checked);
|
|
AppSettings.WriteBool(asExportSQLRemoveAutoIncrement, menuExportRemoveAutoIncrement.Checked);
|
|
AppSettings.WriteBool(asExportSQLRemoveDefiner, menuExportRemoveDefiner.Checked);
|
|
|
|
if not StartsStr(OUTPUT_SERVER, comboExportOutputType.Text) then
|
|
AppSettings.WriteInt(asExportSQLOutput, comboExportOutputType.ItemIndex);
|
|
|
|
// Remove duplicates from recent file pulldown
|
|
if (comboExportOutputType.Text = OUTPUT_FILE)
|
|
or (comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED)
|
|
or (comboExportOutputType.Text = OUTPUT_DIR)
|
|
then begin
|
|
Items := TStringList.Create;
|
|
Items.Assign(comboExportOutputTarget.Items);
|
|
Items.Insert(0, comboExportOutputTarget.Text);
|
|
for i:=Items.Count-1 downto 1 do begin
|
|
if Items[i] = comboExportOutputTarget.Text then
|
|
Items.Delete(i);
|
|
end;
|
|
comboExportOutputTarget.Items.Assign(Items);
|
|
Items.Free;
|
|
end;
|
|
|
|
if comboExportOutputType.Text = OUTPUT_FILE then begin
|
|
AppSettings.WriteString(asExportSQLFilenames, comboExportOutputTarget.Items.Text);
|
|
end else if comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED then begin
|
|
AppSettings.WriteString(asExportZIPFilenames, comboExportOutputTarget.Items.Text);
|
|
end else if comboExportOutputType.Text = OUTPUT_DIR then begin
|
|
AppSettings.WriteString(asExportSQLDirectories, comboExportOutputTarget.Items.Text);
|
|
end else if comboExportOutputType.Text = OUTPUT_DB then begin
|
|
AppSettings.WriteString(asExportSQLDatabase, comboExportOutputTarget.Text);
|
|
end else if copy(comboExportOutputType.Text, 1, Length(OUTPUT_SERVER)) = OUTPUT_SERVER then begin
|
|
AppSettings.WriteString(asExportSQLServerDatabase, comboExportOutputTarget.Text);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.ValidateControls(Sender: TObject);
|
|
var
|
|
SomeChecked, OptionChecked, FindModeSQL: Boolean;
|
|
op: String;
|
|
i: Integer;
|
|
begin
|
|
// Fired after various user clicks, and also on implicit child node checking
|
|
SomeChecked := TreeObjects.CheckedCount > 0;
|
|
btnSeeResults.Visible := tabsTools.ActivePage = tabFind;
|
|
lblCheckedSize.Caption := f_('Selected objects size: %s', [FormatByteNumber(FObjectSizes)]);
|
|
if tabsTools.ActivePage = tabMaintenance then begin
|
|
btnExecute.Caption := _('Execute');
|
|
btnExecute.Enabled := (Pos(_(SUnsupported), comboOperation.Text) = 0) and SomeChecked;
|
|
// Only enable available options
|
|
op := LowerCase(comboOperation.Text);
|
|
chkQuick.Enabled := (op = 'check') or (op = 'checksum') or (op = 'repair');
|
|
chkFast.Enabled := op = 'check';
|
|
chkMedium.Enabled := op = 'check';
|
|
chkExtended.Enabled := (op = 'check') or (op = 'checksum') or (op = 'repair');
|
|
chkChanged.Enabled := op = 'check';
|
|
chkUseFrm.Enabled := op = 'repair';
|
|
chkForUpgrade.Enabled := op = 'check';
|
|
// CHECKSUM's options are mutually exclusive
|
|
if comboOperation.Text = 'Checksum' then begin
|
|
if (Sender = chkExtended) and chkExtended.Checked then chkQuick.Checked := False
|
|
else if chkQuick.Checked then chkExtended.Checked := False;
|
|
end;
|
|
end else if tabsTools.ActivePage = tabFind then begin
|
|
btnExecute.Caption := _('Find');
|
|
btnExecute.Enabled := SomeChecked;
|
|
FindModeSQL := tabsTextType.ActivePage = tabSQL;
|
|
chkCaseSensitive.Enabled := not FindModeSQL;
|
|
lblMatchType.Enabled := not FindModeSQL;
|
|
comboMatchType.Enabled := not FindModeSQL;
|
|
// Enable "See results" button if there were results
|
|
btnSeeResults.Enabled := False;
|
|
if Assigned(FResults) then for i:=0 to FResults.Count-1 do begin
|
|
if MakeInt(FResults[i][2]) > 0 then begin
|
|
btnSeeResults.Enabled := True;
|
|
break;
|
|
end;
|
|
end;
|
|
end else if tabsTools.ActivePage = tabSQLExport then begin
|
|
btnExecute.Caption := _('Export');
|
|
btnExecute.Enabled := SomeChecked and ((comboExportOutputTarget.Text <> '') or (not comboExportOutputTarget.Enabled));
|
|
lblInsertSize.Enabled := comboExportData.ItemIndex > 0;
|
|
editInsertSize.Enabled := lblInsertSize.Enabled;
|
|
updownInsertSize.Enabled := lblInsertSize.Enabled;
|
|
lblInsertSizeUnit.Enabled := lblInsertSize.Enabled;
|
|
end else if tabsTools.ActivePage = tabBulkTableEdit then begin
|
|
btnExecute.Caption := _('Update');
|
|
chkBulkTableEditCollation.Enabled := MainForm.ActiveConnection.IsUnicode;
|
|
chkBulkTableEditCharset.Enabled := MainForm.ActiveConnection.IsUnicode;
|
|
OptionChecked := chkBulkTableEditDatabase.Checked or chkBulkTableEditEngine.Checked or chkBulkTableEditCollation.Checked
|
|
or chkBulkTableEditCharset.Checked or chkBulkTableEditResetAutoinc.Checked;
|
|
btnExecute.Enabled := SomeChecked and OptionChecked;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
|
|
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
|
|
var ContentRect: TRect);
|
|
begin
|
|
MainForm.DBtreeBeforeCellPaint(Sender, TargetCanvas, Node, Column, CellPaintMode, CellRect, ContentRect);
|
|
end;
|
|
|
|
procedure TfrmTableTools.TreeObjectsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
begin
|
|
Mainform.DBtreeChange(Sender, Node);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsChecked(Sender: TBaseVirtualTree; Node: PVirtualNode);
|
|
var
|
|
Obj: PDBObject;
|
|
ObjSize: Int64;
|
|
begin
|
|
// Track sum of checked objects size
|
|
Obj := Sender.GetNodeData(Node);
|
|
ObjSize := Max(Obj.Size, 0);
|
|
if Node.CheckState in CheckedStates then
|
|
Inc(FObjectSizes, ObjSize)
|
|
else
|
|
Dec(FObjectSizes, ObjSize);
|
|
if Obj.NodeType = lntDb then
|
|
FillTargetDatabases;
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.FillTargetDatabases;
|
|
var
|
|
SessionNode, DBNode: PVirtualNode;
|
|
OldSelected: String;
|
|
begin
|
|
// Add unchecked databases
|
|
if comboExportOutputType.Text <> OUTPUT_DB then
|
|
Exit;
|
|
OldSelected := comboExportOutputTarget.Text;
|
|
comboExportOutputTarget.Items.Clear;
|
|
SessionNode := MainForm.GetRootNode(TreeObjects, MainForm.ActiveConnection);
|
|
DBNode := TreeObjects.GetFirstChild(SessionNode);
|
|
while Assigned(DBNode) do begin
|
|
if not (DBNode.CheckState in CheckedStates) then
|
|
comboExportOutputTarget.Items.Add(TreeObjects.Text[DBNode, 0]);
|
|
DBNode := TreeObjects.GetNextSibling(DBNode);
|
|
end;
|
|
comboExportOutputTarget.ItemIndex := comboExportOutputTarget.Items.IndexOf(OldSelected);
|
|
if comboExportOutputTarget.ItemIndex = -1 then
|
|
comboExportOutputTarget.ItemIndex := comboExportOutputTarget.Items.IndexOf(AppSettings.ReadString(asExportSQLDatabase));
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsChecking(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
var NewState: TCheckState; var Allowed: Boolean);
|
|
var
|
|
n: PVirtualNode;
|
|
begin
|
|
// Ensure to also toggle check state of not yet initialized nodes
|
|
Allowed := True;
|
|
// Weird fix: Just iterate through all sub nodes for implicit initialization. Without this
|
|
// loop a checkbox click on a parent node would only auto-check its visible children.
|
|
n := Sender.GetFirstChild(Node);
|
|
while Assigned(n) do
|
|
n := Sender.GetNextSibling(n);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsExpanded(Sender: TBaseVirtualTree;
|
|
Node: PVirtualNode);
|
|
begin
|
|
// Auto-resizes the 2nd/"size" column
|
|
TreeObjectsChange(Sender, Node);
|
|
end;
|
|
|
|
procedure TfrmTableTools.TreeObjectsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex);
|
|
begin
|
|
Mainform.DBtreeGetImageIndex(Sender, Node, Kind, Column, Ghosted, ImageIndex);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
|
|
begin
|
|
MainForm.DBtreeGetNodeDataSize(Sender, NodeDataSize);
|
|
end;
|
|
|
|
procedure TfrmTableTools.TreeObjectsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
|
|
begin
|
|
Mainform.DBtreeGetText(Sender, Node, Column, TextType, CellText);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
|
var ChildCount: Cardinal);
|
|
begin
|
|
Mainform.DBtreeInitChildren(Sender, Node, ChildCount);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
|
|
var InitialStates: TVirtualNodeInitStates);
|
|
begin
|
|
// Attach a checkbox to all nodes
|
|
Mainform.DBtreeInitNode(Sender, ParentNode, Node, InitialStates);
|
|
Node.CheckType := ctTriStateCheckBox;
|
|
Node.CheckState := csUncheckedNormal;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.TreeObjectsPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
|
|
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
|
|
begin
|
|
Mainform.DBtreePaintText(Sender, TargetCanvas, Node, Column, TextType);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.btnHelpMaintenanceClick(Sender: TObject);
|
|
begin
|
|
Mainform.CallSQLHelpWithKeyword(UpperCase(comboOperation.Text) + ' TABLE');
|
|
end;
|
|
|
|
|
|
function TfrmTableTools.GetCheckedObjects(DBNode: PVirtualNode): TDBObjectList;
|
|
var
|
|
Child, GrandChild: PVirtualNode;
|
|
ChildObj, GrandChildObj: PDBObject;
|
|
begin
|
|
// Return list with checked objects from database node
|
|
// The caller doesn't need to care whether type grouping in tree is activated
|
|
Result := TDBObjectList.Create(False);
|
|
Child := TreeObjects.GetFirstChild(DBNode);
|
|
while Assigned(Child) do begin
|
|
if Child.CheckState in CheckedStates then begin
|
|
ChildObj := TreeObjects.GetNodeData(Child);
|
|
|
|
case ChildObj.NodeType of
|
|
|
|
lntGroup: begin
|
|
GrandChild := TreeObjects.GetFirstChild(Child);
|
|
while Assigned(GrandChild) do begin
|
|
if GrandChild.CheckState in CheckedStates then begin
|
|
GrandChildObj := TreeObjects.GetNodeData(GrandChild);
|
|
Result.Add(GrandChildObj^);
|
|
end;
|
|
GrandChild := TreeObjects.GetNextSibling(GrandChild);
|
|
end;
|
|
end
|
|
|
|
else begin
|
|
Result.Add(ChildObj^);
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
Child := TreeObjects.GetNextSibling(Child);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.Execute(Sender: TObject);
|
|
var
|
|
SessionNode, DBNode: PVirtualNode;
|
|
CheckedObjects, Triggers, Views: TDBObjectList;
|
|
DBObj: TDBObject;
|
|
i: Integer;
|
|
Conn: TDBConnection;
|
|
FileName, FileNameZip, FileNameInZip: TFileName;
|
|
Zip: TZipFile;
|
|
StartTime: Cardinal;
|
|
LogRow: TStringlist;
|
|
|
|
procedure ProcessNode(DBObj: TDBObject);
|
|
begin
|
|
try
|
|
case FToolMode of
|
|
tmMaintenance: DoMaintenance(DBObj);
|
|
tmFind: DoFind(DBObj);
|
|
tmSQLExport: DoExport(DBObj);
|
|
tmBulkTableEdit: DoBulkTableEdit(DBObj);
|
|
end;
|
|
except
|
|
on E:EDbError do begin
|
|
// The above SQL can easily throw an exception, e.g. if a table is corrupted.
|
|
// In such cases we create a dummy row, including the error message
|
|
AddNotes(DBObj, 'error', E.Message);
|
|
// Cancel further processing on critical errors
|
|
if E.ErrorCode = 1049 then begin // "Unknown database"
|
|
ErrorDialog(E.Message);
|
|
FCancelled := True;
|
|
end;
|
|
end;
|
|
on E:EFCreateError do begin
|
|
// Occurs when export output file can not be created
|
|
ErrorDialog(E.Message);
|
|
FCancelled := True;
|
|
end;
|
|
on E:EInOutError do begin
|
|
// ForceDirectories failed with "Unable to create directory."
|
|
ErrorDialog(E.Message);
|
|
FCancelled := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Screen.Cursor := crHourGlass;
|
|
// Disable critical controls so ProcessMessages is unable to do things while export is in progress
|
|
btnExecute.Enabled := False;
|
|
btnCloseOrCancel.Caption := _('Cancel');
|
|
btnCloseOrCancel.ModalResult := mrNone;
|
|
tabsTools.Enabled := False;
|
|
treeObjects.Enabled := False;
|
|
if tabsTools.ActivePage = tabMaintenance then
|
|
FToolMode := tmMaintenance
|
|
else if tabsTools.ActivePage = tabFind then
|
|
FToolMode := tmFind
|
|
else if tabsTools.ActivePage = tabSQLExport then
|
|
FToolMode := tmSQLExport
|
|
else if tabsTools.ActivePage = tabBulkTableEdit then
|
|
FToolMode := tmBulkTableEdit;
|
|
ResultGrid.Clear;
|
|
FResults.Clear;
|
|
FFindSeeResultSQL.Clear;
|
|
Triggers := TDBObjectList.Create(False); // False, so we can .Free that object afterwards without loosing the contained objects
|
|
Views := TDBObjectList.Create(False);
|
|
FHeaderCreated := False;
|
|
FCancelled := False;
|
|
|
|
FObjectSizesDone := 0;
|
|
FObjectSizesDoneExact := 0;
|
|
MainForm.EnableProgress(100);
|
|
FStartTimeAll := GetTickCount;
|
|
|
|
SessionNode := TreeObjects.GetFirstChild(nil);
|
|
while Assigned(SessionNode) do begin
|
|
DBNode := TreeObjects.GetFirstChild(SessionNode);
|
|
while Assigned(DBNode) do begin
|
|
if not (DBNode.CheckState in [csUncheckedNormal, csUncheckedPressed]) then begin
|
|
Triggers.Clear;
|
|
Views.Clear;
|
|
FSecondExportPass := False;
|
|
CheckedObjects := GetCheckedObjects(DBNode);
|
|
for DBObj in CheckedObjects do begin
|
|
// Triggers have to be exported at the very end
|
|
if (FToolMode = tmSQLExport) and (DBObj.NodeType = lntTrigger) then
|
|
Triggers.Add(DBObj)
|
|
else begin
|
|
ProcessNode(DBObj);
|
|
FObjectSizesDone := FObjectSizesDone + Max(DBObj.Size, 0);
|
|
FObjectSizesDoneExact := FObjectSizesDone;
|
|
if (FToolMode = tmSQLExport) and (DBObj.NodeType = lntView) then
|
|
Views.Add(DBObj);
|
|
end;
|
|
// File creation exception occurred or user clicked cancel button
|
|
if FCancelled then Break;
|
|
end; // End of db object node loop in db
|
|
|
|
// Special block for late created triggers in export mode
|
|
for i:=0 to Triggers.Count-1 do begin
|
|
ProcessNode(Triggers[i]);
|
|
if FCancelled then Break;
|
|
end;
|
|
|
|
// Special block for exporting final view structure
|
|
FSecondExportPass := True;
|
|
for i:=0 to Views.Count-1 do begin
|
|
ProcessNode(Views[i]);
|
|
if FCancelled then Break;
|
|
end;
|
|
|
|
end;
|
|
if FCancelled then Break;
|
|
DBNode := TreeObjects.GetNextSibling(DBNode);
|
|
end; // End of db item loop
|
|
if FCancelled then Break;
|
|
SessionNode := TreeObjects.GetNextSibling(SessionNode);
|
|
end;
|
|
|
|
Conn := Mainform.ActiveConnection;
|
|
|
|
if Assigned(ExportStream) then begin
|
|
// For output to file or directory:
|
|
Output(EXPORT_FILE_FOOTER, False, True, False, False, False);
|
|
// For direct output to database or server:
|
|
Output('/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */', True, False, False, True, True);
|
|
Output('/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '''') */', True, False, False, True, True);
|
|
Output('/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */', True, False, False, True, True);
|
|
Output('/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, ''system'') */', True, False, False, True, True);
|
|
if comboExportOutputType.Text = OUTPUT_CLIPBOARD then
|
|
StreamToClipboard(ExportStream, nil, false);
|
|
|
|
if comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED then
|
|
FileName := TFileStream(ExportStream).FileName;
|
|
FreeAndNil(ExportStream);
|
|
|
|
if comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED then begin
|
|
AddNotes('', '', _('Compressing')+'...', '');
|
|
StartTime := GetTickCount;
|
|
FileNameZip := FExportFileName;
|
|
if FileExists(FileNameZip) then
|
|
DeleteFileWithUndo(FileNameZip);
|
|
Zip := TZipFile.Create;
|
|
Zip.Open(FileNameZip, zmWrite);
|
|
FileNameInZip := ExtractFileName(ChangeFileExt(FileNameZip, '.sql'));
|
|
Zip.Add(FileName, FileNameInZip);
|
|
Zip.Close;
|
|
DeleteFile(FileName);
|
|
LogRow := FResults.Last;
|
|
LogRow[2] := _('Compressing done.');
|
|
LogRow[3] := FormatTimeNumber((GetTickCount-StartTime) / 1000, True);
|
|
UpdateResultGrid;
|
|
end;
|
|
|
|
// Activate ansi mode or whatever again, locally
|
|
Conn.Query('/*!40101 SET SQL_MODE=IFNULL(@OLD_LOCAL_SQL_MODE, '''') */');
|
|
// Reset timezone for reading to previous value
|
|
Conn.Query('/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, ''system'') */');
|
|
end;
|
|
ExportLastDatabase := '';
|
|
|
|
for i:=0 to FModifiedDbs.Count-1 do begin
|
|
Conn.ClearDbObjects(FModifiedDbs[i]);
|
|
DBNode := MainForm.FindDBNode(TreeObjects, Conn, FModifiedDbs[i]);
|
|
TreeObjects.ReinitNode(DBNode, False);
|
|
TreeObjects.ReinitChildren(DBNode, False)
|
|
end;
|
|
FModifiedDbs.Clear;
|
|
|
|
AddNotes('', '', f_('%s finished', [tabsTools.ActivePage.Caption]), '');
|
|
btnCloseOrCancel.Caption := _('Close');
|
|
btnCloseOrCancel.ModalResult := mrCancel;
|
|
MainForm.ShowStatusMsg;
|
|
MainForm.DisableProgress;
|
|
tabsTools.Enabled := True;
|
|
treeObjects.Enabled := True;
|
|
ValidateControls(Sender);
|
|
SaveSettings(Sender);
|
|
Screen.Cursor := crDefault;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.DoMaintenance(DBObj: TDBObject);
|
|
var
|
|
SQL: String;
|
|
begin
|
|
if not (DBObj.NodeType in [lntTable, lntView]) then begin
|
|
AddNotes(DBObj, STRSKIPPED+'a '+LowerCase(DBObj.ObjType)+' cannot be maintained.', '');
|
|
Exit;
|
|
end;
|
|
SQL := UpperCase(comboOperation.Text) + ' TABLE ' + DBObj.QuotedDatabase + '.' + DBObj.QuotedName;
|
|
if chkQuick.Enabled and chkQuick.Checked then SQL := SQL + ' QUICK';
|
|
if chkFast.Enabled and chkFast.Checked then SQL := SQL + ' FAST';
|
|
if chkMedium.Enabled and chkMedium.Checked then SQL := SQL + ' MEDIUM';
|
|
if chkExtended.Enabled and chkExtended.Checked then SQL := SQL + ' EXTENDED';
|
|
if chkChanged.Enabled and chkChanged.Checked then SQL := SQL + ' CHANGED';
|
|
if chkUseFrm.Enabled and chkUseFrm.Checked then SQL := SQL + ' USE_FRM';
|
|
if chkForUpgrade.Enabled and chkForUpgrade.Checked then SQL := SQL + ' FOR UPGRADE';
|
|
AddResults(SQL, DBObj.Connection);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.DoFind(DBObj: TDBObject);
|
|
var
|
|
Columns: TTableColumnList;
|
|
Col: TTableColumn;
|
|
SQL, Column, RoutineDefinitionColumn, RoutineSchemaColumn, FindText, FindTextJokers: String;
|
|
IsRegExp: Boolean;
|
|
|
|
function esc(Value: String): String;
|
|
begin
|
|
Result := DBObj.Connection.EscapeString(Value);
|
|
end;
|
|
begin
|
|
FFindSeeResultSQL.Add('');
|
|
|
|
FindText := memoFindText.Text;
|
|
case comboMatchType.ItemIndex of
|
|
0,4: FindTextJokers := '%'+FindText+'%'; // Used as wildcard for regex on MSSQL
|
|
1: FindTextJokers := FindText;
|
|
2: FindTextJokers := '%'+FindText;
|
|
3: FindTextJokers := FindText+'%';
|
|
end;
|
|
IsRegExp := comboMatchType.ItemIndex = 4;
|
|
|
|
RoutineDefinitionColumn := DBObj.Connection.QuoteIdent('routine_definition');
|
|
if not chkCaseSensitive.Checked then begin
|
|
FindText := LowerCase(FindText);
|
|
FindTextJokers := LowerCase(FindTextJokers);
|
|
RoutineDefinitionColumn := 'LOWER('+RoutineDefinitionColumn+')';
|
|
if DBObj.Connection.Parameters.IsAnySQLite then begin
|
|
DBObj.Connection.Query('PRAGMA case_sensitive_like=FALSE');
|
|
end;
|
|
end else begin
|
|
if DBObj.Connection.Parameters.IsAnySQLite then begin
|
|
DBObj.Connection.Query('PRAGMA case_sensitive_like=TRUE');
|
|
end;
|
|
end;
|
|
RoutineSchemaColumn := 'routine_schema';
|
|
if DBObj.Connection.Parameters.IsAnyMSSQL then
|
|
RoutineSchemaColumn := 'routine_catalog';
|
|
|
|
Columns := TTableColumnList.Create(True);
|
|
case DBObj.NodeType of
|
|
lntTable, lntView: Columns := DBObj.TableColumns;
|
|
lntProcedure, lntFunction: ;
|
|
// TODO: Triggers + Events
|
|
else AddNotes(DBObj, STRSKIPPED+'a '+LowerCase(DBObj.ObjType)+' does not contain rows.', '');
|
|
end;
|
|
case DBObj.NodeType of
|
|
lntTable, lntView: begin
|
|
if Columns.Count > 0 then begin
|
|
SQL := '';
|
|
for Col in Columns do begin
|
|
Column := DBObj.Connection.QuoteIdent(Col.Name);
|
|
if (comboDatatypes.ItemIndex = 0) or (Integer(Col.DataType.Category) = comboDatatypes.ItemIndex-1) then begin
|
|
|
|
if tabsTextType.ActivePage = tabSQL then begin
|
|
SQL := SQL + Column + ' ' + SynMemoFindText.Text + ' OR ';
|
|
|
|
end else if (Col.DataType.Category in [dtcInteger, dtcReal]) and (comboMatchType.ItemIndex=1) then begin
|
|
// Search numbers
|
|
SQL := SQL + Column + '=' + UnformatNumber(FindText) + ' OR ';
|
|
|
|
end else if chkCaseSensitive.Checked then begin
|
|
// Search case sensitive
|
|
case DBObj.Connection.Parameters.NetTypeGroup of
|
|
ngMySQL: begin
|
|
if IsRegExp then
|
|
SQL := SQL + Column + ' REGEXP BINARY ' + esc(FindText) + ' OR '
|
|
else
|
|
SQL := SQL + Column + ' LIKE BINARY ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
ngMSSQL:
|
|
SQL := SQL + Column+' LIKE ' + esc(FindTextJokers) + ' COLLATE SQL_Latin1_General_CP1_CS_AS OR ';
|
|
ngPgSQL: begin
|
|
if IsRegExp then
|
|
SQL := SQL + 'CAST(' + Column + ' AS TEXT) SIMILAR TO ' + esc(FindTextJokers) + ' OR '
|
|
else
|
|
SQL := SQL + 'CAST(' + Column + ' AS TEXT) LIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
ngSQLite: begin
|
|
if IsRegExp then
|
|
SQL := SQL + Column + ' REGEXP ' + esc(FindText) + ' OR '
|
|
else
|
|
SQL := SQL + Column + ' LIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
end;
|
|
|
|
end else begin
|
|
// Search case insensitive
|
|
case DBObj.Connection.Parameters.NetTypeGroup of
|
|
ngMySQL: begin
|
|
if IsRegExp then
|
|
SQL := SQL + 'LOWER(CONVERT('+Column+' USING '+DBObj.Connection.CharacterSet+')) REGEXP ' + esc(FindText) + ' OR '
|
|
else
|
|
SQL := SQL + 'LOWER(CONVERT('+Column+' USING '+DBObj.Connection.CharacterSet+')) LIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
ngMSSQL: begin
|
|
SQL := SQL + 'LOWER('+Column+') LIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
ngPgSQL: begin
|
|
if IsRegExp then
|
|
SQL := SQL + 'LOWER(CAST('+Column+' AS TEXT)) SIMILAR TO ' + esc(FindTextJokers) + ' OR '
|
|
else
|
|
SQL := SQL + 'CAST('+Column+' AS TEXT) ILIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
ngSQLite: begin
|
|
if IsRegExp then
|
|
SQL := SQL + Column + ' REGEXP ' + esc(FindText) + ' OR '
|
|
else
|
|
SQL := SQL + Column + ' LIKE ' + esc(FindTextJokers) + ' OR ';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end; // end of loop over columns
|
|
|
|
if SQL <> '' then begin
|
|
Delete(SQL, Length(SQL)-3, 3);
|
|
FFindSeeResultSQL[FFindSeeResultSQL.Count-1] := 'SELECT * FROM '+DBObj.QuotedDatabase+'.'+DBObj.QuotedName+' WHERE ' + SQL;
|
|
case DBObj.Connection.Parameters.NetTypeGroup of
|
|
ngMySQL, ngPgSQL:
|
|
SQL := 'SELECT '''+DBObj.Database+''' AS '+DBObj.Connection.QuoteIdent('Database')+', '''+DBObj.Name+''' AS '+DBObj.Connection.QuoteIdent('Table')+', COUNT(*) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '
|
|
+ 'CONCAT(ROUND(100 / '+IntToStr(Max(DBObj.Rows,1))+' * COUNT(*), 1), ''%'') AS '+DBObj.Connection.QuoteIdent('Relevance')+' FROM '+DBObj.QuotedDatabase+'.'+DBObj.QuotedName+' WHERE '
|
|
+ SQL;
|
|
ngMSSQL:
|
|
SQL := 'SELECT '''+DBObj.Database+''' AS '+DBObj.Connection.QuoteIdent('Database')+', '''+DBObj.Name+''' AS '+DBObj.Connection.QuoteIdent('Table')+', COUNT(*) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '
|
|
+ 'CONVERT(VARCHAR(10), ROUND(100 / '+IntToStr(Max(DBObj.Rows,1))+' * COUNT(*), 1)) + ''%'' AS '+DBObj.Connection.QuoteIdent('Relevance')+' FROM '+DBObj.QuotedDatabase+'.'+DBObj.QuotedName+' WHERE '
|
|
+ SQL;
|
|
ngSQLite:
|
|
SQL := 'SELECT '''+DBObj.Database+''' AS '+DBObj.Connection.QuoteIdent('Database')+', '''+DBObj.Name+''' AS '+DBObj.Connection.QuoteIdent('Table')+', COUNT(*) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '
|
|
+ '(ROUND(100 / '+IntToStr(Max(DBObj.Rows,1))+' * COUNT(*), 1) || ''%'') AS '+DBObj.Connection.QuoteIdent('Relevance')+' FROM '+DBObj.QuotedDatabase+'.'+DBObj.QuotedName+' WHERE '
|
|
+ SQL;
|
|
end;
|
|
AddResults(SQL, DBObj.Connection);
|
|
end else begin
|
|
// Prefer a normal log line, so the "Found rows" column has a number, to fix wrong sorting
|
|
//AddNotes(DBObj, f_('%s%s doesn''t have columns of selected type (%s).', [STRSKIPPED, DBObj.ObjType, comboDatatypes.Text]), '');
|
|
AddNotes(DBObj.Database, DBObj.Name, '0', '0%');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
lntProcedure, lntFunction: begin
|
|
SQL := 'SELECT '+
|
|
esc(DBObj.Database)+' AS '+DBObj.Connection.QuoteIdent('Database')+', '+
|
|
esc(DBObj.Name)+' AS '+DBObj.Connection.QuoteIdent('Table')+', '+
|
|
DBObj.Connection.GetSQLSpecifity(spFuncCeil)+'(('+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'('+RoutineDefinitionColumn+') - '+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'(REPLACE('+RoutineDefinitionColumn+', '+esc(FindText)+', '+esc('')+'))) / '+DBObj.Connection.GetSQLSpecifity(spFuncLength)+'('+esc(FindText)+')) AS '+DBObj.Connection.QuoteIdent('Found rows')+', '+
|
|
'0 AS '+DBObj.Connection.QuoteIdent('Relevance')+
|
|
'FROM '+DBObj.Connection.QuoteIdent(DBObj.Connection.InfSch)+'.'+DBObj.Connection.QuoteIdent('routines')+' '+
|
|
'WHERE '+DBObj.Connection.QuoteIdent(RoutineSchemaColumn)+'='+esc(DBObj.Database)+' AND '+DBObj.Connection.QuoteIdent('routine_name')+'='+esc(DBObj.Name);
|
|
AddResults(SQL, DBObj.Connection);
|
|
end;
|
|
|
|
end;
|
|
|
|
Columns.Free;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.btnSeeResultsClick(Sender: TObject);
|
|
var
|
|
SQL: String;
|
|
i: Integer;
|
|
Tab: TQueryTab;
|
|
begin
|
|
// "See results" clicked - auto create new query tab, and execute a batch of SELECT queries
|
|
SQL := '';
|
|
for i:=0 to FResults.Count-1 do begin
|
|
if MakeInt(FResults[i][2]) > 0 then begin
|
|
SQL := SQL + FFindSeeResultSQL[i] + ';' + CRLF;
|
|
end;
|
|
end;
|
|
MainForm.actNewQueryTab.Execute;
|
|
Tab := MainForm.QueryTabs[MainForm.QueryTabs.Count-1];
|
|
Tab.Memo.Text := SQL;
|
|
Tab.TabSheet.Show;
|
|
MainForm.actExecuteQueryExecute(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.AddResults(SQL: String; Connection: TDBConnection);
|
|
var
|
|
i: Integer;
|
|
Row: TStringList;
|
|
Results: TDBQuery;
|
|
Value: String;
|
|
begin
|
|
// Execute query and append results into grid
|
|
Results := Connection.GetResults(SQL);
|
|
if Results = nil then
|
|
Exit;
|
|
|
|
SetupResultGrid(Results);
|
|
Results.First;
|
|
while not Results.Eof do begin
|
|
Row := TStringList.Create;
|
|
for i:=0 to Results.ColumnCount-1 do begin
|
|
Value := Results.Col(i);
|
|
if Results.DataType(i).Category = dtcInteger then begin
|
|
if MakeFloat(Value) >= 0 then
|
|
Row.Add(FormatNumber(Value))
|
|
else
|
|
Row.Add('');
|
|
end else
|
|
Row.Add(Value);
|
|
end;
|
|
FResults.Add(Row);
|
|
Results.Next;
|
|
end;
|
|
Results.Free;
|
|
|
|
UpdateResultGrid;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.AddNotes(Col1, Col2, Col3, Col4: String);
|
|
var
|
|
Row: TStringList;
|
|
begin
|
|
// Adds a row with non SQL results
|
|
SetupResultGrid;
|
|
Row := TStringList.Create;
|
|
Row.Add(Col1);
|
|
Row.Add(Col2);
|
|
Row.Add(Col3);
|
|
Row.Add(Col4);
|
|
FResults.Add(Row);
|
|
UpdateResultGrid;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.AddNotes(DBObject: TDBObject; Msg1, Msg2: String);
|
|
begin
|
|
AddNotes(DBObject.Database, DBObject.Name, Msg1, Msg2);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.SetupResultGrid(Results: TDBQuery=nil);
|
|
var
|
|
ColCount, i: Integer;
|
|
Col: TVirtualTreeColumn;
|
|
begin
|
|
if Assigned(Results) then begin
|
|
ColCount := Results.ColumnCount;
|
|
ResultGrid.Header.Options := ResultGrid.Header.Options + [hoVisible];
|
|
end else begin
|
|
ColCount := 4;
|
|
// Remove column headers if this is the first row
|
|
if FResults.Count = 0 then
|
|
ResultGrid.Header.Options := ResultGrid.Header.Options - [hoVisible];
|
|
end;
|
|
|
|
// Add missing columns
|
|
for i:=ResultGrid.Header.Columns.Count to ColCount-1 do begin
|
|
Col := ResultGrid.Header.Columns.Add;
|
|
Col.Width := 130;
|
|
end;
|
|
// Remove superfluous columns
|
|
for i:=ResultGrid.Header.Columns.Count-1 downto ColCount do
|
|
ResultGrid.Header.Columns[i].Free;
|
|
|
|
// Set column header names
|
|
for i:=0 to ResultGrid.Header.Columns.Count-1 do begin
|
|
Col := ResultGrid.Header.Columns[i];
|
|
if Assigned(Results) then begin
|
|
Col.Text := Results.ColumnNames[i];
|
|
if Results.DataType(i).Category in [dtcInteger, dtcReal] then
|
|
Col.Alignment := taRightJustify
|
|
else
|
|
Col.Alignment := taLeftJustify;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.UpdateResultGrid;
|
|
var
|
|
Percent: Double;
|
|
begin
|
|
// Refresh resultgrid
|
|
ResultGrid.RootNodeCount := FResults.Count;
|
|
ResultGrid.FocusedNode := ResultGrid.GetLast;
|
|
ResultGrid.Selected[ResultGrid.FocusedNode] := True;
|
|
Percent := 100 / Max(FObjectSizes,1) * FObjectSizesDoneExact;
|
|
Percent := Min(Percent, 100);
|
|
lblCheckedSize.Caption := f_('Selected objects size: %s', [FormatByteNumber(FObjectSizes)]) + '. ' +
|
|
f_('%s%% done', [FormatNumber(Percent, 1)]) + '.';
|
|
MainForm.SetProgressPosition(Round(Percent));
|
|
MainForm.ShowStatusMsg(Format(StatusMsg, [tabsTools.ActivePage.Caption, FormatTimeNumber((GetTickCount-FStartTimeAll)/1000, True)]));
|
|
ResultGrid.Header.AutoFitColumns(False);
|
|
Application.ProcessMessages;
|
|
end;
|
|
|
|
procedure TfrmTableTools.ResultGridCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode;
|
|
Column: TColumnIndex; var Result: Integer);
|
|
begin
|
|
Mainform.AnyGridCompareNodes(Sender, Node1, Node2, Column, Result);
|
|
end;
|
|
|
|
procedure TfrmTableTools.ResultGridGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
|
|
begin
|
|
NodeDataSize := SizeOf(TStringList);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.ResultGridInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
|
|
var InitialStates: TVirtualNodeInitStates);
|
|
var
|
|
Data: ^TStringList;
|
|
begin
|
|
// Bind string list to node
|
|
Data := Sender.GetNodeData(Node);
|
|
Data^ := FResults[Node.Index];
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.ResultGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
|
|
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
|
|
var
|
|
VT: TVirtualStringTree;
|
|
Msg: String;
|
|
begin
|
|
// Red text color for errors, purple for notes, grey for skipped tables
|
|
if not (vsSelected in Node.States) then begin
|
|
VT := Sender as TVirtualStringTree;
|
|
Msg := VT.Text[Node, 2];
|
|
if LowerCase(Msg) = 'note' then
|
|
TargetCanvas.Font.Color := clPurple
|
|
else if LowerCase(Msg) = 'error' then
|
|
TargetCanvas.Font.Color := clRed
|
|
else if Pos(STRSKIPPED, Msg) > 0 then
|
|
TargetCanvas.Font.Color := GetThemeColor(clGrayText);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrmTableTools.ResultGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
|
|
TextType: TVSTTextType; var CellText: String);
|
|
var
|
|
Data: ^TStringList;
|
|
begin
|
|
if Column > NoColumn then begin
|
|
Data := Sender.GetNodeData(Node);
|
|
if Data^.Count > Column then
|
|
CellText := Data^[Column]
|
|
else
|
|
CellText := '';
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.ResultGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
|
|
begin
|
|
// Header column clicked to sort
|
|
Mainform.AnyGridHeaderClick(Sender, HitInfo);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.comboExportOutputTypeChange(Sender: TObject);
|
|
var
|
|
SessionName, FilenameHint: String;
|
|
Params: TConnectionParameters;
|
|
Placeholders: TStringList;
|
|
i: Integer;
|
|
begin
|
|
// Target type (file, directory, ...) selected
|
|
comboExportOutputTarget.Enabled := True;
|
|
comboExportOutputTarget.Text := '';
|
|
comboExportOutputTarget.Hint := '';
|
|
|
|
// Create filename placeholders hint
|
|
Placeholders := GetOutputFilenamePlaceholders;
|
|
FilenameHint := _('Allows the following replacement patterns:');
|
|
for i:=0 to Placeholders.Count-1 do begin
|
|
FilenameHint := FilenameHint + CRLF + '%' + Placeholders.Names[i] + ': ' + Placeholders.ValueFromIndex[i];
|
|
end;
|
|
Placeholders.Free;
|
|
|
|
if Assigned(FTargetConnection) then
|
|
FreeAndNil(FTargetConnection);
|
|
if (comboExportOutputType.Text = OUTPUT_FILE)
|
|
or (comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED) then begin
|
|
comboExportOutputTarget.Style := csDropDown;
|
|
comboExportOutputTarget.Hint := FilenameHint;
|
|
if comboExportOutputType.Text = OUTPUT_FILE then
|
|
comboExportOutputTarget.Items.Text := AppSettings.ReadString(asExportSQLFilenames, '')
|
|
else
|
|
comboExportOutputTarget.Items.Text := AppSettings.ReadString(asExportZIPFilenames, '');
|
|
if comboExportOutputTarget.Items.Count > 0 then
|
|
comboExportOutputTarget.ItemIndex := 0;
|
|
lblExportOutputTarget.Caption := _('Filename')+':';
|
|
btnExportOutputTargetSelect.Enabled := True;
|
|
btnExportOutputTargetSelect.ImageIndex := 51;
|
|
end else if comboExportOutputType.Text = OUTPUT_DIR then begin
|
|
comboExportOutputTarget.Style := csDropDown;
|
|
comboExportOutputTarget.Hint := FilenameHint;
|
|
comboExportOutputTarget.Items.Text := AppSettings.ReadString(asExportSQLDirectories, '');
|
|
if comboExportOutputTarget.Items.Count > 0 then
|
|
comboExportOutputTarget.ItemIndex := 0;
|
|
lblExportOutputTarget.Caption := _('Directory')+':';
|
|
btnExportOutputTargetSelect.Enabled := True;
|
|
btnExportOutputTargetSelect.ImageIndex := 51;
|
|
end else if comboExportOutputType.Text = OUTPUT_CLIPBOARD then begin
|
|
comboExportOutputTarget.Enabled := False;
|
|
comboExportOutputTarget.Items.Clear;
|
|
lblExportOutputTarget.Caption := '';
|
|
btnExportOutputTargetSelect.Enabled := False;
|
|
btnExportOutputTargetSelect.ImageIndex := 4;
|
|
end else if comboExportOutputType.Text = OUTPUT_DB then begin
|
|
comboExportOutputTarget.Style := csDropDownList;
|
|
lblExportOutputTarget.Caption := _('Database')+':';
|
|
btnExportOutputTargetSelect.Enabled := False;
|
|
btnExportOutputTargetSelect.ImageIndex := 27;
|
|
FillTargetDatabases;
|
|
end else begin
|
|
// Server selected. Display databases in below dropdown
|
|
comboExportOutputTarget.Style := csDropDownList;
|
|
lblExportOutputTarget.Caption := _('Database')+':';
|
|
btnExportOutputTargetSelect.Enabled := False;
|
|
btnExportOutputTargetSelect.ImageIndex := 27;
|
|
SessionName := Copy(comboExportOutputType.Text, Length(OUTPUT_SERVER)+1, Length(comboExportOutputType.Text));
|
|
FreeAndNil(FTargetConnection);
|
|
Params := TConnectionParameters.Create(SessionName);
|
|
FTargetConnection := Params.CreateConnection(Self);
|
|
FTargetConnection.LogPrefix := SessionName;
|
|
FTargetConnection.OnLog := Mainform.LogSQL;
|
|
Screen.Cursor := crHourglass;
|
|
try
|
|
FTargetConnection.Active := True;
|
|
comboExportOutputTarget.Items := FTargetConnection.AllDatabases;
|
|
comboExportOutputTarget.Items.Insert(0, '['+_('Same as on source server')+']');
|
|
comboExportOutputTarget.ItemIndex := comboExportOutputTarget.Items.IndexOf(AppSettings.ReadString(asExportSQLServerDatabase));
|
|
if comboExportOutputTarget.ItemIndex = -1 then
|
|
comboExportOutputTarget.ItemIndex := 0;
|
|
Screen.Cursor := crDefault;
|
|
except
|
|
on E:EDbError do begin
|
|
Screen.Cursor := crDefault;
|
|
ErrorDialog(E.Message);
|
|
comboExportOutputType.ItemIndex := FLastOutputSelectedIndex;
|
|
comboExportOutputType.OnChange(Sender);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
FLastOutputSelectedIndex := comboExportOutputType.ItemIndex;
|
|
chkExportDatabasesCreate.Enabled :=
|
|
(comboExportOutputType.Text = OUTPUT_FILE)
|
|
or (comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED)
|
|
or (comboExportOutputType.Text = OUTPUT_CLIPBOARD)
|
|
or (Copy(comboExportOutputType.Text, 1, Length(OUTPUT_SERVER)) = OUTPUT_SERVER);
|
|
chkExportDatabasesDrop.Enabled := chkExportDatabasesCreate.Enabled;
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.comboExportOutputTargetChange(Sender: TObject);
|
|
begin
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.chkExportOptionClick(Sender: TObject);
|
|
procedure WarnIfChecked(chk: TCheckBox; LabelText: String);
|
|
begin
|
|
if chk.Checked then begin
|
|
chk.Caption := LabelText + '!!';
|
|
chk.Font.Style := chk.Font.Style + [fsBold];
|
|
end else begin
|
|
chk.Caption := LabelText;
|
|
chk.Font.Style := Font.Style;
|
|
end;
|
|
end;
|
|
begin
|
|
if (Sender = chkExportDatabasesDrop) and chkExportDatabasesDrop.Checked then
|
|
chkExportDatabasesCreate.Checked := True
|
|
else if (Sender = chkExportDatabasesCreate) and (not chkExportDatabasesCreate.Checked) then
|
|
chkExportDatabasesDrop.Checked := False
|
|
else if (Sender = chkExportTablesDrop) and chkExportTablesDrop.Checked then
|
|
chkExportTablesCreate.Checked := True
|
|
else if (Sender = chkExportTablesCreate) and (not chkExportTablesCreate.Checked) then
|
|
chkExportTablesDrop.Checked := False;
|
|
WarnIfChecked(chkExportDatabasesDrop, _('Drop'));
|
|
WarnIfChecked(chkExportTablesDrop, _('Drop'));
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.btnCloseOrCancelClick(Sender: TObject);
|
|
begin
|
|
// Set cancel flag to stop running loop in the next possible loop position
|
|
if TButton(Sender).ModalResult = mrNone then begin
|
|
FCancelled := True;
|
|
Mainform.LogSQL(_('Processing cancelled by user, waiting for current object to finish ...'), lcInfo);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.btnExportOptionsClick(Sender: TObject);
|
|
var
|
|
btn: TButton;
|
|
begin
|
|
btn := Sender as TButton;
|
|
btn.DropDownMenu.Popup(btn.ClientOrigin.X, btn.ClientOrigin.Y+btn.Height);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.btnExportOutputTargetSelectClick(Sender: TObject);
|
|
var
|
|
SaveDialog: TSaveDialog;
|
|
Browse: TBrowseForFolder;
|
|
begin
|
|
if (comboExportOutputType.Text = OUTPUT_FILE) or (comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED) then begin
|
|
// Select filename
|
|
SaveDialog := TSaveDialog.Create(Self);
|
|
SaveDialog.DefaultExt := 'sql';
|
|
if comboExportOutputType.Text = OUTPUT_FILE then
|
|
SaveDialog.Filter := _('SQL files')+' (*.sql)|*.sql|'+_('All files')+' (*.*)|*.*'
|
|
else
|
|
SaveDialog.Filter := _('ZIP files')+' (*.zip)|*.zip|'+_('All files')+' (*.*)|*.*';
|
|
SaveDialog.Options := SaveDialog.Options + [ofOverwritePrompt];
|
|
if SaveDialog.Execute then
|
|
comboExportOutputTarget.Text := SaveDialog.FileName;
|
|
SaveDialog.Free;
|
|
end else if(comboExportOutputType.Text = OUTPUT_DIR) then begin
|
|
Browse := TBrowseForFolder.Create(Self);
|
|
Browse.Folder := comboExportOutputTarget.Text;
|
|
Browse.DialogCaption := _('Select output directory');
|
|
// Enable "Create new folder" button
|
|
Browse.BrowseOptions := Browse.BrowseOptions - [bifNoNewFolderButton] + [bifNewDialogStyle];
|
|
if Browse.Execute then
|
|
comboExportOutputTarget.Text := Browse.Folder;
|
|
Browse.Free;
|
|
end;
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
procedure TfrmTableTools.SetToolMode(Value: TToolMode);
|
|
begin
|
|
FToolMode := Value;
|
|
case FToolMode of
|
|
tmMaintenance: tabsTools.ActivePage := tabMaintenance;
|
|
tmFind: tabsTools.ActivePage := tabFind;
|
|
tmSQLExport: tabsTools.ActivePage := tabSQLExport;
|
|
tmBulkTableEdit: tabsTools.ActivePage := tabBulkTableEdit;
|
|
end;
|
|
end;
|
|
|
|
|
|
// Pass output to file or query, and append semicolon if needed
|
|
procedure TfrmTableTools.Output(SQL: String; IsEndOfQuery, ForFile, ForDir, ForDb, ForServer: Boolean);
|
|
var
|
|
SA: AnsiString;
|
|
ChunkSize: Integer;
|
|
begin
|
|
if (ToFile and ForFile) or (ToDir and ForDir) or (ToClipboard and ForFile) then begin
|
|
if IsEndOfQuery then
|
|
SQL := SQL + ';'+CRLF;
|
|
StreamWrite(ExportStream, SQL);
|
|
if IsEndOfQuery then
|
|
ExportStreamStartOfQueryPos := ExportStream.Size;
|
|
end;
|
|
if (ToDb and ForDb) or (ToServer and ForServer) then begin
|
|
StreamWrite(ExportStream, SQL);
|
|
if IsEndOfQuery then begin
|
|
ExportStream.Position := 0;
|
|
ChunkSize := ExportStream.Size;
|
|
SetLength(SA, ChunkSize div SizeOf(AnsiChar));
|
|
ExportStream.Read(PAnsiChar(SA)^, ChunkSize);
|
|
ExportStream.Size := 0;
|
|
ExportStreamStartOfQueryPos := 0;
|
|
SQL := UTF8ToString(SA);
|
|
if ToDB then MainForm.ActiveConnection.Query(SQL, False, lcScript)
|
|
else if ToServer then FTargetConnection.Query(SQL, False, lcScript);
|
|
SQL := '';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.DoExport(DBObj: TDBObject);
|
|
var
|
|
NeedsDBStructure: Boolean;
|
|
InsertSizeExceeded, RowLimitExceeded: Boolean;
|
|
Struc, Header, DbDir, FinalDbName, BaseInsert, Row, TargetDbAndObject, BinContent, tmp: String;
|
|
i: Integer;
|
|
RowCount, RowCountInChunk: Int64;
|
|
Limit, Offset, ResultCount: Int64;
|
|
StartTime: Cardinal;
|
|
StrucResult, Data: TDBQuery;
|
|
ColumnList: TTableColumnList;
|
|
Column: TTableColumn;
|
|
Quoter: TDBConnection;
|
|
TargetFileName, SetCharsetCode: String;
|
|
const
|
|
TempDelim = '//';
|
|
AssumedAvgRowLen = 10000;
|
|
|
|
procedure LogStatistic(RowsDone: Int64);
|
|
var
|
|
LogRow: TStringlist;
|
|
Percent: Double;
|
|
BytesDone: Int64;
|
|
begin
|
|
LogRow := FResults.Last;
|
|
Percent := 100 / Max(DBObj.Rows,1) * Max(RowsDone,1);
|
|
Percent := Min(Percent, 100);
|
|
BytesDone := Max(DBObj.Size,0) div Max(DBObj.Rows,1) * RowsDone;
|
|
FObjectSizesDoneExact := FObjectSizesDone + BytesDone;
|
|
LogRow[2] := FormatNumber(RowsDone) + ' / ' + FormatNumber(Percent, 0)+'%';
|
|
LogRow[3] := FormatTimeNumber((GetTickCount-StartTime) / 1000, True);
|
|
UpdateResultGrid;
|
|
end;
|
|
|
|
begin
|
|
// Handle one table, view or whatever in SQL export mode
|
|
AddResults('SELECT '+DBObj.Connection.EscapeString(DBObj.Database)+' AS '+DBObj.Connection.QuoteIdent('Database')+', ' +
|
|
DBObj.Connection.EscapeString(DBObj.Name)+' AS '+DBObj.Connection.QuoteIdent('Table')+', ' +
|
|
IntToStr(DBObj.Rows)+' AS '+DBObj.Connection.QuoteIdent('Rows')+', '+
|
|
'0 AS '+DBObj.Connection.QuoteIdent('Duration')
|
|
, DBObj.Connection
|
|
);
|
|
ToFile := (comboExportOutputType.Text = OUTPUT_FILE) or (comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED);
|
|
ToDir := comboExportOutputType.Text = OUTPUT_DIR;
|
|
ToClipboard := comboExportOutputType.Text = OUTPUT_CLIPBOARD;
|
|
ToDb := comboExportOutputType.Text = OUTPUT_DB;
|
|
ToServer := Copy(comboExportOutputType.Text, 1, Length(OUTPUT_SERVER)) = OUTPUT_SERVER;
|
|
if not Assigned(ExportStream) then begin
|
|
// Very first round here. Prevent "SHOW CREATE db|table" from using double quotes
|
|
DBObj.Connection.Query('/*!40101 SET @OLD_LOCAL_SQL_MODE=@@SQL_MODE, SQL_MODE='''' */');
|
|
// Set same timezone for reading date/time values as for the output
|
|
DBObj.Connection.Query('/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */');
|
|
DBObj.Connection.Query('/*!40103 SET TIME_ZONE=''+00:00'' */');
|
|
end;
|
|
|
|
if ToServer then
|
|
Quoter := FTargetConnection
|
|
else
|
|
Quoter := DBObj.Connection;
|
|
|
|
StartTime := GetTickCount;
|
|
ExportStreamStartOfQueryPos := 0;
|
|
if ToDir then begin
|
|
FreeAndNil(ExportStream);
|
|
DbDir := IncludeTrailingPathDelimiter(GetOutputFilename(comboExportOutputTarget.Text, DBObj)) + DBObj.Database + '\';
|
|
if not DirectoryExists(DbDir) then
|
|
ForceDirectories(DbDir);
|
|
ExportStream := TFileStream.Create(DbDir + DBObj.Name+'.sql', fmCreate or fmOpenWrite);
|
|
FHeaderCreated := False;
|
|
end;
|
|
if not Assigned(ExportStream) then begin
|
|
if ToFile then begin
|
|
TargetFileName := GetOutputFilename(comboExportOutputTarget.Text, DBObj);
|
|
FExportFileName := TargetFileName;
|
|
if comboExportOutputType.Text = OUTPUT_FILE_COMPRESSED then
|
|
TargetFileName := ChangeFileExt(TargetFileName, '_temp.sql');
|
|
if not IsValidFilePath(TargetFileName) then
|
|
raise EFCreateError.CreateFmt(_('Filename or path contains illegal characters: "%s"'), [TargetFilename]);
|
|
if not DirectoryExists(ExtractFilePath(FExportFileName)) then
|
|
ForceDirectories(ExtractFilePath(FExportFileName));
|
|
ExportStream := TFileStream.Create(TargetFileName, fmCreate or fmOpenWrite);
|
|
end;
|
|
// ToDir handled above
|
|
if ToClipboard then
|
|
ExportStream := TMemoryStream.Create;
|
|
if ToDb or ToServer then
|
|
ExportStream := TMemoryStream.Create;
|
|
end;
|
|
if not FHeaderCreated then begin
|
|
// For output to file or directory:
|
|
if DBObj.Connection.CharacterSet = 'utf8mb4' then
|
|
SetCharsetCode := '/*!40101 SET NAMES utf8 */;' + CRLF +
|
|
'/*!50503 SET NAMES '+DBObj.Connection.CharacterSet+' */;' + CRLF
|
|
else
|
|
SetCharsetCode := '/*!40101 SET NAMES '+DBObj.Connection.CharacterSet+' */;' + CRLF;
|
|
Header := '';
|
|
if menuExportAddComments.Checked then begin
|
|
Header := Header +
|
|
'-- --------------------------------------------------------' + CRLF +
|
|
Format('-- %-30s%s', [_('Host')+':', DBObj.Connection.Parameters.HostName]) + CRLF +
|
|
Format('-- %-30s%s', [_('Server version')+':', DBObj.Connection.ServerVersionUntouched]) + CRLF +
|
|
Format('-- %-30s%s', [_('Server OS')+':', DBObj.Connection.ServerOS]) + CRLF +
|
|
Format('-- %-30s%s', [APPNAME + ' ' + _('Version')+':', Mainform.AppVersion]) + CRLF +
|
|
'-- --------------------------------------------------------' + CRLF + CRLF;
|
|
end;
|
|
Header := Header +
|
|
'/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;' + CRLF +
|
|
SetCharsetCode +
|
|
'/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;' + CRLF +
|
|
'/*!40103 SET TIME_ZONE=''+00:00'' */;' + CRLF +
|
|
'/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;' + CRLF +
|
|
'/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=''NO_AUTO_VALUE_ON_ZERO'' */;' + CRLF +
|
|
'/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;' + CRLF;
|
|
Output(Header, False, DBObj.Database<>ExportLastDatabase, True, False, False);
|
|
Output(CRLF, False, True, True, False, False);
|
|
|
|
// For direct output to database or server:
|
|
Output('/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */', True, False, False, True, True);
|
|
Output('/*!40103 SET TIME_ZONE=''+00:00'' */', True, False, False, True, True);
|
|
Output('/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */', True, False, False, True, True);
|
|
Output('/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=''NO_AUTO_VALUE_ON_ZERO'' */', True, False, False, True, True);
|
|
Output('/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */', True, False, False, True, True);
|
|
|
|
FHeaderCreated := True;
|
|
end;
|
|
|
|
// Database structure. Do that only in single-file and server mode. drop/create/use in directory or database mode makes no sense
|
|
FinalDbName := DBObj.Database;
|
|
if ToDb or (ToServer and (comboExportOutputTarget.ItemIndex > 0)) then
|
|
FinalDbName := comboExportOutputTarget.Text;
|
|
NeedsDBStructure := FinalDbName <> ExportLastDatabase;
|
|
if chkExportDatabasesDrop.Checked or chkExportDatabasesCreate.Checked then begin
|
|
if menuExportAddComments.Checked then
|
|
Output(CRLF+'-- '+f_('Dumping database structure for %s', [DBObj.Database])+CRLF, False, NeedsDBStructure, False, False, False);
|
|
if chkExportDatabasesDrop.Checked and chkExportDatabasesDrop.Enabled then
|
|
Output('DROP DATABASE IF EXISTS '+Quoter.QuoteIdent(FinalDbName), True, NeedsDBStructure, False, False, NeedsDBStructure);
|
|
if chkExportDatabasesCreate.Checked and chkExportDatabasesCreate.Enabled then begin
|
|
if DBObj.Connection.ServerVersionInt >= 40100 then begin
|
|
Struc := DBObj.Connection.GetVar('SHOW CREATE DATABASE '+DBObj.QuotedDatabase, 1);
|
|
// Gracefully ignore it when target database exists, important in server mode
|
|
Insert('IF NOT EXISTS ', Struc, Pos('DATABASE', Struc) + 9);
|
|
// Create the right dbname
|
|
Struc := StringReplace(Struc, DBObj.Database, FinalDbName, []);
|
|
end else
|
|
Struc := 'CREATE DATABASE IF NOT EXISTS '+Quoter.QuoteIdent(FinalDbName);
|
|
Output(Struc, True, NeedsDBStructure, False, False, NeedsDBStructure);
|
|
Output(Quoter.GetSQLSpecifity(spUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, NeedsDBStructure, False, False, NeedsDBStructure);
|
|
Output(CRLF, False, NeedsDBStructure, False, False, NeedsDBStructure);
|
|
end;
|
|
end;
|
|
if ToServer and (not chkExportDatabasesCreate.Checked) then begin
|
|
// Export to server without "CREATE/USE dbname" and "Same dbs as on source server" - needs a "USE dbname"
|
|
Output(Quoter.GetSQLSpecifity(spUSEQuery, [Quoter.QuoteIdent(FinalDbName)]), True, False, False, False, NeedsDBStructure);
|
|
end;
|
|
|
|
// Table structure
|
|
if chkExportTablesDrop.Checked or chkExportTablesCreate.Checked then begin
|
|
if menuExportAddComments.Checked then
|
|
Output('-- '+f_('Dumping structure for %s %s.%s', [_(LowerCase(DBObj.ObjType)), DBObj.Database, DBObj.Name])+CRLF, False, True, True, False, False);
|
|
if chkExportTablesDrop.Checked then begin
|
|
Struc := 'DROP '+UpperCase(DBObj.ObjType)+' IF EXISTS ';
|
|
if ToDb then
|
|
Struc := Struc + Quoter.QuoteIdent(FinalDbName)+'.';
|
|
Struc := Struc + Quoter.QuoteIdent(DBObj.Name);
|
|
Output(Struc, True, True, True, True, True);
|
|
end;
|
|
if chkExportTablesCreate.Checked then begin
|
|
try
|
|
case DBObj.NodeType of
|
|
lntTable: begin
|
|
Struc := DBObj.GetCreateCode(menuExportRemoveAutoIncrement.Checked, False);
|
|
Insert('IF NOT EXISTS ', Struc, Pos('TABLE', Struc) + 6);
|
|
if ToDb then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('EXISTS', Struc) + 7 );
|
|
if ToServer then
|
|
Struc := TSqlTranspiler.CreateTable(Struc, DBObj.Connection, FTargetConnection);
|
|
end;
|
|
|
|
lntView: begin
|
|
if not FSecondExportPass then begin
|
|
// Create temporary VIEW replacement
|
|
ColumnList := DBObj.TableColumns;
|
|
Struc := '';
|
|
if menuExportAddComments.Checked then
|
|
Struc := Struc + '-- '+_('Creating temporary table to overcome VIEW dependency errors')+CRLF;
|
|
Struc := Struc + 'CREATE TABLE ';
|
|
if ToDb then
|
|
Struc := Struc + Quoter.QuoteIdent(FinalDbName) + '.';
|
|
Struc := Struc + Quoter.QuoteIdent(DBObj.Name)+' (';
|
|
for Column in ColumnList do begin
|
|
// Prevent DEFAULT value from coming in, to fix errors due to multiple CURRENT_TIMESTAMP values
|
|
// See issue #2748
|
|
Column.DefaultType := cdtNothing;
|
|
Struc := Struc + CRLF + #9 + Column.SQLCode + ',';
|
|
end;
|
|
Delete(Struc, Length(Struc), 1);
|
|
Struc := Struc + CRLF + ') ENGINE=MyISAM';
|
|
ColumnList.Free;
|
|
end else begin
|
|
Struc := '';
|
|
if menuExportAddComments.Checked then
|
|
Struc := Struc + '-- '+_('Removing temporary table and create final VIEW structure')+CRLF;
|
|
Struc := Struc + 'DROP TABLE IF EXISTS ';
|
|
if ToDb then
|
|
Struc := Struc + Quoter.QuoteIdent(FinalDbName)+'.';
|
|
Struc := Struc + Quoter.QuoteIdent(DBObj.Name);
|
|
Output(Struc, True, True, True, True, True);
|
|
Struc := DBObj.GetCreateCode(False, menuExportRemoveDefiner.Checked);
|
|
if ToDb then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('VIEW', Struc) + 5 );
|
|
end;
|
|
end;
|
|
|
|
lntTrigger: begin
|
|
StrucResult := DBObj.Connection.GetResults('SHOW TRIGGERS FROM '+DBObj.QuotedDatabase+' WHERE `Trigger`='+DBObj.Connection.EscapeString(DBObj.Name));
|
|
Struc := DBObj.GetCreateCode(False, menuExportRemoveDefiner.Checked);
|
|
if ToDb then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('TRIGGER', Struc) + 8 );
|
|
if ToFile or ToClipboard or ToDir then begin
|
|
Struc := 'SET @OLDTMP_SQL_MODE=@@SQL_MODE, SQL_MODE=' + DBObj.Connection.EscapeString(StrucResult.Col('sql_mode')) + ';' + CRLF +
|
|
'DELIMITER ' + TempDelim + CRLF +
|
|
Struc + TempDelim + CRLF +
|
|
'DELIMITER ;' + CRLF +
|
|
'SET SQL_MODE=@OLDTMP_SQL_MODE';
|
|
end;
|
|
end;
|
|
|
|
lntFunction, lntProcedure: begin
|
|
Struc := DBObj.GetCreateCode(False, menuExportRemoveDefiner.Checked);
|
|
if ToDb then begin
|
|
if DBObj.NodeType = lntProcedure then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('PROCEDURE', Struc) + 10 )
|
|
else if DBObj.NodeType = lntFunction then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('FUNCTION', Struc) + 9 );
|
|
end;
|
|
// Change delimiter for file output, so readers split queries at the right string position
|
|
if ToFile or ToDir or ToClipboard then
|
|
Struc := 'DELIMITER ' + TempDelim + CRLF + Struc + TempDelim + CRLF + 'DELIMITER ';
|
|
end;
|
|
|
|
lntEvent: begin
|
|
Struc := DBObj.GetCreateCode(False, menuExportRemoveDefiner.Checked);
|
|
if ToDb then
|
|
Insert(Quoter.QuoteIdent(FinalDbName)+'.', Struc, Pos('EVENT', Struc) + 6 );
|
|
if ToFile or ToDir or ToClipboard then
|
|
Struc := 'DELIMITER ' + TempDelim + CRLF + Struc + TempDelim + CRLF + 'DELIMITER ';
|
|
end;
|
|
end;
|
|
Struc := fixNewlines(Struc);
|
|
Output(Struc, True, True, True, True, True);
|
|
Output(CRLF, False, True, True, True, True);
|
|
except
|
|
on E:EDbError do begin
|
|
// Catch the exception message and dump it into the export file for debugging reasons
|
|
Output('/* '+E.Message+' */', False, True, True, False, False);
|
|
Raise;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if DBObj.NodeType = lntTable then begin
|
|
// Table data
|
|
if comboExportData.Text = DATA_NO then begin
|
|
if menuExportAddComments.Checked then
|
|
Output('-- '+_('Data exporting was unselected.')+CRLF+CRLF, False, True, True, False, False);
|
|
end else if MatchText(DBObj.Engine, ['MRG_MYISAM', 'FEDERATED']) then begin
|
|
if menuExportAddComments.Checked then
|
|
Output('-- '+f_('Table data not exported because this is %s table which holds its data in separate tables.', [DBObj.Engine])+CRLF+CRLF, False, True, True, False, False);
|
|
end else begin
|
|
tmp := FormatNumber(DBObj.Rows)+' rows';
|
|
if LowerCase(DBObj.Engine) = 'innodb' then
|
|
tmp := '~'+tmp+' ('+_('approximately')+')';
|
|
if menuExportAddComments.Checked then
|
|
Output('-- '+f_('Dumping data for table %s.%s: %s', [DBObj.Database, DBObj.Name, tmp])+CRLF, False, True, True, False, False);
|
|
TargetDbAndObject := Quoter.QuoteIdent(DBObj.Name);
|
|
if ToDb then
|
|
TargetDbAndObject := Quoter.QuoteIdent(FinalDbName) + '.' + TargetDbAndObject;
|
|
Offset := 0;
|
|
RowCount := 0;
|
|
// Calculate limit so we select ~100MB per loop
|
|
// Take care of disabled "Get full table status" session setting, where AvgRowLen is 0
|
|
Limit := Round(100 * SIZE_MB / IfThen(DBObj.AvgRowLen>0, DBObj.AvgRowLen, AssumedAvgRowLen));
|
|
if comboExportData.Text = DATA_REPLACE then
|
|
Output('DELETE FROM '+TargetDbAndObject, True, True, True, True, True);
|
|
if DBObj.Engine.ToLowerInvariant <> 'innodb' then begin
|
|
Output('/*!40000 ALTER TABLE '+TargetDbAndObject+' DISABLE KEYS */', True, True, True, True, True);
|
|
end;
|
|
while true do begin
|
|
Data := DBObj.Connection.GetResults(
|
|
DBObj.Connection.ApplyLimitClause(
|
|
'SELECT',
|
|
'* FROM '+DBObj.QuotedDbAndTableName,
|
|
Limit,
|
|
Offset)
|
|
);
|
|
Inc(Offset, Limit);
|
|
if Data.RecordCount = 0 then
|
|
break;
|
|
Data.PrepareColumnAttributes;
|
|
BaseInsert := 'INSERT INTO ';
|
|
if comboExportData.Text = DATA_INSERTNEW then
|
|
BaseInsert := 'INSERT IGNORE INTO '
|
|
else if comboExportData.Text = DATA_UPDATE then
|
|
BaseInsert := 'REPLACE INTO ';
|
|
BaseInsert := BaseInsert + TargetDbAndObject + ' (';
|
|
for i:=0 to Data.ColumnCount-1 do begin
|
|
if not Data.ColIsVirtual(i) then
|
|
BaseInsert := BaseInsert + Quoter.QuoteIdent(Data.ColumnNames[i]) + ', ';
|
|
end;
|
|
Delete(BaseInsert, Length(BaseInsert)-1, 2);
|
|
BaseInsert := BaseInsert + ') VALUES'+CRLF+#9+'(';
|
|
while true do begin
|
|
Output(BaseInsert, False, True, True, True, True);
|
|
RowCountInChunk := 0;
|
|
|
|
while not Data.Eof do begin
|
|
Row := '';
|
|
if RowCountInChunk > 0 then
|
|
Row := Row + ','+CRLF+#9+'(';
|
|
for i:=0 to Data.ColumnCount-1 do begin
|
|
if Data.ColIsVirtual(i) then
|
|
Continue;
|
|
if Data.IsNull(i) then
|
|
Row := Row + 'NULL'
|
|
else case Data.DataType(i).Category of
|
|
dtcInteger, dtcReal: begin
|
|
if Data.DataType(i).Index = dbdtBit then
|
|
Row := Row + 'b' + Quoter.EscapeString(Data.Col(i))
|
|
else
|
|
Row := Row + Data.Col(i);
|
|
end;
|
|
dtcBinary, dtcSpatial: begin
|
|
BinContent := Data.HexValue(i);
|
|
if Length(BinContent) > 0 then
|
|
Row := Row + '_binary ' + BinContent
|
|
else
|
|
Row := Row + Quoter.EscapeString('');
|
|
end;
|
|
else Row := Row + Quoter.EscapeString(Data.Col(i));
|
|
end;
|
|
Row := Row + ', ';
|
|
end;
|
|
Delete(Row, Length(Row)-1, 2);
|
|
Row := Row + ')';
|
|
// Break if stream would increase over the barrier of 1MB, and throw away current row
|
|
InsertSizeExceeded := ExportStream.Size - ExportStreamStartOfQueryPos + Length(Row) > updownInsertSize.Position*SIZE_KB*0.9;
|
|
// Same with MSSQL which is limited to 1000 rows per INSERT
|
|
RowLimitExceeded := RowCountInChunk >= Quoter.MaxRowsPerInsert;
|
|
if (RowCountInChunk > 0) and (InsertSizeExceeded or RowLimitExceeded) then
|
|
Break;
|
|
Inc(RowCount);
|
|
Inc(RowCountInChunk);
|
|
Output(Row, False, True, True, True, True);
|
|
Data.Next;
|
|
end;
|
|
Output('', True, True, True, True, True);
|
|
LogStatistic(RowCount);
|
|
if Data.Eof then
|
|
break;
|
|
|
|
end;
|
|
ResultCount := Data.RecordCount;
|
|
FreeAndNil(Data);
|
|
// Break if end of table data, avoid one last empty/useless SELECT in next loop
|
|
if ResultCount < Limit then
|
|
break;
|
|
|
|
end;
|
|
if DBObj.Engine.ToLowerInvariant <> 'innodb' then begin
|
|
Output('/*!40000 ALTER TABLE '+TargetDbAndObject+' ENABLE KEYS */', True, True, True, True, True);
|
|
end;
|
|
Output(CRLF, False, True, True, True, True);
|
|
// Cosmetic fix for estimated InnoDB row count
|
|
DBObj.Rows := RowCount;
|
|
LogStatistic(RowCount);
|
|
end;
|
|
end;
|
|
|
|
// Add footer in directory mode after each item
|
|
Output(EXPORT_FILE_FOOTER, False, False, True, False, False);
|
|
|
|
ExportLastDatabase := FinalDbName;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.chkBulkTableEditCheckComboClick(Sender: TObject);
|
|
var
|
|
chk: TCheckBox;
|
|
combo: TWinControl;
|
|
begin
|
|
chk := TCheckBox(Sender);
|
|
if chk = chkBulkTableEditDatabase then combo := comboBulkTableEditDatabase
|
|
else if chk = chkBulkTableEditEngine then combo := comboBulkTableEditEngine
|
|
else if chk = chkBulkTableEditCollation then combo := comboBulkTableEditCollation
|
|
else combo := comboBulkTableEditCharset;
|
|
combo.Enabled := chk.Checked;
|
|
ValidateControls(Sender);
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.DoBulkTableEdit(DBObj: TDBObject);
|
|
var
|
|
Specs, LogRow: TStringList;
|
|
CreateView: String;
|
|
rx: TRegExpr;
|
|
HasCharsetClause: Boolean;
|
|
SelectedCharset: String;
|
|
begin
|
|
AddResults('SELECT '+DBObj.Connection.EscapeString(DBObj.Database)+' AS '+DBObj.Connection.QuoteIdent('Database')+', ' +
|
|
DBObj.Connection.EscapeString(DBObj.Name)+' AS '+DBObj.Connection.QuoteIdent('Table')+', ' +
|
|
DBObj.Connection.EscapeString('Updating...')+' AS '+DBObj.Connection.QuoteIdent('Operation')+', '+
|
|
''''' AS '+DBObj.Connection.QuoteIdent('Result')
|
|
, DBObj.Connection
|
|
);
|
|
Specs := TStringList.Create;
|
|
if chkBulkTableEditDatabase.Checked and (comboBulkTableEditDatabase.Text <> DBObj.Database) then begin
|
|
case DBObj.NodeType of
|
|
lntTable: Specs.Add('RENAME ' + DBObj.Connection.QuoteIdent(comboBulkTableEditDatabase.Text)+'.'+DBObj.QuotedName);
|
|
lntView: begin
|
|
// Although RENAME works for views, that does not work for moving to another database without getting
|
|
// a "Changing schema from x to y is not allowed". Instead, recreate them manually
|
|
CreateView := DBObj.CreateCode;
|
|
rx := TRegExpr.Create;
|
|
rx.ModifierI := True;
|
|
// Replace old database references in VIEW body
|
|
rx.Expression := '(["`])'+QuoteRegExprMetaChars(DBObj.Database)+'(["`])';
|
|
CreateView := rx.Replace(CreateView, DBObj.Connection.QuoteIdent(comboBulkTableEditDatabase.Text), false);
|
|
rx.Free;
|
|
// Temporarily switch to new database for VIEW creation, so the database references are correct
|
|
DBObj.Connection.Database := comboBulkTableEditDatabase.Text;
|
|
DBObj.Connection.Query(CreateView);
|
|
DBObj.Connection.Database := DBObj.Database;
|
|
DBObj.Connection.Query('DROP VIEW '+DBObj.QuotedName);
|
|
end;
|
|
end;
|
|
if FModifiedDbs.IndexOf(DBObj.Database) = -1 then
|
|
FModifiedDbs.Add(DBObj.Database);
|
|
if FModifiedDbs.IndexOf(comboBulkTableEditDatabase.Text) = -1 then
|
|
FModifiedDbs.Add(comboBulkTableEditDatabase.Text);
|
|
end;
|
|
if (DBObj.NodeType = lntTable) and chkBulkTableEditEngine.Checked then begin
|
|
if MainForm.ActiveConnection.ServerVersionInt < 40018 then
|
|
Specs.Add('TYPE '+comboBulkTableEditEngine.Text)
|
|
else
|
|
Specs.Add('ENGINE '+comboBulkTableEditEngine.Text);
|
|
end;
|
|
if DBObj.NodeType = lntTable then begin
|
|
HasCharsetClause := False;
|
|
if chkBulkTableEditCharset.Checked and (comboBulkTableEditCharset.ItemIndex > -1) then begin
|
|
SelectedCharset := RegExprGetMatch('^(\w+)\b', comboBulkTableEditCharset.Text, 1);
|
|
Specs.Add('CONVERT TO CHARSET '+SelectedCharset);
|
|
HasCharsetClause := True;
|
|
end;
|
|
if chkBulkTableEditCollation.Checked and (comboBulkTableEditCollation.ItemIndex > -1) then begin
|
|
if HasCharsetClause then // No comma between charset + collation clause
|
|
Specs[Specs.Count-1] := Specs[Specs.Count-1] + ' COLLATE '+DBObj.Connection.EscapeString(comboBulkTableEditCollation.Text)
|
|
else
|
|
Specs.Add('COLLATE '+DBObj.Connection.EscapeString(comboBulkTableEditCollation.Text));
|
|
end;
|
|
if chkBulkTableEditResetAutoinc.Checked then
|
|
Specs.Add('AUTO_INCREMENT=0');
|
|
end;
|
|
|
|
LogRow := FResults.Last;
|
|
if Specs.Count > 0 then begin
|
|
DBObj.Connection.Query('ALTER TABLE ' + DBObj.QuotedDatabase + '.' + DBObj.QuotedName + ' ' + Implode(', ', Specs));
|
|
LogRow[2] := _('Done');
|
|
LogRow[3] := _('Success');
|
|
end else begin
|
|
LogRow[2] := _('Nothing to do');
|
|
LogRow[3] := f_('Selected operations cannot be applied to a %s', [_(LowerCase(DBObj.ObjType))]);
|
|
end;
|
|
UpdateResultGrid;
|
|
end;
|
|
|
|
|
|
procedure TfrmTableTools.CheckAllClick(Sender: TObject);
|
|
var
|
|
DBNode, ObjNode: PVirtualNode;
|
|
WantedType: TListNodeType;
|
|
DBObj: PDBObject;
|
|
CheckNone: Boolean;
|
|
CheckedNodes: Int64;
|
|
begin
|
|
// Check all/none/by type via context menu
|
|
WantedType := TListNodeType((Sender as TMenuItem).Tag);
|
|
CheckNone := Sender = menuCheckNone;
|
|
case TreeObjects.GetNodeLevel(TreeObjects.FocusedNode) of
|
|
1: DBNode := TreeObjects.FocusedNode;
|
|
2: DBNode := TreeObjects.FocusedNode.Parent;
|
|
3: DBNode := TreeObjects.FocusedNode.Parent.Parent;
|
|
else raise Exception.Create(_('Unhandled tree level'));
|
|
end;
|
|
ObjNode := TreeObjects.GetFirstChild(DBNode);
|
|
CheckedNodes := 0;
|
|
while Assigned(ObjNode) do begin
|
|
DBObj := TreeObjects.GetNodeData(ObjNode);
|
|
if CheckNone then
|
|
TreeObjects.CheckState[ObjNode] := csUncheckedNormal
|
|
else begin
|
|
if (WantedType = lntNone) or (DBObj.NodeType = WantedType) or (DBObj.GroupType = WantedType) then
|
|
TreeObjects.CheckState[ObjNode] := csCheckedNormal
|
|
else
|
|
TreeObjects.CheckState[ObjNode] := csUncheckedNormal;
|
|
end;
|
|
if ObjNode.CheckState = csCheckedNormal then
|
|
Inc(CheckedNodes);
|
|
TreeObjects.RepaintNode(ObjNode);
|
|
ObjNode := TreeObjects.GetNextSibling(ObjNode);
|
|
end;
|
|
if CheckedNodes = 0 then
|
|
TreeObjects.CheckState[DBNode] := csUncheckedNormal
|
|
else if CheckedNodes = TreeObjects.ChildCount[DBNode] then
|
|
TreeObjects.CheckState[DBNode] := csCheckedNormal
|
|
else
|
|
TreeObjects.CheckState[DBNode] := csMixedNormal;
|
|
end;
|
|
|
|
|
|
end.
|