diff --git a/out/locale/en/LC_MESSAGES/default.po b/out/locale/en/LC_MESSAGES/default.po index 51887aa0..baf9489a 100644 --- a/out/locale/en/LC_MESSAGES/default.po +++ b/out/locale/en/LC_MESSAGES/default.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: HeidiSQL\n" "POT-Creation-Date: 2012-11-05 21:40\n" -"PO-Revision-Date: 2021-06-06 17:21+0200\n" +"PO-Revision-Date: 2021-06-25 08:53+0200\n" "Last-Translator: Ansgar Becker \n" "Language-Team: English (http://www.transifex.com/projects/p/heidisql/language/en/)\n" "MIME-Version: 1.0\n" @@ -6690,3 +6690,6 @@ msgstr "Close all query tabs" msgid "Close query tabs to the right" msgstr "Close query tabs to the right" + +msgid "Copy mysqldump command" +msgstr "Copy mysqldump command" diff --git a/packages/Delphi10.4/heidisql.dpr b/packages/Delphi10.4/heidisql.dpr index 12df0ffe..8691805a 100644 --- a/packages/Delphi10.4/heidisql.dpr +++ b/packages/Delphi10.4/heidisql.dpr @@ -47,7 +47,8 @@ uses Vcl.Styles, Vcl.Graphics, theme_preview in '..\..\source\theme_preview.pas' {frmThemePreview}, - csv_detector in '..\..\source\csv_detector.pas' {frmCsvDetector}; + csv_detector in '..\..\source\csv_detector.pas' {frmCsvDetector}, + generic_types in '..\..\source\generic_types.pas'; {.$R *.RES} {$R ..\..\res\icon.RES} diff --git a/packages/Delphi10.4/heidisql.dproj b/packages/Delphi10.4/heidisql.dproj index 6a01ed36..11ceee7e 100644 --- a/packages/Delphi10.4/heidisql.dproj +++ b/packages/Delphi10.4/heidisql.dproj @@ -7,7 +7,7 @@ 3 Application VCL - 19.1 + 19.2 Win64 @@ -238,6 +238,7 @@
frmCsvDetector
dfm + Cfg_2 Base diff --git a/source/about.pas b/source/about.pas index 0aa7d3ce..70601826 100644 --- a/source/about.pas +++ b/source/about.pas @@ -8,7 +8,7 @@ interface uses Windows, Classes, Graphics, Forms, Controls, StdCtrls, ExtCtrls, SysUtils, ComCtrls, pngimage, gnugettext, - Dialogs, SynRegExpr, Vcl.Menus, ClipBrd, extra_controls; + Dialogs, SynRegExpr, Vcl.Menus, ClipBrd, extra_controls, generic_types; type TAboutBox = class(TExtForm) diff --git a/source/apphelpers.pas b/source/apphelpers.pas index 36dc6426..401e8b58 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -383,6 +383,7 @@ type function SynCompletionProposalPrettyText(ImageIndex: Integer; LeftText, CenterText, RightText: String; LeftColor: TColor=-1; CenterColor: TColor=-1; RightColor: TColor=-1): String; function PopupComponent(Sender: TObject): TComponent; function IsWine: Boolean; + function DirSep: Char; var AppSettings: TAppSettings; @@ -2932,6 +2933,14 @@ begin end; +function DirSep: Char; +begin + if IsWine then + Result := '/' + else + Result := '\'; +end; + { Threading stuff } diff --git a/source/dbconnection.pas b/source/dbconnection.pas index 58f970b9..b9b9657c 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -5,7 +5,7 @@ interface uses Classes, SysUtils, windows, dbstructures, SynRegExpr, Generics.Collections, Generics.Defaults, DateUtils, Types, Math, Dialogs, ADODB, DB, DBCommon, ComObj, Graphics, ExtCtrls, StrUtils, - gnugettext, AnsiStrings, Controls, Forms, System.IOUtils; + gnugettext, AnsiStrings, Controls, Forms, System.IOUtils, generic_types; type @@ -323,7 +323,7 @@ type function DefaultPort: Integer; function DefaultUsername: String; function DefaultIgnoreDatabasePattern: String; - function GetExternalCliArguments(Connection: TDBConnection; ReplacePassword: Boolean): String; + function GetExternalCliArguments(Connection: TDBConnection; ReplacePassword: TThreeStateBoolean): String; published property IsFolder: Boolean read FIsFolder write FIsFolder; property NetType: TNetType read FNetType write FNetType; @@ -1723,7 +1723,7 @@ begin end; -function TConnectionParameters.GetExternalCliArguments(Connection: TDBConnection; ReplacePassword: Boolean): String; +function TConnectionParameters.GetExternalCliArguments(Connection: TDBConnection; ReplacePassword: TThreeStateBoolean): String; var Args: TStringList; begin @@ -1756,14 +1756,15 @@ begin Args.Add('--user="'+Username+'"'); if Password <> '' then begin - if ReplacePassword then - Args.Add('--password="***"') - else - Args.Add('--password="'+StringReplace(Password, '"', '\"', [rfReplaceAll])+'"'); + case ReplacePassword of + nbTrue: Args.Add('--password="***"'); + nbFalse: Args.Add('--password="'+StringReplace(Password, '"', '\"', [rfReplaceAll])+'"'); + nbUnset: Args.Add('--password'); // will prompt + end; end; if Compressed then Args.Add('--compress'); - if Connection.Database <> '' then + if Assigned(Connection) and (Connection.Database <> '') then Args.Add('--database="' + Connection.Database + '"'); Result := ' ' + Implode(' ', Args); diff --git a/source/generic_types.pas b/source/generic_types.pas new file mode 100644 index 00000000..a6222d8a --- /dev/null +++ b/source/generic_types.pas @@ -0,0 +1,11 @@ +unit generic_types; + +interface + +type + TThreeStateBoolean = (nbUnset, nbFalse, nbTrue); + + +implementation + +end. diff --git a/source/main.pas b/source/main.pas index 4365a64a..b8cfbc15 100644 --- a/source/main.pas +++ b/source/main.pas @@ -20,7 +20,7 @@ uses JumpList, System.Actions, System.UITypes, pngimage, System.ImageList, Vcl.Styles.UxTheme, Vcl.Styles.Utils.Menus, Vcl.Styles.Utils.Forms, Vcl.VirtualImageList, Vcl.BaseImageCollection, Vcl.ImageCollection, System.IniFiles, extra_controls, - SynEditCodeFolding, texteditor, System.Character; + SynEditCodeFolding, texteditor, System.Character, generic_types; type @@ -156,8 +156,6 @@ type function Compare(const Left, Right: TQueryHistoryItem): Integer; override; end; - TThreeStateBoolean = (nbUnset, nbFalse, nbTrue); - ITaskbarList = interface(IUnknown) [SID_ITaskbarList] function HrInit: HRESULT; stdcall; @@ -3855,10 +3853,10 @@ begin cmd := '$TERM'; end; - log := path + cmd + p + Conn.Parameters.GetExternalCliArguments(Conn, True); + log := path + cmd + p + Conn.Parameters.GetExternalCliArguments(Conn, nbTrue); LogSQL(f_('Launching command line: %s', [log]), lcInfo); - p := p + Conn.Parameters.GetExternalCliArguments(Conn, False); + p := p + Conn.Parameters.GetExternalCliArguments(Conn, nbFalse); ShellExec(cmd, path, p); end; end; diff --git a/source/tabletools.dfm b/source/tabletools.dfm index c0ec2619..cb54b5dc 100644 --- a/source/tabletools.dfm +++ b/source/tabletools.dfm @@ -697,5 +697,9 @@ object frmTableTools: TfrmTableTools AutoCheck = True Caption = 'Remove DEFINER clauses' end + object menuCopyMysqldumpCommand: TMenuItem + Caption = 'Copy mysqldump command' + OnClick = menuCopyMysqldumpCommandClick + end end end diff --git a/source/tabletools.pas b/source/tabletools.pas index d6d9d87c..fb0493a3 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -12,7 +12,7 @@ uses Windows, SysUtils, Classes, Controls, Forms, StdCtrls, ComCtrls, Buttons, Dialogs, StdActns, VirtualTrees, ExtCtrls, Graphics, SynRegExpr, Math, Generics.Collections, extra_controls, dbconnection, apphelpers, Menus, gnugettext, DateUtils, System.Zip, System.UITypes, StrUtils, Messages, - SynEdit, SynMemo; + SynEdit, SynMemo, ClipBrd, generic_types; type TToolMode = (tmMaintenance, tmFind, tmSQLExport, tmBulkTableEdit); @@ -88,9 +88,11 @@ type 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; @@ -133,6 +135,7 @@ type procedure CheckAllClick(Sender: TObject); procedure TreeObjectsExpanded(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure btnExportOptionsClick(Sender: TObject); + procedure menuCopyMysqldumpCommandClick(Sender: TObject); const StatusMsg = '%s %s ...'; private @@ -240,6 +243,7 @@ begin 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'); @@ -379,6 +383,75 @@ begin 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; + Arguments.Add('--databases ' + 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 @@ -638,6 +711,43 @@ begin 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; @@ -678,40 +788,6 @@ var end; end; - procedure SetCheckedObjects(DBNode: PVirtualNode); - var - Child, GrandChild: PVirtualNode; - ChildObj, GrandChildObj: PDBObject; - begin - CheckedObjects.Clear; - 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); - CheckedObjects.Add(GrandChildObj^); - end; - GrandChild := TreeObjects.GetNextSibling(GrandChild); - end; - end - - else begin - CheckedObjects.Add(ChildObj^); - end; - - end; - end; - Child := TreeObjects.GetNextSibling(Child); - end; - end; - begin Screen.Cursor := crHourGlass; // Disable critical controls so ProcessMessages is unable to do things while export is in progress @@ -731,7 +807,6 @@ begin ResultGrid.Clear; FResults.Clear; FFindSeeResultSQL.Clear; - CheckedObjects := TDBObjectList.Create(False); Triggers := TDBObjectList.Create(False); // False, so we can .Free that object afterwards without loosing the contained objects Views := TDBObjectList.Create(False); FHeaderCreated := False; @@ -750,7 +825,7 @@ begin Triggers.Clear; Views.Clear; FSecondExportPass := False; - SetCheckedObjects(DBNode); + 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 diff --git a/source/updatecheck.pas b/source/updatecheck.pas index 8d43f3d1..f3806ba7 100644 --- a/source/updatecheck.pas +++ b/source/updatecheck.pas @@ -5,7 +5,7 @@ interface uses Windows, Messages, SysUtils, Classes, Forms, StdCtrls, IniFiles, Controls, Graphics, apphelpers, gnugettext, ExtCtrls, extra_controls, System.StrUtils, Vcl.Dialogs, - Vcl.Menus, Vcl.Clipbrd; + Vcl.Menus, Vcl.Clipbrd, generic_types; type TfrmUpdateCheck = class(TExtForm)