unit tabletools; // ------------------------------------- // Table-diagnostics // ------------------------------------- interface uses Windows, SysUtils, Classes, Controls, Forms, StdCtrls, ComCtrls, Buttons, Dialogs, StdActns, WideStrings, WideStrUtils, VirtualTrees, ExtCtrls, mysql_connection, Contnrs, Graphics, TntStdCtrls, PngSpeedButton, SynRegExpr, helpers; type TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit); TfrmTableTools = class(TForm) btnClose: 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; btnHelp: TButton; tabFind: TTabSheet; lblFindText: TLabel; memoFindText: TTntMemo; comboDataTypes: TComboBox; lblDataTypes: TLabel; tabSQLexport: TTabSheet; chkExportDatabasesCreate: TCheckBox; chkExportDatabasesDrop: TCheckBox; chkExportTablesDrop: TCheckBox; chkExportTablesCreate: TCheckBox; lblExportData: TLabel; comboExportData: TComboBox; lblExportOutputType: TLabel; comboExportOutputType: TComboBox; comboExportOutputTarget: TTntComboBox; lblExportDatabases: TLabel; lblExportTables: TLabel; lblExportOutputTarget: TLabel; btnExecute: TButton; editSkipLargeTables: TEdit; udSkipLargeTables: TUpDown; lblSkipLargeTablesMB: TLabel; lblSkipLargeTables: TLabel; btnExportOutputTargetSelect: TPngSpeedButton; tabBulkTableEdit: TTabSheet; chkBulkTableEditDatabase: TCheckBox; comboBulkTableEditDatabase: TTntComboBox; chkBulkTableEditResetAutoinc: TCheckBox; chkBulkTableEditCollation: TCheckBox; comboBulkTableEditCollation: TComboBox; chkBulkTableEditEngine: TCheckBox; comboBulkTableEditEngine: TComboBox; chkBulkTableEditCharset: TCheckBox; comboBulkTableEditCharset: TComboBox; procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); procedure btnHelpClick(Sender: TObject); procedure TreeObjectsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString); 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: Integer); 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: WideString); procedure TreeObjectsChecked(Sender: TBaseVirtualTree; Node: PVirtualNode); 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 chkExportOptionClick(Sender: TObject); procedure btnExportOutputTargetSelectClick(Sender: TObject); procedure comboExportOutputTargetExit(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); private { Private declarations } FResults: TObjectList; FToolMode: TToolMode; OutputFiles, OutputDirs: TWideStringList; ExportStream: TStream; ExportLastDatabase: Widestring; FTargetConnection: TMySQLConnection; FLastOutputSelectedIndex: Integer; procedure SetToolMode(Value: TToolMode); procedure AddResults(SQL: WideString); procedure AddNotes(Col1, Col2, Col3, Col4: WideString); procedure UpdateResultGrid; procedure DoMaintenance(db, obj: WideString; NodeType: TListNodeType); procedure DoFind(db, obj: WideString; NodeType: TListNodeType; RowsInTable: Int64); procedure DoExport(db, obj: WideString; NodeType: TListNodeType; RowsInTable, AvgRowLen: Int64); procedure DoBulkTableEdit(db, obj: WideString; NodeType: TListNodeType); public { Public declarations } SelectedTables: TWideStringList; property ToolMode: TToolMode read FToolMode write SetToolMode; end; implementation uses main, mysql_structures; const STRSKIPPED = 'Skipped - '; OUTPUT_FILE = 'One big file'; OUTPUT_DIR = 'Directory - one file per object'; OUTPUT_DB = 'Database'; OUTPUT_SERVER = 'Server: '; DATA_NO = 'No data'; DATA_REPLACE = 'Replace (truncate existing data)'; DATA_INSERT = 'Insert'; DATA_INSERTNEW = 'Insert new data (do not update existing)'; DATA_UPDATE = 'Update existing data'; EXPORT_FILE_FOOTER = '/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;'+CRLF+'/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;'+CRLF; {$R *.DFM} procedure TfrmTableTools.FormCreate(Sender: TObject); var i: Integer; SessionNames: TStringList; begin // Restore GUI setup Width := GetRegValue(REGNAME_TOOLSWINWIDTH, Width); Height := GetRegValue(REGNAME_TOOLSWINHEIGHT, Height); TreeObjects.Width := GetRegValue(REGNAME_TOOLSTREEWIDTH, TreeObjects.Width); // Find text tab memoFindText.Text := Utf8Decode(GetRegValue(REGNAME_TOOLSFINDTEXT, '')); comboDatatypes.Items.Add('All data types'); for i:=Low(DatatypeCategories) to High(DatatypeCategories) do comboDatatypes.Items.Add(DatatypeCategories[i].Name); comboDatatypes.ItemIndex := GetRegValue(REGNAME_TOOLSDATATYPE, 0); // SQL export tab chkExportDatabasesCreate.Checked := GetRegValue(REGNAME_EXP_CREATEDB, chkExportDatabasesCreate.Checked); chkExportDatabasesDrop.Checked := GetRegValue(REGNAME_EXP_DROPDB, chkExportDatabasesDrop.Checked); chkExportTablesCreate.Checked := GetRegValue(REGNAME_EXP_CREATETABLE, chkExportTablesCreate.Checked); chkExportTablesDrop.Checked := GetRegValue(REGNAME_EXP_DROPTABLE, chkExportTablesDrop.Checked); comboExportData.Items.Text := DATA_NO+CRLF +DATA_REPLACE+CRLF +DATA_INSERT+CRLF +DATA_INSERTNEW+CRLF +DATA_UPDATE; comboExportData.ItemIndex := GetRegValue(REGNAME_EXP_DATAHOW, 0); OutputFiles := TWideStringList.Create; OutputDirs := TWideStringList.Create; OutputFiles.Text := GetRegValue(REGNAME_EXP_OUTFILES, ''); OutputDirs.Text := GetRegValue(REGNAME_EXP_OUTDIRS, ''); comboExportOutputType.Items.Text := OUTPUT_FILE+CRLF +OUTPUT_DIR+CRLF +OUTPUT_DB; comboExportOutputType.ItemIndex := GetRegValue(REGNAME_EXP_OUTPUT, 0); comboExportOutputTarget.Text := ''; // Add session names from registry SessionNames := TStringList.Create; MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, True); MainReg.GetKeyNames(SessionNames); for i:=0 to SessionNames.Count-1 do begin if SessionNames[i] <> Mainform.SessionName then comboExportOutputType.Items.Add(OUTPUT_SERVER+SessionNames[i]); end; // Various udSkipLargeTables.Position := GetRegValue(REGNAME_TOOLSSKIPMB, udSkipLargeTables.Position); SetWindowSizeGrip( Self.Handle, True ); InheritFont(Font); FixVT(TreeObjects); FixVT(ResultGrid); FResults := TObjectList.Create; SelectedTables := TWideStringList.Create; end; procedure TfrmTableTools.FormDestroy(Sender: TObject); var OutputItem: Integer; begin // Save GUI setup OpenRegistry; MainReg.WriteInteger( REGNAME_TOOLSWINWIDTH, Width ); MainReg.WriteInteger( REGNAME_TOOLSWINHEIGHT, Height ); MainReg.WriteInteger( REGNAME_TOOLSTREEWIDTH, TreeObjects.Width); MainReg.WriteString( REGNAME_TOOLSFINDTEXT, Utf8Encode(memoFindText.Text)); MainReg.WriteInteger( REGNAME_TOOLSDATATYPE, comboDatatypes.ItemIndex); MainReg.WriteBool(REGNAME_EXP_CREATEDB, chkExportDatabasesCreate.Checked); MainReg.WriteBool(REGNAME_EXP_DROPDB, chkExportDatabasesDrop.Checked); MainReg.WriteBool(REGNAME_EXP_CREATETABLE, chkExportTablesCreate.Checked); MainReg.WriteBool(REGNAME_EXP_DROPTABLE, chkExportTablesDrop.Checked); MainReg.WriteInteger(REGNAME_EXP_DATAHOW, comboExportData.ItemIndex); // Do not remember a selected session name for the next time OutputItem := comboExportOutputType.ItemIndex; if OutputItem > 2 then OutputItem := 0; MainReg.WriteInteger(REGNAME_EXP_OUTPUT, OutputItem); MainReg.WriteString(REGNAME_EXP_OUTFILES, OutputFiles.Text); MainReg.WriteString(REGNAME_EXP_OUTDIRS, OutputDirs.Text); MainReg.WriteInteger( REGNAME_TOOLSSKIPMB, udSkipLargeTables.Position); end; procedure TfrmTableTools.FormShow(Sender: TObject); var DBNode, TableNode, FirstChecked: PVirtualNode; begin // 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; DBNode := TreeObjects.GetFirstChild(TreeObjects.GetFirst); while Assigned(DBNode) do begin if TreeObjects.Text[DBNode, 0] = Mainform.ActiveDatabase then begin if SelectedTables.Count = 0 then begin // Preselect active database DBNode.CheckState := csCheckedNormal; end else begin DBNode.CheckState := csMixedNormal; // Expand db node so checked table nodes are visible TreeObjects.Expanded[DBNode] := true; TableNode := TreeObjects.GetFirstChild(DBNode); while Assigned(TableNode) do begin if SelectedTables.IndexOf(TreeObjects.Text[TableNode, 0]) > -1 then TableNode.CheckState := csCheckedNormal; TableNode := TreeObjects.GetNextSibling(TableNode); end; end; end; DBNode := TreeObjects.GetNextSibling(DBNode); end; FirstChecked := TreeObjects.GetFirstChecked; if Assigned(FirstChecked) then TreeObjects.ScrollIntoView(FirstChecked, True); // CHECKSUM available since MySQL 4.1.1 if Mainform.Connection.ServerVersionInt < 40101 then comboOperation.Items[comboOperation.Items.IndexOf('Checksum')] := 'Checksum ('+STR_NOTSUPPORTED+')'; comboOperation.OnChange(Sender); comboExportOutputType.OnChange(Sender); comboBulkTableEditDatabase.Items.Text := Mainform.Databases.Text; if comboBulkTableEditDatabase.Items.Count > 0 then comboBulkTableEditDatabase.ItemIndex := 0; comboBulkTableEditEngine.Items := Mainform.Connection.TableEngines; if comboBulkTableEditEngine.Items.Count > 0 then comboBulkTableEditEngine.ItemIndex := comboBulkTableEditEngine.Items.IndexOf(Mainform.Connection.TableEngineDefault); comboBulkTableEditCollation.Items := Mainform.Connection.CollationList; if comboBulkTableEditCollation.Items.Count > 0 then comboBulkTableEditCollation.ItemIndex := 0; comboBulkTableEditCharset.Items := Mainform.Connection.CharsetList; if comboBulkTableEditCharset.Items.Count > 0 then comboBulkTableEditCharset.ItemIndex := 0; end; procedure TfrmTableTools.FormClose(Sender: TObject; var Action: TCloseAction); begin // Auto close temorary connection if Assigned(FTargetConnection) then FreeAndNil(FTargetConnection); end; procedure TfrmTableTools.ValidateControls(Sender: TObject); var SomeChecked, OptionChecked: Boolean; op: String; begin SomeChecked := TreeObjects.CheckedCount > 0; if tabsTools.ActivePage = tabMaintenance then begin btnExecute.Caption := 'Execute'; btnExecute.Enabled := (Pos(STR_NOTSUPPORTED, 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'; // 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 and (memoFindText.Text <> ''); end else if tabsTools.ActivePage = tabSQLExport then begin btnExecute.Caption := 'Export'; btnExecute.Enabled := SomeChecked; end else if tabsTools.ActivePage = tabBulkTableEdit then begin btnExecute.Caption := 'Update'; chkBulkTableEditCollation.Enabled := Mainform.Connection.IsUnicode; chkBulkTableEditCharset.Enabled := Mainform.Connection.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.TreeObjectsChecked(Sender: TBaseVirtualTree; Node: PVirtualNode); begin ValidateControls(Sender); end; procedure TfrmTableTools.TreeObjectsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); begin Mainform.DBtreeGetImageIndex(Sender, Node, Kind, Column, Ghosted, ImageIndex); end; procedure TfrmTableTools.TreeObjectsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString); 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); var Results: TMySQLQuery; begin // Attach a checkbox to all nodes Mainform.DBtreeInitNode(Sender, ParentNode, Node, InitialStates); Node.CheckType := ctTriStateCheckBox; Node.CheckState := csUncheckedNormal; case Sender.GetNodeLevel(Node) of 2: begin Results := Mainform.FetchDbTableList(Mainform.Databases[ParentNode.Index]); Results.RecNo := Node.Index; // No checkbox for stored routines if not (GetDBObjectType(Results) in [lntTable, lntCrashedTable, lntView]) then Node.CheckType := ctNone end; end; ValidateControls(Sender); 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.btnHelpClick(Sender: TObject); begin Mainform.CallSQLHelpWithKeyword(UpperCase(comboOperation.Text) + ' TABLE'); end; procedure TfrmTableTools.Execute(Sender: TObject); var DBNode, TableNode: PVirtualNode; Results: TMySQLQuery; NodeType: TListNodeType; db, table: WideString; TableSize, RowsInTable, AvgRowLen: Int64; i: Integer; ViewNodes: TNodeArray; begin Screen.Cursor := crHourGlass; 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; TreeObjects.SetFocus; DBNode := TreeObjects.GetFirstChild(TreeObjects.GetFirst); while Assigned(DBNode) do begin if not (DBNode.CheckState in [csUncheckedNormal, csUncheckedPressed]) then begin TableNode := TreeObjects.GetFirstChild(DBNode); while Assigned(TableNode) do begin if (csCheckedNormal in [TableNode.CheckState, DBNode.CheckState]) and (TableNode.CheckType <> ctNone) then begin Results := Mainform.FetchDbTableList(TreeObjects.Text[DBNode, 0]); Results.RecNo := TableNode.Index; NodeType := GetDBObjectType(Results); db := TreeObjects.Text[DBNode, 0]; table := TreeObjects.Text[TableNode, 0]; // Find table in cashed dataset and check its size - perhaps it has to be skipped TableSize := GetTableSize(Results); RowsInTable := MakeInt(Results.Col(DBO_ROWS)); AvgRowLen := MakeInt(Results.Col(DBO_AVGROWLEN)); if (udSkipLargeTables.Position = 0) or ((TableSize div SIZE_MB) < udSkipLargeTables.Position) then try case FToolMode of tmMaintenance: DoMaintenance(db, table, NodeType); tmFind: DoFind(db, table, NodeType, RowsInTable); tmSQLExport: begin // Views have to be exported at the very end so at least all needed tables are ready when a view gets imported if NodeType = lntView then begin SetLength(ViewNodes, Length(ViewNodes)+1); ViewNodes[Length(ViewNodes)-1] := TableNode; end else DoExport(db, table, NodeType, RowsInTable, AvgRowLen); end; tmBulkTableEdit: DoBulkTableEdit(db, table, NodeType); end; except // 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 on E:Exception do AddNotes(db, table, 'error', E.Message); end else begin AddNotes(db, table, STRSKIPPED+FormatByteNumber(TableSize), ''); end; end; TableNode := TreeObjects.GetNextSibling(TableNode); end; end; DBNode := TreeObjects.GetNextSibling(DBNode); end; // Special block for late created views in export mode if FToolMode = tmSQLExport then for i:=Low(ViewNodes) to High(ViewNodes) do begin db := TreeObjects.Text[ViewNodes[i].Parent, 0]; table := TreeObjects.Text[ViewNodes[i], 0]; try DoExport(db, table, lntView, 0, 0); except on E:Exception do AddNotes(db, table, 'error', E.Message); end; end; if Assigned(ExportStream) then begin if comboExportOutputType.Text = OUTPUT_FILE then StreamWrite(ExportStream, EXPORT_FILE_FOOTER); FreeAndNil(ExportStream); end; ExportLastDatabase := ''; Screen.Cursor := crDefault; end; procedure TfrmTableTools.DoMaintenance(db, obj: WideString; NodeType: TListNodeType); var SQL: WideString; begin SQL := UpperCase(comboOperation.Text) + ' TABLE ' + Mainform.mask(db) + '.' + Mainform.mask(obj); 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'; AddResults(SQL); end; procedure TfrmTableTools.DoFind(db, obj: WideString; NodeType: TListNodeType; RowsInTable: Int64); var Results: TMySQLQuery; SQL: WideString; HasSelectedDatatype: Boolean; i: Integer; begin Results := Mainform.Connection.GetResults('SHOW COLUMNS FROM '+Mainform.mask(db)+'.'+Mainform.mask(obj)); SQL := ''; while not Results.Eof do begin HasSelectedDatatype := comboDatatypes.ItemIndex = 0; if not HasSelectedDatatype then for i:=Low(Datatypes) to High(Datatypes) do begin HasSelectedDatatype := (LowerCase(getFirstWord(Results.Col('Type'))) = LowerCase(Datatypes[i].Name)) and (Integer(Datatypes[i].Category)+1 = comboDatatypes.ItemIndex); if HasSelectedDatatype then break; end; if HasSelectedDatatype then SQL := SQL + Mainform.mask(Results.Col('Field')) + ' LIKE ' + esc('%'+memoFindText.Text+'%') + ' OR '; Results.Next; end; if SQL <> '' then begin Delete(SQL, Length(SQL)-3, 3); SQL := 'SELECT '''+db+''' AS `Database`, '''+obj+''' AS `Table`, COUNT(*) AS `Found rows`, ' + 'CONCAT(ROUND(100 / '+IntToStr(Max(RowsInTable,1))+' * COUNT(*), 1), ''%'') AS `Relevance` FROM '+Mainform.mask(db)+'.'+Mainform.mask(obj)+' WHERE ' + SQL; AddResults(SQL); end else AddNotes(db, obj, STRSKIPPED+'table doesn''t have columns of selected type ('+comboDatatypes.Text+').', ''); end; procedure TfrmTableTools.AddResults(SQL: WideString); var i: Integer; Col: TVirtualTreeColumn; Row: TWideStringlist; Results: TMySQLQuery; begin // Execute query and append results into grid Results := Mainform.Connection.GetResults(SQL); if Results = nil then Exit; // Add missing columns for i:=ResultGrid.Header.Columns.Count to Results.ColumnCount-1 do begin Col := ResultGrid.Header.Columns.Add; Col.Width := 130; end; // Remove superfluous columns for i:=ResultGrid.Header.Columns.Count-1 downto Results.ColumnCount do ResultGrid.Header.Columns[i].Free; // Set column header names for i:=0 to Results.ColumnCount-1 do begin Col := ResultGrid.Header.Columns[i]; Col.Text := Results.ColumnNames[i]; if Results.DataType(i).Category in [dtcInteger, dtcIntegerNamed, dtcReal] then Col.Alignment := taRightJustify else Col.Alignment := taLeftJustify; end; Results.First; while not Results.Eof do begin Row := TWideStringlist.Create; for i:=0 to Results.ColumnCount-1 do begin if Results.DataType(i).Category = dtcInteger then Row.Add(FormatNumber(Results.Col(i))) else Row.Add(Results.Col(i)); end; FResults.Add(Row); Results.Next; end; Results.Free; UpdateResultGrid; end; procedure TfrmTableTools.AddNotes(Col1, Col2, Col3, Col4: WideString); var Row: TWideStringlist; begin // Adds a row with non SQL results Row := TWideStringlist.Create; Row.Add(Col1); Row.Add(Col2); Row.Add(Col3); Row.Add(Col4); FResults.Add(Row); UpdateResultGrid; end; procedure TfrmTableTools.UpdateResultGrid; begin // Refresh resultgrid ResultGrid.RootNodeCount := FResults.Count; ResultGrid.FocusedNode := ResultGrid.GetLast; ResultGrid.Selected[ResultGrid.FocusedNode] := True; ResultGrid.Repaint; end; procedure TfrmTableTools.ResultGridCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer); begin Mainform.vstCompareNodes(Sender, Node1, Node2, Column, Result); end; procedure TfrmTableTools.ResultGridGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); begin NodeDataSize := SizeOf(TWideStringList); end; procedure TfrmTableTools.ResultGridInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); var Data: ^TWideStringList; begin // Bind string list to node Data := Sender.GetNodeData(Node); Data^ := FResults[Node.Index] as TWideStringList; end; procedure TfrmTableTools.ResultGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); var VT: TVirtualStringTree; Msg: WideString; 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 := clGrayText; end; end; procedure TfrmTableTools.ResultGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString); var Data: ^TWideStringList; 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.vstHeaderClick(Sender, HitInfo); end; procedure TfrmTableTools.comboExportOutputTypeChange(Sender: TObject); var OldItem: WideString; NewIdx, NetType: Integer; DBNode: PVirtualNode; SessionName: String; Databases: TWideStringlist; begin // Target type (file, directory, ...) selected OldItem := comboExportOutputTarget.Text; if Assigned(FTargetConnection) then FreeAndNil(FTargetConnection); if comboExportOutputType.Text = OUTPUT_FILE then begin comboExportOutputTarget.Style := csDropDown; comboExportOutputTarget.Items.Text := OutputFiles.Text; lblExportOutputTarget.Caption := 'Filename:'; btnExportOutputTargetSelect.Enabled := True; btnExportOutputTargetSelect.PngImage := Mainform.PngImageListMain.PngImages[10].PngImage; end else if comboExportOutputType.Text = OUTPUT_DIR then begin comboExportOutputTarget.Style := csDropDown; comboExportOutputTarget.Items.Text := OutputDirs.Text; lblExportOutputTarget.Caption := 'Directory:'; btnExportOutputTargetSelect.Enabled := True; btnExportOutputTargetSelect.PngImage := Mainform.PngImageListMain.PngImages[51].PngImage; end else if comboExportOutputType.Text = OUTPUT_DB then begin comboExportOutputTarget.Style := csDropDownList; lblExportOutputTarget.Caption := 'Database:'; btnExportOutputTargetSelect.Enabled := False; btnExportOutputTargetSelect.PngImage := Mainform.PngImageListMain.PngImages[27].PngImage; // Add unchecked databases comboExportOutputTarget.Items.Clear; DBNode := TreeObjects.GetFirstChild(TreeObjects.GetFirst); while Assigned(DBNode) do begin if DBNode.CheckState in [csUncheckedNormal, csUncheckedPressed] then comboExportOutputTarget.Items.Add(TreeObjects.Text[DBNode, 0]); DBNode := TreeObjects.GetNextSibling(DBNode); end; end else begin // Server selected. Display databases in below dropdown comboExportOutputTarget.Style := csDropDownList; lblExportOutputTarget.Caption := 'Database:'; btnExportOutputTargetSelect.Enabled := False; btnExportOutputTargetSelect.PngImage := Mainform.PngImageListMain.PngImages[27].PngImage; SessionName := Copy(comboExportOutputType.Text, Length(OUTPUT_SERVER)+1, Length(comboExportOutputType.Text)); FreeAndNil(FTargetConnection); FTargetConnection := TMySQLConnection.Create(Self); FTargetConnection.Hostname := GetRegValue(REGNAME_HOST, DEFAULT_HOST, SessionName); FTargetConnection.Username := GetRegValue(REGNAME_USER, DEFAULT_USER, SessionName); FTargetConnection.Password := decrypt(GetRegValue(REGNAME_PASSWORD, DEFAULT_PASSWORD, SessionName)); FTargetConnection.Port := StrToInt(GetRegValue(REGNAME_PORT, IntToStr(DEFAULT_PORT), SessionName)); if GetRegValue(REGNAME_COMPRESSED, DEFAULT_COMPRESSED, SessionName) then FTargetConnection.Options := FTargetConnection.Options + [opCompress] else FTargetConnection.Options := FTargetConnection.Options - [opCompress]; NetType := GetRegValue(REGNAME_NETTYPE, NETTYPE_TCPIP, SessionName); if NetType = NETTYPE_TCPIP then FTargetConnection.Socketname := '' else begin FTargetConnection.Socketname := FTargetConnection.Hostname; FTargetConnection.Hostname := '.'; end; FTargetConnection.LogPrefix := '['+SessionName+'] '; FTargetConnection.OnLog := Mainform.LogSQL; Screen.Cursor := crHourglass; try FTargetConnection.Active := True; Databases := FTargetConnection.GetCol('SHOW DATABASES'); comboExportOutputTarget.Items.Text := Databases.Text; comboExportOutputTarget.Items.Insert(0, '[Same as on source server]'); comboExportOutputTarget.ItemIndex := 0; Screen.Cursor := crDefault; except on E:Exception do begin Screen.Cursor := crDefault; MessageDlg(E.Message, mtError, [mbOK], 0); comboExportOutputType.ItemIndex := FLastOutputSelectedIndex; end; end; end; FLastOutputSelectedIndex := comboExportOutputType.ItemIndex; chkExportDatabasesCreate.Enabled := (comboExportOutputType.Text = OUTPUT_FILE) or (Copy(comboExportOutputType.Text, 1, Length(OUTPUT_SERVER)) = OUTPUT_SERVER); chkExportDatabasesDrop.Enabled := chkExportDatabasesCreate.Enabled; NewIdx := comboExportOutputTarget.Items.IndexOf(OldItem); if (NewIdx = -1) and (comboExportOutputTarget.Items.Count > 0) then NewIdx := 0; comboExportOutputTarget.ItemIndex := NewIdx; end; procedure TfrmTableTools.comboExportOutputTargetExit(Sender: TObject); var ItemList: TWideStringList; idx: Integer; begin // Add typed text to recent items if comboExportOutputTarget.Text = '' then Exit; ItemList := nil; if comboExportOutputType.Text = OUTPUT_FILE then ItemList := OutputFiles else if comboExportOutputType.Text = OUTPUT_DIR then ItemList := OutputDirs; if not Assigned(ItemList) then Exit; idx := ItemList.IndexOf(comboExportOutputTarget.Text); if idx > -1 then ItemList.Delete(idx); ItemList.Insert(0, comboExportOutputTarget.Text); end; procedure TfrmTableTools.chkExportOptionClick(Sender: TObject); 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; end; procedure TfrmTableTools.btnExportOutputTargetSelectClick(Sender: TObject); var SaveDialog: TSaveDialog; Browse: TBrowseForFolder; begin case comboExportOutputType.ItemIndex of 0: begin // Select filename SaveDialog := TSaveDialog.Create(Self); SaveDialog.DefaultExt := 'sql'; SaveDialog.Filter := 'SQL-Scripts (*.sql)|*.sql|All Files (*.*)|*.*'; SaveDialog.Options := SaveDialog.Options + [ofOverwritePrompt]; if SaveDialog.Execute then comboExportOutputTarget.Text := SaveDialog.FileName; SaveDialog.Free; end; 1: 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; end; 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; procedure TfrmTableTools.DoExport(db, obj: WideString; NodeType: TListNodeType; RowsInTable, AvgRowLen: Int64); var ToFile, ToDir, ToDb, ToServer, IsLastRowInChunk, NeedsDBStructure: Boolean; Struc, Header, FinalDbName, BaseInsert, Row, TargetDbAndObject, objtype: WideString; LogRow: TWideStringlist; i: Integer; RowCount, MaxRowsInChunk, RowsInChunk, Limit, Offset, ResultCount: Int64; StartTime: Cardinal; Data: TMySQLQuery; rx: TRegExpr; // Short version of Mainform.Mask() function m(s: WideString): WideString; begin Result := Mainform.mask(s); end; // Pass output to file or query, and append semicolon if needed procedure Output(SQL: WideString; IsEndOfQuery, ForFile, ForDir, ForDb, ForServer: Boolean); var SA: AnsiString; ChunkSize: Integer; begin if (ToFile and ForFile) or (ToDir and ForDir) then begin if IsEndOfQuery then SQL := SQL + ';'+CRLF; StreamWrite(ExportStream, SQL); 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; SQL := UTF8Decode(SA); if ToDB then Mainform.Connection.Query(SQL) else if ToServer then FTargetConnection.Query(SQL); SQL := ''; end; end; end; begin // Handle one table, view or routine in SQL export mode AddResults('SELECT '+esc(db)+' AS '+Mainform.mask('Database')+', ' + esc(obj)+' AS '+Mainform.mask('Table')+', ' + IntToStr(RowsInTable)+' AS '+Mainform.mask('Rows')+', '+ '0 AS '+Mainform.mask('Duration') ); ToFile := comboExportOutputType.Text = OUTPUT_FILE; ToDir := comboExportOutputType.Text = OUTPUT_DIR; ToDb := comboExportOutputType.Text = OUTPUT_DB; ToServer := Copy(comboExportOutputType.Text, 1, Length(OUTPUT_SERVER)) = OUTPUT_SERVER; case NodeType of lntTable, lntCrashedTable: objtype := 'table'; lntView: objtype := 'view'; else 'unknown object type'; end; StartTime := GetTickCount; try if ToDir then begin FreeAndNil(ExportStream); if not DirectoryExists(comboExportOutputTarget.Text) then ForceDirectories(comboExportOutputTarget.Text); ExportStream := TFileStream.Create(comboExportOutputTarget.Text+'\'+GoodFileName(obj)+'.sql', fmCreate or fmOpenWrite); end; if ToFile and (not Assigned(ExportStream)) then ExportStream := TFileStream.Create(comboExportOutputTarget.Text, fmCreate or fmOpenWrite); if ToDb or ToServer then ExportStream := TMemoryStream.Create; if (db<>ExportLastDatabase) or ToDir then begin Header := '# --------------------------------------------------------' + CRLF + WideFormat('# %-30s%s', ['Host:', Mainform.Connection.HostName]) + CRLF + WideFormat('# %-30s%s', ['Database:', db]) + CRLF + WideFormat('# %-30s%s', ['Server version:', Mainform.Connection.ServerVersionUntouched]) + CRLF + WideFormat('# %-30s%s', ['Server OS:', Mainform.Connection.GetVar('SHOW VARIABLES LIKE ' + esc('version_compile_os'), 1)]) + CRLF + WideFormat('# %-30s%s', [APPNAME + ' version:', FullAppVersion]) + CRLF + WideFormat('# %-30s%s', ['Date/time:', DateTimeToStr(Now)]) + CRLF + '# --------------------------------------------------------' + CRLF + CRLF + '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;' + CRLF + '/*!40101 SET NAMES '+Mainform.Connection.CharacterSet+' */;' + CRLF + '/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;' + CRLF; Output(Header, False, db<>ExportLastDatabase, True, False, False); end; except on E:Exception do begin MessageDlg(E.Message, mterror, [mbOK], 0); Exit; end; end; // Database structure. Do that only in single-file and server mode. drop/create/use in directory or database mode makes no sense FinalDbName := db; if ToDb or (ToServer and (comboExportOutputTarget.ItemIndex > 0)) then FinalDbName := comboExportOutputTarget.Text; NeedsDBStructure := FinalDbName <> ExportLastDatabase; if chkExportDatabasesDrop.Checked or chkExportDatabasesCreate.Checked then begin Output(CRLF+'# Dumping database structure for '+db+CRLF, False, NeedsDBStructure, False, False, False); if chkExportDatabasesDrop.Checked and chkExportDatabasesDrop.Enabled then Output('DROP DATABASE IF EXISTS '+m(FinalDbName), True, NeedsDBStructure, False, False, NeedsDBStructure); if chkExportDatabasesCreate.Checked and chkExportDatabasesCreate.Enabled then begin if Mainform.Connection.ServerVersionInt >= 40100 then begin Struc := Mainform.Connection.GetVar('SHOW CREATE DATABASE '+m(db), 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 := WideStringReplace(Struc, db, FinalDbName, []); end else Struc := 'CREATE DATABASE IF NOT EXISTS '+m(FinalDbName); Output(Struc, True, NeedsDBStructure, False, False, NeedsDBStructure); Output('USE '+m(FinalDbName), True, 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('USE '+m(FinalDbName), True, False, False, False, NeedsDBStructure); end; // Table structure if chkExportTablesDrop.Checked or chkExportTablesCreate.Checked then begin Output(CRLF+CRLF+'# Dumping structure for '+objtype+' '+db+'.'+obj+CRLF, False, True, True, False, False); if chkExportTablesDrop.Checked then begin Struc := 'DROP TABLE IF EXISTS '; if ToDb then Struc := Struc + m(FinalDbName)+'.'; Struc := Struc + m(obj); Output(Struc, True, True, True, True, True); end; if chkExportTablesCreate.Checked then begin try Struc := Mainform.Connection.GetVar('SHOW CREATE TABLE '+m(db)+'.'+m(obj), 1); Struc := fixNewlines(Struc); // Remove AUTO_INCREMENT clause rx := TRegExpr.Create; rx.ModifierI := True; rx.Expression := '\sAUTO_INCREMENT\s*\=\s*\d+\s'; Struc := rx.Replace(Struc, ' '); rx.Free; if NodeType = lntTable then Insert('IF NOT EXISTS ', Struc, Pos('TABLE', Struc) + 6); if ToDb then begin if NodeType = lntTable then Insert(m(FinalDbName)+'.', Struc, Pos('EXISTS', Struc) + 7 ) else if NodeType = lntView then Insert(m(FinalDbName)+'.', Struc, Pos('VIEW', Struc) + 5 ); end; Output(Struc, True, True, True, True, True); except On E:Exception 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; case NodeType of lntTable, lntCrashedTable: begin // Table data if comboExportData.Text = DATA_NO then begin Output(CRLF+'# Data exporting was unselected.'+CRLF, False, True, True, False, False); end else if RowsInTable = 0 then begin Output(CRLF+'# No rows in table '+db+'.'+obj+CRLF, False, True, True, False, False); end else begin Output(CRLF+'# Dumping data for table '+db+'.'+obj+': '+FormatNumber(RowsInTable)+' rows'+CRLF, False, True, True, False, False); TargetDbAndObject := m(obj); if ToDb then TargetDbAndObject := m(FinalDbName) + '.' + TargetDbAndObject; Offset := 0; RowCount := 0; // Calculate limit so we select ~100MB per loop Limit := Round(100 * SIZE_MB / Max(AvgRowLen,1)); // Calculate max rows per INSERT, so we always get ~800KB MaxRowsInChunk := Round(SIZE_MB * 0.6 / Max(AvgRowLen,1)); if comboExportData.Text = DATA_REPLACE then Output('DELETE FROM '+TargetDbAndObject, True, True, True, True, True); Output('/*!40000 ALTER TABLE '+TargetDbAndObject+' DISABLE KEYS */', True, True, True, True, True); 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 + ' ('; while true do begin Data := Mainform.Connection.GetResults('SELECT * FROM '+m(db)+'.'+m(obj)+' LIMIT '+IntToStr(Offset)+', '+IntToStr(Limit)); Inc(Offset, Limit); if Data.RecordCount = 0 then break; for i:=0 to Data.ColumnCount-1 do BaseInsert := BaseInsert + m(Data.ColumnNames[i]) + ', '; Delete(BaseInsert, Length(BaseInsert)-1, 2); BaseInsert := BaseInsert + ') VALUES ('; while true do begin RowsInChunk := 0; Output(BaseInsert, False, True, True, True, True); while not Data.Eof do begin Inc(RowCount); Inc(RowsInChunk); Row := ''; for i:=0 to Data.ColumnCount-1 do begin if Data.IsNull(i) then Row := Row + 'NULL' else case Data.DataType(i).Category of dtcInteger, dtcReal: Row := Row + Data.Col(i); dtcBinary: Row := Row + '_binary 0x' + BinToWideHex(Data.Col(i)); else Row := Row + esc(Data.Col(i)); end; if i db) then Specs.Add('RENAME ' + Mainform.mask(comboBulkTableEditDatabase.Text)+'.'+Mainform.mask(obj)); if chkBulkTableEditEngine.Checked then begin if Mainform.Connection.ServerVersionInt < 40018 then Specs.Add('TYPE '+comboBulkTableEditEngine.Text) else Specs.Add('ENGINE '+comboBulkTableEditEngine.Text); end; if chkBulkTableEditCollation.Checked and (comboBulkTableEditCollation.ItemIndex > -1) then Specs.Add('COLLATE '+comboBulkTableEditCollation.Text); if chkBulkTableEditCharset.Checked and (comboBulkTableEditCharset.ItemIndex > -1) then begin Mainform.Connection.CharsetTable.RecNo := comboBulkTableEditCharset.ItemIndex; Specs.Add('CONVERT TO CHARSET '+Mainform.Connection.CharsetTable.Col('Charset')); end; if chkBulkTableEditResetAutoinc.Checked then Specs.Add('AUTO_INCREMENT=0'); AddResults('SELECT '+esc(db)+' AS '+Mainform.mask('Database')+', ' + esc(obj)+' AS '+Mainform.mask('Table')+', ' + esc('Updating...')+' AS '+Mainform.mask('Operation')+', '+ ''''' AS '+Mainform.mask('Result') ); Mainform.Connection.Query('ALTER TABLE ' + Mainform.mask(db) + '.' + Mainform.mask(obj) + ' ' + ImplodeStr(', ', Specs)); LogRow := TWideStringList(FResults.Last); LogRow[2] := 'Done'; LogRow[3] := 'Success'; UpdateResultGrid; end; end.