Implement query history in helpers tree. See http://www.heidisql.com/forum.php?t=8927 .

This commit is contained in:
Ansgar Becker
2012-03-19 23:45:09 +00:00
parent 0d0fad6745
commit 20ad253cca
3 changed files with 352 additions and 63 deletions

View File

@ -171,7 +171,6 @@ type
function GetRegValue( valueName: String; defaultValue: Boolean; Session: String = '' ) : Boolean; Overload; function GetRegValue( valueName: String; defaultValue: Boolean; Session: String = '' ) : Boolean; Overload;
function GetRegValue( valueName: String; defaultValue: String; Session: String = '' ) : String; Overload; function GetRegValue( valueName: String; defaultValue: String; Session: String = '' ) : String; Overload;
procedure DeInitializeVTNodes(Sender: TBaseVirtualTree); procedure DeInitializeVTNodes(Sender: TBaseVirtualTree);
function CompareNumbers(List: TStringList; Index1, Index2: Integer): Integer;
function ListIndexByRegExpr(List: TStrings; Expression: String): Integer; function ListIndexByRegExpr(List: TStrings; Expression: String): Integer;
function FindNode(VT: TVirtualStringTree; idx: Cardinal; ParentNode: PVirtualNode): PVirtualNode; function FindNode(VT: TVirtualStringTree; idx: Cardinal; ParentNode: PVirtualNode): PVirtualNode;
procedure SelectNode(VT: TVirtualStringTree; idx: Cardinal; ParentNode: PVirtualNode=nil); overload; procedure SelectNode(VT: TVirtualStringTree; idx: Cardinal; ParentNode: PVirtualNode=nil); overload;
@ -195,6 +194,8 @@ type
function LoadConnectionParams(Session: String): TConnectionParameters; function LoadConnectionParams(Session: String): TConnectionParameters;
function CharAtPos(Str: String; Pos: Integer): Char; function CharAtPos(Str: String; Pos: Integer): Char;
function CompareAnyNode(Text1, Text2: String): Integer; function CompareAnyNode(Text1, Text2: String): Integer;
function StringListCompareAnythingAsc(List: TStringList; Index1, Index2: Integer): Integer;
function StringListCompareAnythingDesc(List: TStringList; Index1, Index2: Integer): Integer;
function GetColumnDefaultType(var Text: String): TColumnDefaultType; function GetColumnDefaultType(var Text: String): TColumnDefaultType;
function GetColumnDefaultClause(DefaultType: TColumnDefaultType; Text: String): String; function GetColumnDefaultClause(DefaultType: TColumnDefaultType; Text: String): String;
function GetImageLinkTimeStamp(const FileName: string): TDateTime; function GetImageLinkTimeStamp(const FileName: string): TDateTime;
@ -1572,22 +1573,6 @@ begin
end; end;
function CompareNumbers(List: TStringList; Index1, Index2: Integer): Integer;
var
Number1, Number2 : Extended;
begin
// Custom sort method for TStringLists
Number1 := MakeFloat( List[Index1] );
Number2 := MakeFloat( List[Index2] );
if Number1 > Number2 then
Result := 1
else if Number1 = Number2 then
Result := 0
else
Result := -1;
end;
function ListIndexByRegExpr(List: TStrings; Expression: String): Integer; function ListIndexByRegExpr(List: TStrings; Expression: String): Integer;
var var
rx: TRegExpr; rx: TRegExpr;
@ -2407,6 +2392,20 @@ begin
end; end;
function StringListCompareAnythingAsc(List: TStringList; Index1, Index2: Integer): Integer;
begin
// Sort TStringList items, containing numbers or strings, ascending
Result := CompareAnyNode(List[Index1], List[Index2]);
end;
function StringListCompareAnythingDesc(List: TStringList; Index1, Index2: Integer): Integer;
begin
// Sort TStringList items, containing numbers or strings, descending
Result := CompareAnyNode(List[Index2], List[Index1]);
end;
function GetColumnDefaultType(var Text: String): TColumnDefaultType; function GetColumnDefaultType(var Text: String): TColumnDefaultType;
begin begin
Result := TColumnDefaultType(MakeInt(Copy(Text, 1, 1))); Result := TColumnDefaultType(MakeInt(Copy(Text, 1, 1)));

View File

