From 30b970c92b907db27d02b3084be0ad7d4d549fca Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Sat, 28 Nov 2009 19:51:57 +0000 Subject: [PATCH] Implement bulk table editing in table tools dialog. Fixes issue #576 --- source/main.dfm | 9 +++ source/main.pas | 6 +- source/mysql_connection.pas | 39 +++++++++--- source/tabletools.dfm | 116 ++++++++++++++++++++++++++++++++---- source/tabletools.pas | 99 ++++++++++++++++++++++++++++-- 5 files changed, 245 insertions(+), 24 deletions(-) diff --git a/source/main.dfm b/source/main.dfm index dd575cfd..88147f79 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -2231,6 +2231,12 @@ object MainForm: TMainForm ShortCut = 16454 OnExecute = actFilterPanelExecute end + object actBulkTableEdit: TAction + Category = 'Tools' + Caption = 'Bulk table editor' + ImageIndex = 19 + OnExecute = actTableToolsExecute + end end object SaveDialog2: TSaveDialog DefaultExt = 'reg' @@ -6093,6 +6099,9 @@ object MainForm: TMainForm object Findtextonserver1: TMenuItem Action = actFindTextOnServer end + object menuBulkTableEdit: TMenuItem + Action = actBulkTableEdit + end object menuEmptyTables: TMenuItem Action = actEmptyTables end diff --git a/source/main.pas b/source/main.pas index e61356ab..e1db75c5 100644 --- a/source/main.pas +++ b/source/main.pas @@ -439,6 +439,8 @@ type actFindTextOnServer: TAction; actFindTextOnServer1: TMenuItem; Findtextonserver1: TMenuItem; + actBulkTableEdit: TAction; + menuBulkTableEdit: TMenuItem; procedure refreshMonitorConfig; procedure loadWindowConfig; procedure saveWindowConfig; @@ -1798,7 +1800,9 @@ begin else if Sender = actFindTextOnServer then TableToolsDialog.ToolMode := tmFind else if Sender = actExportTables then - TableToolsDialog.ToolMode := tmSQLExport; + TableToolsDialog.ToolMode := tmSQLExport + else if Sender = actBulkTableEdit then + TableToolsDialog.ToolMode := tmBulkTableEdit; TableToolsDialog.ShowModal; end; diff --git a/source/mysql_connection.pas b/source/mysql_connection.pas index f101ca04..ca2f9cd8 100644 --- a/source/mysql_connection.pas +++ b/source/mysql_connection.pas @@ -87,7 +87,7 @@ type FTableEngines: TStringList; FTableEngineDefault: String; FCollationTable: TMySQLQuery; - FCollationsUnavailable: Boolean; + FCharsetTable: TMySQLQuery; procedure SetActive(Value: Boolean); procedure SetDatabase(Value: WideString); function GetThreadId: Cardinal; @@ -99,6 +99,8 @@ type function GetTableEngines: TStringList; function GetCollationTable: TMySQLQuery; function GetCollationList: TStringList; + function GetCharsetTable: TMySQLQuery; + function GetCharsetList: TStringList; function GetConnectionUptime: Integer; function GetServerUptime: Integer; procedure Log(Category: TMySQLLogCategory; Msg: WideString); @@ -135,6 +137,8 @@ type property TableEngineDefault: String read FTableEngineDefault; property CollationTable: TMySQLQuery read GetCollationTable; property CollationList: TStringList read GetCollationList; + property CharsetTable: TMySQLQuery read GetCharsetTable; + property CharsetList: TStringList read GetCharsetList; published property Active: Boolean read FActive write SetActive default False; property Hostname: String read FHostname write FHostname; @@ -687,12 +691,8 @@ end; function TMySQLConnection.GetCollationTable: TMySQLQuery; begin - if (not Assigned(FCollationTable)) and (not FCollationsUnavailable) then try + if (not Assigned(FCollationTable)) and (ServerVersionInt >= 40100) then FCollationTable := GetResults('SHOW COLLATION'); - except - // Ignore errors on old servers - FCollationsUnavailable := True; - end; if Assigned(FCollationTable) then FCollationTable.First; Result := FCollationTable; @@ -705,13 +705,36 @@ var begin c := CollationTable; Result := TStringList.Create; - if not FCollationsUnavailable then while not c.Eof do begin + if Assigned(c) then while not c.Eof do begin Result.Add(c.Col('Collation')); c.Next; end; end; +function TMySQLConnection.GetCharsetTable: TMySQLQuery; +begin + if (not Assigned(FCharsetTable)) and (ServerVersionInt >= 40100) then + FCharsetTable := GetResults('SHOW CHARSET'); + if Assigned(FCharsetTable) then + FCharsetTable.First; + Result := FCharsetTable; +end; + + +function TMySQLConnection.GetCharsetList: TStringList; +var + c: TMySQLQuery; +begin + c := CharsetTable; + Result := TStringList.Create; + if Assigned(c) then while not c.Eof do begin + Result.Add(c.Col('Description') + ' (' + c.Col('Charset') + ')'); + c.Next; + end; +end; + + function TMySQLConnection.GetConnectionUptime: Integer; begin // Return seconds since last connect @@ -733,7 +756,7 @@ procedure TMySQLConnection.ClearCache; begin // Free cached lists and results. Called when the connection was closed and/or destroyed FreeAndNil(FCollationTable); - FCollationsUnavailable := False; + FreeAndNil(FCharsetTable); FreeAndNil(FTableEngines); FTableEngineDefault := ''; end; diff --git a/source/tabletools.dfm b/source/tabletools.dfm index 36589ac9..16cc1c37 100644 --- a/source/tabletools.dfm +++ b/source/tabletools.dfm @@ -117,9 +117,9 @@ object frmTableTools: TfrmTableTools TabOrder = 1 object ResultGrid: TVirtualStringTree Left = 0 - Top = 137 + Top = 145 Width = 380 - Height = 199 + Height = 191 Align = alClient Header.AutoSizeIndex = -1 Header.DefaultHeight = 17 @@ -148,7 +148,7 @@ object frmTableTools: TfrmTableTools Left = 0 Top = 0 Width = 380 - Height = 137 + Height = 145 ActivePage = tabSQLexport Align = alTop Images = MainForm.PngImageListMain @@ -159,7 +159,7 @@ object frmTableTools: TfrmTableTools ImageIndex = 39 DesignSize = ( 372 - 108) + 116) object lblOperation: TLabel Left = 3 Top = 14 @@ -263,7 +263,7 @@ object frmTableTools: TfrmTableTools ImageIndex = 30 DesignSize = ( 372 - 108) + 116) object lblFindText: TLabel Left = 3 Top = 14 @@ -273,16 +273,17 @@ object frmTableTools: TfrmTableTools end object lblDataTypes: TLabel Left = 80 - Top = 77 + Top = 93 Width = 151 Height = 13 + Anchors = [akLeft, akBottom] Caption = 'Restrict search to column types' end object memoFindText: TTntMemo Left = 80 Top = 11 Width = 289 - Height = 53 + Height = 73 Anchors = [akLeft, akTop, akRight, akBottom] ScrollBars = ssVertical TabOrder = 0 @@ -290,11 +291,11 @@ object frmTableTools: TfrmTableTools end object comboDataTypes: TComboBox Left = 264 - Top = 74 + Top = 90 Width = 105 Height = 21 Style = csDropDownList - Anchors = [akLeft, akTop, akRight] + Anchors = [akLeft, akRight, akBottom] ItemHeight = 13 TabOrder = 1 end @@ -304,7 +305,7 @@ object frmTableTools: TfrmTableTools ImageIndex = 9 DesignSize = ( 372 - 108) + 116) object lblExportData: TLabel Left = 224 Top = 4 @@ -419,6 +420,101 @@ object frmTableTools: TfrmTableTools OnExit = comboExportOutputTargetExit end end + object tabBulkTableEdit: TTabSheet + Caption = 'Bulk table editor' + ImageIndex = 19 + DesignSize = ( + 372 + 116) + object chkBulkTableEditDatabase: TCheckBox + Left = 3 + Top = 5 + Width = 135 + Height = 17 + Caption = 'Move to database:' + TabOrder = 0 + OnClick = chkBulkTableEditCheckComboClick + end + object comboBulkTableEditDatabase: TTntComboBox + Left = 168 + Top = 3 + Width = 200 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + Enabled = False + ItemHeight = 13 + TabOrder = 1 + end + object chkBulkTableEditResetAutoinc: TCheckBox + Left = 3 + Top = 97 + Width = 166 + Height = 17 + Caption = 'Reset auto increment value' + TabOrder = 2 + end + object chkBulkTableEditCollation: TCheckBox + Left = 3 + Top = 51 + Width = 150 + Height = 17 + Caption = 'Change default collation:' + TabOrder = 3 + OnClick = chkBulkTableEditCheckComboClick + end + object comboBulkTableEditCollation: TComboBox + Left = 168 + Top = 49 + Width = 200 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + Enabled = False + ItemHeight = 13 + TabOrder = 4 + end + object chkBulkTableEditEngine: TCheckBox + Left = 3 + Top = 28 + Width = 150 + Height = 17 + Caption = 'Change table engine:' + TabOrder = 5 + OnClick = chkBulkTableEditCheckComboClick + end + object comboBulkTableEditEngine: TComboBox + Left = 168 + Top = 26 + Width = 200 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + Enabled = False + ItemHeight = 13 + TabOrder = 6 + end + object chkBulkTableEditCharset: TCheckBox + Left = 3 + Top = 74 + Width = 150 + Height = 17 + Caption = 'Convert to charset:' + TabOrder = 7 + OnClick = chkBulkTableEditCheckComboClick + end + object comboBulkTableEditCharset: TComboBox + Left = 168 + Top = 72 + Width = 200 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + Enabled = False + ItemHeight = 13 + TabOrder = 8 + end + end end end end diff --git a/source/tabletools.pas b/source/tabletools.pas index 0640c914..2505f32a 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -14,7 +14,7 @@ uses PngSpeedButton, helpers; type - TToolMode = (tmMaintenance, tmFind, tmSQLExport); + TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit); TfrmTableTools = class(TForm) btnClose: TButton; pnlTop: TPanel; @@ -58,6 +58,16 @@ type 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); @@ -89,6 +99,7 @@ type 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; @@ -105,6 +116,7 @@ type 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; @@ -251,6 +263,22 @@ begin 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; @@ -264,7 +292,7 @@ end; procedure TfrmTableTools.ValidateControls(Sender: TObject); var - SomeChecked: Boolean; + SomeChecked, OptionChecked: Boolean; op: String; begin SomeChecked := TreeObjects.CheckedCount > 0; @@ -290,6 +318,13 @@ begin 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; @@ -372,7 +407,9 @@ begin else if tabsTools.ActivePage = tabFind then FToolMode := tmFind else if tabsTools.ActivePage = tabSQLExport then - FToolMode := tmSQLExport; + FToolMode := tmSQLExport + else if tabsTools.ActivePage = tabBulkTableEdit then + FToolMode := tmBulkTableEdit; ResultGrid.Clear; FResults.Clear; TreeObjects.SetFocus; @@ -393,8 +430,8 @@ begin 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); + 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 @@ -403,6 +440,7 @@ begin 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. @@ -747,6 +785,7 @@ begin chkExportTablesDrop.Checked := False; end; + procedure TfrmTableTools.btnExportOutputTargetSelectClick(Sender: TObject); var SaveDialog: TSaveDialog; @@ -783,6 +822,7 @@ begin tmMaintenance: tabsTools.ActivePage := tabMaintenance; tmFind: tabsTools.ActivePage := tabFind; tmSQLExport: tabsTools.ActivePage := tabSQLExport; + tmBulkTableEdit: tabsTools.ActivePage := tabBulkTableEdit; end; end; @@ -1031,4 +1071,53 @@ begin 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(db, obj: WideString; NodeType: TListNodeType); +var + Specs, LogRow: TWideStringList; +begin + Specs := TWideStringlist.Create; + if chkBulkTableEditDatabase.Checked and (comboBulkTableEditDatabase.Text <> 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.