Files
HeidiSQL/source/childwin.pas

6537 lines
222 KiB
ObjectPascal
Raw Blame History

unit Childwin;
// -------------------------------------
// MDI-Child-Window
// -------------------------------------
interface
uses
Synchronization,
Contnrs,
Windows, Classes, Graphics, Forms, Controls, StdCtrls,
ExtCtrls, ComCtrls, ImgList, SysUtils, Dialogs, Menus,
SynEdit, SynMemo, SynEditHighlighter, SynHighlighterSQL, SynEditSearch,
SynEditTypes, Clipbrd,
Buttons, CheckLst, ToolWin, Db,
helpers,
ZDataset,
ZAbstractRODataset, ZConnection,
ZSqlMonitor, ZDbcLogging,
SynCompletionProposal, HeidiComp, SynEditMiscClasses, MysqlQuery,
MysqlQueryThread, queryprogress, communication, MysqlConn, Tabs,
VirtualTrees, createdatabase, tbl_properties, createtable,
SynRegExpr, EditVar, PngSpeedButton, WideStrings, WideStrUtils, SynUnicode,
TntStdCtrls;
type
TOrderCol = class(TObject)
ColumnName: WideString;
SortDirection: Byte;
end;
TOrderColArray = Array of TOrderCol;
type
TMDIChild = class(TForm)
panelTop: TPanel;
DBtree: TVirtualStringTree;
Splitter1: TSplitter;
PageControlMain: TPageControl;
tabData: TTabSheet;
tabDatabase: TTabSheet;
splitterTopBottom: TSplitter;
tabQuery: TTabSheet;
popupTreeView: TPopupMenu;
menuRefreshDBTree: TMenuItem;
tabTable: TTabSheet;
popupDbGrid: TPopupMenu;
menuproperties: TMenuItem;
menudroptable: TMenuItem;
menuemptytable: TMenuItem;
tabHost: TTabSheet;
PageControlHost: TPageControl;
tabVariables: TTabSheet;
tabProcessList: TTabSheet;
ListVariables: TVirtualStringTree;
ListProcesses: TVirtualStringTree;
popupHost: TPopupMenu;
Kill1: TMenuItem;
NewDatabase1: TMenuItem;
ListTables: TVirtualStringTree;
Refresh1: TMenuItem;
pnlDataTop: TPanel;
menurefresh: TMenuItem;
N2: TMenuItem;
pnlQueryMemo: TPanel;
SynSQLSyn1: TSynSQLSyn;
SynMemoQuery: TSynMemo;
spltQuery: TSplitter;
menucreatetable: TMenuItem;
OpenDialog1: TOpenDialog;
TimerHostUptime: TTimer;
popupTableGrid: TPopupMenu;
Refresh2: TMenuItem;
DropField1: TMenuItem;
N3: TMenuItem;
N5: TMenuItem;
PopupmenuDropDatabase: TMenuItem;
popupDataGrid: TPopupMenu;
Refresh3: TMenuItem;
MenuAddField: TMenuItem;
MenuEditField: TMenuItem;
popupResultGrid: TPopupMenu;
Copyrecords1: TMenuItem;
CopyasCSVData1: TMenuItem;
N9: TMenuItem;
LabelResultinfo: TLabel;
menuAlterTable: TMenuItem;
N10: TMenuItem;
MenuRenameTable: TMenuItem;
TimerConnected: TTimer;
N12: TMenuItem;
popupSqlLog: TPopupMenu;
Clear2: TMenuItem;
Copy1: TMenuItem;
N13: TMenuItem;
EditQuery1: TMenuItem;
Markall3: TMenuItem;
N15: TMenuItem;
menuMaintenance: TMenuItem;
TimerConnectErrorCloseWindow: TTimer;
PopupMenuDropTable: TMenuItem;
N17: TMenuItem;
ListColumns: TVirtualStringTree;
CopycontentsasHTML1: TMenuItem;
CopycontentsasHTML2: TMenuItem;
Copy3: TMenuItem;
Paste2: TMenuItem;
N4: TMenuItem;
DataGrid: TVirtualStringTree;
QueryGrid: TVirtualStringTree;
Copytableas1: TMenuItem;
Delete1: TMenuItem;
N6: TMenuItem;
QF1: TMenuItem;
QF2: TMenuItem;
QuickFilter1: TMenuItem;
QF3: TMenuItem;
QF4: TMenuItem;
N7: TMenuItem;
DropFilter1: TMenuItem;
PrintList2: TMenuItem;
PrintList3: TMenuItem;
PrintList4: TMenuItem;
N1: TMenuItem;
MenuCopyTable: TMenuItem;
SynMemoFilter: TSynMemo;
N18: TMenuItem;
selectall1: TMenuItem;
MenuAutoupdate: TMenuItem;
TimerHost: TTimer;
Set1: TMenuItem;
EnableAutoRefresh: TMenuItem;
DisableAutoRefresh: TMenuItem;
Saveastextfile1: TMenuItem;
QF7: TMenuItem;
QF5: TMenuItem;
QF6: TMenuItem;
QF8: TMenuItem;
QF10: TMenuItem;
QF11: TMenuItem;
QF9: TMenuItem;
QF12: TMenuItem;
CopyasXMLdata1: TMenuItem;
CopyasXMLdata2: TMenuItem;
Exportdata1: TMenuItem;
Exportdata2: TMenuItem;
SaveDialogExportData: TSaveDialog;
N11: TMenuItem;
ProgressBarQuery: TProgressBar;
Copy4: TMenuItem;
N14: TMenuItem;
DataInsertDateTime: TMenuItem;
DataTimestamp: TMenuItem;
DataDateTime: TMenuItem;
DataTime: TMenuItem;
DataDate: TMenuItem;
DataYear: TMenuItem;
ViewasHTML1: TMenuItem;
HTMLview1: TMenuItem;
InsertfilesintoBLOBfields1: TMenuItem;
InsertfilesintoBLOBfields2: TMenuItem;
InsertfilesintoBLOBfields3: TMenuItem;
N19: TMenuItem;
setNULL1: TMenuItem;
ZSQLMonitor1: TZSQLMonitor;
Exporttables1: TMenuItem;
Exporttables2: TMenuItem;
popupDbGridHeader: TPopupMenu;
SynCompletionProposal1: TSynCompletionProposal;
OpenDialogSQLFile: TOpenDialog;
SaveDialogSQLFile: TSaveDialog;
SynEditSearch1: TSynEditSearch;
N16: TMenuItem;
ManageIndexes1: TMenuItem;
tabCommandStats: TTabSheet;
ListCommandStats: TVirtualStringTree;
QF13: TMenuItem;
QF14: TMenuItem;
QF15: TMenuItem;
QF16: TMenuItem;
QF17: TMenuItem;
N21: TMenuItem;
pnlQueryHelpers: TPanel;
tabsetQueryHelpers: TTabSet;
lboxQueryHelpers: TTnTListBox;
popupQuery: TPopupMenu;
MenuRun: TMenuItem;
MenuRunSelection: TMenuItem;
MenuRunLine: TMenuItem;
MenuItem1: TMenuItem;
menucopy: TMenuItem;
menupaste: TMenuItem;
menuload: TMenuItem;
menusave: TMenuItem;
menuclear: TMenuItem;
MenuFind: TMenuItem;
MenuReplace: TMenuItem;
MenuItem2: TMenuItem;
lblDataTop: TTNTLabel;
spltQueryHelpers: TSplitter;
menuRenameColumn: TMenuItem;
N22: TMenuItem;
N23: TMenuItem;
menuSaveSelectionToFile: TMenuItem;
menuSaveAsSnippet: TMenuItem;
menuSaveSelectionAsSnippet: TMenuItem;
popupQueryHelpers: TPopupMenu;
menuDeleteSnippet: TMenuItem;
menuHelp: TMenuItem;
menuLoadSnippet: TMenuItem;
menuInsertSnippetAtCursor: TMenuItem;
menuExplore: TMenuItem;
PopupMenuCreateTable: TMenuItem;
menuSQLhelp: TMenuItem;
N24: TMenuItem;
menuSQLhelpData: TMenuItem;
menuAlterdatabase: TMenuItem;
menuTreeAlterTable: TMenuItem;
menuLogToFile: TMenuItem;
menuOpenLogFolder: TMenuItem;
tabStatus: TTabSheet;
ListStatus: TVirtualStringTree;
Splitter3: TSplitter;
pnlProcessViewBox: TPanel;
pnlProcessView: TPanel;
SynMemoProcessView: TSynMemo;
pnlFilterVariables: TPanel;
lblFilterVariables: TLabel;
editFilterVariables: TEdit;
pnlFilterStatus: TPanel;
lblFilterStatus: TLabel;
editFilterStatus: TEdit;
pnlFilterProcesses: TPanel;
lblFilterProcesses: TLabel;
editFilterProcesses: TEdit;
menuEditVariable: TMenuItem;
actView1: TMenuItem;
Createview1: TMenuItem;
menuTreeCreateView: TMenuItem;
menuTreeEditView: TMenuItem;
menuTreeExpandAll: TMenuItem;
menuTreeCollapseAll: TMenuItem;
tlbDataButtons: TToolBar;
tbtnDataSorting: TToolButton;
tbtnDataColumns: TToolButton;
tbtnDataFilter: TToolButton;
pnlFilter: TPanel;
btnFilterApply: TButton;
lblTableFilter: TLabel;
editFilterSearch: TEdit;
btnFilterClear: TButton;
popupFilter: TPopupMenu;
menuFilterCopy: TMenuItem;
menuFilterPaste: TMenuItem;
N8: TMenuItem;
menuFilterApply: TMenuItem;
menuFilterClear: TMenuItem;
N20: TMenuItem;
SynMemoSQLLog: TSynMemo;
Insert1: TMenuItem;
Cancelediting1: TMenuItem;
DataPost1: TMenuItem;
menuShowSizeColumn: TMenuItem;
procedure menuRenameColumnClick(Sender: TObject);
procedure ListColumnsNewText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; NewText: WideString);
procedure popupQueryPopup(Sender: TObject);
procedure lboxQueryHelpersClick(Sender: TObject);
procedure lboxQueryHelpersDblClick(Sender: TObject);
procedure tabsetQueryHelpersChange(Sender: TObject; NewTab: Integer;
var AllowChange: Boolean);
procedure btnDataClick(Sender: TObject);
procedure ListTablesChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure ListColumnsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure controlsKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure SynCompletionProposal1AfterCodeCompletion(Sender: TObject;
const Value: WideString; Shift: TShiftState; Index: Integer; EndToken: WideChar);
procedure SynCompletionProposal1CodeCompletion(Sender: TObject;
var Value: WideString; Shift: TShiftState; Index: Integer; EndToken: WideChar);
procedure SynCompletionProposal1Execute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
procedure PerformConnect;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure pcChange(Sender: TObject);
procedure ValidateControls(FrmIsFocussed: Boolean = true);
procedure ValidateQueryControls(FrmIsFocussed: Boolean = true);
function FieldContent(ds: TDataSet; ColName: WideString): WideString;
procedure LoadDatabaseProperties(db: WideString);
procedure ShowHost;
procedure ShowDatabase(db: WideString);
procedure ShowDBProperties(db: WideString);
procedure ShowTable(table: WideString; tab: TTabSheet = nil);
procedure ShowTableProperties;
procedure ShowTableData(table: WideString);
procedure EnsureFullWidth(Grid: TBaseVirtualTree; Column: TColumnIndex; Node: PVirtualNode);
procedure EnsureNodeLoaded(Sender: TBaseVirtualTree; Node: PVirtualNode; WhereClause: String);
procedure EnsureChunkLoaded(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure DiscardNodeData(Sender: TVirtualStringTree; Node: PVirtualNode);
procedure viewdata(Sender: TObject);
procedure RefreshFieldListClick(Sender: TObject);
procedure MenuRefreshClick(Sender: TObject);
procedure LogSQL(msg: WideString = ''; comment: Boolean = true );
procedure ShowVariablesAndProcesses(Sender: TObject);
procedure KillProcess(Sender: TObject);
procedure PageControlHostChange(Sender: TObject);
procedure ExecSQLClick(Sender: TObject; Selection: Boolean = false;
CurrentLine: Boolean=false);
procedure SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatusChanges);
procedure TimerHostUptimeTimer(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure ListTablesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; NewText: WideString);
procedure MenuRenameTableClick(Sender: TObject);
procedure TimerConnectedTimer(Sender: TObject);
procedure Clear2Click(Sender: TObject);
procedure EditQuery1Click(Sender: TObject);
procedure Markall3Click(Sender: TObject);
procedure ReadWindowOptions;
procedure ListTablesDblClick(Sender: TObject);
procedure TimerConnectErrorCloseWindowTimer(Sender: TObject);
procedure QuickFilterClick(Sender: TObject);
function GetFilter: WideString;
procedure SaveFilter(Clause: WideString = '');
procedure DropFilter1Click(Sender: TObject);
procedure selectall1Click(Sender: TObject);
procedure popupResultGridPopup(Sender: TObject);
procedure Autoupdate1Click(Sender: TObject);
procedure EnableAutoRefreshClick(Sender: TObject);
procedure ShowProcessList(sender: TObject);
procedure DisableAutoRefreshClick(Sender: TObject);
procedure SynMemoQueryDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
procedure SynMemoQueryDragDrop(Sender, Source: TObject; X, Y: Integer);
procedure SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer;
AFiles: TWideStrings);
procedure popupHostPopup(Sender: TObject);
procedure Saveastextfile1Click(Sender: TObject);
procedure popupTreeViewPopup(Sender: TObject);
procedure SaveDialogExportDataTypeChange(Sender: TObject);
procedure popupDataGridPopup(Sender: TObject);
procedure InsertDate(Sender: TObject);
procedure setNULL1Click(Sender: TObject);
function GetNamedVar( SQLQuery: WideString; x: WideString;
HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideString;
function GetVar( SQLQuery: WideString; x: Integer = 0;
HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideString;
function GetResults( SQLQuery: WideString;
HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet;
function GetCol( SQLQuery: WideString; x: Integer = 0;
HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideStrings.TWideStringList;
procedure ZSQLMonitor1LogTrace(Sender: TObject; Event: TZLoggingEvent);
procedure MenuTablelistColumnsClick(Sender: TObject);
function mask(str: WideString) : WideString;
procedure CheckConnection();
procedure QueryLoad( filename: String; ReplaceContent: Boolean = true );
procedure ExecuteNonQuery(SQLQuery: String);
function ExecuteQuery(query: String): TDataSet;
function CreateOrGetRemoteQueryTab(sender: THandle): THandle;
procedure DataGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure DataGridCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; out EditLink: IVTEditLink);
procedure DataGridEditCancelled(Sender: TBaseVirtualTree; Column: TColumnIndex);
procedure DataGridEdited(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex);
procedure DataGridEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; var Allowed: Boolean);
procedure DataGridFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode:
PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
procedure GridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
procedure DataGridHeaderClick(Sender: TVTHeader; Column: TColumnIndex; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure GridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure DataGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; NewText: WideString);
procedure GridPaintText(Sender: TBaseVirtualTree; const TargetCanvas:
TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure menuDeleteSnippetClick(Sender: TObject);
procedure menuExploreClick(Sender: TObject);
procedure menuInsertSnippetAtCursorClick(Sender: TObject);
procedure menuLoadSnippetClick(Sender: TObject);
procedure RunAsyncPost(ds: TDeferDataSet);
procedure vstGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
procedure vstInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
procedure vstGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
procedure vstHeaderClick(Sender: TVTHeader; Column: TColumnIndex;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure vstCompareNodes(Sender: TBaseVirtualTree; Node1, Node2:
PVirtualNode; Column: TColumnIndex; var Result: Integer);
procedure vstBeforePaint(Sender: TBaseVirtualTree; TargetCanvas:
TCanvas);
procedure vstHeaderDraggedOut(Sender: TVTHeader; Column: TColumnIndex;
DropPosition: TPoint);
procedure DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure DBtreeDblClick(Sender: TObject);
procedure DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var
ImageIndex: Integer);
procedure DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize:
Integer);
procedure DBtreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: WideString);
procedure DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var
ChildCount: Cardinal);
procedure DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure DBtreePaintText(Sender: TBaseVirtualTree; const TargetCanvas:
TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure editFilterSearchChange(Sender: TObject);
procedure editFilterSearchEnter(Sender: TObject);
procedure editFilterSearchExit(Sender: TObject);
procedure menuLogToFileClick(Sender: TObject);
procedure menuOpenLogFolderClick(Sender: TObject);
procedure vstGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var
HintText: WideString);
procedure ProcessSqlLog;
procedure ListCommandStatsBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
procedure ListProcessesChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure editFilterVTChange(Sender: TObject);
procedure ListColumnsDblClick(Sender: TObject);
procedure ListVariablesDblClick(Sender: TObject);
procedure menuEditVariableClick(Sender: TObject);
procedure menuRefreshDBTreeClick(Sender: TObject);
procedure menuTreeCollapseAllClick(Sender: TObject);
procedure menuTreeExpandAllClick(Sender: TObject);
procedure SynMemoFilterChange(Sender: TObject);
procedure tabsetQueryHelpersGetImageIndex(Sender: TObject; TabIndex: Integer;
var ImageIndex: Integer);
procedure DataGridAfterCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellRect: TRect);
procedure menuShowSizeColumnClick(Sender: TObject);
procedure DataGridColumnResize(Sender: TVTHeader; Column: TColumnIndex);
procedure DBtreeClick(Sender: TObject);
procedure GridBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
private
uptime : Integer;
time_connected : Cardinal;
viewingdata : Boolean;
FMysqlConn : TMysqlConn;
FConn : TOpenConnProf;
QueryRunningInterlock : Integer;
lastUsedDB : String;
UserQueryFired : Boolean;
UserQueryFiring : Boolean;
CachedTableLists : WideStrings.TWideStringList;
QueryHelpersSelectedItems : Array[0..3] of Array of Integer;
EditVariableForm : TfrmEditVariable;
FileNameSessionLog : String;
FileHandleSessionLog : Textfile;
SqlMessages : TWideStringList;
SqlMessagesLock : TRtlCriticalSection;
dsShowEngines,
dsHaveEngines : TDataSet;
FilterPanelManuallyOpened : Boolean;
winName : String;
FLastSelectedTableColumns,
FLastSelectedTableKeys : TDataset;
ViewDataPrevTable : WideString;
PrevTableColWidths : WideStrings.TWideStringList;
DataGridHasChanges : Boolean;
function GetQueryRunning: Boolean;
procedure SetQueryRunning(running: Boolean);
function GetActiveGrid: TVirtualStringTree;
function GetActiveData: PGridResult;
procedure WaitForQueryCompletion(WaitForm: TfrmQueryProgress; query: TMySqlQuery; ForceDialog: Boolean);
function RunThreadedQuery(AQuery: WideString; ForceDialog: Boolean): TMysqlQuery;
procedure DisplayRowCountStats;
procedure insertFunction(Sender: TObject);
function GetActiveDatabase: WideString;
function GetSelectedTable: WideString;
procedure SetSelectedDatabase(db: WideString);
procedure SetSelectedTable(table: WideString);
procedure SaveListSetup( List: TVirtualStringTree );
procedure RestoreListSetup( List: TVirtualStringTree );
procedure SetVisibleListColumns( List: TVirtualStringTree; Columns: WideStrings.TWideStringList );
function GetTableSize(ds: TDataSet): Int64;
procedure ToggleFilterPanel(ForceVisible: Boolean = False);
function GetSelTableColumns: TDataset;
function GetSelTableKeys: TDataset;
procedure AutoCalcColWidths(Tree: TVirtualStringTree; PrevLayout: Widestrings.TWideStringlist = nil);
public
DatabasesWanted,
Databases : Widestrings.TWideStringList;
TemporaryDatabase : WideString;
dataselected : Boolean;
editing : Boolean;
mysql_version : Integer;
SessionName : String;
VTRowDataListVariables,
VTRowDataListStatus,
VTRowDataListProcesses,
VTRowDataListCommandStats,
VTRowDataListTables,
VTRowDataListColumns : TVTreeDataArray;
FProgressForm : TFrmQueryProgress;
// Variables set by preferences dialog
prefRememberFilters : Boolean;
prefLogsqlnum,
prefLogSqlWidth,
prefMaxColWidth : Integer;
prefCSVSeparator,
prefCSVEncloser,
prefCSVTerminator : String[10];
prefLogToFile,
prefPreferShowTables,
prefEnableTextEditor,
prefEnableBinaryEditor,
prefEnableDatetimeEditor,
prefEnableEnumEditor,
prefEnableSetEditor,
prefEnableNullBG : Boolean;
prefFieldColorNumeric,
prefFieldColorText,
prefFieldColorBinary,
prefFieldColorDatetime,
prefFieldColorEnum,
prefFieldColorSet,
prefNullColorNumeric,
prefNullColorText,
prefNullColorBinary,
prefNullColorDatetime,
prefNullColorEnum,
prefNullColorSet,
prefNullColorDefault,
prefNullBG : TColor;
CreateDatabaseForm : TCreateDatabaseForm;
CreateTableForm : TCreateTableForm;
TablePropertiesForm : Ttbl_properties_form;
FDataGridResult,
FQueryGridResult : TGridResult;
DataGridCurrentSelect : WideString;
DataGridCurrentFilter : WideString;
DataGridCurrentSort : WideString;
procedure Init(AConn : POpenConnProf; AMysqlConn : TMysqlConn);
//procedure HandleQueryNotification(ASender : TMysqlQuery; AEvent : Integer);
function GetVisualDataset: PGridResult;
function ExecUpdateQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false): Int64;
function ExecSelectQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet;
procedure ExecUseQuery(db: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false);
property FQueryRunning: Boolean read GetQueryRunning write SetQueryRunning;
property ActiveGrid: TVirtualStringTree read GetActiveGrid;
property ActiveData: PGridResult read GetActiveData;
property MysqlConn : TMysqlConn read FMysqlConn;
property Conn : TOpenConnProf read FConn;
property ActiveDatabase : WideString read GetActiveDatabase write SetSelectedDatabase;
property SelectedTable : WideString read GetSelectedTable write SetSelectedTable;
function FetchActiveDbTableList: TDataSet;
function RefreshActiveDbTableList: TDataSet;
function FetchDbTableList(db: WideString): TDataSet;
function RefreshDbTableList(db: WideString): TDataSet;
procedure ClearDbTableList(db: WideString);
function DbTableListCached(db: WideString): Boolean;
procedure ClearAllTableLists;
procedure EnsureDatabase;
procedure TestVTreeDataArray( P: PVTreeDataArray );
function GetVTreeDataArray( VT: TBaseVirtualTree ): PVTreeDataArray;
procedure ActivateFileLogging;
procedure DeactivateFileLogging;
procedure TrimSQLLog;
function HandleOrderColumns( AddOrderCol: TOrderCol = nil ): TOrderColArray;
function ComposeOrderClause( Cols: TOrderColArray ): WideString;
procedure TableEnginesCombo(var Combobox: TCombobox);
function GetNodeType(Node: PVirtualNode): Byte;
function GetSelectedNodeType: Byte;
procedure RefreshTree(DoResetTableCache: Boolean; SelectDatabase: WideString = '');
procedure RefreshTreeDB(db: WideString);
function FindDBNode(db: WideString): PVirtualNode;
function GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
function GridPostInsert(Sender: TBaseVirtualTree): Boolean;
function GridPostDelete(Sender: TBaseVirtualTree): Boolean;
function DataGridPostUpdateOrInsert(Node: PVirtualNode): Boolean;
procedure GridFinalizeEditing(Sender: TBaseVirtualTree);
function GetWhereClause(Row: PGridRow; Columns: PGridColumns): WideString;
function GetKeyColumns: WideStrings.TWideStringlist;
function CheckUniqueKeyClause: Boolean;
procedure DataGridInsertRow;
procedure DataGridCancel(Sender: TObject);
property FSelectedTableColumns: TDataset read GetSelTableColumns write FLastSelectedTableColumns;
property FSelectedTableKeys: TDataset read GetSelTableKeys write FLastSelectedTableKeys;
procedure CalcNullColors;
end;
type
// Represents errors already "handled" (shown to user),
// which can thus safely be ignored.
THandledSQLError = class(Exception)
end;
// -----------------------------------------------------------------------------
implementation
uses
Main, fieldeditor,
copytable, sqlhelp, printlist,
column_selection, data_sorting, runsqlfile, mysql_structures,
Registry, grideditlinks;
type
PMethod = ^TMethod;
{$I const.inc}
{$R *.DFM}
function TMDIChild.CreateOrGetRemoteQueryTab(sender: THandle): THandle;
begin
// Should create a tab for commands from another window,
// or return a handle to an existing tab if one already exists for that window.
//
// TODO: Implement this when multiple tabs are implemented.
// Return a tab's handle instead of the childwin's handle.
result := Self.Handle;
end;
procedure TMDIChild.PerformConnect;
var
v : String[50];
v1, v2, v3 : String;
rx : TRegExpr;
begin
try
time_connected := 0;
TimerConnected.Enabled := true;
LogSQL( 'Connection established with host "' + FMysqlConn.Connection.hostname +
'" on port ' + IntToStr(FMysqlConn.Connection.Port) );
LogSQL( 'Connection-ID: ' + IntToStr( MySQLConn.Connection.GetThreadId ) );
// Detect server version
// Be careful with version suffixes, for example: '4.0.31-20070605_Debian-5-log'
v := GetVar( 'SELECT VERSION()' );
rx := TRegExpr.Create;
rx.ModifierG := True;
rx.Expression := '^(\d+)\.(\d+)\.(\d+)';
if rx.Exec(v) then begin
v1 := rx.Match[1];
v2 := rx.Match[2];
v3 := rx.Match[3];
end;
rx.Free;
mysql_version := MakeInt(v1) *10000 + MakeInt(v2) *100 + MakeInt(v3);
tabHost.Caption := 'Host: '+MySQLConn.Connection.HostName;
Mainform.showstatus('MySQL '+v1+'.'+v2+'.'+v3, 2);
// On Re-Connection, try to restore lost properties
if FMysqlConn.Connection.Database <> '' then
ExecUseQuery( FMysqlConn.Connection.Database );
except
on E: Exception do begin
LogSQL( E.Message, true );
Screen.Cursor := crDefault;
MessageDlg( E.Message, mtError, [mbOK], 0 );
raise;
end;
end;
end;
function TMDIChild.GetQueryRunning: Boolean;
begin
Result := ( QueryRunningInterlock = 1 );
end;
procedure TMDIChild.SetQueryRunning(running: Boolean);
var
newValue : Integer;
oldValue : Integer;
begin
if ( running ) then
begin
newValue := 1;
end
else
begin
newValue := 0;
end;
oldValue := InterlockedExchange( QueryRunningInterlock, newValue );
if ( newValue = oldValue ) then
begin
case ( newValue ) of
1 :
begin
raise Exception.Create( 'Error: Default connection is ' +
'already executing a query.' );
end;
0 :
begin
raise Exception.Create( 'Internal badness: Double reset of running ' +
'flag.' );
end;
end;
end;
end;
procedure TMDIChild.Init(AConn : POpenConnProf; AMysqlConn : TMysqlConn);
var
AutoReconnect : Boolean;
i, j : Integer;
miGroup,
miFilterGroup,
miFunction,
miFilterFunction : TMenuItem;
functioncats : TStringList;
reg : TRegistry;
begin
QueryRunningInterlock := 0;
UserQueryFired := False;
UserQueryFiring := False;
TemporaryDatabase := '';
CachedTableLists := WideStrings.TWideStringList.Create;
InitializeCriticalSection(SqlMessagesLock);
EnterCriticalSection(SqlMessagesLock);
SqlMessages := TWideStringList.Create;
LeaveCriticalSection(SqlMessagesLock);
FConn := AConn^;
FMysqlConn := AMysqlConn; // we're now responsible to free it
FConn.MysqlConn := FMysqlConn.Connection; // use this connection (instead of zConn)
// Initialization: establish connection and read some vars from registry
MainForm.Showstatus( 'Creating window...' );
// Temporarily disable AutoReconnect in Registry
// in case of unexpected application-termination
AutoReconnect := Mainform.GetRegValue(REGNAME_AUTORECONNECT, DEFAULT_AUTORECONNECT);
if AutoReconnect then begin
reg := TRegistry.Create();
reg.OpenKey( REGPATH, true );
reg.WriteBool( REGNAME_AUTORECONNECT, False );
reg.CloseKey;
FreeAndNil(reg);
end;
ReadWindowOptions();
MainForm.Showstatus( 'Connecting to ' + FConn.MysqlParams.Host + '...' );
try
PerformConnect();
except
TimerConnectErrorCloseWindow.Enabled := true;
Exit;
end;
SessionName := FMysqlConn.SessionName;
DatabasesWanted := explode(';', FConn.DatabaseList);
if FConn.DatabaseListSort then
DatabasesWanted.Sort;
// Fill variables-list, processlist and DB-tree
ShowVariablesAndProcesses( Self );
// Invoke population of database tree. It's important to do this here after
// having filled DatabasesWanted, not at design time.
DBtree.RootNodeCount := 1;
// Re-enable AutoReconnect in Registry!
if AutoReconnect then begin
reg := TRegistry.Create;
reg.OpenKey( REGPATH, true );
reg.WriteBool( REGNAME_AUTORECONNECT, true );
reg.CloseKey;
FreeAndNil(reg);
end;
// Define window properties
SetWindowConnected( true );
i := SetWindowName( SessionName );
winName := SessionName;
if ( i <> 0 ) then
begin
winName := winName + Format( ' (%d)', [i] );
end;
Application.Title := winName + ' - ' + APPNAME;
Caption := winName;
// Reselect last used database
if MainForm.GetRegValue( REGNAME_RESTORELASTUSEDDB, DEFAULT_RESTORELASTUSEDDB ) and ( lastUsedDB <> '' ) then
begin
try
ActiveDatabase := Utf8Decode(lastUsedDB);
except
// Suppress exception message when db was dropped externally or
// the session was just opened with "OnlyDBs" in place and the
// last db is not contained in this list.
end;
end else // By default, select the host node
DBtree.Selected[DBtree.GetFirst] := true;
// read function-list into menu
functioncats := GetFunctionCategories;
for i:=0 to functioncats.Count-1 do
begin
// Create a menu item which gets subitems later
miGroup := TMenuItem.Create(popupQuery);
miGroup.Caption := functioncats[i];
popupQuery.Items.add(miGroup);
miFilterGroup := TMenuItem.Create(popupFilter);
miFilterGroup.Caption := miGroup.Caption;
popupFilter.Items.add(miFilterGroup);
for j:=0 to Length(MySqlFunctions)-1 do
begin
if MySqlFunctions[j].Category <> functioncats[i] then
continue;
miFunction := TMenuItem.Create(popupQuery);
miFunction.Caption := MySqlFunctions[j].Name;
miFunction.ImageIndex := 13;
// Prevent generating a hotkey
miFunction.Caption := StringReplace(miFunction.Caption, '&', '&&', [rfReplaceAll]);
// Prevent generating a seperator line
if miFunction.Caption = '-' then
miFunction.Caption := '&-';
miFunction.Hint := MySqlFunctions[j].Name + MySqlFunctions[j].Declaration;
// Take care of needed server version
if MySqlFunctions[j].Version <= mysql_version then
begin
if MySqlFunctions[j].Description <> '' then
miFunction.Hint := miFunction.Hint + ' - ' + Copy(MySqlFunctions[j].Description, 0, 200 );
miFunction.Tag := j;
// Place menuitem on menu
miFunction.OnClick := insertFunction;
end
else
begin
miFunction.Hint := miFunction.Hint + ' - ('+STR_NOTSUPPORTED+', needs >= '+ConvertServerVersion(MySqlFunctions[j].Version)+')';
miFunction.Enabled := False;
end;
// Prevent generating a seperator for ShortHint and LongHint
miFunction.Hint := StringReplace( miFunction.Hint, '|', '<27>', [rfReplaceAll] );
miGroup.Add(miFunction);
// Create a copy of the menuitem for popupFilter
miFilterFunction := TMenuItem.Create(popupFilter);
miFilterFunction.Caption := miFunction.Caption;
miFilterFunction.Hint := miFunction.Hint;
miFilterFunction.ImageIndex := miFunction.ImageIndex;
miFilterFunction.Tag := miFunction.Tag;
miFilterFunction.OnClick := miFunction.OnClick;
miFilterFunction.Enabled := miFunction.Enabled;
miFilterGroup.Add(miFilterFunction);
end;
end;
end;
procedure TMDIChild.ReadWindowOptions;
var
i : Integer;
menuitem : Tmenuitem;
fontname, datafontname : String;
fontsize, datafontsize : Integer;
begin
InheritFont(Font);
InheritFont(tabsetQueryHelpers.Font);
InheritFont(SynCompletionProposal1.Font);
// Fix node height on Virtual Trees for current DPI settings
FixVT(DBTree);
FixVT(ListVariables);
FixVT(ListStatus);
FixVT(ListProcesses);
FixVT(ListCommandStats);
FixVT(ListTables);
FixVT(ListColumns);
// Other values:
pnlQueryMemo.Height := Mainform.GetRegValue(REGNAME_QUERYMEMOHEIGHT, pnlQueryMemo.Height);
pnlQueryHelpers.Width := Mainform.GetRegValue(REGNAME_QUERYHELPERSWIDTH, pnlQueryHelpers.Width);
DBtree.Width := Mainform.GetRegValue(REGNAME_DBTREEWIDTH, DBtree.Width);
SynMemoSQLLog.Height := Mainform.GetRegValue(REGNAME_SQLOUTHEIGHT, SynMemoSQLLog.Height);
prefMaxColWidth := Mainform.GetRegValue(REGNAME_MAXCOLWIDTH, DEFAULT_MAXCOLWIDTH);
// Fix registry entry from older versions which can have 0 here which makes no sense
// since the autosetting was removed
if prefMaxColWidth <= 0 then
prefMaxColWidth := DEFAULT_MAXCOLWIDTH;
prefLogsqlnum := Mainform.GetRegValue(REGNAME_LOGSQLNUM, DEFAULT_LOGSQLNUM);
prefLogSqlWidth := Mainform.GetRegValue(REGNAME_LOGSQLWIDTH, DEFAULT_LOGSQLWIDTH);
prefCSVSeparator := Mainform.GetRegValue(REGNAME_CSV_SEPARATOR, DEFAULT_CSV_SEPARATOR);
prefCSVEncloser := Mainform.GetRegValue(REGNAME_CSV_ENCLOSER, DEFAULT_CSV_ENCLOSER);
prefCSVTerminator := Mainform.GetRegValue(REGNAME_CSV_TERMINATOR, DEFAULT_CSV_TERMINATOR);
prefRememberFilters := Mainform.GetRegValue(REGNAME_REMEMBERFILTERS, DEFAULT_REMEMBERFILTERS);
prefPreferShowTables := Mainform.GetRegValue(REGNAME_PREFER_SHOWTABLES, DEFAULT_PREFER_SHOWTABLES);
// SQL-Font:
fontname := Mainform.GetRegValue(REGNAME_FONTNAME, DEFAULT_FONTNAME);
fontsize := Mainform.GetRegValue(REGNAME_FONTSIZE, DEFAULT_FONTSIZE);
SynMemoQuery.Font.Name := fontname;
SynMemoQuery.Font.Size := fontsize;
SynMemoFilter.Font.Name := fontname;
SynMemoFilter.Font.Size := fontsize;
SynMemoSQLLog.Font.Name := fontname;
SynMemoSQLLog.Font.Size := fontsize;
SynMemoProcessView.Font.Name := fontname;
SynMemoProcessView.Font.Size := fontsize;
// Data-Font:
datafontname := Mainform.GetRegValue(REGNAME_DATAFONTNAME, DEFAULT_DATAFONTNAME);
datafontsize := Mainform.GetRegValue(REGNAME_DATAFONTSIZE, DEFAULT_DATAFONTSIZE);
DataGrid.Font.Name := datafontname;
QueryGrid.Font.Name := datafontname;
DataGrid.Font.Size := datafontsize;
QueryGrid.Font.Size := datafontsize;
FixVT(DataGrid);
FixVT(QueryGrid);
// Load color settings
prefFieldColorNumeric := Mainform.GetRegValue(REGNAME_FIELDCOLOR_NUMERIC, DEFAULT_FIELDCOLOR_NUMERIC);
prefFieldColorText := Mainform.GetRegValue(REGNAME_FIELDCOLOR_TEXT, DEFAULT_FIELDCOLOR_TEXT);
prefFieldColorBinary := Mainform.GetRegValue(REGNAME_FIELDCOLOR_BINARY, DEFAULT_FIELDCOLOR_BINARY);
prefFieldColorDatetime := Mainform.GetRegValue(REGNAME_FIELDCOLOR_DATETIME, DEFAULT_FIELDCOLOR_DATETIME);
prefFieldColorEnum := Mainform.GetRegValue(REGNAME_FIELDCOLOR_ENUM, DEFAULT_FIELDCOLOR_ENUM);
prefFieldColorSet := Mainform.GetRegValue(REGNAME_FIELDCOLOR_SET, DEFAULT_FIELDCOLOR_SET);
prefNullBG := Mainform.GetRegValue(REGNAME_BG_NULL, DEFAULT_BG_NULL);
CalcNullColors;
// Editor enablings
prefEnableTextEditor := Mainform.GetRegValue(REGNAME_FIELDEDITOR_TEXT, DEFAULT_FIELDEDITOR_TEXT);
prefEnableBinaryEditor := Mainform.GetRegValue(REGNAME_FIELDEDITOR_BINARY, DEFAULT_FIELDEDITOR_BINARY);
prefEnableDatetimeEditor := Mainform.GetRegValue(REGNAME_FIELDEDITOR_DATETIME, DEFAULT_FIELDEDITOR_DATETIME);
prefEnableEnumEditor := Mainform.GetRegValue(REGNAME_FIELDEDITOR_ENUM, DEFAULT_FIELDEDITOR_ENUM);
prefEnableSetEditor := Mainform.GetRegValue(REGNAME_FIELDEDITOR_SET, DEFAULT_FIELDEDITOR_SET);
prefEnableNullBG := Mainform.GetRegValue(REGNAME_BG_NULL_ENABLED, DEFAULT_BG_NULL_ENABLED);
// Color coding:
SynSQLSyn1.KeyAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLKEYATTRI, ColorToString(DEFAULT_SQLCOLKEYATTRI)));
SynSQLSyn1.FunctionAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLFUNCTIONATTRI, ColorToString(DEFAULT_SQLCOLFUNCTIONATTRI)));
SynSQLSyn1.DataTypeAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLDATATYPEATTRI, ColorToString(DEFAULT_SQLCOLDATATYPEATTRI)));
SynSQLSyn1.NumberAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLNUMBERATTRI, ColorToString(DEFAULT_SQLCOLNUMBERATTRI)));
SynSQLSyn1.StringAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLSTRINGATTRI, ColorToString(DEFAULT_SQLCOLSTRINGATTRI)));
SynSQLSyn1.CommentAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLCOMMENTATTRI, ColorToString(DEFAULT_SQLCOLCOMMENTATTRI)));
SynSQLSyn1.TablenameAttri.Foreground := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLTABLENAMEATTRI, ColorToString(DEFAULT_SQLCOLTABLENAMEATTRI)));
SynMemoQuery.ActiveLineColor := StringToColor(Mainform.GetRegValue(REGNAME_SQLCOLACTIVELINE, ColorToString(DEFAULT_SQLCOLACTIVELINE)));
// Switch off/on displaying table/db sized in tree
menuShowSizeColumn.Checked := Mainform.GetRegValue(REGNAME_SIZECOL_TREE, DEFAULT_SIZECOL_TREE);
if menuShowSizeColumn.Checked then
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]
else
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible];
// Restore width of columns of all VirtualTrees
RestoreListSetup(ListVariables);
RestoreListSetup(ListStatus);
RestoreListSetup(ListProcesses);
RestoreListSetup(ListCommandStats);
RestoreListSetup(ListTables);
RestoreListSetup(ListColumns);
// Activate logging
if Mainform.GetRegValue(REGNAME_LOGTOFILE, DEFAULT_LOGTOFILE) then
ActivateFileLogging;
// Set last used database, select it later in Init
lastUsedDB := Mainform.GetRegValue(REGNAME_LASTUSEDDB, '', FConn.Description);
// Generate menuitems for popupDbGridHeader (column selection for ListTables)
popupDBGridHeader.Items.Clear;
for i:=0 to ListTables.Header.Columns.Count-1 do
begin
menuitem := TMenuItem.Create( popupDBGridHeader );
menuitem.Caption := ListTables.Header.Columns[i].Text;
menuitem.OnClick := MenuTablelistColumnsClick;
// Disable hiding first column
menuitem.Enabled := i>0;
menuitem.Checked := coVisible in ListTables.Header.Columns[i].Options;
popupDbGridHeader.Items.Add( menuitem );
end;
end;
procedure TMDIChild.FormClose(Sender: TObject; var Action: TCloseAction);
var
reg : TRegistry;
begin
// Post pending UPDATE
if DataGridHasChanges then
Mainform.actDataPostChangesExecute(Sender);
SetWindowConnected( false );
SetWindowName( main.discname );
Application.Title := APPNAME;
debug('mem: clearing query and browse data.');
SetLength(FDataGridResult.Rows, 0);
SetLength(FDataGridResult.Columns, 0);
SetLength(FQueryGridResult.Rows, 0);
SetLength(FQueryGridResult.Columns, 0);
// Closing connection
FMysqlConn.Disconnect;
FreeAndNil(FMysqlConn);
EnterCriticalSection(SqlMessagesLock);
FreeAndNil(SqlMessages);
LeaveCriticalSection(SqlMessagesLock);
reg := TRegistry.Create();
if reg.OpenKey( REGPATH, true ) then
begin
WindowState := wsMaximized;
reg.WriteInteger( REGNAME_QUERYMEMOHEIGHT, pnlQueryMemo.Height );
reg.WriteInteger( REGNAME_QUERYHELPERSWIDTH, pnlQueryHelpers.Width );
reg.WriteInteger( REGNAME_DBTREEWIDTH, DBtree.width );
reg.WriteInteger( REGNAME_SQLOUTHEIGHT, SynMemoSQLLog.Height );
// Save width of probably resized columns of all VirtualTrees
SaveListSetup(ListVariables);
SaveListSetup(ListStatus);
SaveListSetup(ListProcesses);
SaveListSetup(ListCommandStats);
SaveListSetup(ListTables);
SaveListSetup(ListColumns);
// Open server-specific registry-folder.
// relative from already opened folder!
reg.OpenKey( REGKEY_SESSIONS + FConn.Description, true );
reg.WriteString( REGNAME_LASTUSEDDB, Utf8Encode(ActiveDatabase) );
end;
FreeAndNil(reg);
// Clear database and table lists
DBtree.Clear;
ClearAllTableLists;
FreeAndNil(DatabasesWanted);
FreeAndNil(Databases);
FreeAndNil(CachedTableLists);
ValidateControls(False);
Action := caFree;
SetWindowConnected( false );
SetWindowName( main.discname );
Application.Title := APPNAME;
if prefLogToFile then
DeactivateFileLogging;
end;
{**
Add a SQL-command or comment to SynMemoSQLLog
}
procedure TMDIChild.LogSQL(msg: WideString = ''; comment: Boolean = true);
var
snip : boolean;
begin
// Shorten very long messages
snip := (prefLogSqlWidth > 0) and (Length(msg) > prefLogSqlWidth);
if snip then
begin
msg :=
Copy( msg, 0, prefLogSqlWidth ) +
'/* large SQL query, snipped at ' +
FormatNumber( prefLogSqlWidth ) +
' characters */';
end;
msg := WideStringReplace( msg, #9, ' ', [rfReplaceAll] );
msg := WideStringReplace( msg, #10, ' ', [rfReplaceAll] );
msg := WideStringReplace( msg, #13, ' ', [rfReplaceAll] );
msg := WideStringReplace( msg, ' ', ' ', [rfReplaceAll] );
if ( comment ) then
begin
msg := '/* ' + msg + ' */';
end;
EnterCriticalSection(SqlMessagesLock);
try
SqlMessages.Add(msg);
finally
LeaveCriticalSection(SqlMessagesLock);
end;
PostMessage(MainForm.Handle, WM_PROCESSLOG, 0, 0);
end;
procedure TMDIChild.ProcessSqlLog;
var
msg: WideString;
begin
EnterCriticalSection(SqlMessagesLock);
try
if SqlMessages = nil then Exit;
if SqlMessages.Count < 1 then Exit;
msg := SqlMessages[0];
SqlMessages.Delete(0);
finally
LeaveCriticalSection(SqlMessagesLock);
end;
SynMemoSQLLog.Lines.Add( msg );
TrimSQLLog;
// Scroll to last line and repaint
SynMemoSQLLog.GotoLineAndCenter( SynMemoSQLLog.Lines.Count );
SynMemoSQLLog.Repaint;
// Log to file?
if prefLogToFile then
try
WriteLn( FileHandleSessionLog, Format('[%s] %s', [DateTimeToStr(Now), msg]) );
except
DeactivateFileLogging;
MessageDlg('Error writing to session log file:'+CRLF+FileNameSessionLog+CRLF+CRLF+'Logging is disabled now.', mtError, [mbOK], 0);
end;
end;
{**
Delete first line(s) in SQL log and adjust LineNumberStart in gutter
Called by LogSQL and preferences dialog
}
procedure TMDIChild.TrimSQLLog;
var
i : Integer;
begin
i := 0;
while SynMemoSQLLog.Lines.Count > prefLogsqlnum do
begin
SynMemoSQLLog.Lines.Delete(0);
inc(i);
end;
// Increase first displayed number in gutter so it doesn't lie about the log entries
if i > 0 then
SynMemoSQLLog.Gutter.LineNumberStart := SynMemoSQLLog.Gutter.LineNumberStart + i;
end;
procedure TMDIChild.ShowHost;
begin
if (not DBTree.Dragging) and (
(PageControlMain.ActivePage = tabDatabase) or
(PageControlMain.ActivePage = tabTable) or
(PageControlMain.ActivePage = tabData)
) then PageControlMain.ActivePage := tabHost;
tabDatabase.TabVisible := false;
tabTable.TabVisible := false;
tabData.TabVisible := false;
Caption := winName;
pcChange( Self );
end;
procedure TMDIChild.ShowDatabase(db: WideString);
begin
if (not DBtree.Dragging) and (
(PageControlMain.ActivePage = tabHost) or
(PageControlMain.ActivePage = tabTable) or
(PageControlMain.ActivePage = tabData)
) then PageControlMain.ActivePage := tabDatabase;
tabDatabase.TabVisible := true;
tabTable.TabVisible := false;
tabData.TabVisible := false;
Caption := winName + ' - /' + db;
ShowDBProperties( db );
end;
{***
Do the default action (show table properties or table data) for a table.
}
procedure TMDIChild.ShowTable(table: WideString; tab: TTabSheet = nil);
begin
if tab = nil then tab := tabTable; // Alternative default: tabData
if tab = tabTable then ShowTableProperties;
if tab = tabData then ShowTableData( table );
Caption := winName + ' - /' + ActiveDatabase + '/' + SelectedTable;
end;
procedure TMDIChild.viewdata(Sender: TObject);
var
sorting : WideString;
i, count : Integer;
OrderColumns : TOrderColArray;
reg_value : String;
select_base : WideString;
select_from : WideString;
sl_query : TWideStringList;
DisplayedColumnsList,
HiddenKeyCols,
KeyCols : WideStrings.TWideStringList;
Filter, ColName : WideString;
col : TVirtualTreeColumn;
rx : TRegExpr;
ColType : String;
ColExists : Boolean;
procedure InitColumn(idx: Integer; name: WideString);
var
ColType: String;
k: Integer;
begin
FDataGridResult.Columns[idx].Name := name;
col := DataGrid.Header.Columns.Add;
col.Text := name;
col.Options := col.Options + [coSmartResize];
if HiddenKeyCols.IndexOf(name) > -1 then col.Options := col.Options - [coVisible];
// Sorting color and title image
for k:=0 to Length(OrderColumns)-1 do begin
if OrderColumns[k].ColumnName = name then begin
case OrderColumns[k].SortDirection of
ORDER_ASC: begin col.Color := COLOR_SORTCOLUMN_ASC; col.ImageIndex := 109; end;
ORDER_DESC: begin col.Color := COLOR_SORTCOLUMN_DESC; col.ImageIndex := 110; end;
end;
end;
end;
// Right alignment for numeric columns
FSelectedTableColumns.First;
while not FSelectedTableColumns.Eof do begin
if FSelectedTableColumns.FieldByName('Field').AsWideString = name then begin
ColType := FSelectedTableColumns.FieldByName('Type').AsString;
rx.Expression := '^(tiny|small|medium|big)?int\b';
if rx.Exec(ColType) then begin
col.Alignment := taRightJustify;
FDataGridResult.Columns[idx].IsInt := True;
end;
rx.Expression := '^(float|double|decimal)\b';
if rx.Exec(ColType) then begin
col.Alignment := taRightJustify;
FDataGridResult.Columns[idx].IsFloat := True;
end;
rx.Expression := '^(date|datetime|time(stamp)?)\b';
if rx.Exec(ColType) then begin
FDataGridResult.Columns[idx].IsDate := True;
if rx.Match[1] = 'date' then FDataGridResult.Columns[idx].DataType := tpDATE
else if rx.Match[1] = 'time' then FDataGridResult.Columns[idx].DataType := tpTIME
else if rx.Match[1] = 'timestamp' then FDataGridResult.Columns[idx].DataType := tpTIMESTAMP
else FDataGridResult.Columns[idx].DataType := tpDATETIME;
end;
rx.Expression := '^((tiny|medium|long)?text|(var)?char)\b(\(\d+\))?';
if rx.Exec(ColType) then begin
FDataGridResult.Columns[idx].IsText := True;
if rx.Match[4] <> '' then
FDataGridResult.Columns[idx].MaxLength := MakeInt(rx.Match[4])
else if ColType = 'tinytext' then
// 255 is the width in bytes. If characters that use multiple bytes are
// contained, the width in characters is decreased below this number.
FDataGridResult.Columns[idx].MaxLength := 255
else if ColType = 'text' then
FDataGridResult.Columns[idx].MaxLength := 65535
else if ColType = 'mediumtext' then
FDataGridResult.Columns[idx].MaxLength := 16777215
else if ColType = 'longtext' then
FDataGridResult.Columns[idx].MaxLength := 4294967295
else
// Fallback for unknown column types
FDataGridResult.Columns[idx].MaxLength := MaxInt;
end;
rx.Expression := '^((tiny|medium|long)?blob|(var)?binary|bit)\b';
if rx.Exec(ColType) then
FDataGridResult.Columns[idx].IsBinary := True;
if Copy(ColType, 1, 5) = 'enum(' then begin
FDataGridResult.Columns[idx].IsEnum := True;
FDataGridResult.Columns[idx].ValueList := WideStrings.TWideStringList.Create;
FDataGridResult.Columns[idx].ValueList.QuoteChar := '''';
FDataGridResult.Columns[idx].ValueList.Delimiter := ',';
FDataGridResult.Columns[idx].ValueList.DelimitedText := GetEnumValues(ColType);
end;
if Copy(ColType, 1, 4) = 'set(' then begin
FDataGridResult.Columns[idx].IsSet := True;
FDataGridResult.Columns[idx].ValueList := WideStrings.TWideStringList.Create;
FDataGridResult.Columns[idx].ValueList.QuoteChar := '''';
FDataGridResult.Columns[idx].ValueList.Delimiter := ',';
FDataGridResult.Columns[idx].ValueList.DelimitedText := GetEnumValues(ColType);
end;
end;
FSelectedTableColumns.Next;
end;
FSelectedTableKeys.First;
for k := 0 to FSelectedTableKeys.RecordCount - 1 do begin
if (FSelectedTableKeys.FieldByName('Key_name').AsString = 'PRIMARY')
and (FSelectedTableKeys.FieldByName('Column_name').AsWideString = name) then begin
FDataGridResult.Columns[idx].IsPriPart := True;
break;
end;
FSelectedTableKeys.Next;
end;
end;
begin
Screen.Cursor := crHourglass;
// Post pending UPDATE
if DataGridHasChanges then
Mainform.actDataPostChangesExecute(Sender);
viewingdata := true;
sl_query := TWideStringList.Create();
try
// Read cached ORDER-clause and set Grid.Sortcolumns
sorting := '';
OrderColumns := HandleOrderColumns;
if Length(OrderColumns) > 0 then begin
sorting := ComposeOrderClause(OrderColumns);
// Signal for the user that we applied an ORDER-clause
tbtnDataSorting.ImageIndex := 108;
end else
tbtnDataSorting.ImageIndex := 107;
if (SelectedTable <> '') and (ActiveDatabase <> '') then begin
// Ensure <Table> and <Data> are visible
tabTable.TabVisible := true;
tabData.TabVisible := true;
// Switch to <Data>
PageControlMain.ActivePage := tabData;
// Read columns to display from registry
reg_value := Mainform.GetRegValue(REGNAME_DISPLAYEDCOLUMNS + '_' + ActiveDatabase + '.' + SelectedTable, '', SessionName);
DisplayedColumnsList := WideStrings.TWideStringlist.Create;
DisplayedColumnsList.Delimiter := '`';
DisplayedColumnsList.DelimitedText := reg_value;
HiddenKeyCols := WideStrings.TWideStringlist.Create;
SynMemoFilter.Color := clWindow;
rx := TRegExpr.Create;
MainForm.ShowStatus('Freeing data...');
DataGrid.BeginUpdate;
debug('mem: clearing browse data.');
SetLength(FDataGridResult.Columns, 0);
SetLength(FDataGridResult.Rows, 0);
DataGrid.RootNodeCount := 0;
DataGrid.Header.Columns.BeginUpdate;
DataGrid.Header.Options := DataGrid.Header.Options + [hoVisible];
DataGrid.Header.Columns.Clear;
// Prepare SELECT statement
select_base := 'SELECT ';
// Try to calc the rowcount regardless of a given LIMIT
// Only needed if the user specified a WHERE-clause
Filter := GetFilter;
// Selected columns
if DisplayedColumnsList.Count = 0 then begin
FSelectedTableColumns.First;
while not FSelectedTableColumns.Eof do begin
DisplayedColumnsList.Add(FSelectedTableColumns.FieldByName('Field').AsWideString);
FSelectedTableColumns.Next;
end;
tbtnDataColumns.ImageIndex := 107;
end else begin
for i := DisplayedColumnsList.Count - 1 downto 0 do begin
ColExists := False;
FSelectedTableColumns.First;
while not FSelectedTableColumns.Eof do begin
if DisplayedColumnsList[i] = FSelectedTableColumns.FieldByName('Field').AsWideString then begin
ColExists := True;
break;
end;
FSelectedTableColumns.Next;
end;
if not ColExists then
DisplayedColumnsList.Delete(i);
end;
// Signal for the user that we now hide some columns
tbtnDataColumns.ImageIndex := 108;
end;
// Ensure key columns are included to enable editing
KeyCols := GetKeyColumns;
for i := 0 to KeyCols.Count - 1 do
if DisplayedColumnsList.IndexOf(KeyCols[i]) = -1 then begin
DisplayedColumnsList.Add(KeyCols[i]);
HiddenKeyCols.Add(KeyCols[i]);
end;
// Initialize column array to correct length.
debug('mem: initializing browse columns.');
SetLength(FDataGridResult.Columns, DisplayedColumnsList.Count);
for i := 0 to DisplayedColumnsList.Count - 1 do begin
ColName := DisplayedColumnsList[i];
FSelectedTableColumns.First;
while not FSelectedTableColumns.Eof do begin
if FSelectedTableColumns.FieldByName('Field').AsWideString = ColName then begin
ColType := FSelectedTableColumns.FieldByName('Type').AsString;
rx.Expression := '^((tiny|medium|long)?(text|blob)|(var)?(char|binary))\b(\(\d+\))?';
if rx.Exec(ColType) then begin
select_base := select_base + ' ' + 'LEFT(' + Mask(ColName) + ', ' + IntToStr(GridMaxData) + ')' + ',';
end else begin
select_base := select_base + ' ' + Mask(ColName) + ',';
end;
Break;
end;
FSelectedTableColumns.Next;
end;
end;
for i := 0 to DisplayedColumnsList.Count - 1 do begin
ColName := DisplayedColumnsList[i];
InitColumn(i, ColName);
end;
debug('mem: browse column initialization complete.');
// Cut last comma
select_base := copy( select_base, 1, Length(select_base)-1 );
select_from := ' FROM ' + mask( SelectedTable );
try
MainForm.ShowStatus('Counting rows...');
sl_query.Add('SELECT COUNT(*)');
sl_query.Add(select_from);
// Apply custom WHERE filter
if Filter <> '' then sl_query.Add('WHERE ' + Filter);
count := StrToInt(GetVar(sl_query.Text));
except
on E:Exception do begin
// Most likely we have a wrong filter-clause when this happens
// Put the user with his nose onto the wrong filter
// either specified by user or
// created by HeidiSQL by using the search box
ToggleFilterPanel(True);
SynMemoFilter.Color := $008080FF; // light pink
DataGrid.Header.Options := DataGrid.Header.Options - [hoVisible];
MessageDlg( E.Message, mtError, [mbOK], 0 );
raise;
end;
end;
MainForm.ShowStatus( STATUS_MSG_READY );
DataGridCurrentSelect := select_base + select_from;
DataGridCurrentFilter := Filter;
DataGridCurrentSort := sorting;
debug('mem: initializing browse rows (internal data).');
SetLength(FDataGridResult.Rows, count);
for i := 0 to count - 1 do begin
FDataGridResult.Rows[i].Loaded := False;
end;
debug('mem: initializing browse rows (grid).');
DataGrid.RootNodeCount := count;
debug('mem: browse row initialization complete.');
// Switched to another table
if ViewDataPrevTable <> SelectedTable then begin
DataGrid.OffsetXY := Point(0, 0); // Scroll to top left
FreeAndNil(PrevTableColWidths); // Throw away remembered, manually resized column widths
end;
DisplayRowCountStats;
dataselected := true;
pcChange(self);
end;
finally
DataGrid.Header.Columns.EndUpdate;
DataGrid.EndUpdate;
FreeAndNil(sl_query);
AutoCalcColWidths(DataGrid, PrevTableColWidths);
viewingdata := false;
Screen.Cursor := crDefault;
end;
ViewDataPrevTable := SelectedTable;
end;
{***
Calculate + display total rowcount and found rows matching to filter
in data-tab
}
procedure TMDIChild.DisplayRowCountStats;
var
rows_matching : Int64; // rows matching to where-filter
rows_total : Int64; // total rowcount
filter : WideString;
begin
lblDataTop.Caption := ActiveDatabase + '.' + SelectedTable + ': ';
Filter := GetFilter;
if GetSelectedNodeType = NODETYPE_TABLE then begin
if Filter <> '' then begin
// Get rowcount from table
rows_total := StrToInt64( GetVar( 'SELECT COUNT(*) FROM ' + mask( SelectedTable ), 0 ) );
end else begin
rows_total := DataGrid.RootNodeCount
end;
lblDataTop.Caption := lblDataTop.Caption + FormatNumber( rows_total ) + ' rows total';
end else begin
// Don't fetch rowcount from views to fix bug #1844952
rows_total := -1;
lblDataTop.Caption := lblDataTop.Caption + ' [View]';
end;
rows_matching := DataGrid.RootNodeCount;
if( rows_matching <> rows_total ) and
(Filter <> '') then
lblDataTop.Caption := lblDataTop.Caption + ', ' + FormatNumber(rows_matching) + ' matching to filter';
if ( rows_matching = rows_total ) and
(Filter <> '') then
lblDataTop.Caption := lblDataTop.Caption + ', filter matches all rows';
end;
procedure TMDIChild.WaitForQueryCompletion(WaitForm: TfrmQueryProgress; query: TMySqlQuery; ForceDialog: Boolean);
var
signal: Cardinal;
begin
debug( 'Waiting for query to complete.' );
if ForceDialog then begin
debug( 'Showing progress form.' );
WaitForm.ShowModal();
end else begin
signal := WaitForSingleObject(query.EventHandle, QueryWaitTime);
if signal = 0 then debug( 'Query completed within ' + IntToStr(QueryWaitTime) + 'msec.' )
else begin
debug( IntToStr(QueryWaitTime) + 'msec passed, showing progress form.' );
// Hack: Prevent dynamic loading of records in the context of the wait form's message loop.
DataGrid.Visible := False;
WaitForm.ShowModal();
end;
end;
CloseHandle(query.EventHandle);
debug( 'Query complete.' );
end;
{***
Occurs when active tab has changed.
}
procedure TMDIChild.pcChange(Sender: TObject);
begin
// Load data.
// Do this only if the user clicked the new tab. Not on automatic tab changes.
if Sender = PageControlMain then begin
if (PageControlMain.ActivePage = tabData) then viewdata(Sender);
end;
// Move focus to relevant controls in order for them to receive keyboard events.
// Do this only if the user clicked the new tab. Not on automatic tab changes.
if Sender = PageControlMain then begin
if PageControlMain.ActivePage = tabDatabase then ListTables.SetFocus;
if PageControlMain.ActivePage = tabTable then ListColumns.SetFocus;
if PageControlMain.ActivePage = tabData then DataGrid.SetFocus;
if PageControlMain.ActivePage = tabQuery then SynMemoQuery.SetFocus;
end;
// Ensure controls are in a valid state
ValidateControls;
// Show processlist if it's visible now but empty yet
if PageControlMain.ActivePage = tabHost then begin
if ListProcesses.RootNodeCount = 0 then
ShowProcessList( self );
end;
end;
{***
Ensures that we're connected to the currently selected database.
}
procedure TMDIChild.EnsureDatabase;
var
db: WideString;
begin
// Some functions temporarily switch away from the database selected by the user, handle that.
if TemporaryDatabase <> '' then db := TemporaryDatabase
else db := ActiveDatabase;
// Blank = database undefined
if db = '' then Exit;
if (FMysqlConn.Connection.Database <> db) or (UserQueryFired and not UserQueryFiring) then begin
ExecUseQuery(db, false, false);
UserQueryFired := false;
FMysqlConn.Connection.Database := db;
end;
end;
{***
Look for list of tables for current database in cache.
Retrieve from server if necessary.
@return TDataSet The cached list of tables for the active database.
}
function TMDIChild.FetchActiveDbTableList: TDataSet;
begin
Result := FetchDbTableList(ActiveDatabase);
end;
function TMDIChild.FetchDbTableList(db: WideString): TDataSet;
var
ds: TDataSet;
OldCursor: TCursor;
begin
if not DbTableListCached(db) then begin
// Not in cache, load table list.
OldCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
MainForm.ShowStatus('Fetching tables from "' + db + '" ...');
try
if (mysql_version >= 32300) and (not prefPreferShowTables) then begin
ds := GetResults('SHOW TABLE STATUS FROM ' + mask(db), false, false);
end else begin
// contains table names, nothing else.
ds := GetResults('SHOW /*!50002 FULL */ TABLES FROM ' + mask(db), false, false);
// could clean up data (rename first column to 'Name') and
// and add row counters to data set as a new field by using
// SELECT COUNT(*), but that would potentially be rather slow.
end;
CachedTableLists.AddObject(db, ds);
// Add table names to SQL highlighter
SynSQLSyn1.TableNames.BeginUpdate;
while not ds.Eof do begin
SynSQLSyn1.TableNames.Add(ds.Fields[0].AsWideString);
ds.Next;
end;
SynSQLSyn1.TableNames.EndUpdate;
finally
MainForm.ShowStatus(STATUS_MSG_READY);
Screen.Cursor := OldCursor;
end;
end;
Result := TDataSet(CachedTableLists.Objects[CachedTableLists.IndexOf(db)]);
Result.First;
end;
{***
Nukes cached table list for active database, then refreshes it.
@return TDataSet The newly cached list of tables for the active database.
}
function TMDIChild.RefreshActiveDbTableList: TDataSet;
begin
Result := RefreshDbTableList(ActiveDatabase);
end;
function TMDIChild.RefreshDbTableList(db: WideString): TDataSet;
begin
ClearDbTableList(db);
Result := FetchDbTableList(db);
end;
procedure TMDIChild.ClearDbTableList(db: WideString);
var
idx: Integer;
o: TObject;
begin
idx := CachedTableLists.IndexOf(db);
if idx > -1 then begin
o := CachedTableLists.Objects[idx];
FreeAndNil(o);
CachedTableLists.Delete(idx);
end;
end;
{***
Nukes the table list cache.
}
procedure TMDIChild.ClearAllTableLists;
var
idx: Integer;
ds: TDataSet;
begin
for idx := 0 to CachedTableLists.Count - 1 do begin
ds := TDataSet(CachedTableLists.Objects[idx]);
ds.Close;
FreeAndNil(ds);
end;
CachedTableLists.Clear;
end;
// Fetch content from a row cell, avoiding NULLs to cause AVs
function TMDIChild.FieldContent(ds: TDataSet; ColName: WideString): WideString;
begin
Result := '';
if
(ds.FindField(colName) <> nil) and
(not ds.FindField(ColName).IsNull)
then
Result := ds.FieldByName(ColName).AsWideString;
end;
procedure TMDIChild.LoadDatabaseProperties(db: WideString);
var
i : Integer;
bytes : Int64;
ds : TDataSet;
ListCaptions,
SelectedCaptions: WideStrings.TWideStringList;
begin
// DB-Properties
Screen.Cursor := crHourGlass;
// Remember selected nodes
SelectedCaptions := GetVTCaptions(ListTables, True);
try
ds := FetchDbTableList(db);
MainForm.ShowStatus( 'Displaying tables from "' + db + '" ...' );
ListTables.BeginUpdate;
ListTables.Clear;
SetLength(VTRowDataListTables, ds.RecordCount);
for i := 1 to ds.RecordCount do
begin
listcaptions := WideStrings.TWideStringList.Create;
// Table
ListCaptions.Add( ds.Fields[0].AsWideString );
// Treat tables slightly different than views
case GetDBObjectType( ds.Fields) of
NODETYPE_TABLE, NODETYPE_CRASHED_TABLE: // A normal table
begin
if GetDBObjectType(ds.Fields) = NODETYPE_CRASHED_TABLE then begin
VTRowDataListTables[i-1].ImageIndex := ICONINDEX_CRASHED_TABLE;
VTRowDataListTables[i-1].NodeType := NODETYPE_CRASHED_TABLE;
end else begin
VTRowDataListTables[i-1].ImageIndex := ICONINDEX_TABLE;
VTRowDataListTables[i-1].NodeType := NODETYPE_TABLE;
end;
// Rows
if ds.FindField('Rows') <> nil then
ListCaptions.Add( FormatNumber( FieldContent(ds, 'Rows') ) )
else
ListCaptions.Add('');
// Size: Data_length + Index_length
bytes := GetTableSize(ds);
if bytes >= 0 then ListCaptions.Add(FormatByteNumber(bytes))
else ListCaptions.Add('');
// Created:
ListCaptions.Add( FieldContent(ds, 'Create_time') );
// Updated:
ListCaptions.Add( FieldContent(ds, 'Update_time') );
// Engine
if ds.FindField('Type') <> nil then
ListCaptions.Add( FieldContent(ds, 'Type') )
else
ListCaptions.Add( FieldContent(ds, 'Engine') );
// Comment
ListCaptions.Add( FieldContent(ds, 'Comment') );
// Version
ListCaptions.Add( FieldContent(ds, 'Version') );
// Row format
ListCaptions.Add( FieldContent(ds, 'Row_format') );
// Avg row length
if (FieldContent(ds, 'Avg_row_length') <> '') then
ListCaptions.Add( FormatByteNumber(FieldContent(ds, 'Avg_row_length')) )
else ListCaptions.Add('');
// Max data length
if (FieldContent(ds, 'Max_data_length') <> '') then
ListCaptions.Add( FormatByteNumber(FieldContent(ds, 'Max_data_length')) )
else ListCaptions.Add('');
// Index length
if (FieldContent(ds, 'Index_length') <> '') then
ListCaptions.Add( FormatByteNumber(FieldContent(ds, 'Index_length')) )
else ListCaptions.Add('');
// Data free
if (FieldContent(ds, 'Data_free') <> '') then
ListCaptions.Add( FormatByteNumber(FieldContent(ds, 'Data_free')) )
else ListCaptions.Add('');
// Auto increment
if (FieldContent(ds, 'Auto_increment') <> '') then
ListCaptions.Add( FormatNumber(FieldContent(ds, 'Auto_increment')) )
else ListCaptions.Add('');
// Check time
ListCaptions.Add( FieldContent(ds, 'Check_time') );
// Collation
ListCaptions.Add( FieldContent(ds, 'Collation') );
// Checksum
ListCaptions.Add( FieldContent(ds, 'Checksum') );
// Create_options
ListCaptions.Add( FieldContent(ds, 'Create_options') );
// Object type
ListCaptions.Add('Base table');
end;
NODETYPE_VIEW:
begin // View
VTRowDataListTables[i-1].ImageIndex := ICONINDEX_VIEW;
VTRowDataListTables[i-1].NodeType := NODETYPE_VIEW;
// Rows
ListCaptions.Add('');
// Size
ListCaptions.Add('');
// Created:
ListCaptions.Add('');
// Updated:
ListCaptions.Add('');
// Engine
ListCaptions.Add('');
// Comment
ListCaptions.Add(FieldContent(ds, 'Comment'));
// Version
ListCaptions.Add('');
// Row_format
ListCaptions.Add('');
// Avg_row_length
ListCaptions.Add('');
// Max_data_length
ListCaptions.Add('');
// Index_length
ListCaptions.Add('');
// Data_free
ListCaptions.Add('');
// Auto_increment
ListCaptions.Add('');
// Check_time
ListCaptions.Add('');
// Collation
ListCaptions.Add('');
// Checksum
ListCaptions.Add('');
// Create_options
ListCaptions.Add('');
// Object Type
ListCaptions.Add('View');
end;
end;
VTRowDataListTables[i-1].Captions := ListCaptions;
ds.Next;
end;
finally
ListTables.RootNodeCount := Length(VTRowDataListTables);
ListTables.EndUpdate;
SetVTSelection(ListTables, SelectedCaptions);
Mainform.showstatus(db + ': ' + IntToStr(ListTables.RootNodeCount) +' table(s)', 0);
tabDatabase.Caption := sstr('Database: ' + db, 30);
MainForm.ShowStatus(STATUS_MSG_READY);
Screen.Cursor := crDefault;
// Ensure tree db node displays its chidren initialized
DBtree.ReinitChildren(FindDBNode(db), False);
end;
end;
{ Show tables and their properties on the tabsheet "Database" }
procedure TMDIChild.ShowDBProperties(db: WideString);
begin
Screen.Cursor := crHourglass;
pcChange( Self );
MainForm.ShowStatus( STATUS_MSG_READY );
Screen.Cursor := crDefault;
end;
{ Show columns of selected table, indicate indexed columns by certain icons }
procedure TMDIChild.RefreshFieldListClick(Sender: TObject);
begin
ShowTableProperties;
end;
procedure TMDIChild.ShowTableProperties;
var
i,j : Integer;
isFulltext : Boolean;
dummy: Boolean;
hasCommentColumn: Boolean;
SelectedCaptions: WideStrings.TWideStringList;
defaultVal: WideString;
begin
// Table-Properties
dataselected := false;
Screen.Cursor := crHourGlass;
tabTable.Caption := sstr('Table: ' + SelectedTable, 30);
tabDatabase.TabVisible := true;
tabTable.TabVisible := true;
tabData.TabVisible := true;
if (not DBtree.Dragging) and (
(PageControlMain.ActivePage = tabHost) or
(PageControlMain.ActivePage = tabDatabase)
) then PageControlMain.ActivePage := tabTable;
MainForm.ShowStatus( 'Reading table properties...' );
// Remember selected nodes
SelectedCaptions := GetVTCaptions(ListColumns, True);
ListColumns.BeginUpdate;
ListColumns.Clear;
FSelectedTableColumns := nil;
FSelectedTableKeys := nil;
Try
// Hide column "Comment" on old servers.
hasCommentColumn := FSelectedTableColumns.FindField('Comment') <> nil;
if not hasCommentColumn then
ListColumns.Header.Columns[5].Options := ListColumns.Header.Columns[5].Options - [coVisible];
SetLength(VTRowDataListColumns, FSelectedTableColumns.RecordCount);
for i:=1 to FSelectedTableColumns.RecordCount do
begin
VTRowDataListColumns[i-1].ImageIndex := ICONINDEX_FIELD;
VTRowDataListColumns[i-1].Captions := WideStrings.TWideStringList.Create;
VTRowDataListColumns[i-1].Captions.Add( FSelectedTableColumns.FieldByName('Field').AsWideString );
VTRowDataListColumns[i-1].Captions.Add( FSelectedTableColumns.FieldByName('Type').AsWideString );
if lowercase( FSelectedTableColumns.FieldByName('Null').AsString ) = 'yes' then
VTRowDataListColumns[i-1].Captions.Add('Yes')
else VTRowDataListColumns[i-1].Captions.Add('No');
if FSelectedTableColumns.FieldByName('Default').IsNull then
// In MySQL, it is not possible to use fx NOW() as a column default.
// Also, if default is NULL, then the actual default is either NULL
// or nothing at all. Looking at another column, "Null", can help
// determine which one it really is, as can a SHOW CREATE TABLE.
// According with the above, it is not possible in MySQL to create
// a column which may be NULL but which has no default value.
if LowerCase(FSelectedTableColumns.FieldByName('Null').AsString) = 'yes' then
VTRowDataListColumns[i-1].Captions.Add('NULL')
else
// No default value.
VTRowDataListColumns[i-1].Captions.Add('')
else begin
defaultVal := FSelectedTableColumns.FieldByName('Default').AsWideString;
if UpperCase(defaultVal) <> 'CURRENT_TIMESTAMP' then
defaultVal := '''' + defaultVal + '''';
VTRowDataListColumns[i-1].Captions.Add(defaultVal);
end;
VTRowDataListColumns[i-1].Captions.Add( FSelectedTableColumns.FieldByName('Extra').AsWideString );
if hasCommentColumn then
VTRowDataListColumns[i-1].Captions.Add( FSelectedTableColumns.FieldByName('Comment').AsWideString )
else
VTRowDataListColumns[i-1].Captions.Add('');
FSelectedTableColumns.Next;
end;
ListColumns.RootNodeCount := Length(VTRowDataListColumns);
// Manually invoke OnChange event of tabset to fill helper list with data
if tabsetQueryHelpers.TabIndex = 0 then
tabsetQueryHelpers.OnChange( Self, tabsetQueryHelpers.TabIndex, dummy);
Screen.Cursor := crHourglass;
for i:=1 to FSelectedTableKeys.RecordCount do
begin
// Search for the column name in listColumns
for j:=0 to Length(VTRowDataListColumns)-1 do
begin
if FSelectedTableKeys.FieldByName('Column_name').AsWideString = VTRowDataListColumns[j].Captions[0] then
begin
// Only apply a new icon if it was not already changed
if VTRowDataListColumns[j].ImageIndex <> ICONINDEX_FIELD then
break;
// Check if column is part of a fulltext key
if mysql_version < 40002 then
isFulltext := (FSelectedTableKeys.FieldByName('Comment').AsString = 'FULLTEXT')
else
isFulltext := (FSelectedTableKeys.FieldByName('Index_type').AsString = 'FULLTEXT');
// Primary key
if FSelectedTableKeys.FieldByName('Key_name').AsString = 'PRIMARY' then
VTRowDataListColumns[j].ImageIndex := ICONINDEX_PRIMARYKEY
// Fulltext index
else if isFullText then
VTRowDataListColumns[j].ImageIndex := ICONINDEX_FULLTEXTKEY
// Unique index
else if FSelectedTableKeys.FieldByName('Non_unique').AsString = '0' then
VTRowDataListColumns[j].ImageIndex := ICONINDEX_UNIQUEKEY
// Normal index
else
VTRowDataListColumns[j].ImageIndex := ICONINDEX_INDEXKEY;
// Column was found and processed
break;
end;
end;
FSelectedTableKeys.Next;
end;
{
** note, ansgarbecker, 2007-08-26
VT has a pretty autosorting feature, which keeps the sorting even after having
filled it with new data.
But: Don't use this auto-sorting here, neither automatically nor manual
because that would cause big confusion to the user if a just clicked
table displays its fields not in the natural order.
@todo Detect if the list was just refreshed (and then keep sorting)
or if another table get displayed (then don't sort, as below)
}
ListColumns.Header.SortColumn := -1;
ListColumns.Header.SortDirection := sdAscending;
finally
ListColumns.EndUpdate;
// Reselect previous selected nodes
SetVTSelection(ListColumns, SelectedCaptions);
Screen.Cursor := crDefault;
end;
pcChange( Self );
MainForm.ShowStatus( STATUS_MSG_READY );
MainForm.showstatus(ActiveDatabase + ': '+ SelectedTable + ': ' + IntToStr(ListColumns.RootNodeCount) +' column(s)', 0);
Screen.Cursor := crDefault;
end;
{***
Execute a query and return a resultset
The currently active connection is used
@param String The single SQL-query to be executed on the server
}
function TMDIChild.ExecuteQuery(query: String): TDataSet;
begin
result := GetResults(query, false, false);
end;
{***
Execute a query without returning a resultset
The currently active connection is used
@param String The single SQL-query to be executed on the server
}
procedure TMDIChild.ExecuteNonQuery(SQLQuery: String);
begin
ExecUpdateQuery(SQLQuery);
end;
{***
Selection in ListTables is changing
}
procedure TMDIChild.ListTablesChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
begin
ValidateControls;
end;
{***
Enable/disable various buttons and menu items.
Invoked when
- active sheet changes
- highlighted database changes
- ChildWindow is activated / deactivated
@param Boolean Is this form activated in terms of our remaining MDI-functionality?
Only used with False by FormDeactivate-procedure to
deactivate various controls on mainform
}
procedure TMDIChild.ValidateControls( FrmIsFocussed: Boolean = true );
var
DBObjectSelected, TableSelected, ViewSelected,
inDbTab, inTableTab, inDataTab, inQueryTab, inDataOrQueryTab, inDataOrQueryTabNotEmpty,
FieldsSelected, FieldFocused, dummy, DBfocused : Boolean;
NodeData: PVTreeData;
SelectedNodes: TNodeArray;
begin
inDbTab := FrmIsFocussed and (PageControlMain.ActivePage = tabDatabase);
inTableTab := FrmIsFocussed and (PageControlMain.ActivePage = tabTable);
inDataTab := FrmIsFocussed and (PageControlMain.ActivePage = tabData);
inDataOrQueryTab := FrmIsFocussed and ((PageControlMain.ActivePage = tabData) or (PageControlMain.ActivePage = tabQuery));
inDataOrQueryTabNotEmpty := inDataOrQueryTab and (hoVisible in ActiveGrid.Header.Options);
inQueryTab := FrmIsFocussed and (PageControlMain.ActivePage = tabQuery);
SelectedNodes := ListTables.GetSortedSelection(False);
DBObjectSelected := (Length(SelectedNodes)>0) and FrmIsFocussed;
TableSelected := False;
ViewSelected := False;
// Check type of first selected node, to en-/disable certain menu items
if DBObjectSelected then begin
NodeData := ListTables.GetNodeData( SelectedNodes[0] );
TableSelected := (NodeData.NodeType = NODETYPE_TABLE) or (NodeData.NodeType = NODETYPE_CRASHED_TABLE);
ViewSelected := NodeData.NodeType = NODETYPE_VIEW;
end;
// Standard toolbar and main menu
MainForm.actRefresh.Enabled := FrmIsFocussed;
MainForm.actFlushHosts.Enabled := FrmIsFocussed;
MainForm.actFlushLogs.Enabled := FrmIsFocussed;
MainForm.actFlushPrivileges.Enabled := FrmIsFocussed;
MainForm.actFlushTables.Enabled := FrmIsFocussed;
MainForm.actFlushTableswithreadlock.Enabled := FrmIsFocussed;
MainForm.actFlushStatus.Enabled := FrmIsFocussed;
MainForm.actUserManager.Enabled := FrmIsFocussed;
MainForm.actMaintenance.Enabled := FrmIsFocussed;
MainForm.actInsertFiles.Enabled := FrmIsFocussed;
// PrintList should only be active if we're focussing one of the ListViews,
// at least as long we are not able to print DBGrids
MainForm.actPrintList.Enabled := FrmIsFocussed;
MainForm.actSQLhelp.Enabled := (mysql_version >= 40100) and FrmIsFocussed;
MainForm.actImportCSV.Enabled := (mysql_version >= 32206) and FrmIsFocussed;
MainForm.actExportTables.Enabled := FrmIsFocussed;
// Database tab
Mainform.actEmptyTables.Enabled := inDbTab and TableSelected;
Mainform.actEditTableProperties.Enabled := inDbTab and TableSelected;
MenuRenameTable.Enabled := inDbTab and DBObjectSelected;
Mainform.actCopyTable.Enabled := inDbTab and DBObjectSelected;
Mainform.actEditView.Enabled := inDbTab and ViewSelected and (mysql_version >= 50001);
Mainform.actCreateView.Enabled := FrmIsFocussed and (ActiveDatabase <> '') and (mysql_version >= 50001);
MainForm.actCreateDatabase.Enabled := FrmIsFocussed;
DBfocused := Assigned(DBtree.FocusedNode) and (DBtree.GetNodeLevel(DBtree.FocusedNode) = 1);
MainForm.actDropDatabase.Enabled := DBfocused and FrmIsFocussed;
MainForm.actEditDatabase.Enabled := DBfocused and FrmIsFocussed and (mysql_version >= 50002);
if mysql_version < 50002 then
MainForm.actEditDatabase.Hint := STR_NOTSUPPORTED
else
MainForm.actEditDatabase.Hint := 'Rename and/or modify character set of database';
MainForm.actDropTablesAndViews.Enabled := (DBObjectSelected and inDbTab) or ((not inQueryTab) and (SelectedTable <> '') and FrmIsFocussed);
MainForm.actCreateTable.Enabled := (ActiveDatabase <> '') and FrmIsFocussed;
Mainform.actEditTableFields.Enabled := DBObjectSelected and inDbTab;
// Table tab
FieldFocused := inTableTab and Assigned(ListColumns.FocusedNode);
FieldsSelected := inTableTab and (Length(ListColumns.GetSortedSelection(False))>0);
// Toggle state of menuitems and buttons
Mainform.actEditField.Enabled := FieldFocused and FieldsSelected;
Mainform.actCreateField.Enabled := inTableTab;
Mainform.actDropFields.Enabled := FieldsSelected;
Mainform.actEditIndexes.Enabled := inTableTab;
menuRenameColumn.Enabled := FieldFocused and FieldsSelected;
// Data tab - if query results are made editable, these will need
// to be changed to look at which tab is focused.
Mainform.actDataInsert.Enabled := inDataTab;
Mainform.actDataDelete.Enabled := inDataTab and (DataGrid.SelectedCount > 0);
Mainform.actDataFirst.Enabled := inDataTab;
Mainform.actDataLast.Enabled := inDataTab;
Mainform.actDataPostChanges.Enabled := inDataTab and DataGridHasChanges;
Mainform.actDataCancelChanges.Enabled := inDataTab and DataGridHasChanges;
// Activate export-options if we're on Data- or Query-tab
MainForm.actCopyAsCSV.Enabled := inDataOrQueryTabNotEmpty;
MainForm.actCopyAsHTML.Enabled := inDataOrQueryTabNotEmpty;
MainForm.actCopyAsXML.Enabled := inDataOrQueryTabNotEmpty;
MainForm.actExportData.Enabled := inDataOrQueryTabNotEmpty;
MainForm.actHTMLView.Enabled := inDataOrQueryTabNotEmpty;
// Query tab
MainForm.actLoadSQL.Enabled := FrmIsFocussed;
// Manually invoke OnChange event of tabset to fill helper list with data
if inQueryTab and FrmIsFocussed then
tabsetQueryHelpers.OnChange(Self, tabsetQueryHelpers.TabIndex, dummy);
ValidateQueryControls(FrmIsFocussed);
if not FrmIsFocussed then begin
// Empty "connected" and "uptime"
MainForm.showstatus('', 1);
MainForm.showstatus('', 2);
MainForm.showstatus('', 3);
end;
end;
procedure TMDIChild.ValidateQueryControls(FrmIsFocussed: Boolean = true);
var
InQueryTab, NotEmpty, HasSelection: Boolean;
begin
InQueryTab := FrmIsFocussed and (PageControlMain.ActivePage = tabQuery);
NotEmpty := FrmIsFocussed and (SynMemoQuery.GetTextLen > 0);
HasSelection := FrmIsFocussed and SynMemoQuery.SelAvail;
Mainform.actExecuteQuery.Enabled := InQueryTab and NotEmpty;
Mainform.actExecuteSelection.Enabled := InQueryTab and HasSelection;
Mainform.actExecuteLine.Enabled := InQueryTab and (SynMemoQuery.LineText <> '');
MainForm.actSaveSQL.Enabled := InQueryTab and NotEmpty;
MainForm.actSaveSQLselection.Enabled := InQueryTab and HasSelection;
MainForm.actSaveSQLSnippet.Enabled := InQueryTab and NotEmpty;
MainForm.actSaveSQLSelectionSnippet.Enabled := InQueryTab and HasSelection;
MainForm.actQueryFind.Enabled := InQueryTab and NotEmpty;
MainForm.actQueryReplace.Enabled := InQueryTab and NotEmpty;
MainForm.actQueryStopOnErrors.Enabled := InQueryTab;
MainForm.actQueryWordWrap.Enabled := InQueryTab;
Mainform.actClearQueryEditor.Enabled := InQueryTab and NotEmpty;
Mainform.actSetDelimiter.Enabled := InQueryTab;
end;
procedure TMDIChild.ShowTableData(table: WideString);
begin
dataselected := false;
PageControlMain.ActivePage := tabData;
viewdata(self);
pcChange( Self );
end;
procedure TMDIChild.ShowVariablesAndProcesses(Sender: TObject);
procedure addLVitem( caption: WideString; commandCount: Int64; totalCount: Int64 );
var
i : Integer;
tmpval : Double;
begin
SetLength( VTRowDataListCommandStats, Length(VTRowDataListCommandStats)+1 );
i := Length(VTRowDataListCommandStats)-1;
VTRowDataListCommandStats[i].ImageIndex := 25;
VTRowDataListCommandStats[i].Captions := WideStrings.TWideStringList.Create;
caption := Copy( caption, 5, Length(caption) );
caption := WideStringReplace( caption, '_', ' ', [rfReplaceAll] );
VTRowDataListCommandStats[i].Captions.Add( caption );
// Total Frequency
VTRowDataListCommandStats[i].Captions.Add( FormatNumber( commandCount ) );
// Average per hour
uptime := max(uptime, 1);
tmpval := commandCount / ( uptime / 60 / 60 );
VTRowDataListCommandStats[i].Captions.Add( FormatNumber( tmpval, 1 ) );
// Average per second
tmpval := commandCount / uptime;
VTRowDataListCommandStats[i].Captions.Add( FormatNumber( tmpval, 1 ) );
// Percentage. Take care of division by zero errors and Int64's
if commandCount < 1 then
commandCount := 1;
if totalCount < 1 then
totalCount := 1;
tmpval := 100 / totalCount * commandCount;
VTRowDataListCommandStats[i].Captions.Add( FormatNumber( tmpval, 1 ) + ' %' );
end;
var
i : Integer;
questions : Int64;
ds : TDataSet;
SelectedCaptions: WideStrings.TWideStringList;
begin
// Prevent auto update from executing queries if the host tab is not activated
if (Sender is TTimer) and (PageControlMain.ActivePage <> tabHost) then
Exit;
// Refresh variables and process-list
Screen.Cursor := crHourglass;
// Remember selected nodes
SelectedCaptions := GetVTCaptions(ListVariables, True);
// VARIABLES
ListVariables.BeginUpdate;
ListVariables.Clear;
ds := GetResults( 'SHOW VARIABLES', false );
SetLength( VTRowDataListVariables, ds.RecordCount );
for i:=1 to ds.RecordCount do
begin
VTRowDataListVariables[i-1].ImageIndex := 25;
VTRowDataListVariables[i-1].Captions := WideStrings.TWideStringList.Create;
VTRowDataListVariables[i-1].Captions.Add( ds.Fields[0].AsWideString );
VTRowDataListVariables[i-1].Captions.Add( ds.Fields[1].AsWideString );
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
// Tell VirtualTree the number of nodes it will display
ListVariables.RootNodeCount := Length(VTRowDataListVariables);
ListVariables.EndUpdate;
SetVTSelection( ListVariables, SelectedCaptions );
// Apply filter
if editFilterVariables.Text <> '' then
editFilterVTChange(editFilterVariables);
// Display number of listed values on tab
tabVariables.Caption := 'Variables (' + IntToStr(ListVariables.RootNodeCount) + ')';
// STATUS
uptime := 1; // avoids division by zero :)
questions := 1;
// Remember selected nodes
SelectedCaptions := GetVTCaptions(ListStatus, True);
ListStatus.BeginUpdate;
ListStatus.Clear;
ds := GetResults( 'SHOW /*!50002 GLOBAL */ STATUS' );
SetLength( VTRowDataListStatus, ds.RecordCount );
for i:=1 to ds.RecordCount do
begin
VTRowDataListStatus[i-1].ImageIndex := 25;
VTRowDataListStatus[i-1].Captions := WideStrings.TWideStringList.Create;
VTRowDataListStatus[i-1].Captions.Add( ds.Fields[0].AsWideString );
VTRowDataListStatus[i-1].Captions.Add( ds.Fields[1].AsWideString );
if lowercase( ds.Fields[0].AsString ) = 'uptime' then
uptime := MakeInt(ds.Fields[1].AsString);
if lowercase( ds.Fields[0].AsString ) = 'questions' then
questions := MakeInt(ds.Fields[1].AsString);
ds.Next;
end;
// Tell VirtualTree the number of nodes it will display
ListStatus.RootNodeCount := Length(VTRowDataListStatus);
ListStatus.EndUpdate;
SetVTSelection( ListStatus, SelectedCaptions );
// Apply filter
if editFilterStatus.Text <> '' then
editFilterVTChange(editFilterStatus);
// Display number of listed values on tab
tabStatus.Caption := 'Status (' + IntToStr(ListStatus.RootNodeCount) + ')';
// Command-Statistics
SelectedCaptions := GetVTCaptions(ListCommandStats, True);
ListCommandStats.BeginUpdate;
ListCommandStats.Clear;
SetLength( VTRowDataListCommandStats, 0 );
addLVitem( ' All commands', questions, questions );
ds.First;
for i:=1 to ds.RecordCount do
begin
if LowerCase( Copy( ds.Fields[0].AsString, 1, 4 ) ) = 'com_' then
begin
addLVitem( ds.Fields[0].AsWideString, MakeInt(ds.Fields[1].AsString), questions );
end;
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
// Tell VirtualTree the number of nodes it will display
ListCommandStats.RootNodeCount := Length(VTRowDataListCommandStats);
ListCommandStats.EndUpdate;
SetVTSelection( ListCommandStats, SelectedCaptions );
TimerHostUptime.Enabled := true;
TimerHostUptimeTimer(self);
TimerHostUptime.OnTimer := TimerHostUptimeTimer;
Screen.Cursor := crDefault;
ShowProcesslist(self); // look at next procedure
end;
procedure TMDIChild.ShowProcessList(sender: TObject);
var
i,j : Integer;
ds : TDataSet;
SelectedCaptions: WideStrings.TWideStringList;
begin
// No need to update if it's not visible.
if PageControlMain.ActivePage <> tabHost then exit;
if PageControlHost.ActivePage <> tabProcesslist then exit;
Screen.Cursor := crHourglass;
// Remember selected nodes
SelectedCaptions := GetVTCaptions(ListProcesses, True);
try
ListProcesses.BeginUpdate;
ListProcesses.Clear;
debug('ShowProcessList()');
ds := GetResults('SHOW FULL PROCESSLIST', false, false);
SetLength(VTRowDataListProcesses, ds.RecordCount);
for i:=1 to ds.RecordCount do
begin
VTRowDataListProcesses[i-1].Captions := WideStrings.TWideStringList.Create;
VTRowDataListProcesses[i-1].Captions.Add( ds.Fields[0].AsWideString );
if AnsiCompareText( ds.Fields[4].AsString, 'Killed') = 0 then
VTRowDataListProcesses[i-1].ImageIndex := 26 // killed
else begin
if ds.FindField('Info').AsString = '' then
VTRowDataListProcesses[i-1].ImageIndex := 55 // idle
else
VTRowDataListProcesses[i-1].ImageIndex := 57 // running query
end;
for j := 1 to 7 do
VTRowDataListProcesses[i-1].Captions.Add(ds.Fields[j].AsWideString);
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
tabProcessList.Caption := 'Process-List (' + IntToStr(Length(VTRowDataListProcesses)) + ')';
except
on E: Exception do begin
LogSQL('Error loading process list (automatic refresh disabled): ' + e.Message);
TimerHost.Enabled := false;
end;
end;
ListProcesses.RootNodeCount := Length(VTRowDataListProcesses);
ListProcesses.EndUpdate;
// Reselect previous selected nodes
SetVTSelection( ListProcesses, SelectedCaptions );
// Apply filter
if editFilterProcesses.Text <> '' then
editFilterVTChange(editFilterProcesses);
Screen.Cursor := crDefault;
end;
procedure TMDIChild.KillProcess(Sender: TObject);
var t : Boolean;
ProcessIDs : WideStrings.TWideStringList;
i : Integer;
begin
t := TimerHost.Enabled;
TimerHost.Enabled := false; // prevent av (ListProcesses.selected...)
ProcessIDs := GetVTCaptions( ListProcesses, True );
if MessageDlg('Kill '+inttostr(ProcessIDs.count)+' Process(es)?', mtConfirmation, [mbok,mbcancel], 0) = mrok then
begin
for i := 0 to ProcessIDs.Count - 1 do
begin
// Don't kill own process
if ProcessIDs[i] = IntToStr( MySQLConn.Connection.GetThreadId ) then
LogSQL('Ignoring own process id '+ProcessIDs[i]+' when trying to kill it.')
else
ExecUpdateQuery( 'KILL '+ProcessIDs[i] );
end;
ShowVariablesAndProcesses(self);
end;
TimerHost.Enabled := t; // re-enable autorefresh timer
end;
procedure TMDIChild.PageControlHostChange(Sender: TObject);
begin
// Show processlist if it's visible now but empty yet
if ListProcesses.RootNodeCount = 0 then
ShowProcessList( self );
end;
procedure TMDIChild.ExecSQLClick(Sender: TObject; Selection: Boolean=false; CurrentLine: Boolean=false);
var
SQL : WideStrings.TWideStringList;
i, j : Integer;
rowsaffected : Integer;
SQLstart : Integer;
SQLend : Integer;
SQLscriptstart : Integer;
SQLscriptend : Integer;
SQLTime : Double;
LastVistaCheck : Cardinal;
VistaCheck : Boolean;
fieldcount : Integer;
recordcount : Integer;
ds : TDataSet;
ColName : WideString;
col : TVirtualTreeColumn;
begin
if CurrentLine then SQL := parseSQL(SynMemoQuery.LineText)
else if Selection then SQL := parseSQL(SynMemoQuery.SelText)
else SQL := parseSQL(SynMemoQuery.Text);
if ( SQL.Count = 0 ) then
begin
LabelResultinfo.Caption := '(nothing to do)';
Exit;
end;
SQLscriptstart := GetTickCount();
LastVistaCheck := GetTickCount();
LabelResultinfo.Caption := '';
ds := nil;
try
MainForm.showstatus( 'Initializing SQL...' );
Mainform.actExecuteQuery.Enabled := false;
Mainform.actExecuteSelection.Enabled := false;
// Let EnsureActiveDatabase know that we've fired user queries.
UserQueryFiring := true;
rowsaffected := 0;
fieldcount := 0;
recordcount := 0;
ProgressBarQuery.Max := SQL.Count;
ProgressBarQuery.Position := 0;
ProgressBarQuery.Show();
MainForm.showstatus( 'Executing SQL...' );
for i := 0 to (SQL.Count - 1) do
begin
ProgressBarQuery.StepIt();
ProgressBarQuery.Repaint;
if ( sql[i] = '' ) then
begin
continue;
end;
// open last query with data-aware:
LabelResultinfo.Caption := '';
// ok, let's rock
SQLstart := GetTickCount();
try
VistaCheck := false;
if GetTickCount() - LastVistaCheck > 2500 then begin
VistaCheck := true;
LastVistaCheck := GetTickCount();
end;
ds := GetResults( SQL[i], false, false, VistaCheck );
if ( ds <> nil ) then
begin
fieldcount := ds.Fieldcount;
recordcount := ds.Recordcount;
rowsaffected := rowsaffected + TZQuery(ds).RowsAffected;
end
else
begin
fieldcount := 0;
recordcount := 0;
rowsaffected := FMysqlConn.Connection.GetAffectedRowsFromLastPost;
end;
except
on E:Exception do
begin
if Mainform.actQueryStopOnErrors.Checked or (i = SQL.Count - 1) then begin
Screen.Cursor := crDefault;
MessageDlg( E.Message, mtError, [mbOK], 0 );
ProgressBarQuery.Hide();
Mainform.actExecuteQuery.Enabled := true;
Mainform.actExecuteSelection.Enabled := true;
Break;
end;
end;
end;
SQLend := GetTickCount();
SQLTime := (SQLend - SQLstart) / 1000;
LabelResultinfo.Caption :=
FormatNumber( rowsaffected ) +' row(s) affected, '+
FormatNumber( fieldcount ) +' column(s) x '+
FormatNumber( recordcount ) +' row(s) in last result set.';
if ( SQL.Count = 1 ) then
begin
LabelResultinfo.Caption := LabelResultinfo.Caption +
' Query time: '+ FormatNumber( SQLTime, 3) +' sec.';
end;
end;
ProgressBarQuery.Hide();
ValidateQueryControls;
if ( SQL.Count > 1 ) then
begin
SQLscriptend := GetTickCount();
SQLTime := (SQLscriptend - SQLscriptstart) / 1000;
LabelResultinfo.Caption := LabelResultinfo.Caption +' Batch time: '+
FormatNumber( SQLTime, 3 ) +' sec.';
end;
finally
// Let EnsureActiveDatabase know that we've fired user queries.
UserQueryFired := true;
UserQueryFiring := false;
// Avoid excessive GridHighlightChanged() when flicking controls.
viewingdata := true;
if ds <> nil then begin
QueryGrid.BeginUpdate;
QueryGrid.Header.Options := QueryGrid.Header.Options + [hoVisible];
QueryGrid.Header.Columns.BeginUpdate;
QueryGrid.Header.Columns.Clear;
debug('mem: clearing and initializing query columns.');
SetLength(FQueryGridResult.Columns, 0);
SetLength(FQueryGridResult.Columns, ds.FieldCount);
for i:=0 to ds.FieldCount-1 do begin
ColName := ds.Fields[i].FieldName;
col := QueryGrid.Header.Columns.Add;
col.Text := ColName;
col.Options := col.Options - [coAllowClick];
FQueryGridResult.Columns[i].Name := ColName;
if ds.Fields[i].DataType in [ftSmallint, ftInteger, ftWord, ftLargeint] then begin
FQueryGridResult.Columns[i].IsInt := True;
col.Alignment := taRightJustify;
end else if ds.Fields[i].DataType in [ftFloat] then begin
FQueryGridResult.Columns[i].IsFloat := True;
col.Alignment := taRightJustify;
end else if ds.Fields[i].DataType in [ftDate, ftTime, ftDateTime, ftTimeStamp] then
FQueryGridResult.Columns[i].IsDate := True
else if ds.Fields[i].DataType in [ftWideString, ftMemo, ftWideMemo] then
FQueryGridResult.Columns[i].IsText := True
else if ds.Fields[i].DataType in [ftBlob] then
FQueryGridResult.Columns[i].IsBinary := True;
end;
debug('mem: query column initialization complete.');
debug('mem: clearing and initializing query rows (internal data).');
SetLength(FQueryGridResult.Rows, 0);
SetLength(FQueryGridResult.Rows, ds.RecordCount);
ds.First;
for i:=0 to ds.RecordCount-1 do begin
FQueryGridResult.Rows[i].Loaded := True;
SetLength(FQueryGridResult.Rows[i].Cells, ds.FieldCount);
for j:=0 to ds.FieldCount-1 do begin
if FQueryGridResult.Columns[j].IsBinary then
FQueryGridResult.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString)
else
FQueryGridResult.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString;
FQueryGridResult.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull;
end;
ds.Next;
end;
ds.Free;
debug('mem: initializing query rows (grid).');
QueryGrid.RootNodeCount := Length(FQueryGridResult.Rows);
debug('mem: query row initialization complete.');
QueryGrid.Header.Columns.EndUpdate;
QueryGrid.ClearSelection;
QueryGrid.OffsetXY := Point(0, 0);
QueryGrid.EndUpdate;
AutoCalcColWidths(QueryGrid);
end;
// Ensure controls are in a valid state
ValidateControls;
viewingdata := false;
Screen.Cursor := crDefault;
MainForm.ShowStatus( STATUS_MSG_READY );
end;
end;
{**
Clicked somewhere in the field-list of the "Table"-tabsheet
}
procedure TMDIChild.ListColumnsChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
begin
ValidateControls;
end;
{ Proposal about to insert a String into synmemo }
procedure TMDIChild.SynCompletionProposal1CodeCompletion(Sender: TObject;
var Value: WideString; Shift: TShiftState; Index: Integer; EndToken: WideChar);
begin
SynCompletionProposal1.Editor.UndoList.AddGroupBreak;
end;
procedure TMDIChild.SynCompletionProposal1AfterCodeCompletion(Sender: TObject;
const Value: WideString; Shift: TShiftState; Index: Integer; EndToken: WideChar);
begin
SynCompletionProposal1.Editor.UndoList.AddGroupBreak;
end;
{ Proposal-Combobox pops up }
procedure TMDIChild.SynCompletionProposal1Execute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: WideString; var x, y: Integer;
var CanExecute: Boolean);
var
i,j : Integer;
ds : TDataset;
sql, TableClauses: WideString;
Tables : TStringList;
tablename : WideString;
rx : TRegExpr;
procedure addTable( Fields: TFields );
var ObjName, ObjType: WideString;
begin
ObjName := Fields[0].AsWideString;
case GetDBObjectType(Fields) of
NODETYPE_CRASHED_TABLE: ObjType := 'table';
NODETYPE_TABLE: ObjType := 'table';
NODETYPE_VIEW: ObjType := 'view';
else ObjType := 'unknown';
end;
SynCompletionProposal1.InsertList.Add( ObjName );
SynCompletionProposal1.ItemList.Add( '\hspace{2}\color{'+ColorToString(SynSQLSyn1.TableNameAttri.Foreground)+'}'+ObjType+'\color{clWindowText}\column{}' + ObjName );
end;
procedure addColumns( tablename: WideString );
var
dbname : WideString;
i : Integer;
ds : TDataSet;
begin
dbname := ActiveDatabase;
if Pos( '.', tablename ) > -1 then
begin
dbname := Copy( tablename, 0, Pos( '.', tablename )-1 );
tablename := Copy( tablename, Pos( '.', tablename )+1, Length(tablename) );
end;
// Do not mask db and table name to avoid double masking.
// Rely on what the user typed is already a valid masked/quoted identifier.
if dbname <> '' then
tablename := dbname + '.' + tablename;
ds := getResults( 'SHOW COLUMNS FROM '+tablename, true, false );
if ds = nil then exit;
for i:=0 to ds.RecordCount-1 do
begin
SynCompletionProposal1.InsertList.Add( ds.FieldByName( 'Field' ).AsWideString );
SynCompletionProposal1.ItemList.Add( '\hspace{2}\color{'+ColorToString(clTeal)+'}column\color{clWindowText}\column{}' + ds.FieldByName( 'Field' ).AsWideString + '\style{-B} ' + ds.FieldByName( 'Type' ).AsString );
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
end;
begin
SynCompletionProposal1.InsertList.Clear;
SynCompletionProposal1.ItemList.Clear;
// Get column-names into the proposal pulldown
// when we write sql like "SELECT t.|col FROM table [AS] t"
// Current limitation: Identifiers (masked or not) containing
// spaces are not detected correctly.
// 1. find the currently edited sql-statement around the cursor position in synmemo
j := Length(SynCompletionProposal1.Editor.Text);
for i := SynCompletionProposal1.Editor.SelStart+1024 downto SynCompletionProposal1.Editor.SelStart-1024 do
begin
if i > j then
continue;
if i < 1 then
break;
sql := SynCompletionProposal1.Editor.Text[i] + sql;
end;
// 2. Parse FROM clause to detect relevant table/view, probably aliased
rx := TRegExpr.Create;
rx.ModifierG := True;
rx.ModifierI := True;
rx.Expression := '\b(FROM|INTO|UPDATE)\s+(.+)(WHERE|HAVING|ORDER|GROUP)?';
if rx.Exec(sql) then begin
TableClauses := rx.Match[2];
// Ensure tables in JOIN clause(s) are splitted by comma
TableClauses := WideStringReplace(TableClauses, 'JOIN', ',', [rfReplaceAll, rfIgnoreCase]);
// Split table clauses by commas
Tables := TStringList.Create;
Tables.Delimiter := ',';
Tables.StrictDelimiter := true;
Tables.DelimitedText := TableClauses;
rx.Expression := '`?(\w+)`?(\s+(AS\s+)?`?(\w+)`?)?';
for i := 0 to Tables.Count - 1 do begin
// If the just typed word equals the alias of this table or the
// tablename itself, set tablename var and break loop
if rx.Exec(Tables[i]) then begin
if (WideDequotedStr(SynCompletionProposal1.PreviousToken,'`') = WideDequotedStr(rx.Match[4],'`') )
or (SynCompletionProposal1.PreviousToken = rx.Match[1]) then begin
tablename := Trim(rx.Match[1]);
break;
end;
end;
end;
end;
rx.Free;
if (tablename <> '') then begin
// add columns to proposal
addColumns( tablename );
end else if SynCompletionProposal1.PreviousToken <> '' then begin
// assuming previoustoken itself is a table
addColumns( SynCompletionProposal1.PreviousToken );
end;
if Length(CurrentInput) = 0 then // makes only sense if the user has typed "database."
begin
i := Databases.IndexOf( SynCompletionProposal1.PreviousToken );
if i > -1 then begin
// Only display tables from specified db
Screen.Cursor := crHourGlass;
ds := FetchDbTableList(Databases[i]);
while not ds.Eof do begin
addTable(ds.Fields);
ds.Next;
end;
Screen.Cursor := crDefault;
end;
end;
if (SynCompletionProposal1.ItemList.count = 0) and (Length(CurrentInput)>0) then
begin
// Add databases
for i := 0 to Databases.Count - 1 do begin
SynCompletionProposal1.InsertList.Add(Databases[i]);
SynCompletionProposal1.ItemList.Add(Databases[i]);
end;
for i:=0 to SynCompletionProposal1.ItemList.count-1 do
SynCompletionProposal1.ItemList[i] := '\hspace{2}\color{'+ColorToString(SynSQLSyn1.TableNameAttri.Foreground)+'}database\color{clWindowText}\column{}' + SynCompletionProposal1.ItemList[i];
if ActiveDatabase <> '' then begin
// Display tables from current db
ds := FetchActiveDbTableList;
while not ds.Eof do begin
addTable(ds.Fields);
ds.Next;
end;
if Length(CurrentInput) = 0 then // assume that we have already a dbname in memo
SynCompletionProposal1.Position := Databases.Count;
end;
// Add functions
for i := 0 to Length(MySQLFunctions) - 1 do
begin
// Don't display unsupported functions here
if MySqlFunctions[i].Version > mysql_version then
continue;
SynCompletionProposal1.InsertList.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration );
SynCompletionProposal1.ItemList.Add( '\hspace{2}\color{'+ColorToString(SynSQLSyn1.FunctionAttri.Foreground)+'}function\color{clWindowText}\column{}' + MySQLFunctions[i].Name + '\style{-B}' + MySQLFunctions[i].Declaration );
end;
// Add keywords
for i := 0 to MYSQL_KEYWORDS.Count - 1 do
begin
SynCompletionProposal1.InsertList.Add( MYSQL_KEYWORDS[i] );
SynCompletionProposal1.ItemList.Add( '\hspace{2}\color{'+ColorToString(SynSQLSyn1.KeyAttri.Foreground)+'}keyword\color{clWindowText}\column{}'+MYSQL_KEYWORDS[i] );
end;
end;
end;
procedure TMDIChild.SynMemoQueryStatusChange(Sender: TObject; Changes:
TSynStatusChanges);
begin
ValidateQueryControls;
end;
procedure TMDIChild.TimerHostUptimeTimer(Sender: TObject);
var
days, hours, minutes, seconds : Integer;
msg: string;
begin
// Host-Uptime
days:= uptime div (60*60*24);
seconds := uptime mod (60*60*24);
hours := seconds div (60*60);
seconds := seconds mod (60*60);
minutes := seconds div 60;
seconds := seconds mod 60;
inc(uptime);
msg := Format('%d days, %.2d:%.2d:%.2d', [days,hours,minutes,seconds]);
if TimerHostUptime.Enabled then msg := Format('Uptime: %s', [msg])
else msg := '';
Mainform.showstatus(msg, 3);
end;
procedure TMDIChild.FormActivate(Sender: TObject);
begin
TimerConnected.OnTimer(self);
end;
procedure TMDIChild.FormShow(Sender: TObject);
begin
DataGridHasChanges := False;
{ TODO : only load file when autoconnected ?? }
if (paramstr(1) <> '') and Main.loadsqlfile then
try
// load sql-file from paramstr
SynMemoQuery.Lines.LoadFromFile(paramstr(1));
Main.loadsqlfile := false;
except
MessageDLG('File could not be opened: ' + paramstr(1), mtError, [mbOK], 0);
end;
//TODO:
//ds.DisableControls;
end;
{***
Rename table after checking the new name for invalid characters
}
procedure TMDIChild.ListTablesNewText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; NewText: WideString);
var
NodeData : PVTreeData;
begin
// Fetch data from node
NodeData := Sender.GetNodeData(Node);
// Try to rename, on any error abort and don't rename ListItem
try
ensureValidIdentifier( NewText );
// rename table
ExecUpdateQuery( 'RENAME TABLE ' + mask(NodeData.Captions[0]) + ' TO ' + mask(NewText), False, False );
if SynSQLSyn1.TableNames.IndexOf( NewText ) = -1 then begin
SynSQLSyn1.TableNames.Add(NewText);
end;
// Update nodedata
NodeData.Captions[0] := NewText;
// Now the active tree db has to be updated. But calling RefreshTreeDB here causes an AV
// so we do it manually here
RefreshActiveDbTableList;
DBTree.InvalidateChildren(FindDBNode(ActiveDatabase), True);
except
On E : Exception do
begin
MessageDlg( E.Message, mtError, [mbOK], 0 );
end;
end;
end;
procedure TMDIChild.MenuRenameTableClick(Sender: TObject);
begin
// menuitem for edit table-name
ListTables.EditNode( ListTables.FocusedNode, 0 );
end;
procedure TMDIChild.TimerConnectedTimer(Sender: TObject);
begin
if not TimerConnected.Enabled then begin
MainForm.showstatus('Disconnected.', 1);
exit;
end;
inc(time_connected);
// calculate and display connection-time
MainForm.showstatus( 'Connected: ' + FormatTimeNumber(time_connected), 1 );
end;
procedure TMDIChild.Clear2Click(Sender: TObject);
begin
// clear history-memo
Screen.Cursor := crHourglass;
SynMemoSQLLog.Lines.Clear;
Screen.Cursor := crDefault;
end;
procedure TMDIChild.EditQuery1Click(Sender: TObject);
begin
// take query from history to query-tab
SynMemoQuery.Text := SynMemoSQLLog.SelText;
PageControlMain.ActivePage := tabQuery;
pcChange(self);
end;
procedure TMDIChild.Markall3Click(Sender: TObject);
begin
// select all in history
SynMemoSQLLog.SelectAll;
end;
procedure TMDIChild.ListTablesDblClick(Sender: TObject);
begin
Mainform.actEditTableFields.Execute;
end;
procedure TMDIChild.TimerConnectErrorCloseWindowTimer(Sender: TObject);
begin
// can't connect -> close MDI-Child
TimerConnectErrorCloseWindow.Enabled := false;
Mainform.Showstatus('', 1);
MainForm.ShowStatus( STATUS_MSG_READY );
close;
end;
{**
Column-title clicked -> generate "ORDER BY"
}
procedure TMDIChild.QuickFilterClick(Sender: TObject);
var
filter,value,column : WideString;
menuitem : TMenuItem;
begin
// Set filter for "where..."-clause
value := DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn];
menuitem := (Sender as TMenuItem);
column := mask(DataGrid.Header.Columns[DataGrid.FocusedColumn].Text);
if menuitem = QF1 then
filter := column + ' =' + ' ' + esc( value )
else if menuitem = QF2 then
filter := column + ' !=' + ' ' + esc( value )
else if menuitem = QF3 then
filter := column + ' >' + ' ' + esc( value )
else if menuitem = QF4 then
filter := column + ' <' + ' ' + esc( value )
else if menuitem = QF5 then
filter := column + ' LIKE' + ' ''' + esc( value, true ) + '%'''
else if menuitem = QF6 then
filter := column + ' LIKE' + ' ''%' + esc( value, true ) + ''''
else if menuitem = QF7 then
filter := column + ' LIKE' + ' ''%' + esc( value, true ) + '%'''
else if menuitem = QF8 then
begin
filter := InputBox('Specify filter-value...', column+' = ', 'Value');
if filter = 'Value' then
abort;
filter := column + ' = ''' + filter + '''';
end
else if menuitem = QF9 then
begin
filter := InputBox('Specify filter-value...', column+' != ', 'Value');
if filter = 'Value' then
abort;
filter := column + ' != ''' + filter + '''';
end
else if menuitem = QF10 then
begin
filter := InputBox('Specify filter-value...', column+' > ', 'Value');
if filter = 'Value' then
abort;
filter := column + ' > ''' + filter + '''';
end
else if menuitem = QF11 then
begin
filter := InputBox('Specify filter-value...', column+' < ', 'Value');
if filter = 'Value' then
abort;
filter := column + ' < ''' + filter + '''';
end
else if menuitem = QF12 then
begin
filter := InputBox('Specify filter-value...', column+' LIKE ', 'Value');
if filter = 'Value' then
abort;
filter := column + ' LIKE ''%' + filter + '%''';
end
// Filters with text from clipboard
else if (menuitem = QF13) or (menuitem = QF14) or (menuitem = QF15) or (menuitem = QF16) or (menuitem = QF17) then
begin
filter := menuitem.Caption;
end;
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.SelectAll;
SynmemoFilter.SelText := filter;
SaveFilter(filter);
viewdata(Sender);
end;
function TMDIChild.GetFilter: WideString;
var
SomeFilter: Boolean;
reg: TRegistry;
regCrashIndicName, regFilterName: String;
begin
// Read cached WHERE-clause and set filter
if prefRememberFilters then begin
regFilterName := Utf8Encode(REGPREFIX_WHERECLAUSE + ActiveDatabase + '.' + SelectedTable);
Result := Mainform.GetRegValue(regFilterName, '', FConn.Description );
if Result <> '' then begin
// Check for crash indicator on current table
regCrashIndicName := Utf8Encode(REGPREFIX_CRASH_IN_DATA + ActiveDatabase + '.' + SelectedTable);
if(Mainform.GetRegValue(regCrashIndicName, False, FConn.Description)) then begin
LogSQL('A crash in the previous data loading for this table ('+SelectedTable+') was detected. Filter was automatically reset to avoid the same crash for now.');
Result := '';
reg := TRegistry.Create;
reg.OpenKey( REGPATH + REGKEY_SESSIONS + FConn.Description, true );
// Filter was nuked. Reset crash indicator.
reg.DeleteValue(regFilterName);
reg.CloseKey;
reg.Free;
end;
end;
end else
Result := SynMemoFilter.Text;
SomeFilter := Result <> '';
if SomeFilter then tbtnDataFilter.ImageIndex := 108
else tbtnDataFilter.ImageIndex := 107;
// Ensure filter panel is visible
if SomeFilter then
ToggleFilterPanel(True);
// Hide it if it was auto opened previously
if (not SomeFilter) and pnlFilter.Visible and (not FilterPanelManuallyOpened) then
ToggleFilterPanel;
if SynMemoFilter.Text <> Result then begin
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.SelectAll;
SynMemoFilter.SelText := Result;
end;
SynMemoFilterChange(Self);
end;
procedure TMDIChild.SaveFilter(Clause: WideString = '');
var
regname: String;
begin
// Store whereclause in Registry
if prefRememberFilters then begin
Mainform.regMain.openkey( REGPATH + REGKEY_SESSIONS + FConn.Description, false );
regname := REGPREFIX_WHERECLAUSE + ActiveDatabase + '.' + SelectedTable;
if Clause <> '' then Mainform.regMain.WriteString( regname, Clause )
else if Mainform.regMain.ValueExists( regname ) then Mainform.regMain.DeleteValue( regname );
end
end;
procedure TMDIChild.DropFilter1Click(Sender: TObject);
begin
// Drop Filter
SaveFilter;
viewdata(Sender);
end;
// select all tables
procedure TMDIChild.selectall1Click(Sender: TObject);
begin
ListTables.SelectAll(False);
end;
procedure TMDIChild.popupQueryPopup(Sender: TObject);
begin
// Sets cursor into memo and activates TAction(s) like paste
SynMemoQuery.SetFocus;
end;
procedure TMDIChild.popupResultGridPopup(Sender: TObject);
begin
// data available?
// MainForm.Save2CSV.enabled :=
end;
procedure TMDIChild.controlsKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
Grid : TVirtualStringTree;
CB: TUniClipboard;
begin
// Check for F1-pressed
if Key = VK_F1 then
Mainform.actSQLhelp.Execute
// Simulate Ctrl+A-behaviour of common editors
else if ( Shift = [ssCtrl] ) and ( Key = Ord('A') ) then
begin
if Sender is TCustomEdit then
TCustomEdit(Sender).SelectAll;
end
// Enable copy + paste shortcuts in dbgrids
else if (Sender is TVirtualStringTree) and (not TVirtualStringTree(Sender).IsEditing)
and (Shift = [ssCtrl]) and (Assigned(TVirtualStringTree(Sender).FocusedNode))
then begin
Grid := Sender as TVirtualStringTree;
// TODO: Clipboard.AsText is not Unicode safe!
if Key = Ord('C') then
CopyToClipboard(Grid.Text[Grid.FocusedNode, Grid.FocusedColumn])
else if Key = Ord('V') then begin
CB := TUniClipboard.Create;
Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := CB.AsWideString;
end
else if Key = Ord('X') then begin
CopyToClipboard(Grid.Text[Grid.FocusedNode, Grid.FocusedColumn]);
Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := '';
end;
end;
end;
procedure TMDIChild.Autoupdate1Click(Sender: TObject);
var
seconds : String;
secondsInt : Integer;
begin
// set interval for autorefresh-timer
seconds := IntToStr(TimerHost.interval div 1000);
if inputquery('Auto-refresh processlist','Update list every ... seconds:', seconds) then begin
secondsInt := StrToIntDef(seconds, 0);
if secondsInt > 0 then begin
TimerHost.Interval := secondsInt * 1000;
TimerHost.Enabled := true;
EnableAutoRefresh.Checked := true;
DisableAutoRefresh.Checked := false;
end
else
MessageDLG('Seconds must be between 1 and ' + IntToStr(maxint) + '.', mtError, [mbOK], 0);
end;
end;
procedure TMDIChild.EnableAutoRefreshClick(Sender: TObject);
begin
// enable autorefresh-timer
TimerHost.Enabled := true;
EnableAutoRefresh.Checked := true;
DisableAutoRefresh.Checked := false;
end;
procedure TMDIChild.DisableAutoRefreshClick(Sender: TObject);
begin
// enable autorefresh-timer
TimerHost.Enabled := false;
EnableAutoRefresh.Checked := false;
DisableAutoRefresh.Checked := true;
end;
procedure TMDIChild.SynMemoQueryDragOver(Sender, Source: TObject; X,
Y: Integer; State: TDragState; var Accept: Boolean);
var
src : TControl;
begin
// dragging an object over the query-memo
src := Source as TControl;
// Accepting drag's from DBTree and QueryHelpers
Accept := (src = DBtree) or (src = lboxQueryHelpers);
// set x-position of cursor
SynMemoQuery.CaretX := (x - SynMemoQuery.Gutter.Width) div SynMemoQuery.CharWidth - 1 + SynMemoQuery.LeftChar;
// set y-position of cursor
SynMemoQuery.CaretY := y div SynMemoQuery.LineHeight + SynMemoQuery.TopLine;
if not SynMemoQuery.Focused then
SynMemoQuery.SetFocus;
end;
procedure TMDIChild.SynMemoQueryDragDrop(Sender, Source: TObject; X,
Y: Integer);
var
src : TControl;
Text : WideString;
LoadText : Boolean;
i: Integer;
begin
// dropping a tree node or listbox item into the query-memo
SynMemoQuery.UndoList.AddGroupBreak;
src := Source as TControl;
Text := 'Error: Unspecified source control in drag''n drop operation!';
LoadText := True;
// Check for allowed controls as source has already
// been performed in OnDragOver. So, only do typecasting here.
if src = DBtree then
Text := DBtree.Text[DBtree.GetFirstSelected, 0]
else if (src = lboxQueryHelpers) and (lboxQueryHelpers.ItemIndex > -1) then begin
// Snippets tab
if tabsetQueryHelpers.TabIndex = 3 then begin
QueryLoad( DIRNAME_SNIPPETS + lboxQueryHelpers.Items[lboxQueryHelpers.ItemIndex] + '.sql', False );
LoadText := False;
// All other tabs
end else begin
Text := '';
for i := 0 to lboxQueryHelpers.Items.Count - 1 do begin
if lboxQueryHelpers.Selected[i] then
Text := Text + lboxQueryHelpers.Items[i] + ', ';
end;
Delete(Text, Length(Text)-1, 2);
end;
end;
// Only insert text if no previous action did the job.
// Should be false when dropping a snippet-file here
if LoadText then
SynMemoQuery.SelText := Text;
SynMemoQuery.UndoList.AddGroupBreak;
end;
procedure TMDIChild.SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer;
AFiles: TWideStrings);
var
i : Integer;
begin
// one or more files from explorer or somewhere else was
// dropped onto the query-memo - let's load their contents:
for i:=0 to AFiles.Count-1 do
begin
if fileExists(AFiles[i]) then
begin
QueryLoad( AFiles[i], false );
end;
end;
end;
procedure TMDIChild.popupHostPopup(Sender: TObject);
begin
Kill1.Enabled := (PageControlHost.ActivePage = tabProcessList) and Assigned(ListProcesses.FocusedNode);
menuEditVariable.Enabled := False;
if mysql_version >= 40003 then
menuEditVariable.Enabled := (PageControlHost.ActivePage = tabVariables) and Assigned(ListVariables.FocusedNode)
else
menuEditVariable.Hint := STR_NOTSUPPORTED;
end;
procedure TMDIChild.Saveastextfile1Click(Sender: TObject);
begin
with TSaveDialog.Create(self) do begin
Filter := 'Textfiles (*.txt)|*.txt|All Files (*.*)|*.*';
DefaultExt := 'txt';
FilterIndex := 1;
Options := [ofOverwritePrompt,ofHideReadOnly,ofEnableSizing];
if Execute then begin
Screen.Cursor := crHourglass;
SynMemoSQLLog.Lines.SaveToFile(Filename);
Screen.Cursor := crdefault;
end;
end;
end;
procedure TMDIChild.popupTreeViewPopup(Sender: TObject);
var
L: Cardinal;
begin
// toggle drop-items and remember right-clicked item
if DBtree.GetFirstSelected = nil then
L := 0
else
L := DBtree.GetNodeLevel(DBtree.GetFirstSelected);
Mainform.actCreateTable.Enabled := L in [1,2];
Mainform.actCreateView.Enabled := (L in [1,2]) and (mysql_version >= 50001);
Mainform.actEditTableProperties.Enabled := (L = 2) and ((GetSelectedNodeType = NODETYPE_TABLE) or (GetSelectedNodeType = NODETYPE_CRASHED_TABLE));
Mainform.actEditView.Enabled := (L = 2) and (GetSelectedNodeType = NODETYPE_VIEW);
MainForm.actDropTablesAndViews.Enabled := (L = 2);
end;
procedure TMDIChild.QueryLoad( filename: String; ReplaceContent: Boolean = true );
var
filecontent : WideString;
msgtext : String;
begin
// Ask for action when loading a big file
if FileExists(filename) and (_GetFileSize( filename ) > 5*SIZE_MB) then
begin
msgtext := 'The file you are about to load is bigger than '+FormatByteNumber(5*SIZE_MB, 0)+'.' + CRLF + CRLF +
'Do you want to just run the file to avoid loading it completely into the query-editor ( = memory ) ?' + CRLF + CRLF +
'Press' + CRLF +
' [Yes] to run the file without loading it into the editor' + CRLF +
' [No] to load the file into the query editor' + CRLF +
' [Cancel] to cancel file opening.';
case MessageDlg( msgtext, mtWarning, [mbYes, mbNo, mbCancel], 0 ) of
// Run the file, don't load it into the editor
mrYes:
begin
RunSQLFileWindow( Self, filename );
// Add filename to history menu
if Pos( DIRNAME_SNIPPETS, filename ) = 0 then
Mainform.AddOrRemoveFromQueryLoadHistory( filename, true );
// Don't load into editor
Abort;
end;
// Do nothing here, go ahead and load the file normally into the editor
mrNo:;
// Cancel opening file
mrCancel: Abort;
end;
end;
// Load file and add that to the undo-history of SynEdit.
// Normally we would do a simple SynMemo.Lines.LoadFromFile but
// this would prevent SynEdit from adding this step to the undo-history
// so we have to do it by replacing the SelText property
Screen.Cursor := crHourGlass;
try
filecontent := ReadTextfile(filename);
if Pos( DIRNAME_SNIPPETS, filename ) = 0 then
Mainform.AddOrRemoveFromQueryLoadHistory( filename, true );
Mainform.FillPopupQueryLoad;
PagecontrolMain.ActivePage := tabQuery;
SynCompletionProposal1.Editor.UndoList.AddGroupBreak;
SynMemoQuery.BeginUpdate;
if ReplaceContent then
SynMemoQuery.SelectAll;
SynMemoQuery.SelText := filecontent;
SynMemoQuery.SelStart := SynMemoQuery.SelEnd;
SynMemoQuery.EndUpdate;
except on E:Exception do
// File does not exist, is locked or broken
MessageDlg(E.message, mtError, [mbOK], 0);
end;
Screen.Cursor := crDefault;
end;
procedure TMDIChild.SaveDialogExportDataTypeChange(Sender: TObject);
begin
// Set default file-extension of saved file and options on the dialog to show
with SaveDialogExportData do begin
Case FilterIndex of
1: DefaultExt := 'csv';
2: DefaultExt := 'html';
3: DefaultExt := 'xml';
end;
end;
end;
{**
A cell in a DBGrid is painted. Sets custom background color NULL fields.
}
procedure TMDIChild.popupDataGridPopup(Sender: TObject);
var
y,m,d,h,i,s,ms : Word;
cpText, selectedColumn, value : String;
const
CLPBRD : String = 'CLIPBOARD';
begin
{DONE -oFrancisco -cData-browsing:Bugfix: [1650528] Access violation with F5}
if Assigned(DataGrid.FocusedNode) and (DataGrid.FocusedColumn > -1) then
begin
DataInsertDateTime.Enabled := FDataGridResult.Columns[DataGrid.FocusedColumn].IsDate;
if DataInsertDateTime.Enabled then
begin
decodedate(now, y, m, d);
decodetime(now, h, i, s, ms);
DataDateTime.Caption := Format('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', [y,m,d,h,i,s]);
DataDate.Caption := Format('%.4d-%.2d-%.2d', [y,m,d]);
DataTime.Caption := Format('%.2d:%.2d:%.2d', [h,i,s]);
DataTimestamp.caption := Format('%.4d%.2d%.2d%.2d%.2d%.2d', [y,m,d,h,i,s]);
DataYear.Caption := Format('%.4d', [y]);
end;
// Manipulate the Quick-filter menuitems
selectedColumn := mask(DataGrid.Header.Columns[DataGrid.FocusedColumn].Text);
// 1. block: include selected columnname and value from datagrid in caption
value := sstr(DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn], 100);
QF1.Caption := selectedColumn + ' = ' + esc( value );
QF2.Caption := selectedColumn + ' != ' + esc( value );
QF3.Caption := selectedColumn + ' > ' + esc( value );
QF4.Caption := selectedColumn + ' < ' + esc( value );
QF5.Caption := selectedColumn + ' LIKE ''' + esc( value, true ) + '%''';
QF6.Caption := selectedColumn + ' LIKE ''%' + esc( value, true ) + '''';
QF7.Caption := selectedColumn + ' LIKE ''%' + esc( value, true ) + '%''';
// 2. block: include only selected columnname in caption
QF8.Caption := selectedColumn + ' = "..."';
QF9.Caption := selectedColumn + ' != "..."';
QF10.Caption := selectedColumn + ' > "..."';
QF11.Caption := selectedColumn + ' < "..."';
QF12.Caption := selectedColumn + ' LIKE "%...%"';
// 3. block: include selected columnname and clipboard-content in caption for one-click-filtering
cpText := Clipboard.AsText;
if Length(cpText) < 100 then
begin
QF13.Enabled := true; QF13.Caption := selectedColumn + ' = ' + esc( cpText );
QF14.Enabled := true; QF14.Caption := selectedColumn + ' != ' + esc( cpText );
QF15.Enabled := true; QF15.Caption := selectedColumn + ' > ' + esc( cpText );
QF16.Enabled := true; QF16.Caption := selectedColumn + ' < ' + esc( cpText );
QF17.Enabled := true; QF17.Caption := selectedColumn + ' LIKE ''%' + esc( cpText, true ) + '%''';
end
else
begin
QF13.Enabled := false; QF13.Caption := selectedColumn + ' = ' + CLPBRD;
QF14.Enabled := false; QF14.Caption := selectedColumn + ' != ' + CLPBRD;
QF15.Enabled := false; QF15.Caption := selectedColumn + ' > ' + CLPBRD;
QF16.Enabled := false; QF16.Caption := selectedColumn + ' < ' + CLPBRD;
QF17.Enabled := false; QF17.Caption := selectedColumn + ' LIKE %' + CLPBRD + '%';
end;
end;
end;
procedure TMDIChild.InsertDate(Sender: TObject);
var d : String;
begin
// Insert date/time-value into table
d := (sender as TMenuItem).Caption;
delete(d, Pos('&', d), 1);
DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn] := d;
end;
procedure TMDIChild.ExecUseQuery(db: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false);
begin
ExecUpdateQuery('USE ' + mask(db), HandleErrors, DisplayErrors);
FConn.MysqlParams.Database := db;
end;
{***
Execute a query without returning a resultset
The currently active connection is used
@param String The single SQL-query to be executed on the server
}
function TMDIChild.ExecUpdateQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false): Int64;
var
MysqlQuery : TMysqlQuery;
ds: TDataSet;
begin
Result := -1; // Silence compiler warning.
MysqlQuery := nil;
try
try
// Start query execution
MysqlQuery := RunThreadedQuery(sql, false);
Result := FMysqlConn.Connection.GetAffectedRowsFromLastPost;
// Inspect query result code and log / notify user on failure
if MysqlQuery.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then
begin
raise Exception.Create(MysqlQuery.Comment);
end;
except
on E: Exception do begin
LogSQL( E.Message, True );
if DisplayErrors then MessageDlg( E.Message, mtError, [mbOK], 0 );
// Recreate exception, since we free it below the caller
// won't know what happened otherwise.
if not HandleErrors then raise THandledSQLError.Create(MysqlQuery.Comment);
Result := -1;
end;
end;
finally
// Cleanup the MysqlQuery object, we won't need it anymore
if MysqlQuery <> nil then begin
if MysqlQuery.MysqlDataset <> nil then
MysqlQuery.MysqlDataset.Close;
ds := MysqlQuery.MysqlDataset;
FreeAndNil(ds);
end;
FreeAndNil (MysqlQuery);
end;
end;
{***
Execute a query which may return a resultset. The caller is responsible for
freeing the MysqlQuery object and its Dataset member, only on returnvalue True.
The currently active connection is used
@param String The single SQL-query to be executed on the server
@return TMysqlQuery Containing the dataset and info data availability
}
function TMDIChild.ExecSelectQuery(sql: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet;
var
res: TMysqlQuery;
begin
res := nil;
result := nil;
try
try
// Start query execution
res := RunThreadedQuery(sql, ForceDialog);
result := res.MysqlDataset;
// Inspect query result code and log / notify user on failure
if res.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then
begin
raise Exception.Create(res.Comment);
end;
except
on E: Exception do begin
LogSQL( E.Message, True );
if DisplayErrors then MessageDlg( E.Message, mtError, [mbOK], 0 );
if not HandleErrors then raise THandledSQLError.Create(E.Message);
Result := nil;
end;
end;
finally
FreeAndNil(res);
end;
end;
{***
Executes a query.
}
function TMDIChild.GetResults( SQLQuery: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false; ForceDialog: Boolean = false): TDataSet;
begin
result := ExecSelectQuery(SQLQuery, HandleErrors, DisplayErrors, ForceDialog);
end;
{***
Execute a query and return String from column x
}
function TMDIChild.GetVar( SQLQuery: WideString; x: Integer = 0; HandleErrors: Boolean = false; DisplayErrors: Boolean = false) : WideString;
var
ds: TDataSet;
begin
ds := GetResults( SQLQuery, HandleErrors, DisplayErrors );
if ds = nil then exit;
Result := ds.Fields[x].AsWideString;
ds.Close;
FreeAndNil(ds);
end;
function TMDIChild.GetNamedVar( SQLQuery: WideString; x: WideString; HandleErrors: Boolean = false; DisplayErrors: Boolean = false) : WideString;
var
ds: TDataSet;
begin
ds := GetResults( SQLQuery, HandleErrors, DisplayErrors );
if ds = nil then exit;
Result := ds.Fields.FieldByName(x).AsWideString;
ds.Close;
FreeAndNil(ds);
end;
{***
This returns the GridResult object that is currently visible to the user,
depending on with tabsheet is active.
@return PGridResult if data/query tab is active, nil otherwise.
}
function TMDIChild.GetVisualDataset: PGridResult;
begin
Result := nil;
case PageControlMain.ActivePageIndex of
3: Result := @FDataGridResult;
4: Result := @FQueryGridResult;
end;
end;
{***
Execute a query and return column x as Stringlist
@param String SQL query String
@param Integer 0-based column index in the resultset to return
@return TStringList
}
function TMDIChild.GetCol( SQLQuery: WideString; x: Integer = 0; HandleErrors: Boolean = false; DisplayErrors: Boolean = false ) : WideStrings.TWideStringList;
var
i: Integer;
ds: TDataSet;
begin
ds := GetResults( SQLQuery, HandleErrors, DisplayErrors);
Result := WideStrings.TWideStringList.Create;
if ds = nil then exit;
for i := 0 to ds.RecordCount - 1 do
begin
Result.Add( ds.Fields[x].AsWideString );
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
end;
{***
Event procedure handler for the ZSQLMonitor1 object
}
procedure TMDIChild.ZSQLMonitor1LogTrace(Sender: TObject;
Event: TZLoggingEvent);
begin
LogSQL( Event.Message, (Event.Category <> lcExecute) );
end;
procedure TMDIChild.RunAsyncPost(ds: TDeferDataSet);
var
res: TMysqlQuery;
begin
FQueryRunning := true;
try
try
CheckConnection;
except
on E: Exception do begin
raise Exception.Create('Failed to reconnect, giving up. (' + E.Message + ')');
end;
end;
FProgressForm := TFrmQueryProgress.Create(Self);
debug('RunThreadedQuery(): Launching asynchronous query.');
res := ExecPostAsync(FConn,FProgressForm.Handle,ds);
WaitForQueryCompletion(FProgressForm, res, false);
if res.Result in [MQR_CONNECT_FAIL,MQR_QUERY_FAIL] then
begin
raise Exception.Create(res.Comment);
end;
finally
FQueryRunning := false;
end;
end;
{***
Run a query in a separate thread of execution on the current connection.
}
function TMDIChild.RunThreadedQuery(AQuery: WideString; ForceDialog: Boolean): TMysqlQuery;
begin
Result := nil;
if (Copy(AQuery, 1, 3) <> 'USE') then EnsureDatabase;
// Indicate a querythread is active (only one thread allow at this moment)
FQueryRunning := true;
try
// Check if the connection of the current window is still alive
// Otherwise reconnect
try
CheckConnection;
except
on E: Exception do begin
// Ensure auto-updating processlist is disabled, see bug #1865305
DisableAutoRefreshClick(self);
Screen.Cursor := crDefault;
raise Exception.Create('Failed to reconnect, giving up. (' + E.Message + ')');
end;
end;
// Create instance of the progress form (but don't show it yet)
FProgressForm := TFrmQueryProgress.Create(Self);
{ Launch a thread of execution that passes the query to the server
The progressform serves as receiver of the status
messages (WM_MYSQL_THREAD_NOTIFY) of the thread:
* After the thread starts it notifies the progressform (MQE_INITED)
(which calls ShowModal on itself)
* Waits for a completion message from the thread (MQE_FINISHED) to remove itself
* Set FQueryRunning to false
}
debug('RunThreadedQuery(): Launching asynchronous query.');
Result := ExecMysqlStatementAsync (AQuery,FConn,FProgressForm.Handle,RunAsyncPost);
{ Repeatedly check if the query has finished by inspecting FQueryRunning
Allow repainting of user interface
}
WaitForQueryCompletion(FProgressForm, Result, ForceDialog);
finally
FQueryRunning := false;
end;
// Hack: Un-prevent dynamic loading of records in the context of the wait form's message loop.
if not DataGrid.Visible then DataGrid.Visible := True;
end;
// Searchbox unfocused
function TMDIChild.mask(str: WideString) : WideString;
begin
result := maskSql(mysql_version, str);
end;
procedure TMDIChild.CheckConnection;
begin
if not FMysqlConn.IsAlive then begin
LogSQL('Connection failure detected. Trying to reconnect.', true);
TimerConnected.Enabled := false;
TimerConnectedTimer(self);
TimerHostUptime.Enabled := false;
TimerHostUptimeTimer(self);
// 1) CheckConnection is always called from
// within an FQueryRunning-enabled block.
// 2) PerformConnect (see below) will make calls
// that open an FQueryRunning block, causing an
// error message.
//
// Therefore, flick the state of the running
// flag before running PerformConnect().
FQueryRunning := false;
try
FMysqlConn.Connection.Reconnect;
PerformConnect;
finally
FQueryRunning := true;
end;
end;
end;
function TMDIChild.GetActiveGrid: TVirtualStringTree;
begin
Result := nil;
if PageControlMain.ActivePage = tabData then Result := DataGrid
else if PageControlMain.ActivePage = tabQuery then Result := QueryGrid;
end;
function TMDIChild.GetActiveData: PGridResult;
begin
Result := nil;
if PageControlMain.ActivePage = tabData then Result := @FDataGridResult
else if PageControlMain.ActivePage = tabQuery then Result := @FQueryGridResult;
end;
function TMDIChild.GetActiveDatabase: WideString;
var
s: PVirtualNode;
begin
// Find currently selected database node in database tree,
// or the parent if a table is currently selected.
s := DBtree.GetFirstSelected;
if not Assigned(s) then Result := ''
else case DBtree.GetNodeLevel(s) of
2: Result := Databases[s.Parent.Index];
1: Result := Databases[s.Index];
else Result := '';
end;
end;
function TMDIChild.GetSelectedTable: WideString;
begin
if DBtree.GetFirstSelected = nil then Result := ''
else case DBtree.GetNodeLevel(DBtree.GetFirstSelected) of
2: Result := DBtree.Text[DBtree.GetFirstSelected, 0];
else Result := '';
end;
end;
function TMDIChild.GetNodeType(Node: PVirtualNode): Byte;
var
ds: TDataset;
begin
Result := NODETYPE_DEFAULT;
if Assigned(Node) then case DBtree.GetNodeLevel(Node) of
1: Result := NODETYPE_DB;
2: begin
ds := FetchDbTableList(DBTree.Text[Node.Parent, 0]);
ds.RecNo := Node.Index+1;
Result := GetDBObjectType(ds.Fields);
end;
end;
end;
function TMDIChild.GetSelectedNodeType: Byte;
begin
Result := GetNodeType(DBtree.GetFirstSelected);
end;
procedure TMDIChild.SetSelectedTable(table: WideString);
var
i: integer;
dbnode, tnode, snode: PVirtualNode;
begin
// Detect db node
case DBtree.GetNodeLevel( DBtree.GetFirstSelected ) of
1: dbnode := DBtree.GetFirstSelected;
2: dbnode := DBtree.GetFirstSelected.Parent;
else raise Exception.Create('No selection in tree, could not determine active db.');
end;
snode := nil;
// 1st search, case sensitive for lower-case-tablenames=0 servers
tnode := DBtree.GetFirstChild(dbnode);
for i := 0 to dbnode.ChildCount - 1 do begin
// Select table node if it has the wanted caption
if DBtree.Text[tnode, 0] = table then begin
snode := tnode;
break;
end;
tnode := DBtree.GetNext(tnode);
end;
// 2nd search, case insensitive now
if not Assigned(snode) then begin
tnode := DBtree.GetFirstChild(dbnode);
for i := 0 to dbnode.ChildCount - 1 do begin
// Select table node if it has the wanted caption
if AnsiCompareText(DBtree.Text[tnode, 0], table) = 0 then begin
snode := tnode;
break;
end;
tnode := DBtree.GetNext(tnode);
end;
end;
if Assigned(snode) then begin
// Ensure table node will be visible
DBtree.Expanded[dbnode] := True;
DBtree.Selected[snode] := True;
exit;
end;
raise Exception.Create('Table node ' + table + ' not found in tree.');
end;
procedure TMDIChild.SetSelectedDatabase(db: WideString);
var
n: PVirtualNode;
begin
n := FindDBNode(db);
if Assigned(n) then begin
DBtree.Selected[n] := true;
DBtree.FocusedNode := n;
end else
raise Exception.Create('Database node ' + db + ' not found in tree.');
end;
{**
Column selection for datagrid
}
procedure TMDIChild.btnDataClick(Sender: TObject);
var
btn : TToolButton;
frm : TForm;
begin
btn := (Sender as TToolButton);
if (btn = tbtnDataColumns) or (btn = tbtnDataSorting) then begin
// Create desired form for SELECT and ORDER buttons
btn.Down := not btn.Down;
if not btn.Down then Exit;
if btn = tbtnDataColumns then
frm := TColumnSelectionForm.Create(self)
else if btn = tbtnDataSorting then
frm := TDataSortingForm.Create(self)
else
frm := TForm.Create(self); // Dummy fallback, should never get created
// Position new form relative to btn's position
frm.Top := btn.ClientOrigin.Y + btn.Height;
frm.Left := btn.ClientOrigin.X + btn.Width - frm.Width;
// Display form
frm.Show;
end else if btn = tbtnDataFilter then begin
// Unhide inline filter panel
ToggleFilterPanel;
FilterPanelManuallyOpened := pnlFilter.Visible;
end;
end;
{**
Tabset right to query-memo was clicked
}
procedure TMDIChild.tabsetQueryHelpersChange(Sender: TObject; NewTab: Integer;
var AllowChange: Boolean);
var
i : Integer;
SnippetsAccessible : Boolean;
Files: TStringList;
begin
lboxQueryHelpers.Items.BeginUpdate;
lboxQueryHelpers.Items.Clear;
// By default sorted alpabetically
lboxQueryHelpers.Sorted := True;
// By default disable all items in popupmenu, enable them when needed
menuInsertSnippetAtCursor.Enabled := False;
menuLoadSnippet.Enabled := False;
menuDeleteSnippet.Enabled := False;
menuExplore.Enabled := False;
menuHelp.Enabled := False;
lboxQueryHelpers.MultiSelect := True;
case NewTab of
0: // Cols
begin
// Keep native order of columns
lboxQueryHelpers.Sorted := False;
if SelectedTable <> '' then for i := 0 to High(VTRowDataListColumns) do
begin
lboxQueryHelpers.Items.Add(VTRowDataListColumns[i].Captions[0]);
end;
end;
1: // SQL functions
begin
// State of items in popupmenu
menuHelp.Enabled := True;
for i := 0 to Length(MySQLFunctions) - 1 do
begin
// Don't display unsupported functions here
if MySqlFunctions[i].Version > mysql_version then
continue;
lboxQueryHelpers.Items.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration );
end;
end;
2: // SQL keywords
begin
// State of items in popupmenu
menuHelp.Enabled := True;
for i := 0 to MYSQL_KEYWORDS.Count - 1 do
lboxQueryHelpers.Items.Add(MYSQL_KEYWORDS[i]);
end;
3: // SQL Snippets
begin
lboxQueryHelpers.MultiSelect := False;
Files := getFilesFromDir( DIRNAME_SNIPPETS, '*.sql', true );
for i := 0 to Files.Count - 1 do
lboxQueryHelpers.Items.Add(Files[i]);
Files.Free;
// State of items in popupmenu
SnippetsAccessible := lboxQueryHelpers.Items.Count > 0;
menuDeleteSnippet.Enabled := SnippetsAccessible;
menuInsertSnippetAtCursor.Enabled := SnippetsAccessible;
menuLoadSnippet.Enabled := SnippetsAccessible;
menuExplore.Enabled := True;
end;
end;
// Restore last selected item in tab
if (Length(QueryHelpersSelectedItems[NewTab]) > 0)
and (Length(QueryHelpersSelectedItems[NewTab]) <= lboxQueryHelpers.Count) then
begin
for i := 0 to Length(QueryHelpersSelectedItems[NewTab]) - 1 do begin
lboxQueryHelpers.Selected[QueryHelpersSelectedItems[NewTab][i]] := True;
end;
end;
lboxQueryHelpers.Items.EndUpdate;
end;
{**
Insert string from listbox with query helpers into SQL
memo at doubleclick
}
procedure TMDIChild.lboxQueryHelpersDblClick(Sender: TObject);
var
text: WideString;
i: Integer;
begin
for i := 0 to lboxQueryHelpers.Items.Count - 1 do begin
if lboxQueryHelpers.Selected[i] then
text := text + lboxQueryHelpers.Items[i] + ', ';
end;
Delete(text, Length(text)-1, 2);
case tabsetQueryHelpers.TabIndex of
3: // Load snippet file <20>nto query-memo
QueryLoad( DIRNAME_SNIPPETS + lboxQueryHelpers.Items[lboxQueryHelpers.ItemIndex] + '.sql', False );
else // For all other tabs just insert the item from the list
SynMemoQuery.SelText := text;
end;
SynMemoQuery.SetFocus;
end;
{**
Remember last used items in query helper tabs
}
procedure TMDIChild.lboxQueryHelpersClick(Sender: TObject);
var
i, s, idx: Integer;
begin
s := tabsetQueryHelpers.TabIndex;
SetLength(QueryHelpersSelectedItems[s], 0);
for i := 0 to lboxQueryHelpers.Count - 1 do if lboxQueryHelpers.Selected[i] then begin
idx := Length(QueryHelpersSelectedItems[s]);
SetLength(QueryHelpersSelectedItems[s], idx+1);
QueryHelpersSelectedItems[s][idx] := i;
end;
end;
{**
Insert function name from popupmenu to query memo
}
procedure TMDIChild.insertFunction(Sender: TObject);
var
f : String;
sm : TSynMemo;
begin
// Detect which memo is focused
if SynMemoFilter.Focused then
sm := SynMemoFilter
else
sm := SynMemoQuery;
// Restore function name from array
f := MySQLFunctions[TControl(Sender).tag].Name
+ MySQLFunctions[TControl(Sender).tag].Declaration;
sm.UndoList.AddGroupBreak;
sm.SelText := f;
sm.UndoList.AddGroupBreak;
if not SynMemoFilter.Focused then
ValidateQueryControls;
end;
{**
Activate inline-item-editor of listColumns
}
procedure TMDIChild.menuRenameColumnClick(Sender: TObject);
begin
ListColumns.EditNode(ListColumns.FocusedNode, 0);
end;
{**
Rename a column name from within listColumns
}
procedure TMDIChild.ListColumnsNewText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; NewText: WideString);
var
def : TDataSet;
sql_update, sql_null, sql_default, sql_extra, sql_comment, DefaultValue : WideString;
NodeData : PVTreeData;
begin
// Try to rename, on any error abort and don't rename ListItem
try
ensureValidIdentifier( NewText );
// Fetch data from listitem
NodeData := ListColumns.GetNodeData(Node);
// Fetch column definition
def := GetResults( 'SHOW FULL COLUMNS FROM ' + mask(SelectedTable) + ' LIKE ' + esc(NodeData.Captions[0]), False, False );
// Check NOT NULL
sql_null := 'NULL ';
if UpperCase(def.FieldByName('Null').AsString) = 'NO' then
sql_null := 'NOT NULL ';
// Check default value, take care of non-literals / functions
sql_default := '';
DefaultValue := def.FieldByName('Default').AsWideString;
if DefaultValue <> '' then
begin
if (UpperCase(def.FieldByName('Type').AsString) <> 'TIMESTAMP') and (DefaultValue <> 'CURRENT_TIMESTAMP') then
DefaultValue := esc(DefaultValue);
sql_default := 'DEFAULT ' + DefaultValue + ' ';
end;
// Check extra options (auto_increment)
sql_extra := '';
if def.FieldByName('Extra').AsString <> '' then
sql_extra := ' '+WideUpperCase(def.FieldByName('Extra').AsString);
// Comment
sql_comment := '';
if def.FieldByName('Comment').AsWideString <> '' then
sql_comment := ' COMMENT '+esc(def.FieldByName('Comment').AsWideString);
// Concat column definition
sql_update := 'ALTER TABLE ' + mask(SelectedTable) +
' CHANGE ' + mask(NodeData.Captions[0]) +
' ' + mask(NewText) + ' ' +
def.FieldByName('Type').AsString + ' ' +
sql_null +
sql_default +
sql_extra +
sql_comment;
// Cleanup
def.Close;
FreeAndNil(def);
// Fire ALTER query
ExecUpdateQuery( sql_update, False, False );
FSelectedTableColumns := nil;
FSelectedTableKeys := nil;
// Update listitem
NodeData.Captions[0] := NewText;
except
On E : Exception do
begin
MessageDlg( E.Message, mtError, [mbOK], 0 );
end;
end;
end;
{**
Delete a snippet file
}
procedure TMDIChild.menuDeleteSnippetClick(Sender: TObject);
var
snippetfile : String;
mayChange : Boolean;
begin
// Don't do anything if no item was selected
if lboxQueryHelpers.ItemIndex = -1 then
abort;
snippetfile := DIRNAME_SNIPPETS + lboxQueryHelpers.Items[ lboxQueryHelpers.ItemIndex ] + '.sql';
if MessageDlg( 'Delete snippet file? ' + CRLF + snippetfile, mtConfirmation, [mbOk, mbCancel], 0) = mrOk then
begin
Screen.Cursor := crHourGlass;
if DeleteFile( snippetfile ) then
begin
// Refresh list with snippets
mayChange := True; // Unused; satisfies callee parameter collection which is probably dictated by tabset.
tabsetQueryHelpersChange( Sender, tabsetQueryHelpers.TabIndex, mayChange );
Mainform.FillPopupQueryLoad;
end
else
begin
Screen.Cursor := crDefault;
MessageDlg( 'Failed deleting ' + snippetfile, mtError, [mbOK], 0 );
end;
Screen.Cursor := crDefault;
end;
end;
{**
Load snippet at cursor
}
procedure TMDIChild.menuInsertSnippetAtCursorClick(Sender: TObject);
begin
QueryLoad( DIRNAME_SNIPPETS + lboxQueryHelpers.Items[lboxQueryHelpers.ItemIndex] + '.sql', False );
end;
{**
Load snippet and replace content
}
procedure TMDIChild.menuLoadSnippetClick(Sender: TObject);
begin
QueryLoad( DIRNAME_SNIPPETS + lboxQueryHelpers.Items[lboxQueryHelpers.ItemIndex] + '.sql', True );
end;
{**
Open snippets-directory in Explorer
}
procedure TMDIChild.menuExploreClick(Sender: TObject);
begin
// Normally the snippets folder is created at installation. But it sure
// can be the case that it has been deleted or that the application was
// not installed properly. Ask if we should create the folder now.
if DirectoryExists( DIRNAME_SNIPPETS ) then
ShellExec( '', DIRNAME_SNIPPETS )
else
if MessageDlg( 'Snippets folder does not exist: ' + DIRNAME_SNIPPETS + CRLF + CRLF + 'This folder is normally created when you install '+appname+'.' + CRLF + CRLF + 'Shall it be created now?',
mtWarning, [mbYes, mbNo], 0 ) = mrYes then
try
Screen.Cursor := crHourglass;
ForceDirectories( DIRNAME_SNIPPETS );
finally
Screen.Cursor := crDefault;
end;
end;
{**
Tell a VirtualStringTree the mem size to allocate per node
}
procedure TMDIChild.vstGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(TVTreeData);
end;
{**
Various lists initialize their nodes by calling the following procedure
once per node
}
procedure TMDIChild.vstInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
NodeData : PVTreeData;
a : TVTreeDataArray;
begin
// Get the pointer to the node data
NodeData := Sender.GetNodeData(Node);
// Fetch data array
a := GetVTreeDataArray( Sender )^;
// Bind data to node
NodeData.Captions := a[Node.Index].Captions;
NodeData.ImageIndex := a[Node.Index].ImageIndex;
NodeData.NodeType := a[Node.Index].NodeType;
end;
{**
Free data of a node
}
procedure TMDIChild.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
b : PVTreeDataArray;
begin
// Detect which global array should be processed
b := GetVTreeDataArray( Sender );
// TODO: If you optimize 'b' out of the code, the compiler will
// sometimes generate code that causes a new array here instead of
// a reference to the global array, thus breaking SetLength. Find
// out why...
//TestVTreeDataArray(b);
if (Low(b^) < 0) or (High(b^) < 0) then raise Exception.Create('Internal error: unsupported array bounds.');
if Node.Index + 1 < Cardinal(High(b^)) then
begin
// Delete node somewhere in the middle of the array
// Taken from http://delphi.about.com/cs/adptips2004/a/bltip0204_2.htm
System.Move(
b^[Node.Index + 1],
b^[Node.Index],
(Cardinal(Length(b^)) - (Node.Index - Cardinal(Low(b^))) - 1) * SizeOf(TVTreeData)
);
end;
SetLength(b^, Length(b^) - 1);
end;
{**
A node in a VirtualStringTree gets visible and asks which text it shall display
}
procedure TMDIChild.vstGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText:
WideString);
var
NodeData : PVTreeData;
i : Integer;
begin
// Get pointer to node which gets displayed
NodeData := Sender.GetNodeData(Node);
// Column is -1 if no column headers are defined
if Column = -1 then
i := 0
else
i := Column;
// Avoid AV, don't exceed Captions content
if NodeData.Captions.Count > i then
CellText := NodeData.Captions[i]
else
CellText := '';
end;
{**
A node in a VirtualStringTree gets visible and asks which icon it shall display
}
procedure TMDIChild.vstGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
var
NodeData : PVTreeData;
begin
// Display icon only for leftmost cell (0) or for tree nodes (-1)
if Column > 0 then
exit;
// Get pointer to node which gets displayed
NodeData := Sender.GetNodeData(Node);
ImageIndex := NodeData.ImageIndex;
end;
{**
A column header of a VirtualStringTree was clicked:
Toggle the sort direction
}
procedure TMDIChild.vstHeaderClick(Sender: TVTHeader; Column:
TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
// Don't call sorting procedure on right click
// Some list-headers have a contextmenu which should popup then.
if Button = mbRight then
Exit;
if Sender.SortColumn <> Column then
Sender.SortColumn := Column
else if Sender.SortDirection = sdAscending then
Sender.SortDirection := sdDescending
else
Sender.SortDirection := sdAscending;
Sender.Treeview.SortTree( Column, Sender.SortDirection );
end;
{**
Sorting a column of a VirtualTree by comparing two cells
}
procedure TMDIChild.vstCompareNodes(Sender: TBaseVirtualTree; Node1,
Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
NodeData1, NodeData2 : PVTreeData;
CellText1, CellText2 : String;
Number1, Number2 : Extended;
begin
NodeData1 := Sender.GetNodeData(Node1);
NodeData2 := Sender.GetNodeData(Node2);
// If captions-item from either nodes is not set, assume empty string
if NodeData1.Captions.Count >= Column then
CellText1 := NodeData1.Captions[Column]
else
CellText1 := '';
if NodeData2.Captions.Count >= Column then
CellText2 := NodeData2.Captions[Column]
else
CellText2 := '';
// Map value "0" to "N/A" strings
if CellText1 = '' then
CellText1 := '0';
if CellText2 = '' then
CellText2 := '0';
// Apply different comparisons for numbers and text
if StrToIntDef( copy(CellText1,0,1), -1 ) <> -1 then
begin
// Assuming numeric values
Number1 := MakeFloat( CellText1 );
Number2 := MakeFloat( CellText2 );
if Number1 > Number2 then
Result := 1
else if Number1 = Number2 then
Result := 0
else if Number1 < Number2 then
Result := -1;
end
else begin
// Compare Strings
Result := AnsiCompareText( CellText1, CellText2 );
end;
end;
{**
VirtualTree gets painted. Adjust background color of sorted column.
}
procedure TMDIChild.vstBeforePaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas);
var
i : Integer;
h : TVTHeader;
begin
h := TVirtualStringTree(Sender).Header;
for i := 0 to h.Columns.Count - 1 do
begin
if h.SortColumn = i then
case h.SortDirection of
sdAscending: h.Columns[i].Color := COLOR_SORTCOLUMN_ASC;
sdDescending: h.Columns[i].Color := COLOR_SORTCOLUMN_DESC;
end else
h.Columns[i].Color := clWindow;
end;
end;
{**
Return the data array which belongs to a VirtualTree component
}
function TMDIChild.GetVTreeDataArray( VT: TBaseVirtualTree ): PVTreeDataArray;
begin
if VT = ListVariables then
Result := @VTRowDataListVariables
else if VT = ListStatus then
Result := @VTRowDataListStatus
else if VT = ListCommandStats then
Result := @VTRowDataListCommandStats
else if VT = ListProcesses then
Result := @VTRowDataListProcesses
else if VT = ListTables then
Result := @VTRowDataListTables
else if VT = ListColumns then
Result := @VTRowDataListColumns
else begin
raise Exception.Create( VT.ClassName + ' "' + VT.Name + '" doesn''t have an assigned array with data.' );
end;
end;
{**
Internal: Test quality of code/compiler.
}
procedure TMDIChild.TestVTreeDataArray( P: PVTreeDataArray );
begin
if P = @VTRowDataListVariables then Exit;
if P = @VTRowDataListStatus then Exit;
if P = @VTRowDataListCommandStats then Exit;
if P = @VTRowDataListProcesses then Exit;
if P = @VTRowDataListTables then Exit;
if P = @VTRowDataListColumns then Exit;
raise Exception.Create('Assertion failed: Invalid global VT array.');
end;
{**
Click on popupDBGridHeader
}
procedure TMDIChild.MenuTablelistColumnsClick(Sender: TObject);
var
menuitem : TMenuItem;
VisibleColumns : WideStrings.TWideStringList;
i : Integer;
begin
VisibleColumns := WideStrings.TWideStringList.Create;
menuitem := TMenuItem( Sender );
menuitem.Checked := not menuitem.Checked;
for i := 0 to ListTables.Header.Columns.Count - 1 do
begin
menuitem := popupDbGridHeader.Items[i];
if menuitem.Checked then
VisibleColumns.Add(IntToStr(i));
end;
SetVisibleListColumns( ListTables, VisibleColumns );
end;
{**
Save setup of a VirtualStringTree to registry
}
procedure TMDIChild.SaveListSetup( List: TVirtualStringTree );
var
i : Byte;
ColWidths, ColsVisible, ColPos : String;
reg : TRegistry;
begin
reg := TRegistry.Create;
reg.OpenKey( REGPATH, true );
ColWidths := '';
ColsVisible := '';
ColPos := '';
for i := 0 to List.Header.Columns.Count - 1 do
begin
// Column widths
if ColWidths <> '' then
ColWidths := ColWidths + ',';
ColWidths := ColWidths + IntToStr(List.Header.Columns[i].Width);
// Column visibility
if coVisible in List.Header.Columns[i].Options then
begin
if ColsVisible <> '' then
ColsVisible := ColsVisible + ',';
ColsVisible := ColsVisible + IntToStr(i);
end;
// Column position
if ColPos <> '' then
ColPos := ColPos + ',';
ColPos := ColPos + IntToStr(List.Header.Columns[i].Position);
end;
reg.WriteString( REGPREFIX_COLWIDTHS + List.Name, ColWidths );
reg.WriteString( REGPREFIX_COLSVISIBLE + List.Name, ColsVisible );
reg.WriteString( REGPREFIX_COLPOS + List.Name, ColPos );
FreeAndNil(reg);
end;
{**
Restore setup of VirtualStringTree from registry
}
procedure TMDIChild.RestoreListSetup( List: TVirtualStringTree );
var
i : Byte;
colwidth, colpos : Integer;
Value : WideString;
ValueList : WideStrings.TWideStringList;
begin
ValueList := WideStrings.TWideStringList.Create;
// Column widths
Value := Mainform.GetRegValue(REGPREFIX_COLWIDTHS + List.Name, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
for i := 0 to ValueList.Count - 1 do
begin
colwidth := MakeInt(ValueList[i]);
// Check if column number exists and width is at least 1 pixel
if (List.Header.Columns.Count > i) and (colwidth > 0) then
List.Header.Columns[i].Width := colwidth;
end;
end;
// Column visibility
Value := Mainform.GetRegValue(REGPREFIX_COLSVISIBLE + List.Name, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
SetVisibleListColumns( List, ValueList );
end;
// Column position
Value := Mainform.GetRegValue(REGPREFIX_COLPOS + List.Name, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
for i := 0 to ValueList.Count - 1 do
begin
colpos := MakeInt(ValueList[i]);
// Check if column number exists
if List.Header.Columns.Count > i then
List.Header.Columns[i].Position := colpos;
end;
end;
ValueList.Free;
end;
{**
(Un)hide columns in a VirtualStringTree.
}
procedure TMDIChild.SetVisibleListColumns( List: TVirtualStringTree; Columns: WideStrings.TWideStringList );
var
i : Integer;
begin
for i := 0 to List.Header.Columns.Count - 1 do
begin
// Only ListTables' column visibility is currently customizable
// so, make sure to unhide the newer "Comment" column in ListColumns for some users
if (Columns.IndexOf( IntToStr(i) ) > -1) or (List <> ListTables) then
List.Header.Columns[i].Options := List.Header.Columns[i].Options + [coVisible]
else
List.Header.Columns[i].Options := List.Header.Columns[i].Options - [coVisible];
end;
end;
{**
Start writing logfile.
Called either in FormShow or after closing preferences dialog
}
procedure TMDIChild.ActivateFileLogging;
var
LogfilePattern : String;
i : Integer;
begin
// Ensure directory exists
ForceDirectories( DirnameSessionLogs );
// Determine free filename if it's emtpy yet
if FileNameSessionLog = '' then
begin
LogfilePattern := '%s %.6u.log';
i := 1;
FileNameSessionLog := DirnameSessionLogs + goodfilename(Format(LogfilePattern, [FConn.Description, i]));
while FileExists( FileNameSessionLog ) do
begin
inc(i);
FileNameSessionLog := DirnameSessionLogs + goodfilename(Format(LogfilePattern, [FConn.Description, i]));
end;
end;
// Be sure file is closed before we (re-)open it
DeactivateFileLogging;
// Create file handle for writing
AssignFile( FileHandleSessionLog, FileNameSessionLog );
{$I-} // Supress errors
if FileExists(FileNameSessionLog) then
Append(FileHandleSessionLog)
else
Rewrite(FileHandleSessionLog);
{$I+}
if IOResult <> 0 then
begin
MessageDlg('Error opening session log file:'+CRLF+FileNameSessionLog+CRLF+CRLF+'Logging is disabled now.', mtError, [mbOK], 0);
prefLogToFile := False;
end else
prefLogToFile := True;
// Update popupMenu items
menuLogToFile.Checked := prefLogToFile;
menuOpenLogFolder.Enabled := prefLogToFile;
end;
{**
Close logfile.
Called in FormClose, in ActivateFileLogging and on closing preferences dialog
}
procedure TMDIChild.DeactivateFileLogging;
begin
prefLogToFile := False;
{$I-} // Supress errors
CloseFile(FileHandleSessionLog);
{$I+}
// Reset IOResult so later checks in ActivateFileLogging doesn't get an old value
IOResult;
// Update popupMenu items
menuLogToFile.Checked := prefLogToFile;
menuOpenLogFolder.Enabled := prefLogToFile;
end;
{**
Display tooltips in VirtualTrees. Imitates default behaviour of TListView.
}
procedure TMDIChild.vstGetHint(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; var LineBreakStyle:
TVTTooltipLineBreakStyle; var HintText: WideString);
var
r : TRect;
DisplayedWidth,
NeededWidth : Integer;
Tree: TVirtualStringTree;
begin
Tree := TVirtualStringTree(Sender);
HintText := Tree.Text[Node, Column];
// Check if the list has shortened the text
r := Tree.GetDisplayRect(Node, Column, True);
DisplayedWidth := r.Right-r.Left;
NeededWidth := Canvas.TextWidth(HintText) + Tree.TextMargin*2;
//debug(format('need: %d, given: %d, font: %s %d', [NeededWidth, DisplayedWidth, canvas.Font.Name, canvas.Font.Size]));
// Disable displaying hint if text is displayed completely in list
if NeededWidth <= DisplayedWidth then
HintText := '';
end;
{**
Enable/disable file logging by popupmenuclick
}
procedure TMDIChild.menuLogToFileClick(Sender: TObject);
var
reg : TRegistry;
OldprefLogToFile: Boolean;
begin
OldprefLogToFile := prefLogToFile;
if not prefLogToFile then
ActivateFileLogging
else
DeactivateFileLogging;
// Save option
if prefLogToFile <> OldprefLogToFile then
begin
reg := TRegistry.Create;
reg.OpenKey(REGPATH, true);
reg.WriteBool('LogToFile', prefLogToFile);
reg.Free;
end;
end;
{**
Open folder with session logs
}
procedure TMDIChild.menuOpenLogFolderClick(Sender: TObject);
begin
ShellExec( '', DirnameSessionLogs );
end;
{**
A header column of a VirtualTree was "dragged out", which means:
dragged down or up, not to the left or right.
We imitate the behaviour of various applications (fx Outlook) and
hide this dragged column
}
procedure TMDIChild.vstHeaderDraggedOut(Sender: TVTHeader; Column:
TColumnIndex; DropPosition: TPoint);
begin
if Sender.Treeview = ListTables then
begin
// Keep "Tables" column
if Column = 0 then
Exit;
// Uncheck menuitem in header's contextmenu
popupDBGridHeader.Items[Column].Checked := False;
end;
// Hide the draggedout column
Sender.Columns[Column].Options := Sender.Columns[Column].Options - [coVisible];
end;
{**
A cell in ListCommandStats gets painted.
Draw a progress bar on it to visualize its percentage value.
}
procedure TMDIChild.ListCommandStatsBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
percent : Extended;
barwidth, cellwidth: Integer;
NodeData: PVTreeData;
begin
// Only paint bar in percentage column
if Column <> 4 then
Exit;
// Add minimal margin to cell edges
InflateRect(CellRect, -1, -1);
cellwidth := CellRect.Right - CellRect.Left;
// Calculate value to display
NodeData := Sender.GetNodeData(Node);
percent := MakeFloat(NodeData.Captions[Column]);
barwidth := Round(cellwidth / 100 * percent);
// Adjust width of rect and paint the bar
CellRect.Right := CellRect.Right - cellwidth + barwidth;
TargetCanvas.Pen.Color := clGray;
TargetCanvas.Brush.Color := clInfoBk;
TargetCanvas.Rectangle(CellRect);
end;
function TMDIChild.HandleOrderColumns( AddOrderCol: TOrderCol = nil ): TOrderColArray;
var
i, j : Integer;
reg : TRegistry;
reg_name : WideString;
old_orderclause, new_orderclause, columnname : WideString;
order_parts, ValidColumns : WideStrings.TWideStringList;
columnexists : Boolean;
regCrashIndicName: String;
begin
SetLength( Result, 0 );
// Read ORDER clause from registry
reg_name := Utf8Encode(REGPREFIX_ORDERCLAUSE + ActiveDatabase + '.' + SelectedTable);
old_orderclause := Utf8Decode(Mainform.GetRegValue(reg_name, '', FConn.Description));
if old_orderclause <> '' then
begin
// Check for crash indicator on current table
regCrashIndicName := Utf8Encode(REGPREFIX_CRASH_IN_DATA + ActiveDatabase + '.' + SelectedTable);
if(Mainform.GetRegValue(regCrashIndicName, False, FConn.Description)) then begin
LogSQL('A crash in the previous data loading for this table ('+SelectedTable+') was detected. A stored ORDER clause was automatically reset to avoid the same crash for now.');
reg := TRegistry.Create;
reg.OpenKey( REGPATH + REGKEY_SESSIONS + FConn.Description, true );
// Remove ORDER BY clause from registry
reg.DeleteValue(reg_name);
reg.CloseKey;
reg.Free;
end else begin
// Parse ORDER clause
order_parts := explode( ',', old_orderclause );
ValidColumns := GetVTCaptions( ListColumns );
for i := 0 to order_parts.Count - 1 do
begin
columnname := Trim( Copy( order_parts[i], 0, LastPos( ' ', order_parts[i] ) ) );
columnname := WideDequotedStr(columnname, '`');
columnexists := ValidColumns.IndexOf(columnname) > -1;
if not columnexists then
begin
LogSQL( 'Notice: A stored ORDER-BY clause could not be applied, '+
'because the column "' + columnname + '" does not exist!');
Continue;
end;
// Add part of order clause to result array
SetLength(Result, Length(Result)+1);
Result[Length(Result)-1] := TOrderCol.Create;
Result[Length(Result)-1].ColumnName := columnname;
Result[Length(Result)-1].SortDirection := Integer( Copy( order_parts[i], ( Length( order_parts[i] ) - 3 ), 4 ) = 'DESC' );
end;
end;
end;
// Add a new order column after a columns title has been clicked
if AddOrderCol <> nil then
begin
// Check if order column is already existant
columnexists := False;
for i := Low(Result) to High(Result) do
begin
if Result[i].ColumnName = AddOrderCol.ColumnName then
begin
// AddOrderCol is already in the list. Switch its direction:
// ASC > DESC > [delete col]
columnexists := True;
if Result[i].SortDirection = ORDER_ASC then
Result[i].SortDirection := ORDER_DESC
else
begin
// Delete order col
for j := i to High(Result) - 1 do
Result[j] := Result[j+1];
SetLength(Result, Length(Result)-1);
end;
// We found the matching column, no need to loop further
break;
end;
end;
if not columnexists then
begin
SetLength(Result, Length(Result)+1);
Result[Length(Result)-1] := AddOrderCol;
end;
end;
// Update registry
new_orderclause := ComposeOrderClause(Result);
if new_orderclause <> old_orderclause then
begin
reg := TRegistry.Create();
reg.OpenKey( REGPATH + REGKEY_SESSIONS + FConn.Description, true );
if new_orderclause <> '' then
reg.WriteString(reg_name , Utf8Encode(new_orderclause))
else
reg.DeleteValue(reg_name);
reg.Free;
end;
end;
{**
Concat all sort options to a ORDER clause
}
function TMDIChild.ComposeOrderClause(Cols: TOrderColArray): WideString;
var
i : Integer;
sort : String;
begin
result := '';
for i := 0 to Length(Cols) - 1 do
begin
if result <> '' then
result := result + ', ';
if Cols[i].SortDirection = ORDER_ASC then
sort := TXT_ASC
else
sort := TXT_DESC;
result := result + Mainform.Mask( Cols[i].ColumnName ) + ' ' + sort;
end;
end;
{**
Fetch table engines from server
Currently used in tbl_properties and createtable
}
procedure TMDIChild.TableEnginesCombo(var Combobox: TCombobox);
var
engineName, defaultEngine, engineSupport : String;
HaveEngineList : TStrings;
begin
Combobox.Items.BeginUpdate;
Combobox.Items.Clear;
// Cache datasets
if (dsShowEngines = nil) and (dsHaveEngines = nil) then
begin
dsShowEngines := Mainform.Childwin.GetResults('SHOW ENGINES', True);
if dsShowEngines = nil then
dsHaveEngines := Mainform.Childwin.GetResults('SHOW VARIABLES LIKE ''have%''');
end;
if dsShowEngines <> nil then begin
dsShowEngines.First;
while not dsShowEngines.Eof do begin
engineName := dsShowEngines.FieldByName('Engine').AsString;
engineSupport := LowerCase(dsShowEngines.FieldByName('Support').AsString);
// Add to dropdown if supported
if engineSupport <> 'no' then
Combobox.Items.Add(engineName);
// Check if this is the default engine
if engineSupport = 'default' then
defaultEngine := engineName;
dsShowEngines.Next;
end;
end
else begin
// Manually fetch available engine types by analysing have_* options
// This is for servers below 4.1 or when the SHOW ENGINES statement has
// failed for some other reason
// Add default engines which will not show in a have_* variable:
Combobox.Items.CommaText := 'MyISAM,MRG_MyISAM,HEAP';
defaultEngine := 'MyISAM';
// Possible other engines:
HaveEngineList := TStringList.Create;
HaveEngineList.CommaText := 'ARCHIVE,BDB,BLACKHOLE,CSV,EXAMPLE,FEDERATED,INNODB,ISAM';
dsHaveEngines.First;
while not dsHaveEngines.Eof do begin
engineName := copy(dsHaveEngines.Fields[0].AsString, 6, Length(dsHaveEngines.Fields[0].AsString) );
// Strip additional "_engine" suffix, fx from "have_blackhole_engine"
if Pos('_', engineName) > 0 then
engineName := copy(engineName, 0, Pos('_', engineName)-1);
engineName := UpperCase(engineName);
// Add engine to dropdown if it's a) in HaveEngineList and b) activated
if (HaveEngineList.IndexOf(engineName) > -1)
and (LowerCase(dsHaveEngines.Fields[1].AsString) = 'yes') then
Combobox.Items.Add(engineName);
dsHaveEngines.Next;
end;
end;
Combobox.Sorted := True;
// Select default
Combobox.ItemIndex := Combobox.Items.IndexOf(defaultEngine);
Combobox.Items.EndUpdate;
end;
{**
A row in the process list was selected. Fill SynMemoProcessView with
the SQL of that row.
}
procedure TMDIChild.ListProcessesChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
var
NodeData : PVTreeData;
enableSQLView : Boolean;
begin
enableSQLView := Assigned(Node);
SynMemoProcessView.Enabled := enableSQLView;
pnlProcessView.Enabled := enableSQLView;
if enableSQLView then begin
NodeData := ListProcesses.GetNodeData(Node);
SynMemoProcessView.Text := NodeData.Captions[7];
end
else SynMemoProcessView.Clear;
end;
{***
Apply a filter to a Virtual Tree.
Currently used for ListVariables, ListStatus and ListProcesses
}
procedure TMDIChild.editFilterVTChange(Sender: TObject);
var
Node : PVirtualNode;
NodeData : PVTreeData;
VT : TVirtualStringTree;
Edit : TEdit;
i : Integer;
match : Boolean;
search : String;
somefiltered : Boolean;
begin
// Find the correct VirtualTree that shall be filtered
if Sender = editFilterVariables then
VT := ListVariables
else if Sender = editFilterStatus then
VT := ListStatus
else if Sender = editFilterProcesses then
VT := ListProcesses
else
Raise Exception.Create('editFilterVTChange() called with wrong sender control ('+(Sender as TControl).Name+')' );
Edit := Sender as TEdit;
// Loop through all nodes to adjust their vsVisible state
Node := VT.GetFirst;
search := LowerCase( Edit.Text );
somefiltered := False;
while Assigned(Node) do begin
NodeData := VT.GetNodeData(Node);
// Don't filter anything if the filter text is empty
match := search = '';
// Search for given text in node's captions
if not match then for i := 0 to NodeData.Captions.Count - 1 do begin
if Pos( search, LowerCase(NodeData.Captions[i]) ) > 0 then begin
match := True;
break;
end;
end;
if match then
Node.States := Node.States + [vsVisible]
else
Node.States := Node.States - [vsVisible];
if (not somefiltered) and (not match) then
somefiltered := True;
Node := VT.GetNext(Node);
end;
// Colorize TEdit with filter string to signalize that some nodes are hidden now
if somefiltered then begin
Edit.Font.Color := clRed;
Edit.Color := clYellow;
end else begin
Edit.Font.Color := clWindowText;
Edit.Color := clWindow;
end;
// Needs a refresh to apply visible states
VT.Refresh;
end;
procedure TMDIChild.ListVariablesDblClick(Sender: TObject);
begin
menuEditVariable.Click;
end;
{**
Edit a server variable
}
procedure TMDIChild.menuEditVariableClick(Sender: TObject);
var
NodeData: PVTreeData;
begin
if EditVariableForm = nil then
EditVariableForm := TfrmEditVariable.Create(Self);
NodeData := ListVariables.GetNodeData(ListVariables.FocusedNode);
EditVariableForm.VarName := NodeData.Captions[0];
EditVariableForm.VarValue := NodeData.Captions[1];
// Refresh relevant list node
if EditVariableForm.ShowModal = mrOK then
NodeData.Captions[1] := GetVar('SHOW VARIABLES LIKE '+esc(NodeData.Captions[0]), 1);
end;
{**
Apply icons to tabs of query helpers box
}
procedure TMDIChild.tabsetQueryHelpersGetImageIndex(Sender: TObject; TabIndex:
Integer; var ImageIndex: Integer);
begin
case TabIndex of
0: ImageIndex := 42;
1: ImageIndex := 13;
2: ImageIndex := 25;
3: ImageIndex := 35;
end;
end;
{**
The database tree doesn't use any structure for its nodes.
}
procedure TMDIChild.DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
begin
NodeDataSize := 0;
end;
{**
Set text of a treenode before it gets displayed or fetched in any way
}
procedure TMDIChild.DBtreeGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText:
WideString);
var
ds: TDataset;
db, eng: WideString;
i: Integer;
Bytes: Int64;
AllListsCached: Boolean;
begin
case Column of
0: case Sender.GetNodeLevel(Node) of
0: CellText := FConn.MysqlParams.User + '@' + FConn.MysqlParams.Host;
1: CellText := Databases[Node.Index];
2: begin
ds := FetchDbTableList(Databases[Node.Parent.Index]);
ds.RecNo := Node.Index+1;
CellText := ds.Fields[0].AsWideString;
end;
end;
1: case GetNodeType(Node) of
// Calculate and display the sum of all table sizes in ALL dbs if all table lists are cached
NODETYPE_DEFAULT: begin
AllListsCached := true;
for i := 0 to Databases.Count - 1 do begin
if not DbTableListCached(Databases[i]) then begin
AllListsCached := false;
break;
end;
end;
// Will be also set to a negative value by GetTableSize and results of SHOW TABLES
Bytes := -1;
if AllListsCached then begin
Bytes := 0;
for i := 0 to Databases.Count - 1 do begin
ds := FetchDbTableList(Databases[i]);
while not ds.Eof do begin
Bytes := Bytes + GetTableSize(ds);
ds.Next;
end;
end;
end;
if Bytes >= 0 then CellText := FormatByteNumber(Bytes)
else CellText := '';
end;
// Calculate and display the sum of all table sizes in ONE db, if the list is already cached.
NODETYPE_DB: begin
db := DBtree.Text[Node, 0];
if not DbTableListCached(db) then
CellText := ''
else begin
Bytes := 0;
ds := FetchDbTableList(db);
while not ds.Eof do begin
if ds.FindField('Type') <> nil then eng := FieldContent(ds, 'Type')
else eng := FieldContent(ds, 'Engine');
if UpperCase(eng) <> 'MRG_MYISAM' then
Bytes := Bytes + GetTableSize(ds);
ds.Next;
end;
if Bytes >= 0 then CellText := FormatByteNumber(Bytes)
else CellText := '';
end;
end;
NODETYPE_TABLE: begin
db := DBtree.Text[Node.Parent, 0];
ds := FetchDbTableList(db);
ds.RecNo := Node.Index + 1;
Bytes := GetTableSize(ds);
CellText := FormatByteNumber(Bytes);
end
else CellText := ''; // Applies for views and crashed tables
end;
end;
end;
{**
Set icon of a treenode before it gets displayed
}
procedure TMDIChild.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
var
ds: TDataset;
begin
if Column > 0 then
Exit;
case Sender.GetNodeLevel(Node) of
0: ImageIndex := ICONINDEX_SERVER;
1: if (Kind = ikSelected) or ((Sender.GetFirstSelected<>nil) and (Node=Sender.GetFirstSelected.Parent)) then
ImageIndex := ICONINDEX_DB_HIGHLIGHT
else ImageIndex := ICONINDEX_DB;
2: begin
ds := FetchDbTableList(Databases[Node.Parent.Index]);
ds.RecNo := Node.Index+1;
case GetDBObjectType(ds.Fields) of
NODETYPE_TABLE:
if Kind = ikSelected then
ImageIndex := ICONINDEX_TABLE_HIGHLIGHT
else ImageIndex := ICONINDEX_TABLE;
NODETYPE_VIEW:
if Kind = ikSelected then
ImageIndex := ICONINDEX_VIEW_HIGHLIGHT
else ImageIndex := ICONINDEX_VIEW;
NODETYPE_CRASHED_TABLE:
if Kind = ikSelected then
ImageIndex := ICONINDEX_CRASHED_TABLE_HIGHLIGHT
else ImageIndex := ICONINDEX_CRASHED_TABLE;
end;
end;
end;
end;
{**
Set childcount of an expanding treenode
}
procedure TMDIChild.DBtreeInitChildren(Sender: TBaseVirtualTree; Node:
PVirtualNode; var ChildCount: Cardinal);
var
ds: TDataset;
specialDbs: WideStrings.TWideStringList;
dbName: WideString;
i: Integer;
begin
case Sender.GetNodeLevel(Node) of
// Root node has only one single child (user@host)
0: begin
Screen.Cursor := crHourglass;
mainform.Showstatus( 'Reading Databases...' );
try
Databases := WideStrings.TWideStringList.Create;
if DatabasesWanted.Count = 0 then begin
ds := GetResults( 'SHOW DATABASES' );
specialDbs := WideStrings.TWideStringList.Create;
for i:=1 to ds.RecordCount do begin
dbName := ds.FieldByName('Database').AsWideString;
if dbName = DBNAME_INFORMATION_SCHEMA then specialDbs.Insert( 0, dbName )
else Databases.Add( dbName );
ds.Next;
end;
ds.Close;
FreeAndNil(ds);
Databases.Sort;
// Prioritised position of system-databases
for i := specialDbs.Count - 1 downto 0 do
Databases.Insert( 0, specialDbs[i] );
end else for i:=0 to DatabasesWanted.Count-1 do
Databases.Add(DatabasesWanted[i]);
Mainform.showstatus( IntToStr( Databases.Count ) + ' Databases', 0 );
ChildCount := Databases.Count;
// Avoids excessive InitializeKeywordLists() calls.
SynSQLSyn1.TableNames.BeginUpdate;
SynSQLSyn1.TableNames.Clear;
// Let synedit know all database names so that they can be highlighted
// TODO: Is this right? Adding "<db name>.<table name>" seems to make more sense..
for i := 0 to Databases.Count - 1 do
SynSQLSyn1.TableNames.Add(Databases[i]);
SynSQLSyn1.TableNames.EndUpdate;
finally
MainForm.ShowStatus( STATUS_MSG_READY );
Screen.Cursor := crDefault;
end;
end;
// DB node expanding
1: begin
Screen.Cursor := crHourglass;
mainform.Showstatus( 'Reading Tables...' );
try
ds := FetchDbTableList(Databases[Node.Index]);
ChildCount := ds.RecordCount;
finally
MainForm.ShowStatus( STATUS_MSG_READY );
Screen.Cursor := crDefault;
end;
end;
else Exit;
end;
end;
{**
Set initial options of a treenode
}
procedure TMDIChild.DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
level: Cardinal;
begin
level := Sender.GetNodeLevel(Node);
// Ensure plus sign is visible for root and dbs
if level in [0,1] then
Include( InitialStates, ivsHasChildren);
// Host node is always expanded
if level = 0 then
Include( InitialStates, ivsExpanded );
end;
{**
Selection in database tree has changed
}
procedure TMDIChild.DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
newDb: WideString;
begin
if not Assigned(Node) then
Exit;
case Sender.GetNodeLevel(Node) of
0: ShowHost;
1: begin
newDb := Databases[Node.Index];
ShowDatabase( newDb );
end;
2: begin
newDb := Databases[Node.Parent.Index];
ShowTable( (Sender as TVirtualStringTree).Text[Node, 0] );
end;
end;
if newDb <> '' then
LoadDatabaseProperties(newDb);
if PageControlMain.ActivePage = tabData then
viewData(self);
end;
procedure TMDIChild.DBtreeDblClick(Sender: TObject);
var
Node: PVirtualNode;
begin
// Paste DB or table name into query window on treeview double click.
Node := DBtree.GetFirstSelected;
if not Assigned(Node) then Exit;
if DBtree.GetNodeLevel(Node) = 0 then Exit;
if PageControlMain.ActivePage <> tabQuery then Exit;
SynMemoQuery.SelText := DBtree.Text[Node, 0];
SynMemoQuery.SetFocus;
end;
procedure TMDIChild.DBtreePaintText(Sender: TBaseVirtualTree; const
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType:
TVSTTextType);
begin
// Grey out rather unimportant "Size" column
if Column <> 1 then
Exit;
case DBtree.GetNodeLevel(Node) of
0: TargetCanvas.Font.Color := clWindowText;
1: TargetCanvas.Font.Color := $008f8f8f;
2: TargetCanvas.Font.Color := $00cfcfcf;
end;
end;
{**
Refresh database tab
}
procedure TMDIChild.MenuRefreshClick(Sender: TObject);
begin
RefreshTreeDB(ActiveDatabase);
LoadDatabaseProperties(ActiveDatabase);
end;
{**
Refresh whole database tree
}
procedure TMDIChild.menuRefreshDBTreeClick(Sender: TObject);
begin
RefreshTree(True);
end;
{**
Refresh the whole tree
}
procedure TMDIChild.RefreshTree(DoResetTableCache: Boolean; SelectDatabase: WideString = '');
var
oldActiveDatabase, oldSelectedTable, db: WideString;
Node: PVirtualNode;
ExpandedDBs, TablesFetched: WideStrings.TWideStringList;
i: Integer;
begin
// Remember currently active database and table
oldActiveDatabase := ActiveDatabase;
oldSelectedTable := SelectedTable;
// Temporary unselect any node to postpone event handlings
if (DBtree.GetFirstSelected <> nil) and (DBtree.GetNodeLevel(DBtree.GetFirstSelected) > 0) then
DBtree.ClearSelection;
// Remember expandation status of all dbs and whether their tables were fetched
ExpandedDBs := WideStrings.TWideStringList.Create;
TablesFetched := WideStrings.TWideStringList.Create;
Node := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
db := DBtree.Text[Node, 0];
if DBtree.ChildrenInitialized[Node] then
TablesFetched.Add(db);
if vsExpanded in Node.States then
ExpandedDBs.Add(db);
Node := DBtree.GetNextSibling(Node);
end;
// ReInit tree population
DBTree.BeginUpdate;
DBtree.ReinitChildren(DBTree.GetFirst, False); // .ResetNode(DBtree.GetFirst);
if DoResetTableCache then
ClearAllTableLists;
// Reselect active or new database if present. Could have been deleted or renamed.
try
if SelectDatabase <> '' then ActiveDatabase := SelectDatabase
else if oldActiveDatabase <> '' then ActiveDatabase := oldActiveDatabase;
except
end;
// Expand nodes which were previously expanded
Node := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
db := DBtree.Text[Node, 0];
if TablesFetched.IndexOf(db) > -1 then
DBtree.ReinitChildren(Node, False);
DBtree.Expanded[Node] := ExpandedDBs.IndexOf(db) > -1;
Node := DBtree.GetNextSibling(Node);
end;
ExpandedDBs.Free;
TablesFetched.Free;
try
if oldSelectedTable <> '' then SelectedTable := oldSelectedTable;
except
end;
DBTree.EndUpdate;
end;
{**
Refresh one database node in the db tree
}
procedure TMDIChild.RefreshTreeDB(db: WideString);
var
oldActiveDatabase: WideString;
dbnode: PVirtualNode;
begin
oldActiveDatabase := ActiveDatabase;
DBtree.ClearSelection;
DBNode := FindDBNode(db);
RefreshDbTableList(db);
DBTree.ReinitNode(dbnode, true);
DBtree.InvalidateChildren(dbnode, false);
ActiveDatabase := oldActiveDatabase;
end;
{**
Find a database node in the tree by passing its name
}
function TMDIChild.FindDBNode(db: WideString): PVirtualNode;
var
i, s: Integer;
n: PVirtualNode;
begin
Result := nil;
// Ensure Databases list is instantiated (by DBtree.InitChildren)
if Databases = nil then
DBtree.ReinitNode(DBtree.GetFirst, False);
// TStringList.CaseSensitive= True|False is only used in .IndexOf and .Sort procs,
// it does not avoid or remove duplicate items
Databases.CaseSensitive := True;
s := Databases.IndexOf(db);
if s = -1 then begin
Databases.CaseSensitive := False;
s := Databases.IndexOf(db);
end;
if s > -1 then begin
n := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
if Integer(n.Index) = s then begin
Result := n;
Exit;
end;
n := DBtree.GetNextSibling(n);
end;
end;
end;
{**
Expand all db nodes
}
procedure TMDIChild.menuTreeExpandAllClick(Sender: TObject);
begin
DBtree.FullExpand;
DBtree.ScrollIntoView(DBtree.GetFirstSelected, False);
end;
{**
Collapse all db nodes
}
procedure TMDIChild.menuTreeCollapseAllClick(Sender: TObject);
var
n: PVirtualNode;
i: Integer;
begin
n := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
DBtree.FullCollapse(n);
n := DBtree.GetNextSibling(n);
end;
DBtree.ScrollIntoView(DBtree.GetFirstSelected, False);
end;
function TMDIChild.GetTableSize(ds: TDataSet): Int64;
var
d, i: String;
begin
d := FieldContent(ds, 'Data_length');
i := FieldContent(ds, 'Index_length');
if (d = '') or (i = '') then Result := -1
else Result := MakeInt(d) + MakeInt(i);
end;
function TMDIChild.DbTableListCached(db: WideString): Boolean;
begin
Result := CachedTableLists.IndexOf(db) > -1;
end;
procedure TMDIChild.editFilterSearchChange(Sender: TObject);
var
Add, Clause: WideString;
i: Integer;
ed: TEdit;
begin
ed := TEdit(Sender);
Clause := '';
Add := '';
if ed.Text <> '' then begin
for i := 0 to Length(VTRowDataListColumns) - 1 do begin
if i > 0 then
Add := Add + ' OR ';
Add := Add + mask(VTRowDataListColumns[i].Captions[0]) + ' LIKE ' + esc('%'+ed.Text+'%');
if Length(Add) > 45 then begin
Clause := Clause + Add + CRLF;
Add := '';
end;
end;
if Add <> '' then
Clause := Clause + Add;
end;
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.SelectAll;
SynMemoFilter.SelText := Clause;
SynMemoFilterChange(Sender);
end;
procedure TMDIChild.ListColumnsDblClick(Sender: TObject);
begin
Mainform.actEditField.Execute;
end;
procedure TMDIChild.SynMemoFilterChange(Sender: TObject);
var
SomeText: Boolean;
begin
SomeText := (SynMemoFilter.GetTextLen > 0) or (editFilterSearch.Text <> '');
Mainform.actClearFilterEditor.Enabled := SomeText;
end;
procedure TMDIChild.ToggleFilterPanel(ForceVisible: Boolean = False);
var
ShowIt: Boolean;
begin
ShowIt := ForceVisible or (not pnlFilter.Visible);
tbtnDataFilter.Down := ShowIt;
pnlFilter.Visible := ShowIt;
end;
procedure TMDIChild.editFilterSearchEnter(Sender: TObject);
begin
// Enables triggering apply button with Enter
btnFilterApply.Default := True;
end;
procedure TMDIChild.editFilterSearchExit(Sender: TObject);
begin
btnFilterApply.Default := False;
end;
procedure TMDIChild.EnsureNodeLoaded(Sender: TBaseVirtualTree; Node: PVirtualNode; WhereClause: String);
var
res: PGridResult;
query: WideString;
ds: TDataSet;
i, j: LongInt;
begin
if Sender = DataGrid then res := @FDataGridResult
else res := @FQueryGridResult;
if (not res.Rows[Node.Index].Loaded) and (res.Rows[Node.Index].State <> grsInserted) then begin
query := DataGridCurrentSelect;
// Passed WhereClause has prio over current filter, fixes bug #754
if WhereClause <> '' then begin
query := query + ' WHERE ' + WhereClause;
end else if DataGridCurrentFilter <> '' then begin
query := query + ' WHERE ' + DataGridCurrentFilter;
end;
// start query
MainForm.ShowStatus('Retrieving data...');
ds := GetResults(query);
// If new data does not match current filter, remove from tree.
if Cardinal(ds.RecordCount) < 1 then begin
// Remove entry from dynamic array.
for i := Node.Index to Length(res.Rows) - 1 do begin
if i < Length(res.Rows) - 1 then res.Rows[i] := res.Rows[i + 1];
end;
SetLength(res.Rows, Length(res.Rows) - 1);
// Remove entry from node list.
Sender.DeleteNode(Node);
end;
// fill in data
MainForm.ShowStatus('Filling grid with record-data...');
SetLength(res.Rows[Node.Index].Cells, ds.Fields.Count);
i := Node.Index;
for j := 0 to ds.Fields.Count - 1 do begin
if res.Columns[j].IsBinary then
res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString)
else
res.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString;
res.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull;
end;
res.Rows[Node.Index].Loaded := True;
MainForm.ShowStatus( STATUS_MSG_READY );
FreeAndNil(ds);
end;
end;
procedure TMDIChild.EnsureChunkLoaded(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
res: PGridResult;
start, limit: Cardinal;
query: WideString;
ds: TDataSet;
i, j: LongInt;
reg: TRegistry;
regCrashIndicName: String;
begin
if Sender = DataGrid then res := @FDataGridResult
else res := @FQueryGridResult;
if (not res.Rows[Node.Index].Loaded) and (res.Rows[Node.Index].State <> grsInserted) then begin
start := Node.Index - (Node.Index mod GridMaxRows);
limit := TVirtualStringTree(Sender).RootNodeCount - start;
if limit > GridMaxRows then limit := GridMaxRows;
query := DataGridCurrentSelect;
if DataGridCurrentFilter <> '' then query := query + ' WHERE ' + DataGridCurrentFilter;
if DataGridCurrentSort <> '' then query := query + ' ORDER BY ' + DataGridCurrentSort;
query := query + WideFormat(' LIMIT %d, %d', [start, limit]);
// Set indicator for possibly crashing query
reg := TRegistry.Create;
reg.OpenKey( REGPATH + REGKEY_SESSIONS + FConn.Description, true );
regCrashIndicName := Utf8Encode(REGPREFIX_CRASH_IN_DATA + ActiveDatabase + '.' + SelectedTable);
reg.WriteBool(regCrashIndicName, True);
// start query
MainForm.ShowStatus('Retrieving data...');
debug(Format('mem: loading data chunk from row %d to %d', [start, limit]));
ds := GetResults(query);
if Cardinal(ds.RecordCount) < limit then begin
limit := ds.RecordCount;
TVirtualStringTree(Sender).RootNodeCount := start + limit;
SetLength(res.Rows, start + limit);
end;
debug(Format('mem: loaded data chunk from row %d to %d', [start, limit]));
// Query was completed successfully. Reset crash indicator.
reg.DeleteValue(regCrashIndicName);
reg.CloseKey;
reg.Free;
// fill in data
MainForm.ShowStatus('Filling grid with record-data...');
for i := start to start + limit - 1 do begin
SetLength(res.Rows[i].Cells, ds.Fields.Count);
for j := 0 to ds.Fields.Count - 1 do begin
if res.Columns[j].IsBinary then
res.Rows[i].Cells[j].Text := '0x' + BinToWideHex(ds.Fields[j].AsString)
else
res.Rows[i].Cells[j].Text := ds.Fields[j].AsWideString;
res.Rows[i].Cells[j].IsNull := ds.Fields[j].IsNull;
end;
res.Rows[i].Loaded := True;
ds.Next;
end;
MainForm.ShowStatus( STATUS_MSG_READY );
FreeAndNil(ds);
end;
end;
procedure TMDIChild.DiscardNodeData(Sender: TVirtualStringTree; Node: PVirtualNode);
var
Data: PGridResult;
begin
// Avoid discarding query data as it will never be reloaded.
if Sender <> DataGrid then Exit;
Data := @FDataGridResult;
// Avoid rows being edited.
if Data.Rows[Node.Index].State = grsDefault then begin
Data.Rows[Node.Index].Loaded := false;
SetLength(Data.Rows[Node.Index].Cells, 0);
end;
end;
{**
A grid cell fetches its text content
}
procedure TMDIChild.GridGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText:
WideString);
var
c: PGridCell;
gr: PGridResult;
EditingCell: Boolean;
begin
if Column = -1 then
Exit;
if Sender = DataGrid then gr := @FDataGridResult
else gr := @FQueryGridResult;
if Node.Index >= Cardinal(Length(gr.Rows)) then Exit;
EnsureChunkLoaded(Sender, Node);
if Node.Index >= Cardinal(Length(gr.Rows)) then Exit;
c := @gr.Rows[Node.Index].Cells[Column];
EditingCell := Sender.IsEditing and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn);
if prefEnableNullBG and (c.IsNull or c.NewIsNull) then begin
// Don't display any text if NULL background was activated. In most cases better readable
CellText := '';
end else if c.Modified then begin
if c.NewIsNull then begin
if EditingCell then CellText := ''
else CellText := TEXT_NULL;
end else CellText := c.NewText;
end else begin
if c.IsNull then begin
if EditingCell then CellText := ''
else CellText := TEXT_NULL;
end else begin
CellText := c.Text;
if Length(c.Text) = GridMaxData then CellText := CellText + ' [...]';
end;
end;
end;
procedure TMDIChild.CalcNullColors;
begin
prefNullColorNumeric := ColorAdjustBrightness(prefFieldColorNumeric, COLORSHIFT_NULLFIELDS);
prefNullColorText := ColorAdjustBrightness(prefFieldColorText, COLORSHIFT_NULLFIELDS);
prefNullColorBinary := ColorAdjustBrightness(prefFieldColorBinary, COLORSHIFT_NULLFIELDS);
prefNullColorDatetime := ColorAdjustBrightness(prefFieldColorDatetime, COLORSHIFT_NULLFIELDS);
prefNullColorEnum := ColorAdjustBrightness(prefFieldColorEnum, COLORSHIFT_NULLFIELDS);
prefNullColorSet := ColorAdjustBrightness(prefFieldColorSet, COLORSHIFT_NULLFIELDS);
prefNullColorDefault := ColorAdjustBrightness(clWindow, COLORSHIFT_NULLFIELDS);
end;
{**
Cell in data- or query grid gets painted. Colorize font. This procedure is
called extremely often for repainting the grid cells. Keep it highly optimized.
}
procedure TMDIChild.GridPaintText(Sender: TBaseVirtualTree; const
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType:
TVSTTextType);
var
isNull: Boolean;
cl: TColor;
r: PGridResult;
begin
if Column = -1 then
Exit;
if Sender = DataGrid then r := @FDataGridResult
else r := @FQueryGridResult;
if Node.Index >= Cardinal(Length(r.Rows)) then
Exit;
// Make primary key columns bold
if r.Columns[Column].IsPriPart then
TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
// Do not apply any color on a selected, highlighted node to keep readability
if vsSelected in Node.States then
Exit;
// NULL value
isNull := r.Rows[Node.Index].Cells[Column].IsNull;
// Numeric field
if r.Columns[Column].isInt or r.Columns[Column].isFloat then
if isNull then cl := prefNullColorNumeric else cl := prefFieldColorNumeric
// Date field
else if r.Columns[Column].isDate then
if isNull then cl := prefNullColorDatetime else cl := prefFieldColorDatetime
// Text field
else if r.Columns[Column].isText then
if isNull then cl := prefNullColorText else cl := prefFieldColorText
// Text field
else if r.Columns[Column].isBinary then
if isNull then cl := prefNullColorBinary else cl := prefFieldColorBinary
// Enum field
else if r.Columns[Column].isEnum then
if isNull then cl := prefNullColorEnum else cl := prefFieldColorEnum
// Set field
else if r.Columns[Column].isSet then
if isNull then cl := prefNullColorSet else cl := prefFieldColorSet
else
if isNull then cl := prefNullColorDefault else cl := clWindowText;
TargetCanvas.Font.Color := cl;
end;
procedure TMDIChild.DataGridAfterCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellRect: TRect);
begin
// Don't waist time
if Column = -1 then Exit;
if Node.Index >= Cardinal(Length(FDataGridResult.Rows)) then Exit;
// Paint a red triangle at the top left corner of the cell
if FDataGridResult.Rows[Node.Index].Cells[Column].Modified then
Mainform.PngImageListMain.Draw(TargetCanvas, CellRect.Left, CellRect.Top, 111);
end;
{**
Header column in datagrid clicked.
Left button: handle ORDER BY
Right button: show column selection box
}
procedure TMDIChild.DataGridHeaderClick(Sender: TVTHeader; Column:
TColumnIndex; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
c : TOrderCol;
frm: TForm;
begin
if Button = mbLeft then begin
c := TOrderCol.Create;
c.ColumnName := Sender.Columns[Column].Text;
HandleOrderColumns(c);
ViewData(Sender);
end else begin
frm := TColumnSelectionForm.Create(self);
// Position new form relative to btn's position
frm.Top := Y + DataGrid.ClientOrigin.Y - Integer(DataGrid.Header.Height);
frm.Left := X + DataGrid.ClientOrigin.X;
// Display form
frm.Show;
end;
end;
{**
Only allow grid editing if there is a good key available
}
procedure TMDIChild.setNULL1Click(Sender: TObject);
begin
if not CheckUniqueKeyClause then
Exit;
DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn] := '';
FDataGridResult.Rows[DataGrid.FocusedNode.Index].Cells[DataGrid.FocusedColumn].NewIsNull := True;
FDataGridResult.Rows[DataGrid.FocusedNode.Index].Cells[DataGrid.FocusedColumn].Modified := True;
FDataGridResult.Rows[DataGrid.FocusedNode.Index].State := grsModified;
DataGridHasChanges := True;
DataGrid.RepaintNode(DataGrid.FocusedNode);
ValidateControls;
end;
{**
Content of a grid cell was modified
}
procedure TMDIChild.DataGridNewText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; NewText: WideString);
var
Row: PGridRow;
begin
Row := @FDataGridResult.Rows[Node.Index];
// Remember new value
Row.Cells[Column].NewText := NewText;
Row.Cells[Column].NewIsNull := False;
Row.Cells[Column].Modified := True;
// Set state of row for UPDATE mode, don't touch grsInserted
if Row.State = grsDefault then
FDataGridResult.Rows[Node.Index].State := grsModified;
DataGridHasChanges := True;
ValidateControls;
end;
{**
Checks if there is a unique key available which can be used for UPDATEs and INSERTs
}
function TMDIChild.CheckUniqueKeyClause: Boolean;
var
mres: Integer;
begin
Result := GetKeyColumns.Count > 0;
if not Result then begin
mres := MessageDlg('Grid editing is blocked because this table does not have a primary '+
'or a unique key, or it only contains a unique key which allows NULLs which turns that '+
'key to be non unique again. You can create or edit the keys using the index manager.'+CRLF+CRLF+
'Press'+CRLF+
' [Ok] to cancel editing and call the index manager'+CRLF+
' [Cancel] to cancel editing.',
mtWarning, [mbOK, mbCancel], 0);
if mres = mrOK then
MainForm.actEditIndexesExecute(DataGrid);
end;
end;
{**
DataGrid: node focus has changed
}
procedure TMDIChild.DataGridChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
begin
ValidateControls;
end;
{**
DataGrid: node and/or column focus is about to change. See if we allow that.
}
procedure TMDIChild.DataGridFocusChanging(Sender: TBaseVirtualTree; OldNode,
NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed:
Boolean);
begin
// Detect changed focus and update row
if Assigned(OldNode) and Assigned(NewNode) and (OldNode <> NewNode) then
Allowed := DataGridPostUpdateOrInsert(OldNode)
else
Allowed := True;
end;
{**
DataGrid: invoke update or insert routine
}
function TMDIChild.DataGridPostUpdateOrInsert(Node: PVirtualNode): Boolean;
begin
Result := True;
if Cardinal(High(FDataGridResult.Rows)) >= Node.Index then
case FDataGridResult.Rows[Node.Index].State of
grsModified: Result := GridPostUpdate(DataGrid);
grsInserted: Result := GridPostInsert(DataGrid);
end;
end;
{**
DataGrid: compose and fire UPDATE query
}
function TMDIChild.GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
var
i: Integer;
sql, Val: WideString;
Row: PGridRow;
begin
sql := 'UPDATE '+mask(SelectedTable)+' SET';
Row := @FDataGridResult.Rows[Sender.FocusedNode.Index];
for i := 0 to Length(FDataGridResult.Columns) - 1 do begin
if Row.Cells[i].Modified then begin
Val := Row.Cells[i].NewText;
if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val);
if not FDataGridResult.Columns[i].IsBinary then Val := esc(Val);
if FDataGridResult.Columns[i].IsBinary then CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + FDataGridResult.Columns[i].Name + '".');
if Val = '0x' then Val := esc('');
if Row.Cells[i].NewIsNull then Val := 'NULL';
sql := sql + ' ' + mask(FDataGridResult.Columns[i].Name) + '=' + Val + ', ';
end;
end;
// Cut trailing comma
sql := Copy(sql, 1, Length(sql)-2);
sql := sql + ' WHERE ' + GetWhereClause(Row, @FDataGridResult.Columns);
try
// Send UPDATE query
ExecUpdateQuery(sql, False, True);
Result := True;
except
Result := False;
end;
if Result then begin
// Reselect just updated row in grid from server to ensure displaying
// correct values which were silently converted by the server
for i := 0 to Length(FDataGridResult.Columns) - 1 do begin
if not Row.Cells[i].Modified then
Continue;
Row.Cells[i].Text := Row.Cells[i].NewText;
Row.Cells[i].IsNull := Row.Cells[i].NewIsNull;
end;
GridFinalizeEditing(Sender);
Row.Loaded := false;
EnsureNodeLoaded(Sender, Sender.FocusedNode, GetWhereClause(Row, @FDataGridResult.Columns));
end;
end;
{**
Repaint edited node and reset state of grid row
}
procedure TMDIChild.GridFinalizeEditing(Sender: TBaseVirtualTree);
var
i, c: Integer;
begin
c := Sender.FocusedNode.Index;
FDataGridResult.Rows[c].State := grsDefault;
for i := 0 to Length(FDataGridResult.Rows[c].Cells) - 1 do begin
FDataGridResult.Rows[c].Cells[i].NewText := '';
FDataGridResult.Rows[c].Cells[i].Modified := False;
end;
Sender.RepaintNode(Sender.FocusedNode);
DataGridHasChanges := False;
ValidateControls;
end;
{**
Compose a WHERE clause used for UPDATEs and DELETEs
}
function TMDIChild.GetWhereClause(Row: PGridRow; Columns: PGridColumns): WideString;
var
i, j: Integer;
KeyVal: WideString;
KeyCols: WideStrings.TWideStringlist;
begin
Result := '';
KeyCols := GetKeyColumns;
for i := 0 to KeyCols.Count - 1 do begin
for j := 0 to Length(Columns^) - 1 do begin
if Columns^[j].Name = KeyCols[i] then
break;
end;
// Find old value of key column
KeyVal := Row.Cells[j].Text;
// Quote if needed
if FDataGridResult.Columns[j].IsFloat then KeyVal := FloatStr(KeyVal);
if not FDataGridResult.Columns[j].IsBinary then KeyVal := esc(KeyVal);
if KeyVal = '0x' then KeyVal := esc('');
if Row.Cells[j].IsNull then KeyVal := ' IS NULL'
else KeyVal := '=' + KeyVal;
Result := Result + mask(KeyCols[i]) + KeyVal + ' AND ';
end;
// Cut trailing AND
Result := Copy(Result, 1, Length(Result)-5);
end;
{**
Find key columns for a WHERE clause by analysing a SHOW KEYS FROM ... resultset
}
function TMDIChild.GetKeyColumns: WideStrings.TWideStringlist;
var
i: Integer;
AllowsNull: Boolean;
procedure FindColumns(const KeyName: WideString);
begin
// Find relevant key column names
Result.Clear;
FSelectedTableKeys.First;
while not FSelectedTableKeys.Eof do begin
if FSelectedTableKeys.FieldByName('Key_name').AsWideString = KeyName then
Result.Add(FSelectedTableKeys.FieldByName('Column_name').AsWideString);
FSelectedTableKeys.Next;
end;
end;
begin
Result := WideStrings.TWideStringlist.Create;
// Find best key for updates
FSelectedTableKeys.First;
// 1. round: find a primary key
while not FSelectedTableKeys.Eof do begin
if FSelectedTableKeys.FieldByName('Key_name').AsWideString = 'PRIMARY' then begin
FindColumns(FSelectedTableKeys.FieldByName('Key_name').AsWideString);
Exit;
end;
FSelectedTableKeys.Next;
end;
// no primary key available -> 2. round: find a unique key
FSelectedTableKeys.First;
while not FSelectedTableKeys.Eof do begin
if FSelectedTableKeys.FieldByName('Non_unique').AsInteger = 0 then begin
// We found a UNIQUE key - better than nothing. Check if one of the key
// columns allows NULLs which makes it dangerous to use in UPDATES + DELETES.
FindColumns(FSelectedTableKeys.FieldByName('Key_name').AsWideString);
FSelectedTableColumns.First;
AllowsNull := False;
for i := 0 to Result.Count - 1 do begin
while (not FSelectedTableColumns.Eof) and (not AllowsNull) do begin
if FSelectedTableColumns.FieldByName('Field').AsWideString = Result[i] then
AllowsNull := UpperCase(FSelectedTableColumns.FieldByName('Null').AsString) = 'YES';
FSelectedTableColumns.Next;
end;
if AllowsNull then break;
end;
if AllowsNull then Result.Clear
else break;
end;
FSelectedTableKeys.Next;
end;
end;
{**
DataGrid: compose and fire UPDATE query
}
procedure TMDIChild.DataGridInsertRow;
var
i, j: Integer;
begin
i := Length(FDataGridResult.Rows);
SetLength(FDataGridResult.Rows, i+1);
SetLength(FDataGridResult.Rows[i].Cells, Length(FDataGridResult.Columns));
FDataGridResult.Rows[i].State := grsInserted;
for j := 0 to Length(FDataGridResult.Rows[i].Cells) - 1 do begin
FDataGridResult.Rows[i].Cells[j].Text := '';
end;
DataGrid.AddChild(nil);
DataGrid.FocusedNode := DataGrid.GetLast;
DataGrid.ClearSelection;
DataGrid.Selected[DataGrid.FocusedNode] := True;
DataGridHasChanges := True;
ValidateControls;
end;
{**
DataGrid: compose and fire INSERT query
}
function TMDIChild.GridPostInsert(Sender: TBaseVirtualTree): Boolean;
var
Row: PGridRow;
sql, Cols, Val, Vals: WideString;
i: Integer;
Node: PVirtualNode;
begin
Node := Sender.FocusedNode;
Row := @FDataGridResult.Rows[Node.Index];
Cols := '';
Vals := '';
for i := 0 to Length(FDataGridResult.Columns) - 1 do begin
FSelectedTableColumns.RecNo := i;
if Row.Cells[i].Modified then begin
Cols := Cols + mask(FDataGridResult.Columns[i].Name) + ', ';
Val := Row.Cells[i].NewText;
if FDataGridResult.Columns[i].IsFloat then Val := FloatStr(Val);
if not FDataGridResult.Columns[i].IsBinary then Val := esc(Val);
if FDataGridResult.Columns[i].IsBinary then CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + FDataGridResult.Columns[i].Name + '".');
if Val = '0x' then Val := esc('');
if Row.Cells[i].NewIsNull then Val := 'NULL';
Vals := Vals + Val + ', ';
end;
end;
if Length(Cols) = 0 then begin
// No field was manually modified, cancel the INSERT in that case
Sender.BeginUpdate;
Sender.DeleteNode(Node);
SetLength(FDataGridResult.Rows, Length(FDataGridResult.Rows) - 1);
Sender.EndUpdate;
DataGridHasChanges := False;
ValidateControls;
Result := True; // Important for DataGridFocusChanging to allow moving focus
end else begin
// At least one field was modified, assume this INSERT should be posted
Vals := Copy(Vals, 1, Length(Vals)-2);
Cols := Copy(Cols, 1, Length(Cols)-2);
sql := 'INSERT INTO '+mask(SelectedTable)+' ('+Cols+') VALUES ('+Vals+')';
// Send INSERT query
ExecUpdateQuery(sql, False, True);
Result := True;
Row.Loaded := false;
EnsureNodeLoaded(Sender, Node, GetWhereClause(Row, @FDataGridResult.Columns));
GridFinalizeEditing(Sender);
end;
end;
{**
DataGrid: compose and fire DELETE query
}
function TMDIChild.GridPostDelete(Sender: TBaseVirtualTree): Boolean;
var
Node: PVirtualNode;
Nodes: TNodeArray;
sql: WideString;
Affected: Int64;
Selected, i, j: Integer;
msg: String;
begin
Node := Sender.GetFirstSelected;
sql := 'DELETE FROM '+mask(SelectedTable)+' WHERE';
while Assigned(Node) do begin
sql := sql + ' (' +
GetWhereClause(@FDataGridResult.Rows[Node.Index], @FDataGridResult.Columns) +
') OR';
Node := Sender.GetNextSelected(Node);
end;
sql := Copy(sql, 1, Length(sql)-3);
try
// Send DELETE query
ExecUpdateQuery(sql, False, True);
Result := True;
except
Result := False;
end;
if Result then begin
// Remove deleted row nodes out of the grid
Affected := FMysqlConn.Connection.GetAffectedRowsFromLastPost;
Selected := Sender.SelectedCount;
if Affected = Selected then begin
// Fine. Number of deleted rows equals the selected node count.
// In this case, just remove the selected nodes, avoid a full reload
Sender.BeginUpdate;
Nodes := Sender.GetSortedSelection(True);
for i:=High(Nodes) downto Low(Nodes) do begin
for j := Nodes[i].Index to High(FDataGridResult.Rows)-1 do begin
// Move upper rows by one so the selected row gets overwritten
FDataGridResult.Rows[j] := FDataGridResult.Rows[j+1];
end;
end;
SetLength(FDataGridResult.Rows, Length(FDataGridResult.Rows) - Selected);
Sender.DeleteSelectedNodes;
Sender.EndUpdate;
end else begin
// Should never get called as we block DELETEs on tables without a unique key
ViewData(Sender);
msg := 'Warning: Consistency problem detected.' + CRLF + CRLF
+ 'The last DELETE query affected ' + FormatNumber(Affected) + ' rows, when it should have touched '+FormatNumber(Selected)+' row(s)!'
+ CRLF + CRLF
+ 'This is most likely caused by not having a primary key in the table''s definition.';
LogSQL( msg );
MessageDlg( msg, mtWarning, [mbOK], 0);
end;
end;
end;
{**
DataGrid: cancel INSERT or UPDATE mode, reset modified node data
}
procedure TMDIChild.DataGridCancel(Sender: TObject);
var
i: Integer;
begin
case FDataGridResult.Rows[DataGrid.FocusedNode.Index].State of
grsModified: GridFinalizeEditing(DataGrid);
grsInserted: begin
i := Length(FDataGridResult.Rows);
DataGrid.DeleteNode(DataGrid.FocusedNode, False);
SetLength(FDataGridResult.Rows, i-1);
// Focus+select last node if possible
Mainform.actDataLastExecute(Sender);
end;
end;
DataGridHasChanges := False;
ValidateControls;
end;
procedure TMDIChild.GridKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
var
g: TVirtualStringTree;
begin
g := TVirtualStringTree(Sender);
case Key of
VK_HOME: g.FocusedColumn := 0;
VK_END: g.FocusedColumn := g.Header.Columns.Count-1;
VK_RETURN: if Assigned(g.FocusedNode) then g.EditNode(g.FocusedNode, g.FocusedColumn);
VK_DOWN: if (g = DataGrid) and Assigned(g.FocusedNode) and (g.FocusedNode.Index = g.RootNodeCount-1) then
Mainform.actDataInsertExecute(Sender);
end;
end;
// TODO: Version of EnsureFullWidth() that fetches all width limited columns
// for a row, and fetches 500 rows at a time, for use with GridTo{Xml,Csv,Html}.
// Would reduce number of database roundtrips; also the per-query overhead
// right now is horrendous for some reason (thinking mysqlquerythread).
procedure TMDIChild.EnsureFullWidth(Grid: TBaseVirtualTree; Column: TColumnIndex; Node: PVirtualNode);
var
Data: PGridResult;
Cell: PGridCell;
Row: PGridRow;
Col: PGridColumn;
sql: WideString;
len: Int64;
ds: TDataSet;
begin
// Only the data grid uses delayed loading of full-width data.
if Grid <> DataGrid then Exit;
Data := @FDataGridResult;
// Load entire data for field.
Col := @Data.Columns[Column];
Row := @Data.Rows[Node.Index];
Cell := @Data.Rows[Node.Index].Cells[Column];
len := Length(Cell.Text);
// Recalculate due to textual formatting of raw binary data.
if (Col.IsBinary) and (len > 2) then len := (len - 2) div 2;
// Assume width limit in effect if data exactly at limit threshold.
if len = GridMaxData then begin
sql :=
'SELECT ' + mask(Col.Name) +
' FROM ' + mask(SelectedTable) +
' WHERE ' + GetWhereClause(Row, @Data.Columns)
;
ds := GetResults(sql);
if Col.IsBinary then Cell.Text := '0x' + BinToWideHex(ds.Fields[0].AsString)
else Cell.Text := ds.Fields[0].AsWideString;
Cell.IsNull := ds.Fields[0].IsNull;
end;
end;
procedure TMDIChild.DataGridEditing(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
begin
Allowed := True;
if FDataGridResult.Rows[Node.Index].State = grsDefault then
Allowed := CheckUniqueKeyClause;
if Allowed then begin
// Move Esc shortcut from "Cancel row editing" to "Cancel cell editing"
Mainform.actDataCancelChanges.ShortCut := 0;
Mainform.actDataPostChanges.ShortCut := 0;
EnsureFullWidth(Sender, Column, Node);
end;
end;
procedure TMDIChild.DataGridEdited(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex);
begin
// Reassign Esc to "Cancel row editing" action
Mainform.actDataCancelChanges.ShortCut := TextToShortcut('Esc');
Mainform.actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter');
AutoCalcColWidths(DataGrid, PrevTableColWidths);
end;
procedure TMDIChild.DataGridEditCancelled(Sender: TBaseVirtualTree; Column:
TColumnIndex);
begin
// Reassign Esc to "Cancel row editing" action
Mainform.actDataCancelChanges.ShortCut := TextToShortcut('Esc');
Mainform.actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter');
end;
procedure TMDIChild.DataGridCreateEditor(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
var
MemoEditor: TMemoEditorLink;
DateTimeEditor: TDateTimeEditorLink;
EnumEditor: TEnumEditorLink;
SetEditor: TSetEditorLink;
begin
if
(FDataGridResult.Columns[Column].IsText and prefEnableTextEditor) or
(FDataGridResult.Columns[Column].IsBinary and prefEnableBinaryEditor)
then begin
MemoEditor := TMemoEditorLink.Create;
MemoEditor.MaxLength := FDataGridResult.Columns[Column].MaxLength;
EditLink := MemoEditor;
end else if FDataGridResult.Columns[Column].IsDate and prefEnableDatetimeEditor then begin
DateTimeEditor := TDateTimeEditorLink.Create;
DateTimeEditor.DataType := FDataGridResult.Columns[Column].DataType;
EditLink := DateTimeEditor;
end else if FDataGridResult.Columns[Column].IsEnum and prefEnableEnumEditor then begin
EnumEditor := TEnumEditorLink.Create;
EnumEditor.ValueList := FDataGridResult.Columns[Column].ValueList;
EditLink := EnumEditor;
end else if FDataGridResult.Columns[Column].IsSet and prefEnableSetEditor then begin
SetEditor := TSetEditorLink.Create;
SetEditor.ValueList := FDataGridResult.Columns[Column].ValueList;
EditLink := SetEditor;
end else
EditLink := TStringEditLink.Create;
end;
function TMDIChild.GetSelTableColumns: TDataset;
begin
if FLastSelectedTableColumns = nil then
FLastSelectedTableColumns := GetResults( 'SHOW /*!32332 FULL */ COLUMNS FROM ' + mask(SelectedTable), false );
Result := FLastSelectedTableColumns;
end;
function TMDIChild.GetSelTableKeys: TDataset;
begin
if FLastSelectedTableKeys = nil then
FLastSelectedTableKeys := GetResults( 'SHOW KEYS FROM ' + mask(SelectedTable) );
Result := FLastSelectedTableKeys;
end;
procedure TMDIChild.menuShowSizeColumnClick(Sender: TObject);
var
reg: TRegistry;
NewVal: Boolean;
begin
NewVal := not TMenuItem(Sender).Checked;
TMenuItem(Sender).Checked := newVal;
if NewVal then
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]
else
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible];
reg := TRegistry.Create;
reg.OpenKey(REGPATH, true);
reg.WriteBool(REGNAME_SIZECOL_TREE, NewVal);
reg.CloseKey;
FreeAndNil(reg);
end;
procedure TMDIChild.AutoCalcColWidths(Tree: TVirtualStringTree; PrevLayout: Widestrings.TWideStringlist = nil);
var
Node: PVirtualNode;
i, j, ColTextWidth: Integer;
Rect: TRect;
Col: TVirtualTreeColumn;
begin
// Find optimal default width for columns. Needs to be done late, after the SQL
// composing to enable text width calculation based on actual table content
Tree.BeginUpdate;
// Weird: Fixes first time calculation always based on Tahoma/8pt font
Tree.Canvas.Font := Tree.Font;
for i := 0 to Tree.Header.Columns.Count - 1 do begin
Col := Tree.Header.Columns[i];
if not (coVisible in Col.Options) then
continue;
if (PrevLayout <> nil) and (PrevLayout.IndexOfName(Col.Text) > -1) then begin
Col.Width := MakeInt(PrevLayout.Values[Col.Text]);
continue;
end;
ColTextWidth := Tree.Canvas.TextWidth(Tree.Header.Columns[i].Text);
// Add space for sort glyph
if Col.ImageIndex > -1 then
ColTextWidth := ColTextWidth + 20;
Node := Tree.GetFirstVisible;
// Go backwards 50 nodes from focused one if tree was scrolled
j := 0;
if Assigned(Tree.FocusedNode) then begin
Node := Tree.FocusedNode;
while Assigned(Node) do begin
inc(j);
if (Node = Tree.GetFirst) or (j > 50) then
break;
Node := Tree.GetPreviousVisible(Node);
end;
end;
j := 0;
while Assigned(Node) do begin
Rect := Tree.GetDisplayRect(Node, i, True, True);
ColTextWidth := Max(ColTextWidth, Rect.Right - Rect.Left);
inc(j);
if j > 100 then
break;
Node := Tree.GetNextVisible(Node);
end;
// text margins and minimal extra space
ColTextWidth := ColTextWidth + Tree.TextMargin*2 + 5;
ColTextWidth := Min(ColTextWidth, prefMaxColWidth);
Col.Width := ColTextWidth;
end;
Tree.EndUpdate;
end;
procedure TMDIChild.DataGridColumnResize(Sender: TVTHeader;
Column: TColumnIndex);
var
col: TVirtualTreeColumn;
begin
// Avoid AVs
if Column < 0 then
Exit;
// Don't waste time storing changes while a column is automatically resized
if tsUpdating in Sender.Treeview.TreeStates then
Exit;
if PrevTableColWidths = nil then
PrevTableColWidths := WideStrings.TWideStringList.Create;
col := Sender.Columns[Column];
PrevTableColWidths.Values[col.Text] := inttostr(col.Width);
end;
procedure TMDIChild.DBtreeClick(Sender: TObject);
begin
// Auto resize "Size" column in dbtree when needed
if coVisible in DBTree.Header.Columns[1].Options then
DBTree.Header.AutoFitColumns(False, smaUseColumnOption, 1, 1);
end;
procedure TMDIChild.GridBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
gr: PGridResult;
begin
if Column = -1 then
Exit;
if Sender = DataGrid then gr := @FDataGridResult
else gr := @FQueryGridResult;
if prefEnableNullBG and gr.Rows[Node.Index].Cells[Column].IsNull then begin
TargetCanvas.Brush.Color := prefNullBG;
TargetCanvas.FillRect(CellRect);
end;
end;
end.