unit exportsql; // ------------------------------------- // Export Tables // ------------------------------------- interface uses Threading, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, CheckLst, Buttons, comctrls, Registry, ToolWin, DB, SynEdit, SynMemo, ZDataSet; type TExportSQLForm = class(TForm) btnExport: TButton; btnCancel: TButton; dialogSave: TSaveDialog; barProgress: TProgressBar; lblProgress: TLabel; pageControl1: TPageControl; TabSheet1: TTabSheet; TabSheet2: TTabSheet; lblSelectDbTables: TLabel; checkListTables: TCheckListBox; comboSelectDatabase: TComboBox; toolbarSelectTools: TToolBar; ToolButton1: TToolButton; ToolButton2: TToolButton; groupOutput: TGroupBox; btnFileBrowse: TBitBtn; editFileName: TEdit; radioOtherDatabase: TRadioButton; radioFile: TRadioButton; comboOtherDatabase: TComboBox; radioOtherHost: TRadioButton; comboOtherHost: TComboBox; comboOtherHostDatabase: TComboBox; groupExampleSql: TGroupBox; SynMemoExampleSQL: TSynMemo; groupOptions: TGroupBox; lblTargetCompat: TLabel; cbxStructure: TCheckBox; cbxDatabase: TCheckBox; comboDatabase: TComboBox; cbxTables: TCheckBox; comboTables: TComboBox; cbxData: TCheckBox; comboData: TComboBox; comboTargetCompat: TComboBox; procedure comboTargetCompatChange(Sender: TObject); procedure comboOtherHostSelect(Sender: TObject); procedure comboDataChange(Sender: TObject); procedure comboTablesChange(Sender: TObject); procedure comboDatabaseChange(Sender: TObject); procedure cbxTablesClick(Sender: TObject); procedure cbxDatabaseClick(Sender: TObject); procedure btnCancelClick(Sender: TObject); procedure FormShow(Sender: TObject); procedure comboSelectDatabaseChange(Sender: TObject); procedure CheckListToggle(Sender: TObject); procedure btnFileBrowseClick(Sender: TObject); procedure btnExportClick(Sender: TObject); procedure radioOtherDatabaseClick(Sender: TObject); procedure radioFileClick(Sender: TObject); procedure fillcombo_anotherdb(Sender: TObject); procedure generateExampleSQL; procedure validateRadioControls(Sender: TObject); procedure validateControls(Sender: TObject); procedure cbxStructureClick(Sender: TObject); procedure radioOtherHostClick(Sender: TObject); procedure cbxDataClick(Sender: TObject); procedure checkListTablesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure SaveSettings; private { Private declarations } public { Public declarations } end; function ExportTablesWindow (AOwner : TComponent; Flags : String = '') : Boolean; {$I const.inc} implementation uses Main, Childwin, Helpers, Synchronization, Communication; {$R *.DFM} const // Order of items in combo box: comboDatabase DB_DROP_CREATE = 0; DB_CREATE = 1; DB_CREATE_IGNORE = 2; // Order of items in combo box: comboTables TAB_DROP_CREATE = 0; TAB_CREATE = 1; TAB_CREATE_IGNORE = 2; // Order of items in combo box: comboData DATA_TRUNCATE_INSERT = 0; DATA_INSERT = 1; DATA_INSERT_IGNORE = 2; DATA_REPLACE_INTO = 3; // Order of radiobutton group "Output" OUTPUT_FILE = 1; OUTPUT_DB = 2; OUTPUT_HOST = 3; // Default output compatibility SQL_VERSION_DEFAULT = SQL_VERSION_ANSI; var appHandles: array of THandle; cancelDialog: TForm = nil; remote_version: integer; remote_max_allowed_packet : Int64; target_versions : TStringList; function ExportTablesWindow (AOwner : TComponent; Flags : String = '') : Boolean; var f : TExportSQLForm; begin f := TExportSQLForm.Create(AOwner); // todo: pass params if needed Result := (f.ShowModal = mrOK); FreeAndNil (f); end; procedure TExportSQLForm.btnCancelClick(Sender: TObject); begin close; end; procedure TExportSQLForm.FormShow(Sender: TObject); var tn : TTreeNode; i, OutputTo : Integer; dbtree_db : String; list: TWindowDataArray; begin barProgress.Position := 0; lblProgress.Caption := ''; PageControl1.ActivePageIndex := 0; SynMemoExampleSQL.Highlighter := Mainform.ChildWin.SynSQLSyn1; SynMemoExampleSQL.Font := Mainform.ChildWin.SynMemoQuery.Font; // read dbs and Tables from treeview comboSelectDatabase.Items.Clear; Caption := Mainform.ChildWin.MysqlConn.Description + ' - Export Tables...'; for i:=0 to Mainform.ChildWin.DBTree.Items.Count-1 do begin tn := Mainform.ChildWin.DBTree.Items[i]; if tn.Level = 1 then comboSelectDatabase.Items.Add(tn.Text); end; if Mainform.ChildWin.DBRightClickSelectedItem <> nil then begin case Mainform.ChildWin.DBRightClickSelectedItem.Level of 1 : dbtree_db := Mainform.ChildWin.DBRightClickSelectedItem.Text; 2 : dbtree_db := Mainform.ChildWin.DBRightClickSelectedItem.Parent.Text; 3 : dbtree_db := Mainform.ChildWin.DBRightClickSelectedItem.Parent.Parent.Text; end; end; for i:=0 to comboSelectDatabase.Items.Count-1 do begin if ((dbtree_db = '') and (comboSelectDatabase.Items[i] = Mainform.ChildWin.ActualDatabase)) or ((dbtree_db <> '') and (comboSelectDatabase.Items[i] = dbtree_db)) then begin comboSelectDatabase.ItemIndex := i; break; end; end; if comboSelectDatabase.ItemIndex = -1 then comboSelectDatabase.ItemIndex := 0; comboSelectDatabaseChange(self); // Initialize and fill list with target versions target_versions := TStringList.Create; with target_versions do begin Add( IntToStr( SQL_VERSION_ANSI ) + '=Standard ANSI SQL' ); Add( '32300=MySQL 3.23' ); Add( '40000=MySQL 4.0' ); Add( '40100=MySQL 4.1' ); Add( '50000=MySQL 5.0' ); Add( '50100=MySQL 5.1' ); Add( IntToStr( Mainform.ChildWin.mysql_version ) + '=Same as source server (MySQL '+Mainform.ChildWin.GetVar('SELECT VERSION()') +')' ); end; // Add all target versions to combobox and set default option comboTargetCompat.Items.Clear; for i := 0 to target_versions.Count - 1 do begin comboTargetCompat.Items.Add( target_versions.ValueFromIndex[i] ); if( target_versions.Names[i] = IntToStr( SQL_VERSION_DEFAULT ) ) then begin comboTargetCompat.ItemIndex := i; end; end; // Read options with TRegistry.Create do if OpenKey(REGPATH, true) then begin // WithUseDB, UseBackticks, CompleteInserts: deprecated (hardcoded true now) if Valueexists('ExportStructure') then cbxStructure.Checked := ReadBool('ExportStructure'); if Valueexists('WithCreateDatabase') then cbxDatabase.Checked := ReadBool('WithCreateDatabase'); if Valueexists('WithCreateTable') then cbxTables.Checked := ReadBool('WithCreateTable'); if Valueexists('ExportData') then cbxData.Checked := ReadBool('ExportData'); if Valueexists('CreateDatabaseHow') then comboDatabase.ItemIndex := ReadInteger('CreateDatabaseHow'); if Valueexists('CreateTablesHow') then comboTables.ItemIndex := ReadInteger('CreateTablesHow') else if Valueexists('WithDropTable') and ReadBool('WithDropTable') then comboTables.ItemIndex := TAB_DROP_CREATE; if Valueexists('CreateDataHow') then comboData.ItemIndex := ReadInteger('CreateDataHow'); if Valueexists('Compatibility') then comboTargetCompat.ItemIndex := ReadInteger('Compatibility'); if Valueexists('exportfilename') then editFileName.Text := ReadString('exportfilename'); if Valueexists('ExportSQL_OutputTo') then begin OutputTo := ReadInteger('ExportSQL_OutputTo'); {*** @note ansgarbecker, 2007-02-24 If OutputTo is now OUTPUT_HOST and there are no other windows to export to, reset OutputTo to OUTPUT_FILE to avoid the error-popup "You need at least 2 windows...", which should not be fired in FormShow rather than only when the user has really clicked that radiobutton. As a benefit, the OUTPUT_FILE won't be saved to registry here so the OUTPUT_HOST is still enabled in registry and will be used the next time if we have more than 1 window @see bug #1666054 } // Check if all the heidisql windows are still alive. CheckForCrashedWindows; // Fetch list of heidisql windows. list := GetWindowList; if Length(list) < 2 then OutputTo := OUTPUT_FILE; case OutputTo of OUTPUT_FILE : radioFile.Checked := true; OUTPUT_DB : radioOtherDatabase.Checked := true; OUTPUT_HOST : radioOtherHost.Checked := true; end; end; if ValueExists('ExportSQL_WindowWidth') then Width := ReadInteger('ExportSQL_WindowWidth'); if ValueExists('ExportSQL_WindowHeight') then Height := ReadInteger('ExportSQL_WindowHeight'); end; if EditFileName.Text = '' then EditFileName.Text := ExtractFilePath(paramstr(0)) + 'export.sql'; validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.comboDatabaseChange(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.comboDataChange(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.comboOtherHostSelect(Sender: TObject); var data: TDataSet; j: integer; versions : TStringList; begin // Get both databases and version right when the radio // is clicked, so we can switch to the 'file' radio // immediately when something goes wrong. try data := RemoteExecQuery( appHandles[comboOtherHost.ItemIndex], 'SHOW DATABASES', 'Fetching remote list of databases...' ); comboOtherHostDatabase.Clear; for j:=0 to data.RecordCount - 1 do begin comboOtherHostDatabase.Items.Add(data.FieldByName('Database').AsString); data.Next; end; data.Free; data := RemoteExecQuery( appHandles[comboOtherHost.ItemIndex], 'SELECT VERSION()', 'Probing for remote version...' ); versions := explode('.', data.Fields[0].AsString); remote_version := MakeInt(versions[0]) * 10000 + MakeInt(versions[1]) * 100 + MakeInt(versions[2]); data.Free; // Fetch the max_allowed_packet variable to be sure not to // overload the server when using "Extended Insert" data := RemoteExecQuery( appHandles[comboOtherHost.ItemIndex], 'SHOW VARIABLES LIKE "max_allowed_packet"', 'Checking for maximum allowed SQL-packet size on server '+comboOtherHost.Text+'...' ); remote_max_allowed_packet := MakeInt( data.FieldByName('Value').AsString ); data.Free; except on E: Exception do begin ShowMessage(E.Message); radioFile.Checked := true; E.Free; end; end; end; procedure TExportSQLForm.comboSelectDatabaseChange(Sender: TObject); var i,j : Integer; dbtree_table : String; begin // read tables from db checkListTables.Items.Clear; // Fetch tables from DB // todo: skip views or add complete support for views. checkListTables.Items := Mainform.ChildWin.GetCol( 'SHOW TABLES FROM ' + MainForm.mask(comboSelectDatabase.Text) ); // select all/some: for i:=0 to checkListTables.Items.Count-1 do begin if Mainform.ChildWin.DBRightClickSelectedItem <> nil then begin case Mainform.ChildWin.DBRightClickSelectedItem.Level of 2 : dbtree_table := Mainform.ChildWin.DBRightClickSelectedItem.Text; 3 : dbtree_table := Mainform.ChildWin.DBRightClickSelectedItem.Parent.Text; end; case Mainform.ChildWin.DBRightClickSelectedItem.Level of 1 : checkListTables.checked[i] := true; 2,3 : checkListTables.checked[i] := dbtree_table = checkListTables.Items[i]; end; end else if Mainform.ChildWin.ActualDatabase = comboSelectDatabase.Text then for j:=0 to Mainform.ChildWin.ListTables.Items.Count-1 do begin if checkListTables.Items[i] = Mainform.ChildWin.ListTables.Items[j].Caption then begin checkListTables.checked[i] := Mainform.ChildWin.ListTables.Items[j].Selected; break; end; end else checkListTables.checked[i] := true; end; Mainform.ChildWin.DBRightClickSelectedItem := nil; // write items for "Another Databases": fillcombo_anotherdb(self); end; procedure TExportSQLForm.comboTablesChange(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.comboTargetCompatChange(Sender: TObject); begin generateExampleSQL; end; procedure TExportSQLForm.CheckListToggle(Sender: TObject); begin // check all or none ToggleCheckListBox(checkListTables, ((Sender as TControl).Tag = 1)); end; procedure TExportSQLForm.btnFileBrowseClick(Sender: TObject); begin dialogSave.Filename := comboSelectDatabase.Text; if dialogSave.Execute then if dialogSave.Filename <> '' then EditFileName.Text := dialogSave.Filename; end; procedure TExportSQLForm.btnExportClick(Sender: TObject); var f : TFileStream; i,j,k,m : Integer; exportdb,exporttables : boolean; exportdata : boolean; dropquery,createquery,insertquery, columnnames : String; keylist : Array of TMyKey; keystr,DB2export : String; which : Integer; tofile,todb,tohost : boolean; tcount,tablecounter : Integer; win2export : THandle; StrProgress : String; value : String; Escaped,fullvalue : PChar; extended_insert : Boolean; max_allowed_packet : Int64; thesevalues : String; valuescount, limit : Integer; donext : Boolean; PBuffer : PChar; sql, current_characterset : String; target_version, loopnumber: Integer; ansi : Boolean; RecordCount_all, RecordCount_one, RecordNo_all, offset : Int64; sql_select : String; cwin : TMDIChild; query : TDataSet; OldActualDatabase : String; begin // export! pageControl1.ActivePageIndex := 0; Screen.Cursor := crHourGlass; // Initialize default-variables target_version := SQL_VERSION_DEFAULT; max_allowed_packet := 1024*1024; // export what? exportdb := cbxDatabase.Enabled and cbxDatabase.Checked; exporttables := cbxTables.Enabled and cbxTables.Checked; exportdata := cbxData.Checked; // to where? tofile := radioFile.Checked; todb := radioOtherDatabase.Checked; tohost := radioOtherHost.Checked; // for easy use of methods in childwin cwin := Mainform.ChildWin; {*** @note ansgarbecker For "export to file" set max_allowed_packet to the default-value in mysql-server to be safe on most servers. For "export to another db" set max_allowed_packet to the value set on current host For "export to other host" set max_allowed_packet to the value set on remote host @see http://dev.mysql.com/doc/refman/5.0/en/packet-too-large.html } // Export to .sql-file on disk if tofile then begin // Extract name part of selected target version target_version := StrToIntDef( target_versions.Names[ comboTargetCompat.ItemIndex ], SQL_VERSION_DEFAULT ); try f := TFileStream.Create(EditFileName.Text, fmCreate); except messagedlg('File "'+EditFileName.Text+'" could not be opened!' + crlf + 'Maybe in use by another application?', mterror, [mbOK], 0); f.free; Screen.Cursor := crDefault; abort; end; wfs(f, '# ' + APPNAME + ' Dump '); wfs(f, '#'); DB2export := comboSelectDatabase.Text; end; // Export to other database in the same window if todb then begin target_version := cwin.mysql_version; max_allowed_packet := MakeInt( cwin.GetVar( 'SHOW VARIABLES LIKE ''max_allowed_packet''', 1 ) ); DB2export := comboOtherDatabase.Text; end; // Export to other window/host if tohost then begin target_version := remote_version; max_allowed_packet := remote_max_allowed_packet; win2export := appHandles[comboOtherHost.ItemIndex]; if cbxDatabase.Checked then begin // Use original DB-name from source-server DB2export := comboSelectDatabase.Text; end else // Use existing DB-name on target-server DB2export := comboOtherHostDatabase.Items[comboOtherHostDatabase.ItemIndex]; end; // MySQL has supported extended insert since 3.23. extended_insert := not (target_version = SQL_VERSION_ANSI); try // Be sure to read everything from the correct database OldActualDatabase := cwin.ActualDatabase; cwin.ActualDatabase := comboSelectDatabase.Text; cwin.EnsureActiveDatabase; {*** Ouput useful header information only when exporting to file } if tofile then begin wfs(f, '# --------------------------------------------------------'); wfs(f, '# Host: ' + cwin.MysqlConn.Connection.HostName ); wfs(f, '# Database: ' + DB2export ); wfs(f, '# Server version: ' + cwin.GetVar( 'SELECT VERSION()' ) ); wfs(f, '# Server OS: ' + cwin.GetVar( 'SHOW VARIABLES LIKE "version_compile_os"', 1 ) ); wfs(f, '# Target-Compatibility: ' + comboTargetCompat.Text ); if extended_insert then begin wfs(f, '# max_allowed_packet: ' + inttostr(max_allowed_packet) ); end; wfs(f, '# ' + APPNAME + ' version: ' + appversion ); wfs(f, '# --------------------------------------------------------'); wfs(f); end; {*** Some actions which are only needed if we're not in OtherDatabase-mode: Set character set, create and use database. } if tofile or tohost then begin {*** Set characterset to current one } if cwin.mysql_version > 40100 then current_characterset := cwin.GetVar( 'SHOW VARIABLES LIKE "character_set_connection"', 1 ) else if cwin.mysql_version > 40000 then // todo: test this, add charolation --> charset conversion table from 4.0 to 4.1+ current_characterset := cwin.GetVar( 'SHOW VARIABLES LIKE "character_set"', 1 ) else // todo: test this current_characterset := 'binary'; if current_characterset <> '' then begin sql := '/*!40100 SET CHARACTER SET ' + current_characterset + ';*/'; sql := fixSQL( sql, target_version ); if tofile then wfs(f, sql) else if tohost then RemoteExecNonQuery(win2export, sql ); end; // Switch to correct SQL_MODE so MySQL doesn't reject ANSI SQL if target_version = SQL_VERSION_ANSI then begin sql := '/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=''ANSI'' ;*/'; if tofile then wfs(f, sql) else if tohost then RemoteExecNonQuery(win2export, sql ); end; if exportdb then begin {*** DROP statement for database } if tofile then begin wfs(f); wfs(f); wfs(f, '#'); wfs(f, '# Database structure for database ''' + DB2export + ''''); wfs(f, '#'); wfs(f); end; if comboDatabase.ItemIndex = DB_DROP_CREATE then begin sql := 'DROP DATABASE IF EXISTS ' + maskSql(target_version, DB2export) + ';'; if tofile then wfs(f, sql) else if tohost then RemoteExecNonQuery(win2export, sql ); end; {*** CREATE statement for database plus database-switching } if cwin.mysql_version < 50002 then begin sql := 'CREATE DATABASE '; if comboDatabase.ItemIndex = DB_CREATE_IGNORE then begin sql := sql + '/*!32312 IF NOT EXISTS*/ '; end; sql := sql + maskSql(target_version, DB2export) + ';'; end else begin sql := cwin.GetVar( 'SHOW CREATE DATABASE ' + mainform.mask(DB2export), 1 ); sql := fixNewlines(sql) + ';'; if target_version = SQL_VERSION_ANSI then sql := StringReplace(sql, '`', '"', [rfReplaceAll]); if comboDatabase.ItemIndex = DB_CREATE_IGNORE then begin Insert('/*!32312 IF NOT EXISTS*/ ', sql, Pos('DATABASE', sql) + 9); end; end; sql := fixSQL( sql, target_version); if tofile then wfs(f, sql ) else if tohost then RemoteExecNonQuery(win2export, sql ); if exporttables then begin sql := 'USE ' + maskSql(target_version, DB2export) + ';'; if tofile then begin wfs(f); wfs(f, sql); end else if tohost then RemoteExecNonQuery(win2export, sql); end; end; end; // How many tables? tcount := 0; for i:=0 to checkListTables.Items.Count-1 do if checkListTables.checked[i] then inc(tcount); barProgress.Max := 0; if exporttables then barProgress.Max := tcount; if exportdata then barProgress.Max := barProgress.Max + tcount; checkListTables.ItemIndex := -1; tablecounter := 0; for i:=0 to checkListTables.Items.Count-1 do if checkListTables.checked[i] then begin inc(tablecounter); if checkListTables.ItemIndex > -1 then checkListTables.Checked[checkListTables.ItemIndex] := false; checkListTables.ItemIndex := i; StrProgress := 'Table ' + inttostr(tablecounter) + '/' + inttostr(tcount) + ': ' + checkListTables.Items[i]; lblProgress.caption := StrProgress; if exporttables then begin dropquery := ''; if comboTables.ItemIndex = TAB_DROP_CREATE then begin if tofile then dropquery := 'DROP TABLE IF EXISTS ' + maskSql(target_version, checkListTables.Items[i]) else dropquery := 'DROP TABLE IF EXISTS ' + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]); end; createquery := ''; if tofile then begin createquery := '#' + crlf; createquery := createquery + '# Table structure for table ''' + checkListTables.Items[i] + '''' + crlf; createquery := createquery + '#' + crlf + crlf; end; {*** Let the server generate the CREATE TABLE statement if the version allows that } if cwin.mysql_version >= 32320 then begin Query := cwin.GetResults('SHOW CREATE TABLE ' + mainform.mask(checkListTables.Items[i])); sql := Query.Fields[1].AsString; sql := fixNewlines(sql); // Skip VIEWS. Not foolproof, but good enough until more support for information_schema (fallback to? regexp?) is added. // todo: implement working support for views. if (Pos(' TABLE `', sql) = 0) and (Pos(' VIEW `', sql) > 0) then continue; if Pos('DEFAULT CHARSET', sql) > 0 then begin Insert('/*!40100 ', sql, Pos('DEFAULT CHARSET', sql)); sql := sql + '*/'; end; if target_version = SQL_VERSION_ANSI then begin sql := StringReplace(sql, '`', '"', [rfReplaceAll]); j := max(pos('TYPE=', sql), pos('ENGINE=', sql)); // Delphi's Pos() lacks a start-at parameter. Admittedly very ugly hack to achieve said effect. k := 0; while k <= j do k := k + 1 + Pos(' ', Copy(sql, k + 1, Length(sql))); Delete(sql, j, k - j); end; {*** @note ansgarbecker The ENGINE and TYPE options specify the storage engine for the table. ENGINE was added in MySQL 4.0.18 (for 4.0) and 4.1.2 (for 4.1). It is the preferred option name as of those versions, and TYPE has become deprecated. TYPE is supported throughout the 4.x series, but likely will be removed in the future. @see http://dev.mysql.com/doc/refman/4.1/en/create-table.html } if target_version < 40000 then begin sql := stringreplace(sql, 'ENGINE=', 'TYPE=', [rfReplaceAll]); end else if target_version >= 51000 then begin sql := stringreplace(sql, 'TYPE=', 'ENGINE=', [rfReplaceAll]); end; sql := fixSQL( sql, target_version ); end {*** Generate CREATE TABLE statement by hand on old servers } else if cwin.mysql_version < 32320 then begin Query := cwin.GetResults( 'SHOW COLUMNS FROM ' + mainform.mask(checkListTables.Items[i])); if tofile then sql := 'CREATE TABLE IF NOT EXISTS ' + maskSql(target_version, checkListTables.Items[i]) + ' (' + crlf else sql := sql + 'CREATE TABLE IF NOT EXISTS ' + maskSql(target_version, DB2Export) + '.' + cwin.mask(checkListTables.Items[i]) + ' (' + crlf; for j := 1 to Query.Fieldcount do begin sql := sql + ' ' + maskSql(target_version, Query.Fields[0].AsString) + ' ' + Query.Fields[1].AsString; if Query.Fields[2].AsString <> 'YES' then sql := sql + ' NOT NULL'; if Query.Fields[4].AsString <> '' then sql := sql + ' DEFAULT ''' + Query.Fields[4].AsString + ''''; if Query.Fields[5].AsString <> '' then sql := sql + ' ' + Query.Fields[5].AsString; if j < Query.Fieldcount then sql := sql + ',' + crlf; end; // Keys: Query := cwin.GetResults( 'SHOW KEYS FROM ' + cwin.mask(checkListTables.Items[i])); setLength(keylist, 0); keystr := ''; if Query.RecordCount > 0 then keystr := ','; for j := 1 to Query.RecordCount do begin which := -1; for k:=0 to length(keylist)-1 do begin if keylist[k].Name = Query.Fields[2].AsString then // keyname exists! which := k; end; if which = -1 then begin setlength(keylist, length(keylist)+1); which := high(keylist); keylist[which].Columns := TStringList.Create; with keylist[which] do // set properties for new key begin Name := Query.Fields[2].AsString; if Query.Fields[2].AsString = 'PRIMARY' then _type := 'PRIMARY' else if Query.FieldCount >= 10 then if Query.Fields[9].AsString = 'FULLTEXT' then _type := 'FULLTEXT' else if Query.Fields[1].AsString = '1' then _type := '' else if Query.Fields[1].AsString = '0' then _type := 'UNIQUE'; end; end; keylist[which].Columns.add(maskSql(target_version, Query.Fields[4].AsString)); // add column(s) Query.Next; end; for k:=0 to high(keylist) do begin if k > 0 then keystr := keystr + ','; if keylist[k].Name = 'PRIMARY' then keystr := keystr + crlf + ' PRIMARY KEY (' else keystr := keystr + crlf + ' ' + keylist[k]._type + ' KEY ' + maskSql(target_version, keylist[k].Name) + ' ('; keystr := keystr + implodestr(',', keylist[k].Columns) + ')'; end; sql := sql + keystr + crlf + ')'; end; // mysql_version < 32320 if comboTables.ItemIndex = TAB_CREATE_IGNORE then begin Insert('/*!32312 IF NOT EXISTS*/ ', sql, Pos('TABLE', sql) + 6); end; createquery := createquery + sql; // Output CREATE TABLE to file if tofile then begin createquery := createquery + ';' + crlf; if dropquery <> '' then dropquery := dropquery + ';' + crlf; wfs(f); wfs(f); if dropquery <> '' then wfs(f, dropquery); wfs(f, createquery); end // Run CREATE TABLE on another Database else if todb then begin cwin.ExecUseQuery( DB2export ); if comboTables.ItemIndex = TAB_DROP_CREATE then cwin.ExecUpdateQuery( dropquery ); cwin.ExecUpdateQuery( createquery ); cwin.ExecUseQuery( comboSelectDatabase.Text ); end // Run CREATE TABLE on another host else if tohost then begin RemoteExecUseNonQuery(win2export, cwin.mysql_version, DB2Export); if comboTables.ItemIndex = TAB_DROP_CREATE then RemoteExecNonQuery(win2export, dropquery); RemoteExecNonQuery(win2export, createquery); end; barProgress.StepIt; end; {*** Export data } if exportdata then begin // Set to mysql-readable char: DecimalSeparator := '.'; columnnames := ' ('; Query := cwin.GetResults( 'SHOW FIELDS FROM ' + mainform.mask(checkListTables.Items[i])); for k:=1 to Query.RecordCount do begin if k>1 then columnnames := columnnames + ', '; columnnames := columnnames + maskSql(target_version, Query.Fields[0].AsString); Query.Next; end; columnnames := columnnames+')'; if tofile then begin wfs(f); wfs(f); wfs(f, '#'); wfs(f, '# Dumping data for table ''' + checkListTables.Items[i] + ''''); wfs(f, '#'); wfs(f); end; if comboData.ItemIndex = DATA_TRUNCATE_INSERT then begin if tofile then begin wfs(f, 'TRUNCATE TABLE ' + maskSql(target_version, checkListTables.Items[i]) + ';'); end else if todb then begin cwin.ExecUpdateQuery('TRUNCATE TABLE ' + cwin.mask(DB2Export) + '.' + checkListTables.Items[i]); end else if tohost then begin RemoteExecNonQuery(win2export, 'TRUNCATE TABLE ' + maskSql(target_version, DB2Export) + '.' + checkListTables.Items[i]); end; end; {*** Detect average row size and limit the number of rows fetched at once if more than ~ 5 MB of data Be sure to do this step before the table is locked! } RecordCount_all := MakeInt( cwin.GetVar( 'SELECT COUNT(*) FROM ' + cwin.mask(checkListTables.Items[i]) ) ); limit := cwin.GetCalculatedLimit( checkListTables.Items[i] ); if tofile then begin wfs(f, fixSQL( '/*!40000 ALTER TABLE '+ maskSql(target_version, checkListTables.Items[i]) +' DISABLE KEYS;*/', target_version) ); wfs(f, 'LOCK TABLES '+ maskSql(target_version, checkListTables.Items[i]) +' WRITE;' ); end else if todb then begin cwin.ExecUseQuery(DB2Export); if target_version > 40000 then cwin.ExecUpdateQuery( 'ALTER TABLE ' + cwin.mask(checkListTables.Items[i])+' DISABLE KEYS' ); {*** @note ansgarbecker, 2007-02-18: Normally we would have to apply a WRITE-LOCK to the target-table. Unfortunately the server invokes a "Table xyz was not locked"-error on the source-table if we do that: cwin.ExecQuery( 'LOCK TABLES ' + cwin.mask(DB2Export) + '.' + cwin.mask(checkListTables.Items[i])+' WRITE' ); Even when applying a WRITE- or READ-LOCK also to the source-table, the INSERTs seem to be not executed. So the best solution for now seems to be to not LOCK the target-table, running the risk that the table is edited by other concurrent users } cwin.ExecUseQuery(comboSelectDatabase.Text); end else if tohost then begin if target_version > 40000 then RemoteExecNonQuery(win2export, 'ALTER TABLE ' + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]) + ' DISABLE KEYS'); RemoteExecNonQuery(win2export, 'LOCK TABLES ' + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]) + ' WRITE'); end; offset := 0; loopnumber := 0; RecordNo_all := 0; // Loop as long as (offset+limit) have not reached (recordcount) while true do begin inc( loopnumber ); debug('loopnumber: '+formatnumber(loopnumber)); // Check if end of data has been reached if ( (offset) >= RecordCount_all) or ( (limit = -1) and (loopnumber > 1) ) then begin break; end; sql_select := 'SELECT * FROM ' + cwin.mask(comboSelectDatabase.Text) + '.' + cwin.mask(checkListTables.Items[i]); if limit > -1 then begin sql_select := sql_select + ' LIMIT ' + IntToStr( offset ) + ', ' + IntToStr( limit ); offset := offset + limit; end; // Execute SELECT Query := cwin.GetResults( sql_select ); insertquery := ''; valuescount := 0; j := 0; donext := true; RecordCount_one := Query.RecordCount; while not Query.Eof do begin inc(j); inc(RecordNo_all); lblProgress.caption := StrProgress + ' (Record ' + FormatNumber(RecordNo_all) + ')'; if j mod 100 = 0 then lblProgress.Repaint; if insertquery = '' then begin case comboData.ItemIndex of DATA_TRUNCATE_INSERT: insertquery := 'INSERT INTO '; DATA_INSERT: insertquery := 'INSERT INTO '; DATA_INSERT_IGNORE: insertquery := 'INSERT IGNORE INTO '; DATA_REPLACE_INTO: insertquery := 'REPLACE INTO '; end; if tofile then insertquery := insertquery + maskSql(target_version, checkListTables.Items[i]) else insertquery := insertquery + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]); insertquery := insertquery + columnnames; insertquery := insertquery + ' VALUES' + crlf + #9; end; thesevalues := '('; for k := 0 to Query.fieldcount-1 do begin if Query.Fields[k].IsNull then value := 'NULL' else case Query.Fields[k].DataType of ftInteger, ftSmallint, ftWord: value := Query.Fields[k].AsString; ftBoolean: value := esc( Bool2Str( Query.Fields[k].AsBoolean ) ); else value := escapeAuto( Query.Fields[k].AsString, current_characterset, target_version ); end; thesevalues := thesevalues + value; if k < Query.Fieldcount-1 then thesevalues := thesevalues + ','; end; thesevalues := thesevalues + ')'; if extended_insert then begin if (valuescount > 1) and (length(insertquery)+length(thesevalues)+2 >= max_allowed_packet) then begin // Rewind one record and throw thesevalues away donext := false; dec(j); delete( insertquery, length(insertquery)-3, 4 ); end else if j = RecordCount_one then begin insertquery := insertquery + thesevalues; end else begin inc(valuescount); insertquery := insertquery + thesevalues + ',' + crlf + #9; Query.Next; continue; end; end else begin insertquery := insertquery + thesevalues; end; if tofile then wfs(f, insertquery + ';') else if todb then cwin.ExecUpdateQuery(insertquery) else if tohost then RemoteExecNonQuery(win2export, insertquery); if donext then Query.Next; donext := true; insertquery := ''; end; Query.Close; end; // Set back to local setting: setLocales; if tofile then begin wfs(f, 'UNLOCK TABLES;' ); wfs(f, fixSQL( '/*!40000 ALTER TABLE '+maskSql(target_version, checkListTables.Items[i])+' ENABLE KEYS;*/', target_version) ); end else if todb then begin if target_version > 40000 then cwin.ExecUpdateQuery( 'ALTER TABLE ' + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]) + ' ENABLE KEYS' ); end else if tohost then begin RemoteExecNonQuery(win2export, 'UNLOCK TABLES'); if target_version > 40000 then RemoteExecNonQuery(win2export, 'ALTER TABLE ' + maskSql(target_version, DB2Export) + '.' + maskSql(target_version, checkListTables.Items[i]) + ' ENABLE KEYS'); end; barProgress.StepIt; end; end; // Restore old value for SQL_MODE if (tofile or tohost) and (target_version = SQL_VERSION_ANSI) then begin sql := '/*!40101 SET SQL_MODE=@OLD_SQL_MODE ;*/'; if tofile then wfs(f, sql) else if tohost then RemoteExecNonQuery(win2export, sql ); end; if OldActualDatabase <> '' then begin cwin.ActualDatabase := OldActualDatabase; cwin.EnsureActiveDatabase; end; FINALLY if tofile then f.Free; Screen.Cursor := crDefault; END; SaveSettings; close; end; procedure TExportSQLForm.radioOtherDatabaseClick(Sender: TObject); begin if comboSelectDatabase.Items.Count <= 1 then begin MessageDLG('There must be more than one database to enable this option.', mtError, [mbOK], 0); radioFile.OnClick(self); abort; end; validateRadioControls(Sender); validateControls(Sender); generateExampleSql; end; procedure TExportSQLForm.radioFileClick(Sender: TObject); begin validateRadioControls(Sender); validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.fillcombo_anotherdb(Sender: TObject); begin comboOtherDatabase.Items := comboSelectDatabase.Items; comboOtherDatabase.Items.delete(comboSelectDatabase.ItemIndex); if comboOtherDatabase.ItemIndex = -1 then comboOtherDatabase.ItemIndex := 0; end; procedure TExportSQLForm.generateExampleSQL; const STR_DROP_DB = 'DROP DATABASE ;' + CRLF; STR_CREATE_DB = 'CREATE DATABASE ;' + CRLF; STR_CREATE_DB_IGNORE = 'CREATE DATABASE IF NOT EXISTS ;' + CRLF; STR_DROP_TABLE = 'DROP TABLE ;' + CRLF; STR_CREATE_TABLE = 'CREATE TABLE
;' + CRLF; STR_CREATE_TABLE_IGNORE = 'CREATE TABLE IF NOT EXISTS
;' + CRLF; STR_TRUNCATE_TABLE = 'TRUNCATE TABLE
;' + CRLF; STR_INSERT = 'INSERT INTO
() VALUES ()'; STR_INSERT_IGNORE = 'INSERT IGNORE INTO
() VALUES ()'; STR_REPLACE_INTO = 'REPLACE INTO
() VALUES ()'; STR_END_INSERT_REG = ';' + CRLF + '(...)' + CRLF; STR_END_INSERT_EXT = ', ()...;' + CRLF; var s: string; procedure add(str: string); overload; begin s := s + str; end; procedure add(str1: string; str2: string); overload; begin s := s + str1 + str2; end; begin s := ''; if cbxStructure.Enabled and cbxStructure.Checked then begin if cbxDatabase.Enabled and cbxDatabase.Checked then begin case comboDatabase.ItemIndex of DB_DROP_CREATE: add(STR_DROP_DB, STR_CREATE_DB); DB_CREATE: add(STR_CREATE_DB); DB_CREATE_IGNORE: add(STR_CREATE_DB_IGNORE); end; add( CRLF ); end; if cbxTables.Enabled and cbxTables.Checked then begin case comboTables.ItemIndex of TAB_DROP_CREATE: add(STR_DROP_TABLE, STR_CREATE_TABLE); TAB_CREATE: add(STR_CREATE_TABLE); TAB_CREATE_IGNORE: add(STR_CREATE_TABLE_IGNORE); end; add( CRLF ); end; end; if cbxData.Enabled and cbxData.Checked then begin case comboData.ItemIndex of DATA_TRUNCATE_INSERT: add(STR_TRUNCATE_TABLE, STR_INSERT); DATA_INSERT: add(STR_INSERT); DATA_INSERT_IGNORE: add(STR_INSERT_IGNORE); DATA_REPLACE_INTO: add(STR_REPLACE_INTO); end; if comboTargetCompat.ItemIndex > 0 then add(STR_END_INSERT_EXT) else add(STR_END_INSERT_REG); end; s := TrimRight(s); SynMemoExampleSql.Text := s; end; procedure TExportSQLForm.validateRadioControls(Sender: TObject); begin if radioFile.Checked then begin EditFileName.Enabled := true; EditFileName.Color := clWindow; btnFileBrowse.Enabled := true; EditFileName.SetFocus; end else begin EditFileName.Enabled := false; EditFileName.Color := clBtnFace; btnFileBrowse.Enabled := false; end; if radioOtherDatabase.Checked then begin comboOtherDatabase.Enabled := true; comboOtherDatabase.Color := clWindow; if comboOtherDatabase.CanFocus then comboOtherDatabase.SetFocus; end else begin comboOtherDatabase.Enabled := false; comboOtherDatabase.Color := clBtnFace; end; if radioOtherHost.Checked then begin comboOtherHost.Enabled := true; comboOtherHost.Color := clWindow; comboOtherHostDatabase.Enabled := not (cbxStructure.Checked and cbxDatabase.Checked); comboOtherHostDatabase.Color := clWindow; if comboOtherHost.CanFocus then comboOtherHost.SetFocus; end else begin comboOtherHost.Enabled := false; comboOtherHost.Color := clBtnFace; comboOtherHostDatabase.Enabled := false; comboOtherHostDatabase.Color := clBtnFace; end; // Disable target selection if exporting to known session. comboTargetCompat.Enabled := radioFile.Checked; end; procedure TExportSQLForm.validateControls(Sender: TObject); begin cbxDatabase.Enabled := cbxStructure.Checked and ( radioFile.Checked or radioOtherHost.Checked ); comboDatabase.Enabled := cbxStructure.Checked and ( radioFile.Checked or radioOtherHost.Checked ) and cbxDatabase.Checked; comboOtherHostDatabase.Enabled := not cbxDatabase.Checked; cbxTables.Enabled := cbxStructure.Checked; comboTables.Enabled := cbxStructure.Checked and cbxTables.Checked; comboData.Enabled := cbxData.Checked; // Prevent choosing export of db struct + data but no table struct. if cbxData.Checked then begin if Sender = cbxTables then cbxDatabase.Checked := cbxDatabase.Checked And cbxTables.Checked else cbxTables.Checked := cbxTables.Checked or cbxDatabase.Checked; end; btnExport.Enabled := cbxData.Checked or (cbxStructure.Checked and (cbxDatabase.Checked or cbxTables.Checked)); if cbxStructure.Checked and cbxDatabase.Checked and (comboDatabase.ItemIndex = DB_DROP_CREATE) then begin // 'drop tables', 'truncate data', 'insert ignore' and 'replace into' is useless. comboTables.ItemIndex := TAB_CREATE; comboTables.Enabled := false; comboData.ItemIndex := DATA_INSERT; comboData.Enabled := false; end; if cbxStructure.Checked and cbxTables.Checked and (comboTables.ItemIndex = TAB_DROP_CREATE) then begin // 'truncate data', 'insert ignore' and 'replace into' is useless. comboData.ItemIndex := DATA_INSERT; comboData.Enabled := false; end; end; procedure TExportSQLForm.cbxStructureClick(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.cbxDataClick(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.cbxTablesClick(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.checkListTablesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var i: Integer; begin // if exist tables and is more than one, 'cause // if exists only one, this action is not needed // and CTRL Key is pressed if ((checkListTables.Count > 1) and (ssCtrl in Shift)) then begin case (Key) of VK_UP: // UP Key begin // find the selected, starting from the second table for i := 1 to (checkListTables.Count - 1) do begin if (checkListTables.Selected[i]) then begin // move the selected to up checkListTables.Items.Move(i, (i - 1)); // select it again checkListTables.Selected[i] := True; // stop the find Break; end; end; end; VK_DOWN: // DOWN Key begin // find the selected, starting from the first table, but // ignore the last for i := 0 to (checkListTables.Count - 2) do begin if (checkListTables.Selected[i]) then begin // move the selected to down checkListTables.Items.Move(i, (i + 1)); // select it again checkListTables.Selected[i] := True; // stop the find Break; end; end; end; end; end; end; procedure TExportSQLForm.cbxDatabaseClick(Sender: TObject); begin validateControls(Sender); generateExampleSQL; end; procedure TExportSQLForm.radioOtherHostClick(Sender: TObject); var list: TWindowDataArray; i, k: integer; begin // Check if all the heidisql windows are still alive. CheckForCrashedWindows; // Fetch list of heidisql windows. list := GetWindowList; // Fill list of hosts. comboOtherHost.Items.Clear; SetLength(appHandles, High(list)); k := 0; for i := 0 to High(list) do with list[i] do begin // Do not include current window. if appHandle <> MainForm.Handle then begin // Do not include non-connected windows. if connected then begin if namePostfix <> 0 then name := name + Format(' (%d)', [namePostFix]); comboOtherHost.Items.Add(name); appHandles[k] := appHandle; k := k + 1; end; end; end; // Abort if no other windows. if comboOtherHost.Items.Count = 0 then begin MessageDLG('You need at least two open connection-windows to enable this option.', mtError, [mbOK], 0); radioFile.Checked := true; abort; end; // Select first host and first database. comboOtherHost.ItemIndex := 0; comboOtherHost.OnSelect(comboOtherHost); comboOtherHostDatabase.ItemIndex := 0; // De-select database structure to enable database dropdown box. cbxDatabase.Checked := false; validateRadioControls(Sender); validateControls(Sender); generateExampleSql; end; {*** Save settings in registry, should be called just before closing the form, but not when Cancel was pressed. } procedure TExportSQLForm.SaveSettings; var OutputTo : Byte; begin with TRegistry.Create do begin OpenKey(REGPATH, true); // WithUseDB, UseBackticks, CompleteInserts, WithDropTable: deprecated (currently not automagically removed) WriteBool('ExportStructure', cbxStructure.Checked); WriteBool('WithCreateDatabase', cbxDatabase.Checked); WriteBool('WithCreateTable', cbxTables.Checked); WriteBool('ExportData', cbxData.Checked); WriteInteger('CreateDatabaseHow', comboDatabase.ItemIndex); WriteInteger('CreateTablesHow', comboTables.ItemIndex); WriteInteger('CreateDataHow', comboData.ItemIndex); WriteInteger('Compatibility', comboTargetCompat.ItemIndex); WriteString('exportfilename', EditFileName.Text); OutputTo := OUTPUT_FILE; if radioOtherDatabase.checked then OutputTo := OUTPUT_DB else if radioOtherHost.checked then OutputTo := OUTPUT_HOST; WriteInteger('ExportSQL_OutputTo', OutputTo ); WriteInteger('ExportSQL_WindowWidth', Width ); WriteInteger('ExportSQL_WindowHeight', Height ); CloseKey(); end; end; end.