@ -1537,7 +1537,7 @@ object MainForm: TMainForm
Images = ImageListMain Images = ImageListMain
IncrementalSearch = isAll IncrementalSearch = isAll
PopupMenu = popupQueryHelpers PopupMenu = popupQueryHelpers
RootNodeCount = 5 RootNodeCount = 6
TabOrder = 1 TabOrder = 1
TextMargin = 0 TextMargin = 0
TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScrollOnExpand, toAutoSpanColumns, toAutoTristateTracking, toAutoDeleteMovedNodes] TreeOptions.AutoOptions = [toAutoDropExpand, toAutoScrollOnExpand, toAutoSpanColumns, toAutoTristateTracking, toAutoDeleteMovedNodes]
@ -1548,6 +1548,7 @@ object MainForm: TMainForm
OnContextPopup = treeQueryHelpersContextPopup OnContextPopup = treeQueryHelpersContextPopup
OnDblClick = treeQueryHelpersDblClick OnDblClick = treeQueryHelpersDblClick
OnFocusChanging = treeQueryHelpersFocusChanging OnFocusChanging = treeQueryHelpersFocusChanging
OnFreeNode = treeQueryHelpersFreeNode
OnGetText = treeQueryHelpersGetText OnGetText = treeQueryHelpersGetText
OnPaintText = treeQueryHelpersPaintText OnPaintText = treeQueryHelpersPaintText
OnGetImageIndex = treeQueryHelpersGetImageIndex OnGetImageIndex = treeQueryHelpersGetImageIndex

View File

@ -15,7 +15,7 @@ uses
SynEdit, SynEditTypes, SynEditKeyCmds, VirtualTrees, DateUtils, SyncObjs, SynEdit, SynEditTypes, SynEditKeyCmds, VirtualTrees, DateUtils, SyncObjs,
ShlObj, SynEditMiscClasses, SynEditSearch, SynEditRegexSearch, SynCompletionProposal, SynEditHighlighter, ShlObj, SynEditMiscClasses, SynEditSearch, SynEditRegexSearch, SynCompletionProposal, SynEditHighlighter,
SynHighlighterSQL, Tabs, SynUnicode, SynRegExpr, ExtActns, IOUtils, Types, Themes, ComObj, SynHighlighterSQL, Tabs, SynUnicode, SynRegExpr, ExtActns, IOUtils, Types, Themes, ComObj,
CommCtrl, Contnrs, Generics.Collections, SynEditExport, SynExportHTML, Math, ExtDlgs, Registry, AppEvnts, CommCtrl, Contnrs, Generics.Collections, Generics.Defaults, SynEditExport, SynExportHTML, Math, ExtDlgs, Registry, AppEvnts,
routine_editor, trigger_editor, event_editor, options, EditVar, helpers, createdatabase, table_editor, routine_editor, trigger_editor, event_editor, options, EditVar, helpers, createdatabase, table_editor,
TableTools, View, Usermanager, SelectDBObject, connections, sqlhelp, dbconnection, TableTools, View, Usermanager, SelectDBObject, connections, sqlhelp, dbconnection,
insertfiles, searchreplace, loaddata, copytable, VTHeaderPopup, Cromis.DirectoryWatch, SyncDB; insertfiles, searchreplace, loaddata, copytable, VTHeaderPopup, Cromis.DirectoryWatch, SyncDB;
@ -59,6 +59,7 @@ type
QueryProfile: TDBQuery; QueryProfile: TDBQuery;
ProfileTime, MaxProfileTime: Extended; ProfileTime, MaxProfileTime: Extended;
LeftOffsetInMemo: Integer; LeftOffsetInMemo: Integer;
HistoryDays: TStringList;
function GetActiveResultTab: TResultTab; function GetActiveResultTab: TResultTab;
procedure DirectoryWatchNotify(const Sender: TObject; const Action: TWatchAction; const FileName: string); procedure DirectoryWatchNotify(const Sender: TObject; const Action: TWatchAction; const FileName: string);
procedure MemofileModifiedTimerNotify(Sender: TObject); procedure MemofileModifiedTimerNotify(Sender: TObject);
@ -70,6 +71,24 @@ type
destructor Destroy; override; destructor Destroy; override;
end; end;
TQueryHistoryItem = class(TObject)
Time: TDateTime;
Database: String;
SQL: String;
Duration: Cardinal;
RegValue: Integer;
end;
TQueryHistory = class(TObjectList<TQueryHistoryItem>)
private
FMaxDuration: Cardinal;
public
property MaxDuration: Cardinal read FMaxDuration;
function ReadItem(RegValue: Integer): TQueryHistoryItem;
end;
TQueryHistoryItemComparer = class(TComparer<TQueryHistoryItem>)
function Compare(const Left, Right: TQueryHistoryItem): Integer; override;
end;
ITaskbarList = interface(IUnknown) ITaskbarList = interface(IUnknown)
[SID_ITaskbarList] [SID_ITaskbarList]
function HrInit: HRESULT; stdcall; function HrInit: HRESULT; stdcall;
@ -742,6 +761,7 @@ type
function ActiveQueryTab: TQueryTab; function ActiveQueryTab: TQueryTab;
function ActiveOrEmptyQueryTab(ConsiderActiveTab: Boolean): TQueryTab; function ActiveOrEmptyQueryTab(ConsiderActiveTab: Boolean): TQueryTab;
function GetQueryTabByNumber(Number: Integer): TQueryTab; function GetQueryTabByNumber(Number: Integer): TQueryTab;
function GetQueryTabByHelpers(FindTree: TBaseVirtualTree): TQueryTab;
function ActiveQueryMemo: TSynMemo; function ActiveQueryMemo: TSynMemo;
function ActiveQueryHelpers: TVirtualStringTree; function ActiveQueryHelpers: TVirtualStringTree;
function ActiveSynMemo: TSynMemo; function ActiveSynMemo: TSynMemo;
@ -825,6 +845,7 @@ type
var ContentRect: TRect); var ContentRect: TRect);
procedure treeQueryHelpersDblClick(Sender: TObject); procedure treeQueryHelpersDblClick(Sender: TObject);
procedure treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); procedure treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
procedure treeQueryHelpersFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; procedure treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure treeQueryHelpersFocusChanging(Sender: TBaseVirtualTree; OldNode, procedure treeQueryHelpersFocusChanging(Sender: TBaseVirtualTree; OldNode,
@ -2320,11 +2341,16 @@ end;
procedure TMainForm.FinishedQueryExecution(Thread: TQueryThread); procedure TMainForm.FinishedQueryExecution(Thread: TQueryThread);
var var
Tab, WarningsTab: TQueryTab; Tab, WarningsTab: TQueryTab;
MetaInfo, ErroneousSQL, MsgTitle, MsgText: String; MetaInfo, ErroneousSQL, RegName, RegItem, MsgTitle, MsgText: String;
ProfileAllTime: Extended; ProfileAllTime: Extended;
ProfileNode: PVirtualNode; ProfileNode: PVirtualNode;
AllRegItems: TStringList;
History: TQueryHistory;
HistoryItem: TQueryHistoryItem;
Warnings: TDBQuery; Warnings: TDBQuery;
MaxWarnings: Integer; HistoryNum, MaxWarnings, RegItemsSize: Integer;
DoDelete: Boolean;
MinDate: TDateTime;
procedure GoToErrorPos(Err: String); procedure GoToErrorPos(Err: String);
var var
@ -2430,6 +2456,59 @@ begin
end; end;
end; end;
// Store successful query packet in history if it's not a batch.
// Assume that a bunch of up to 5 queries is not a batch.
if IsEmpty(Thread.ErrorMessage) and (Thread.Batch.Count <= 5) and (Thread.Batch.Size <= SIZE_MB) then begin
ShowStatusMsg('Updating query history ...');
OpenRegistry(Thread.Connection.Parameters.SessionName);
MainReg.OpenKey(REGKEY_QUERYHISTORY, true);
// Load all items so we can clean up
AllRegItems := TStringList.Create;
MainReg.GetValueNames(AllRegItems);
History := TQueryHistory.Create;
for RegItem in AllRegItems do begin
History.ReadItem(StrToInt(RegItem));
end;
// Find lowest unused item number
HistoryNum := 0;
while True do begin
Inc(HistoryNum);
RegName := IntToStr(HistoryNum);
if AllRegItems.IndexOf(RegName) = -1 then
break;
end;
// Sort by date
History.Sort(TQueryHistoryItemComparer.Create);
// Delete identical history items to avoid spam
// Delete old items
// Delete items which exceed a max datasize barrier
MinDate := IncDay(Now, -30);
RegItemsSize := Thread.Batch.Size;
for HistoryItem in History do begin
Inc(RegItemsSize, Length(HistoryItem.SQL));
DoDelete := (HistoryItem.SQL = Thread.Batch.SQL)
or (HistoryItem.Time < MinDate)
or (RegItemsSize > SIZE_MB);
if DoDelete then
MainReg.DeleteValue(IntToStr(HistoryItem.RegValue));
end;
History.Free;
// Store history item and closing registry key to ensure writing has finished
MainReg.WriteString(RegName, DateTimeToStr(Now) + DELIM +
Thread.Connection.Database + DELIM +
IntToStr(Thread.QueryTime+Thread.QueryNetTime) + DELIM +
Thread.Batch.SQL);
MainReg.CloseKey;
RefreshHelperNode(HELPERNODE_HISTORY);
end;
// Clean up // Clean up
DisableProgress; DisableProgress;
Tab.QueryRunning := False; Tab.QueryRunning := False;
@ -3823,7 +3902,7 @@ begin
Filters.Add(Trim(SynMemoFilter.Text)); Filters.Add(Trim(SynMemoFilter.Text));
MainReg.OpenKey(GetRegKeyTable+'\'+REGNAME_FILTERS, True); MainReg.OpenKey(GetRegKeyTable+'\'+REGNAME_FILTERS, True);
MainReg.GetValueNames(OldNumbers); MainReg.GetValueNames(OldNumbers);
OldNumbers.CustomSort(CompareNumbers); OldNumbers.CustomSort(StringListCompareAnythingAsc);
// Add old filters // Add old filters
for i := 0 to OldNumbers.Count - 1 do begin for i := 0 to OldNumbers.Count - 1 do begin
nr := MakeInt(OldNumbers[i]); nr := MakeInt(OldNumbers[i]);
@ -5304,7 +5383,7 @@ begin
src := Source as TControl; src := Source as TControl;
// Accepting drag's from DBTree and QueryHelpers // Accepting drag's from DBTree and QueryHelpers
H := ActiveQueryHelpers; H := ActiveQueryHelpers;
Accept := (src = DBtree) or ((src = H) and Assigned(H.FocusedNode) and (H.GetNodeLevel(H.FocusedNode)=1)); Accept := (src = DBtree) or ((src = H) and Assigned(H.FocusedNode) and (H.GetNodeLevel(H.FocusedNode) in [1,2]));
// set x-position of cursor // set x-position of cursor
Memo.CaretX := (x - Memo.Gutter.Width) div Memo.CharWidth - 1 + Memo.LeftChar; Memo.CaretX := (x - Memo.Gutter.Width) div Memo.CharWidth - 1 + Memo.LeftChar;
// set y-position of cursor // set y-position of cursor
@ -5322,6 +5401,7 @@ var
ShiftPressed: Boolean; ShiftPressed: Boolean;
Tree: TVirtualStringTree; Tree: TVirtualStringTree;
Node: PVirtualNode; Node: PVirtualNode;
History: TQueryHistory;
begin begin
// dropping a tree node or listbox item into the query-memo // dropping a tree node or listbox item into the query-memo
ActiveQueryMemo.UndoList.AddGroupBreak; ActiveQueryMemo.UndoList.AddGroupBreak;
@ -5342,27 +5422,37 @@ begin
end; end;
end; end;
end else if src = Tree then begin end else if src = Tree then begin
if (Tree.GetNodeLevel(Tree.FocusedNode) = 1) and Assigned(Tree.FocusedNode) then begin case Tree.GetNodeLevel(Tree.FocusedNode) of
case Tree.FocusedNode.Parent.Index of 1:
HELPERNODE_SNIPPETS: case Tree.FocusedNode.Parent.Index of
Text := ReadTextFile(FDirnameSnippets + Tree.Text[Tree.FocusedNode, 0] + '.sql', nil); HELPERNODE_SNIPPETS:
else begin Text := ReadTextFile(FDirnameSnippets + Tree.Text[Tree.FocusedNode, 0] + '.sql', nil);
Node := Tree.GetFirstChild(Tree.FocusedNode.Parent); HELPERNODE_HISTORY:
while Assigned(Node) do begin Text := '';
if Tree.Selected[Node] then begin else begin
ItemText := Tree.Text[Node, 0]; Node := Tree.GetFirstChild(Tree.FocusedNode.Parent);
if Node.Parent.Index = HELPERNODE_COLUMNS then while Assigned(Node) do begin
ItemText := ActiveConnection.QuoteIdent(ItemText, False); // Quote column names if Tree.Selected[Node] then begin
if ShiftPressed then ItemText := Tree.Text[Node, 0];
Text := Text + ItemText + ',' + CRLF if Node.Parent.Index = HELPERNODE_COLUMNS then
else ItemText := ActiveConnection.QuoteIdent(ItemText, False); // Quote column names
Text := Text + ItemText + ', '; if ShiftPressed then
Text := Text + ItemText + ',' + CRLF
else
Text := Text + ItemText + ', ';
end;
Node := Tree.GetNextSibling(Node);
end; end;
Node := Tree.GetNextSibling(Node); Delete(Text, Length(Text)-1, 2);
end; end;
Delete(Text, Length(Text)-1, 2);
end; end;
end; 2:
case Tree.FocusedNode.Parent.Parent.Index of
HELPERNODE_HISTORY: begin
History := ActiveQueryTab.HistoryDays.Objects[Tree.FocusedNode.Parent.Index] as TQueryHistory;
Text := History[Tree.FocusedNode.Index].SQL;
end;
end;
end; end;
end else end else
raise Exception.Create('Unspecified source control in drag''n drop operation!'); raise Exception.Create('Unspecified source control in drag''n drop operation!');
@ -7042,6 +7132,7 @@ begin
if (PrevDBObj = nil) or (PrevDBObj.Connection <> FActiveDbObj.Connection) then begin if (PrevDBObj = nil) or (PrevDBObj.Connection <> FActiveDbObj.Connection) then begin
LogSQL('Entering session "'+FActiveDbObj.Connection.Parameters.SessionName+'"', lcInfo); LogSQL('Entering session "'+FActiveDbObj.Connection.Parameters.SessionName+'"', lcInfo);
DBTree.Color := GetRegValue(REGNAME_TREEBACKGROUND, clWindow, FActiveDbObj.Connection.Parameters.SessionName); DBTree.Color := GetRegValue(REGNAME_TREEBACKGROUND, clWindow, FActiveDbObj.Connection.Parameters.SessionName);
RefreshHelperNode(HELPERNODE_HISTORY);
case FActiveDbObj.Connection.Parameters.NetTypeGroup of case FActiveDbObj.Connection.Parameters.NetTypeGroup of
ngMySQL: ngMySQL:
SynSQLSyn1.SQLDialect := sqlMySQL; SynSQLSyn1.SQLDialect := sqlMySQL;
@ -7188,6 +7279,8 @@ procedure TMainForm.DatabaseChanged(Connection: TDBConnection; Database: String)
begin begin
// Immediately force db icons to repaint, so the user sees the active db state // Immediately force db icons to repaint, so the user sees the active db state
DBtree.Repaint; DBtree.Repaint;
if ActiveQueryHelpers <> nil then
ActiveQueryHelpers.Invalidate;
end; end;
@ -8770,6 +8863,7 @@ begin
QueryTab.treeHelpers.OnBeforeCellPaint := treeQueryHelpers.OnBeforeCellPaint; QueryTab.treeHelpers.OnBeforeCellPaint := treeQueryHelpers.OnBeforeCellPaint;
QueryTab.treeHelpers.OnContextPopup := treeQueryHelpers.OnContextPopup; QueryTab.treeHelpers.OnContextPopup := treeQueryHelpers.OnContextPopup;
QueryTab.treeHelpers.OnDblClick := treeQueryHelpers.OnDblClick; QueryTab.treeHelpers.OnDblClick := treeQueryHelpers.OnDblClick;
QueryTab.treeHelpers.OnFreeNode := treeQueryHelpers.OnFreeNode;
QueryTab.treeHelpers.OnGetImageIndex := treeQueryHelpers.OnGetImageIndex; QueryTab.treeHelpers.OnGetImageIndex := treeQueryHelpers.OnGetImageIndex;
QueryTab.treeHelpers.OnGetText := treeQueryHelpers.OnGetText; QueryTab.treeHelpers.OnGetText := treeQueryHelpers.OnGetText;
QueryTab.treeHelpers.OnInitChildren := treeQueryHelpers.OnInitChildren; QueryTab.treeHelpers.OnInitChildren := treeQueryHelpers.OnInitChildren;
@ -9154,6 +9248,21 @@ begin
end; end;
function TMainForm.GetQueryTabByHelpers(FindTree: TBaseVirtualTree): TQueryTab;
var
Tab: TQueryTab;
begin
// Find query tab where passed treeHelpers resides
Result := nil;
for Tab in QueryTabs do begin
if Tab.treeHelpers = FindTree then begin
Result := Tab;
break;
end;
end;
end;
function TMainForm.ActiveQueryMemo: TSynMemo; function TMainForm.ActiveQueryMemo: TSynMemo;
var var
Tab: TQueryTab; Tab: TQueryTab;
@ -9973,21 +10082,36 @@ procedure TMainForm.treeQueryHelpersBeforeCellPaint(Sender: TBaseVirtualTree; Ta
var ContentRect: TRect); var ContentRect: TRect);
var var
Tab: TQueryTab; Tab: TQueryTab;
History: TQueryHistory;
begin begin
// Paint green value bar in cell // Paint green value bar in cell
if (Node.Parent.Index=HELPERNODE_PROFILE) if (Node.Parent.Index=HELPERNODE_PROFILE)
and (Column=1) and (Column=1)
and (Sender.GetNodeLevel(Node)=1) and (Sender.GetNodeLevel(Node)=1)
then begin then begin
Tab := ActiveQueryTab; Tab := GetQueryTabByHelpers(Sender);
Tab.QueryProfile.RecNo := Node.Index; if Tab <> nil then begin
PaintColorBar(MakeFloat(Tab.QueryProfile.Col(Column)), Tab.MaxProfileTime, TargetCanvas, CellRect); Tab.QueryProfile.RecNo := Node.Index;
PaintColorBar(MakeFloat(Tab.QueryProfile.Col(Column)), Tab.MaxProfileTime, TargetCanvas, CellRect);
end;
end;
if (Sender.GetNodeLevel(Node)=2)
and (Column=1)
and (Node.Parent.Parent.Index=HELPERNODE_HISTORY) then begin
Tab := GetQueryTabByHelpers(Sender);
if Tab <> nil then begin
History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory;
PaintColorBar(History[Node.Index].Duration, History.MaxDuration, TargetCanvas, CellRect);
end;
end; end;
end; end;
procedure TMainForm.treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; procedure TMainForm.treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
var
History: TQueryHistory;
Tab: TQueryTab;
begin begin
// Paint text in datatype's color // Paint text in datatype's color
if (Node.Parent.Index=HELPERNODE_COLUMNS) if (Node.Parent.Index=HELPERNODE_COLUMNS)
@ -9997,6 +10121,15 @@ begin
then begin then begin
TargetCanvas.Font.Color := DatatypeCategories[SelectedTableColumns[Node.Index].DataType.Category].Color; TargetCanvas.Font.Color := DatatypeCategories[SelectedTableColumns[Node.Index].DataType.Category].Color;
end; end;
if (Sender.GetNodeLevel(Node)=2)
and (Node.Parent.Parent.Index=HELPERNODE_HISTORY) then begin
Tab := GetQueryTabByHelpers(Sender);
if Tab <> nil then begin
History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory;
if ActiveConnection.Database <> History[Node.Index].Database then
TargetCanvas.Font.Color := clGrayText;
end;
end;
end; end;
@ -10037,6 +10170,24 @@ begin
end; end;
procedure TMainForm.treeQueryHelpersFreeNode(Sender: TBaseVirtualTree;
Node: PVirtualNode);
var
Values: TQueryHistory;
Tab: TQueryTab;
begin
// Free some memory, taken by probably big SQL query history items
if (Sender.GetNodeLevel(Node)=1)
and (Node.Parent.Index = HELPERNODE_HISTORY) then begin
Tab := GetQueryTabByHelpers(Sender);
if Tab <> nil then begin
Tab.HistoryDays.Objects[Node.Index].Free;
Tab.HistoryDays.Delete(Node.Index);
end;
end;
end;
procedure TMainForm.treeQueryHelpersGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; procedure TMainForm.treeQueryHelpersGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
begin begin
@ -10054,6 +10205,7 @@ begin
HELPERNODE_FUNCTIONS: ImageIndex := 13; HELPERNODE_FUNCTIONS: ImageIndex := 13;
HELPERNODE_KEYWORDS: ImageIndex := 25; HELPERNODE_KEYWORDS: ImageIndex := 25;
HELPERNODE_SNIPPETS: ImageIndex := 51; HELPERNODE_SNIPPETS: ImageIndex := 51;
HELPERNODE_HISTORY: ImageIndex := 149;
HELPERNODE_PROFILE: ImageIndex := 145; HELPERNODE_PROFILE: ImageIndex := 145;
end; end;
1: case Node.Parent.Index of 1: case Node.Parent.Index of
@ -10061,6 +10213,7 @@ begin
HELPERNODE_FUNCTIONS: ImageIndex := 13; HELPERNODE_FUNCTIONS: ImageIndex := 13;
HELPERNODE_KEYWORDS: ImageIndex := 25; HELPERNODE_KEYWORDS: ImageIndex := 25;
HELPERNODE_SNIPPETS: ImageIndex := 68; HELPERNODE_SNIPPETS: ImageIndex := 68;
HELPERNODE_HISTORY: ImageIndex := 80;
HELPERNODE_PROFILE: ImageIndex := 145; HELPERNODE_PROFILE: ImageIndex := 145;
end; end;
end; end;
@ -10069,9 +10222,13 @@ end;
procedure TMainForm.treeQueryHelpersGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; procedure TMainForm.treeQueryHelpersGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
History: TQueryHistory;
Tab: TQueryTab;
begin begin
// Query helpers tree fetching node text // Query helpers tree fetching node text
CellText := ''; CellText := '';
Tab := GetQueryTabByHelpers(Sender);
case Column of case Column of
0: case Sender.GetNodeLevel(Node) of 0: case Sender.GetNodeLevel(Node) of
0: case Node.Index of 0: case Node.Index of
@ -10085,10 +10242,11 @@ begin
HELPERNODE_FUNCTIONS: CellText := 'SQL Functions'; HELPERNODE_FUNCTIONS: CellText := 'SQL Functions';
HELPERNODE_KEYWORDS: CellText := 'SQL Keywords'; HELPERNODE_KEYWORDS: CellText := 'SQL Keywords';
HELPERNODE_SNIPPETS: CellText := 'Snippets'; HELPERNODE_SNIPPETS: CellText := 'Snippets';
HELPERNODE_HISTORY: CellText := 'Query history';
HELPERNODE_PROFILE: begin HELPERNODE_PROFILE: begin
CellText := 'Query profile'; CellText := 'Query profile';
if Assigned(ActiveQueryTab.QueryProfile) then if Assigned(Tab.QueryProfile) then
CellText := CellText + ' ('+FormatNumber(ActiveQueryTab.ProfileTime, 6)+'s)'; CellText := CellText + ' ('+FormatNumber(Tab.ProfileTime, 6)+'s)';
end; end;
end; end;
1: case Node.Parent.Index of 1: case Node.Parent.Index of
@ -10103,13 +10261,27 @@ begin
HELPERNODE_FUNCTIONS: CellText := MySQLFunctions[Node.Index].Name; HELPERNODE_FUNCTIONS: CellText := MySQLFunctions[Node.Index].Name;
HELPERNODE_KEYWORDS: CellText := MySQLKeywords[Node.Index]; HELPERNODE_KEYWORDS: CellText := MySQLKeywords[Node.Index];
HELPERNODE_SNIPPETS: CellText := FSnippetFilenames[Node.Index]; HELPERNODE_SNIPPETS: CellText := FSnippetFilenames[Node.Index];
HELPERNODE_HISTORY: begin
CellText := Tab.HistoryDays[Node.Index];
if CellText = DateToStr(Today) then
CellText := CellText + ', today'
else if CellText = DateToStr(Yesterday) then
CellText := CellText + ', yesterday';
end;
HELPERNODE_PROFILE: begin HELPERNODE_PROFILE: begin
if Assigned(ActiveQueryTab.QueryProfile) then begin if Assigned(Tab.QueryProfile) then begin
ActiveQueryTab.QueryProfile.RecNo := Node.Index; Tab.QueryProfile.RecNo := Node.Index;
CellText := ActiveQueryTab.QueryProfile.Col(Column); CellText := Tab.QueryProfile.Col(Column);
end; end;
end; end;
end; end;
2: case Node.Parent.Parent.Index of
HELPERNODE_HISTORY: begin
History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory;
CellText := Copy(TimeToStr(History[Node.Index].Time), 1, 5)+': '+History[Node.Index].SQL;
end
else CellText := ''; // unused
end;
end; end;
1: case Sender.GetNodeLevel(Node) of 1: case Sender.GetNodeLevel(Node) of
0: CellText := ''; 0: CellText := '';
@ -10119,13 +10291,19 @@ begin
CellText := SelectedTableColumns[Node.Index].DataType.Name; CellText := SelectedTableColumns[Node.Index].DataType.Name;
HELPERNODE_FUNCTIONS: CellText := MySQLFunctions[Node.Index].Declaration; HELPERNODE_FUNCTIONS: CellText := MySQLFunctions[Node.Index].Declaration;
HELPERNODE_PROFILE: begin HELPERNODE_PROFILE: begin
if Assigned(ActiveQueryTab.QueryProfile) then begin if Assigned(Tab.QueryProfile) then begin
ActiveQueryTab.QueryProfile.RecNo := Node.Index; Tab.QueryProfile.RecNo := Node.Index;
CellText := FormatNumber(ActiveQueryTab.QueryProfile.Col(Column))+'s'; CellText := FormatNumber(Tab.QueryProfile.Col(Column))+'s';
end; end;
end; end;
else CellText := ''; else CellText := '';
end; end;
2: case Node.Parent.Parent.Index of
HELPERNODE_HISTORY: begin
History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory;
CellText := FormatNumber(History[Node.Index].Duration / 1000, 3)+'s';
end;
end;
end; end;
end; end;
end; end;
@ -10135,17 +10313,30 @@ procedure TMainForm.treeQueryHelpersInitNode(Sender: TBaseVirtualTree; ParentNod
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin begin
// Query helpers tree asking if plus/minus button should be displayed // Query helpers tree asking if plus/minus button should be displayed
if Sender.GetNodeLevel(Node) = 0 then begin case Sender.GetNodeLevel(Node) of
Include(InitialStates, ivsHasChildren); 0: begin
if Node.Index = HELPERNODE_PROFILE then Include(InitialStates, ivsHasChildren);
Node.CheckType := ctCheckbox; if Node.Index = HELPERNODE_PROFILE then
Node.CheckType := ctCheckbox;
end;
1: begin
if Node.Parent.Index = HELPERNODE_HISTORY then
Include(InitialStates, ivsHasChildren);
end;
end; end;
end; end;
procedure TMainForm.treeQueryHelpersInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; procedure TMainForm.treeQueryHelpersInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode;
var ChildCount: Cardinal); var ChildCount: Cardinal);
var
Values: TStringList;
v, QueryDay: String;
History: TQueryHistory;
Item: TQueryHistoryItem;
Tab: TQueryTab;
begin begin
Tab := GetQueryTabByHelpers(Sender);
case Sender.GetNodeLevel(Node) of case Sender.GetNodeLevel(Node) of
0: case Node.Index of 0: case Node.Index of
HELPERNODE_COLUMNS: begin HELPERNODE_COLUMNS: begin
@ -10163,10 +10354,50 @@ begin
HELPERNODE_FUNCTIONS: ChildCount := Length(MySQLFunctions); HELPERNODE_FUNCTIONS: ChildCount := Length(MySQLFunctions);
HELPERNODE_KEYWORDS: ChildCount := MySQLKeywords.Count; HELPERNODE_KEYWORDS: ChildCount := MySQLKeywords.Count;
HELPERNODE_SNIPPETS: ChildCount := FSnippetFilenames.Count; HELPERNODE_SNIPPETS: ChildCount := FSnippetFilenames.Count;
HELPERNODE_PROFILE: if not Assigned(ActiveQueryTab.QueryProfile) then ChildCount := 0 HELPERNODE_HISTORY: begin
else ChildCount := ActiveQueryTab.QueryProfile.RecordCount; // Find all unique days in history
if not Assigned(Tab.HistoryDays) then
Tab.HistoryDays := TStringList.Create;
Tab.HistoryDays.Clear;
OpenRegistry(ActiveConnection.Parameters.SessionName);
MainReg.OpenKey(REGKEY_QUERYHISTORY, true);
Values := TStringList.Create;
MainReg.GetValueNames(Values);
History := TQueryHistory.Create;
for v in Values do begin
Item := History.ReadItem(StrToInt(v));
QueryDay := DateToStr(Item.Time);
if Tab.HistoryDays.IndexOf(QueryDay) = -1 then
Tab.HistoryDays.Add(QueryDay);
end;
History.Free;
Values.Free;
Tab.HistoryDays.CustomSort(StringListCompareAnythingDesc);
ChildCount := Tab.HistoryDays.Count;
end;
HELPERNODE_PROFILE: if not Assigned(Tab.QueryProfile) then ChildCount := 0
else ChildCount := Tab.QueryProfile.RecordCount;
end; end;
1: ChildCount := 0; 1: case Node.Parent.Index of
HELPERNODE_HISTORY: begin
History := TQueryHistory.Create;
Tab.HistoryDays.Objects[Node.Index] := History;
OpenRegistry(ActiveConnection.Parameters.SessionName);
MainReg.OpenKey(REGKEY_QUERYHISTORY, true);
Values := TStringList.Create;
MainReg.GetValueNames(Values);
for v in Values do begin
Item := History.ReadItem(StrToInt(v));
QueryDay := DateToStr(Item.Time);
if QueryDay <> Tab.HistoryDays[Node.Index] then
History.Remove(Item);
end;
History.Sort(TQueryHistoryItemComparer.Create);
ChildCount := History.Count;
Values.Free;
end;
else ChildCount := 0;
end;
end; end;
end; end;
@ -10240,16 +10471,39 @@ end;
procedure TMainForm.RefreshHelperNode(NodeIndex: Cardinal); procedure TMainForm.RefreshHelperNode(NodeIndex: Cardinal);
var var
Tab: TQueryTab; Tab: TQueryTab;
Node: PVirtualNode; Node, Child: PVirtualNode;
OldStates: TVirtualNodeStates;
OldCheckState: TCheckState;
ExpandedChildren: TStringList;
begin begin
if not Assigned(QueryTabs) then if not Assigned(QueryTabs) then
Exit; Exit;
for Tab in QueryTabs do begin for Tab in QueryTabs do begin
Node := FindNode(Tab.treeHelpers, NodeIndex, nil); Node := FindNode(Tab.treeHelpers, NodeIndex, nil);
if vsInitialized in Node.States then begin // Store node + children states
Node.States := Node.States - [vsInitialized]; OldStates := Node.States;
Tab.treeHelpers.InvalidateNode(Node); OldCheckState := Node.CheckState;
ExpandedChildren := TStringList.Create;
Child := Tab.treeHelpers.GetFirstChild(Node);
while Assigned(Child) do begin
if vsExpanded in Child.States then
ExpandedChildren.Add(IntToStr(Child.Index));
Child := Tab.treeHelpers.GetNextSibling(Child);
end; end;
// Keep scroll offset
Tab.treeHelpers.BeginUpdate;
// Remove children and grandchildren
Tab.treeHelpers.ResetNode(Node);
// Restore old node + children states
Tab.treeHelpers.CheckState[Node] := OldCheckState;
Tab.treeHelpers.Expanded[Node] := vsExpanded in OldStates;
Child := Tab.treeHelpers.GetFirstChild(Node);
while Assigned(Child) do begin
Tab.treeHelpers.Expanded[Child] := ExpandedChildren.IndexOf(IntToStr(Child.Index)) > -1;
Child := Tab.treeHelpers.GetNextSibling(Child);
end;
ExpandedChildren.Free;
Tab.treeHelpers.EndUpdate;
end; end;
end; end;
@ -10601,6 +10855,41 @@ begin
end; end;
{ TQueryHistory }
function TQueryHistory.ReadItem(RegValue: Integer): TQueryHistoryItem;
var
p: Integer;
Raw: String;
begin
Result := TQueryHistoryItem.Create;
Result.RegValue := RegValue;
Raw := MainReg.ReadString(IntToStr(RegValue));
p := Pos(DELIM, Raw);
Result.Time := StrToDateTime(Copy(Raw, 1, p-1));
System.Delete(Raw, 1, p);
p := Pos(DELIM, Raw);
Result.Database := Copy(Raw, 1, p-1);
System.Delete(Raw, 1, p);
p := Pos(DELIM, Raw);
Result.Duration := StrToIntDef(Copy(Raw, 1, p-1), 0);
FMaxDuration := Max(FMaxDuration, Result.Duration);
Result.SQL := Copy(Raw, p+1, Length(Raw));
Add(Result);
end;
function TQueryHistoryItemComparer.Compare(const Left, Right: TQueryHistoryItem): Integer;
begin
// Simple sort method for a TDBObjectList
if Left.Time > Right.Time then
Result := -1
else if Left.Time = Right.Time then
Result := 0
else
Result := 1;
end;
end. end.