mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
Add button and global action for executing selected functions and/or procedures by click. Fixes issue #1818. This requires some parsing code to be moved to helpers unit so it's also available in ListTables' context menu. Also, to avoid new AVs, any db object editor now focuses the edited object in the tree, which is important for creating new ones which were neither existant nor focused.
This commit is contained in:
@ -10,7 +10,7 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, GraphUtil, ClipBrd, Dialogs, Forms, Controls, ComCtrls, ShellApi, CheckLst,
|
Classes, SysUtils, Graphics, GraphUtil, ClipBrd, Dialogs, Forms, Controls, ComCtrls, ShellApi, CheckLst,
|
||||||
Windows, Contnrs, ShlObj, ActiveX, VirtualTrees, SynRegExpr, Messages,
|
Windows, Contnrs, ShlObj, ActiveX, VirtualTrees, SynRegExpr, Messages, WideStrUtils,
|
||||||
Registry, SynEditHighlighter, DateUtils, Generics.Collections, StrUtils, AnsiStrings, TlHelp32, Types,
|
Registry, SynEditHighlighter, DateUtils, Generics.Collections, StrUtils, AnsiStrings, TlHelp32, Types,
|
||||||
mysql_connection, mysql_structures;
|
mysql_connection, mysql_structures;
|
||||||
|
|
||||||
@ -132,6 +132,11 @@ type
|
|||||||
end;
|
end;
|
||||||
TForeignKeyList = TObjectList<TForeignKey>;
|
TForeignKeyList = TObjectList<TForeignKey>;
|
||||||
|
|
||||||
|
TRoutineParam = class(TObject)
|
||||||
|
Name, Context, Datatype: String;
|
||||||
|
end;
|
||||||
|
TRoutineParamList = TObjectList<TRoutineParam>;
|
||||||
|
|
||||||
TDBObjectEditor = class(TFrame)
|
TDBObjectEditor = class(TFrame)
|
||||||
private
|
private
|
||||||
FModified: Boolean;
|
FModified: Boolean;
|
||||||
@ -241,6 +246,8 @@ type
|
|||||||
function GetLightness(AColor: TColor): Byte;
|
function GetLightness(AColor: TColor): Byte;
|
||||||
procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
||||||
procedure ParseViewStructure(ViewName: String; Columns: TTableColumnList);
|
procedure ParseViewStructure(ViewName: String; Columns: TTableColumnList);
|
||||||
|
procedure ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
||||||
|
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
||||||
function ReformatSQL(SQL: String): String;
|
function ReformatSQL(SQL: String): String;
|
||||||
function ParamBlobToStr(lpData: Pointer): TStringlist;
|
function ParamBlobToStr(lpData: Pointer): TStringlist;
|
||||||
function ParamStrToBlob(out cbData: DWORD): Pointer;
|
function ParamStrToBlob(out cbData: DWORD): Pointer;
|
||||||
@ -3031,6 +3038,97 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
||||||
|
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
||||||
|
var
|
||||||
|
Params: String;
|
||||||
|
ParenthesesCount: Integer;
|
||||||
|
rx: TRegExpr;
|
||||||
|
i: Integer;
|
||||||
|
Param: TRoutineParam;
|
||||||
|
begin
|
||||||
|
// Parse CREATE code of stored function or procedure to detect parameters
|
||||||
|
rx := TRegExpr.Create;
|
||||||
|
rx.ModifierI := True;
|
||||||
|
rx.ModifierG := True;
|
||||||
|
// CREATE DEFINER=`root`@`localhost` PROCEDURE `bla2`(IN p1 INT, p2 VARCHAR(20))
|
||||||
|
// CREATE DEFINER=`root`@`localhost` FUNCTION `test3`(`?b` varchar(20)) RETURNS tinyint(4)
|
||||||
|
// CREATE DEFINER=`root`@`localhost` PROCEDURE `test3`(IN `Param1` int(1) unsigned)
|
||||||
|
ParenthesesCount := 0;
|
||||||
|
Params := '';
|
||||||
|
for i:=1 to Length(CreateCode) do begin
|
||||||
|
if CreateCode[i] = ')' then begin
|
||||||
|
Dec(ParenthesesCount);
|
||||||
|
if ParenthesesCount = 0 then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
if ParenthesesCount >= 1 then
|
||||||
|
Params := Params + CreateCode[i];
|
||||||
|
if CreateCode[i] = '(' then
|
||||||
|
Inc(ParenthesesCount);
|
||||||
|
end;
|
||||||
|
rx.Expression := '(^|,)\s*((IN|OUT|INOUT)\s+)?(\S+)\s+([^\s,\(]+(\([^\)]*\))?[^,]*)';
|
||||||
|
if rx.Exec(Params) then while true do begin
|
||||||
|
Param := TRoutineParam.Create;
|
||||||
|
Param.Context := UpperCase(rx.Match[3]);
|
||||||
|
if Param.Context = '' then
|
||||||
|
Param.Context := 'IN';
|
||||||
|
Param.Name := WideDequotedStr(rx.Match[4], '`');
|
||||||
|
Param.Datatype := rx.Match[5];
|
||||||
|
Parameters.Add(Param);
|
||||||
|
if not rx.ExecNext then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Cut left part including parameters, so it's easier to parse the rest
|
||||||
|
CreateCode := Copy(CreateCode, i+1, MaxInt);
|
||||||
|
// CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body
|
||||||
|
// CREATE FUNCTION sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body
|
||||||
|
// LANGUAGE SQL
|
||||||
|
// | [NOT] DETERMINISTIC // IS_DETERMINISTIC
|
||||||
|
// | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } // DATA_ACCESS
|
||||||
|
// | SQL SECURITY { DEFINER | INVOKER } // SECURITY_TYPE
|
||||||
|
// | COMMENT 'string' // COMMENT
|
||||||
|
|
||||||
|
rx.Expression := '\bLANGUAGE SQL\b';
|
||||||
|
if rx.Exec(CreateCode) then
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
rx.Expression := '\bRETURNS\s+(\w+(\([^\)]*\))?)';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Returns := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\b(NOT\s+)?DETERMINISTIC\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Deterministic := rx.MatchLen[1] = -1;
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\b(CONTAINS SQL|NO SQL|READS SQL DATA|MODIFIES SQL DATA)\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
DataAccess := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\bSQL\s+SECURITY\s+(DEFINER|INVOKER)\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Security := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.ModifierG := False;
|
||||||
|
rx.Expression := '\bCOMMENT\s+''((.+)[^''])''[^'']';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Comment := StringReplace(rx.Match[1], '''''', '''', [rfReplaceAll]);
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
||||||
|
end;
|
||||||
|
rx.Expression := '^\s*CHARSET\s+[\w\d]+\s';
|
||||||
|
if rx.Exec(CreateCode) then
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
||||||
|
// Tata, remaining code is the routine body
|
||||||
|
Body := TrimLeft(CreateCode);
|
||||||
|
|
||||||
|
rx.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
function ReformatSQL(SQL: String): String;
|
function ReformatSQL(SQL: String): String;
|
||||||
var
|
var
|
||||||
AllKeywords, ImportantKeywords: TStringList;
|
AllKeywords, ImportantKeywords: TStringList;
|
||||||
|
@ -2461,6 +2461,12 @@ object MainForm: TMainForm
|
|||||||
ShortCut = 49187
|
ShortCut = 49187
|
||||||
OnExecute = actDataShowAllExecute
|
OnExecute = actDataShowAllExecute
|
||||||
end
|
end
|
||||||
|
object actRunRoutines: TAction
|
||||||
|
Category = 'Database'
|
||||||
|
Caption = 'Run routine(s) ...'
|
||||||
|
ImageIndex = 35
|
||||||
|
OnExecute = actRunRoutinesExecute
|
||||||
|
end
|
||||||
end
|
end
|
||||||
object SaveDialog2: TSaveDialog
|
object SaveDialog2: TSaveDialog
|
||||||
DefaultExt = 'reg'
|
DefaultExt = 'reg'
|
||||||
@ -7533,6 +7539,9 @@ object MainForm: TMainForm
|
|||||||
object menuEmptyTables: TMenuItem
|
object menuEmptyTables: TMenuItem
|
||||||
Action = actEmptyTables
|
Action = actEmptyTables
|
||||||
end
|
end
|
||||||
|
object Runroutines1: TMenuItem
|
||||||
|
Action = actRunRoutines
|
||||||
|
end
|
||||||
object menuCreateObject: TMenuItem
|
object menuCreateObject: TMenuItem
|
||||||
Caption = 'Create new'
|
Caption = 'Create new'
|
||||||
ImageIndex = 130
|
ImageIndex = 130
|
||||||
|
104
source/main.pas
104
source/main.pas
@ -470,6 +470,8 @@ type
|
|||||||
tabDatabases: TTabSheet;
|
tabDatabases: TTabSheet;
|
||||||
ListDatabases: TVirtualStringTree;
|
ListDatabases: TVirtualStringTree;
|
||||||
menuFetchDBitems: TMenuItem;
|
menuFetchDBitems: TMenuItem;
|
||||||
|
actRunRoutines: TAction;
|
||||||
|
Runroutines1: TMenuItem;
|
||||||
procedure actCreateDBObjectExecute(Sender: TObject);
|
procedure actCreateDBObjectExecute(Sender: TObject);
|
||||||
procedure menuConnectionsPopup(Sender: TObject);
|
procedure menuConnectionsPopup(Sender: TObject);
|
||||||
procedure actExitApplicationExecute(Sender: TObject);
|
procedure actExitApplicationExecute(Sender: TObject);
|
||||||
@ -762,6 +764,7 @@ type
|
|||||||
procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
||||||
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
|
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
|
||||||
procedure ListDatabasesDblClick(Sender: TObject);
|
procedure ListDatabasesDblClick(Sender: TObject);
|
||||||
|
procedure actRunRoutinesExecute(Sender: TObject);
|
||||||
private
|
private
|
||||||
FDelimiter: String;
|
FDelimiter: String;
|
||||||
FileNameSessionLog: String;
|
FileNameSessionLog: String;
|
||||||
@ -921,7 +924,7 @@ type
|
|||||||
function GetTreeNodeType(Tree: TBaseVirtualTree; Node: PVirtualNode): TListNodeType;
|
function GetTreeNodeType(Tree: TBaseVirtualTree; Node: PVirtualNode): TListNodeType;
|
||||||
function GetFocusedTreeNodeType: TListNodeType;
|
function GetFocusedTreeNodeType: TListNodeType;
|
||||||
procedure RefreshTree(DoResetTableCache: Boolean; SelectDatabase: String = '');
|
procedure RefreshTree(DoResetTableCache: Boolean; SelectDatabase: String = '');
|
||||||
procedure RefreshTreeDB(db: String);
|
procedure RefreshTreeDB(db: String; FocusObjectName: String=''; FocusObjectType: TListNodeType=lntNone);
|
||||||
function FindDBNode(db: String): PVirtualNode;
|
function FindDBNode(db: String): PVirtualNode;
|
||||||
function GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
|
function GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
|
||||||
function GridPostInsert(Sender: TBaseVirtualTree): Boolean;
|
function GridPostInsert(Sender: TBaseVirtualTree): Boolean;
|
||||||
@ -2545,6 +2548,68 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMainForm.actRunRoutinesExecute(Sender: TObject);
|
||||||
|
var
|
||||||
|
Tab: TQueryTab;
|
||||||
|
Query, ParamInput, ProcOrFunc,
|
||||||
|
Returns, DataAccess, Security, Comment, Body: String;
|
||||||
|
Deterministic: Boolean;
|
||||||
|
i: Integer;
|
||||||
|
pObj: PDBObject;
|
||||||
|
Obj: TDBObject;
|
||||||
|
Objects: TDBObjectList;
|
||||||
|
Node: PVirtualNode;
|
||||||
|
Parameters: TRoutineParamList;
|
||||||
|
begin
|
||||||
|
// Run stored function(s) or procedure(s)
|
||||||
|
Objects := TDBObjectList.Create(False);
|
||||||
|
if ListTables.Focused then begin
|
||||||
|
Node := ListTables.GetFirstSelected;
|
||||||
|
while Assigned(Node) do begin
|
||||||
|
pObj := ListTables.GetNodeData(Node);
|
||||||
|
if pObj.NodeType in [lntProcedure, lntFunction] then
|
||||||
|
Objects.Add(pObj^);
|
||||||
|
Node := ListTables.GetNextSelected(Node);
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
Objects.Add(SelectedTable);
|
||||||
|
|
||||||
|
if Objects.Count = 0 then
|
||||||
|
MessageDlg('Please select one or more stored function(s) or routine(s).', mtError, [mbOK], 0);
|
||||||
|
|
||||||
|
for Obj in Objects do begin
|
||||||
|
actNewQueryTab.Execute;
|
||||||
|
Tab := QueryTabs[MainForm.QueryTabs.Count-1];
|
||||||
|
case Obj.NodeType of
|
||||||
|
lntProcedure: begin
|
||||||
|
Query := 'CALL ';
|
||||||
|
ProcOrFunc := 'PROCEDURE';
|
||||||
|
end;
|
||||||
|
lntFunction: begin
|
||||||
|
Query := 'SELECT ';
|
||||||
|
ProcOrFunc := 'FUNCTION';
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Parameters := TRoutineParamList.Create;
|
||||||
|
ParseRoutineStructure(Connection.GetVar('SHOW CREATE '+ProcOrFunc+' '+mask(Obj.Name), 2),
|
||||||
|
Parameters,
|
||||||
|
Deterministic, Returns, DataAccess, Security, Comment, Body
|
||||||
|
);
|
||||||
|
Query := Query + mask(Obj.Name);
|
||||||
|
ParamInput := '';
|
||||||
|
for i:=0 to Parameters.Count-1 do begin
|
||||||
|
if ParamInput <> '' then
|
||||||
|
ParamInput := ParamInput + ', ';
|
||||||
|
ParamInput := ParamInput + '''' + InputBox(Obj.Name, 'Parameter #'+IntToStr(i+1)+': '+Parameters[i].Name+' ('+Parameters[i].Datatype+')', '') + '''';
|
||||||
|
end;
|
||||||
|
Parameters.Free;
|
||||||
|
Query := Query + '('+ParamInput+')';
|
||||||
|
Tab.Memo.Text := Query;
|
||||||
|
actExecuteQueryExecute(Sender);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure TMainForm.actNewWindowExecute(Sender: TObject);
|
procedure TMainForm.actNewWindowExecute(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
debug('perf: new connection clicked.');
|
debug('perf: new connection clicked.');
|
||||||
@ -4706,6 +4771,7 @@ var
|
|||||||
L: Cardinal;
|
L: Cardinal;
|
||||||
HasFocus, InDBTree: Boolean;
|
HasFocus, InDBTree: Boolean;
|
||||||
Obj: PDBObject;
|
Obj: PDBObject;
|
||||||
|
NodeType: TListNodeType;
|
||||||
begin
|
begin
|
||||||
// DBtree and ListTables both use popupDB as menu. Find out which of them was rightclicked.
|
// DBtree and ListTables both use popupDB as menu. Find out which of them was rightclicked.
|
||||||
if Sender is TPopupMenu then
|
if Sender is TPopupMenu then
|
||||||
@ -4721,14 +4787,16 @@ begin
|
|||||||
L := DBtree.GetNodeLevel(DBtree.FocusedNode)
|
L := DBtree.GetNodeLevel(DBtree.FocusedNode)
|
||||||
else
|
else
|
||||||
L := 0;
|
L := 0;
|
||||||
|
NodeType := GetFocusedTreeNodeType;
|
||||||
actCreateDatabase.Enabled := L = 0;
|
actCreateDatabase.Enabled := L = 0;
|
||||||
actCreateTable.Enabled := L in [1,2];
|
actCreateTable.Enabled := L in [1,2];
|
||||||
actCreateView.Enabled := L in [1,2];
|
actCreateView.Enabled := L in [1,2];
|
||||||
actCreateRoutine.Enabled := L in [1,2];
|
actCreateRoutine.Enabled := L in [1,2];
|
||||||
actCreateTrigger.Enabled := L in [1,2];
|
actCreateTrigger.Enabled := L in [1,2];
|
||||||
actDropObjects.Enabled := L in [1,2];
|
actDropObjects.Enabled := L in [1,2];
|
||||||
actCopyTable.Enabled := HasFocus and (GetFocusedTreeNodeType in [lntTable, lntView]);
|
actCopyTable.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
|
||||||
actEmptyTables.Enabled := HasFocus and (GetFocusedTreeNodeType in [lntTable, lntView]);
|
actEmptyTables.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
|
||||||
|
actRunRoutines.Enabled := HasFocus and (NodeType in [lntProcedure, lntFunction]);
|
||||||
actEditObject.Enabled := L > 0;
|
actEditObject.Enabled := L > 0;
|
||||||
// Show certain items which are valid only here
|
// Show certain items which are valid only here
|
||||||
menuTreeExpandAll.Visible := True;
|
menuTreeExpandAll.Visible := True;
|
||||||
@ -4743,6 +4811,7 @@ begin
|
|||||||
actCreateRoutine.Enabled := True;
|
actCreateRoutine.Enabled := True;
|
||||||
actDropObjects.Enabled := ListTables.SelectedCount > 0;
|
actDropObjects.Enabled := ListTables.SelectedCount > 0;
|
||||||
actEmptyTables.Enabled := False;
|
actEmptyTables.Enabled := False;
|
||||||
|
actRunRoutines.Enabled := True;
|
||||||
if HasFocus then begin
|
if HasFocus then begin
|
||||||
Obj := ListTables.GetNodeData(ListTables.FocusedNode);
|
Obj := ListTables.GetNodeData(ListTables.FocusedNode);
|
||||||
actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView];
|
actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView];
|
||||||
@ -5167,7 +5236,6 @@ var
|
|||||||
SnippetsAccessible : Boolean;
|
SnippetsAccessible : Boolean;
|
||||||
Files: TStringList;
|
Files: TStringList;
|
||||||
Col: TTableColumn;
|
Col: TTableColumn;
|
||||||
Param: String;
|
|
||||||
begin
|
begin
|
||||||
ActiveQueryHelpers.Items.BeginUpdate;
|
ActiveQueryHelpers.Items.BeginUpdate;
|
||||||
ActiveQueryHelpers.Items.Clear;
|
ActiveQueryHelpers.Items.Clear;
|
||||||
@ -5200,10 +5268,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
lntFunction, lntProcedure: if Assigned(RoutineEditor) then begin
|
lntFunction, lntProcedure: if Assigned(RoutineEditor) then begin
|
||||||
for i:=0 to RoutineEditor.Parameters.Count-1 do begin
|
for i:=0 to RoutineEditor.Parameters.Count-1 do
|
||||||
Param := Copy(RoutineEditor.Parameters[i], 1, Pos(DELIM, RoutineEditor.Parameters[i])-1);
|
ActiveQueryHelpers.Items.Add(RoutineEditor.Parameters[i].Name);
|
||||||
ActiveQueryHelpers.Items.Add(Param);
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -6579,10 +6645,9 @@ end;
|
|||||||
{**
|
{**
|
||||||
Refresh one database node in the db tree
|
Refresh one database node in the db tree
|
||||||
}
|
}
|
||||||
procedure TMainForm.RefreshTreeDB(db: String);
|
procedure TMainForm.RefreshTreeDB(db: String; FocusObjectName: String=''; FocusObjectType: TListNodeType=lntNone);
|
||||||
var
|
var
|
||||||
oldActiveDatabase, oldSelectedTableName: String;
|
oldActiveDatabase: String;
|
||||||
oldSelectedTableType: TListNodeType;
|
|
||||||
DBNode, FNode: PVirtualNode;
|
DBNode, FNode: PVirtualNode;
|
||||||
TableHereHadFocus: Boolean;
|
TableHereHadFocus: Boolean;
|
||||||
DBObjects: TDBObjectList;
|
DBObjects: TDBObjectList;
|
||||||
@ -6592,12 +6657,15 @@ var
|
|||||||
NewColumn: TColumnIndex; var Allowed: Boolean) of object;
|
NewColumn: TColumnIndex; var Allowed: Boolean) of object;
|
||||||
begin
|
begin
|
||||||
debug('RefreshTreeDB()');
|
debug('RefreshTreeDB()');
|
||||||
oldActiveDatabase := ActiveDatabase;
|
|
||||||
oldSelectedTableName := SelectedTable.Name;
|
|
||||||
oldSelectedTableType := SelectedTable.NodeType;
|
|
||||||
DBNode := FindDBNode(db);
|
DBNode := FindDBNode(db);
|
||||||
FNode := DBtree.FocusedNode;
|
FNode := DBtree.FocusedNode;
|
||||||
TableHereHadFocus := Assigned(FNode) and (FNode.Parent = DBNode);
|
TableHereHadFocus := Assigned(FNode) and ((FNode.Parent = DBNode) or (FocusObjectName <> ''));
|
||||||
|
oldActiveDatabase := ActiveDatabase;
|
||||||
|
if FocusObjectName = '' then begin
|
||||||
|
// Most cases just go here and focus the old table afterwards
|
||||||
|
FocusObjectName := SelectedTable.Name;
|
||||||
|
FocusObjectType := SelectedTable.NodeType;
|
||||||
|
end;
|
||||||
// Suspend focus changing event, to avoid tab jumping
|
// Suspend focus changing event, to avoid tab jumping
|
||||||
FocusChangingEvent := DBtree.OnFocusChanging;
|
FocusChangingEvent := DBtree.OnFocusChanging;
|
||||||
FocusChangeEvent := DBtree.OnFocusChanged;
|
FocusChangeEvent := DBtree.OnFocusChanged;
|
||||||
@ -6612,9 +6680,9 @@ begin
|
|||||||
DBObjects := Connection.GetDBObjects(db);
|
DBObjects := Connection.GetDBObjects(db);
|
||||||
for i:=0 to DBObjects.Count-1 do begin
|
for i:=0 to DBObjects.Count-1 do begin
|
||||||
// Need to check if table was renamed, in which case oldSelectedTable is no longer available
|
// Need to check if table was renamed, in which case oldSelectedTable is no longer available
|
||||||
if (DBObjects[i].Name = oldSelectedTableName)
|
if (DBObjects[i].Name = FocusObjectName)
|
||||||
and (DBObjects[i].NodeType = oldSelectedTableType) then begin
|
and (DBObjects[i].NodeType = FocusObjectType) then begin
|
||||||
SelectDBObject(oldSelectedTableName, oldSelectedTableType);
|
SelectDBObject(FocusObjectName, FocusObjectType);
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
@ -351,4 +351,14 @@ object frmRoutineEditor: TfrmRoutineEditor
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
object btnRunProc: TButton
|
||||||
|
Left = 480
|
||||||
|
Top = 455
|
||||||
|
Width = 123
|
||||||
|
Height = 25
|
||||||
|
Action = MainForm.actRunRoutines
|
||||||
|
Anchors = [akRight, akBottom]
|
||||||
|
Images = MainForm.ImageListMain
|
||||||
|
TabOrder = 5
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -38,6 +38,7 @@ type
|
|||||||
btnRemoveParam: TToolButton;
|
btnRemoveParam: TToolButton;
|
||||||
btnClearParams: TToolButton;
|
btnClearParams: TToolButton;
|
||||||
SynMemoCREATEcode: TSynMemo;
|
SynMemoCREATEcode: TSynMemo;
|
||||||
|
btnRunProc: TButton;
|
||||||
procedure comboTypeSelect(Sender: TObject);
|
procedure comboTypeSelect(Sender: TObject);
|
||||||
procedure btnSaveClick(Sender: TObject);
|
procedure btnSaveClick(Sender: TObject);
|
||||||
procedure btnHelpClick(Sender: TObject);
|
procedure btnHelpClick(Sender: TObject);
|
||||||
@ -75,7 +76,7 @@ type
|
|||||||
function ComposeCreateStatement(NameOfObject: String): String;
|
function ComposeCreateStatement(NameOfObject: String): String;
|
||||||
public
|
public
|
||||||
{ Public declarations }
|
{ Public declarations }
|
||||||
Parameters: TStringList;
|
Parameters: TRoutineParamList;
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone); override;
|
procedure Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone); override;
|
||||||
@ -111,7 +112,7 @@ begin
|
|||||||
Mainform.SynCompletionProposal.AddEditor(SynMemoBody);
|
Mainform.SynCompletionProposal.AddEditor(SynMemoBody);
|
||||||
FixVT(listParameters);
|
FixVT(listParameters);
|
||||||
Mainform.RestoreListSetup(listParameters);
|
Mainform.RestoreListSetup(listParameters);
|
||||||
Parameters := TStringList.Create;
|
Parameters := TRoutineParamList.Create;
|
||||||
editName.MaxLength := NAME_LEN;
|
editName.MaxLength := NAME_LEN;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -127,11 +128,8 @@ end;
|
|||||||
|
|
||||||
procedure TfrmRoutineEditor.Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone);
|
procedure TfrmRoutineEditor.Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone);
|
||||||
var
|
var
|
||||||
Create, Params: String;
|
Create, Returns, DataAccess, Security, Comment, Body: String;
|
||||||
ParenthesesCount: Integer;
|
Deterministic: Boolean;
|
||||||
Context: String;
|
|
||||||
rx: TRegExpr;
|
|
||||||
i: Integer;
|
|
||||||
begin
|
begin
|
||||||
inherited;
|
inherited;
|
||||||
if ObjectType = lntProcedure then FAlterRoutineType := 'PROCEDURE'
|
if ObjectType = lntProcedure then FAlterRoutineType := 'PROCEDURE'
|
||||||
@ -149,84 +147,16 @@ begin
|
|||||||
if FEditObjectName <> '' then begin
|
if FEditObjectName <> '' then begin
|
||||||
// Editing existing routine
|
// Editing existing routine
|
||||||
comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b');
|
comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b');
|
||||||
|
|
||||||
Create := Mainform.Connection.GetVar('SHOW CREATE '+FAlterRoutineType+' '+Mainform.mask(editName.Text), 2);
|
Create := Mainform.Connection.GetVar('SHOW CREATE '+FAlterRoutineType+' '+Mainform.mask(editName.Text), 2);
|
||||||
rx := TRegExpr.Create;
|
ParseRoutineStructure(Create, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
||||||
rx.ModifierI := True;
|
comboReturns.Text := Returns;
|
||||||
rx.ModifierG := True;
|
chkDeterministic.Checked := Deterministic;
|
||||||
// CREATE DEFINER=`root`@`localhost` PROCEDURE `bla2`(IN p1 INT, p2 VARCHAR(20))
|
if DataAccess <> '' then
|
||||||
// CREATE DEFINER=`root`@`localhost` FUNCTION `test3`(`?b` varchar(20)) RETURNS tinyint(4)
|
comboDataAccess.ItemIndex := comboDataAccess.Items.IndexOf(DataAccess);
|
||||||
// CREATE DEFINER=`root`@`localhost` PROCEDURE `test3`(IN `Param1` int(1) unsigned)
|
if Security <> '' then
|
||||||
ParenthesesCount := 0;
|
comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(Security);
|
||||||
for i:=1 to Length(Create) do begin
|
editComment.Text := Comment;
|
||||||
if Create[i] = ')' then begin
|
SynMemoBody.Text := Body;
|
||||||
Dec(ParenthesesCount);
|
|
||||||
if ParenthesesCount = 0 then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
if ParenthesesCount >= 1 then
|
|
||||||
Params := Params + Create[i];
|
|
||||||
if Create[i] = '(' then
|
|
||||||
Inc(ParenthesesCount);
|
|
||||||
end;
|
|
||||||
rx.Expression := '(^|,)\s*((IN|OUT|INOUT)\s+)?(\S+)\s+([^\s,\(]+(\([^\)]*\))?[^,]*)';
|
|
||||||
if rx.Exec(Params) then while true do begin
|
|
||||||
Context := UpperCase(rx.Match[3]);
|
|
||||||
if Context = '' then
|
|
||||||
Context := 'IN';
|
|
||||||
Parameters.Add(WideDequotedStr(rx.Match[4], '`') + DELIM + rx.Match[5] + DELIM + Context);
|
|
||||||
if not rx.ExecNext then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Cut left part including parameters, so it's easier to parse the rest
|
|
||||||
Create := Copy(Create, i+1, MaxInt);
|
|
||||||
// CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body
|
|
||||||
// CREATE FUNCTION sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body
|
|
||||||
// LANGUAGE SQL
|
|
||||||
// | [NOT] DETERMINISTIC // IS_DETERMINISTIC
|
|
||||||
// | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } // DATA_ACCESS
|
|
||||||
// | SQL SECURITY { DEFINER | INVOKER } // SECURITY_TYPE
|
|
||||||
// | COMMENT 'string' // COMMENT
|
|
||||||
|
|
||||||
rx.Expression := '\bLANGUAGE SQL\b';
|
|
||||||
if rx.Exec(Create) then
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
rx.Expression := '\bRETURNS\s+(\w+(\([^\)]*\))?)';
|
|
||||||
if rx.Exec(Create) then begin
|
|
||||||
comboReturns.Text := rx.Match[1];
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\b(NOT\s+)?DETERMINISTIC\b';
|
|
||||||
if rx.Exec(Create) then begin
|
|
||||||
chkDeterministic.Checked := rx.MatchLen[1] = -1;
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\b('+UpperCase(ImplodeStr('|', comboDataAccess.Items))+')\b';
|
|
||||||
if rx.Exec(Create) then begin
|
|
||||||
comboDataAccess.ItemIndex := comboDataAccess.Items.IndexOf(rx.Match[1]);
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\bSQL\s+SECURITY\s+(DEFINER|INVOKER)\b';
|
|
||||||
if rx.Exec(Create) then begin
|
|
||||||
comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(rx.Match[1]);
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.ModifierG := False;
|
|
||||||
rx.Expression := '\bCOMMENT\s+''((.+)[^''])''[^'']';
|
|
||||||
if rx.Exec(Create) then begin
|
|
||||||
editComment.Text := StringReplace(rx.Match[1], '''''', '''', [rfReplaceAll]);
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]-1);
|
|
||||||
end;
|
|
||||||
rx.Expression := '^\s*CHARSET\s+[\w\d]+\s';
|
|
||||||
if rx.Exec(Create) then
|
|
||||||
Delete(Create, rx.MatchPos[0], rx.MatchLen[0]-1);
|
|
||||||
// Tata, remaining code is the routine body
|
|
||||||
Create := TrimLeft(Create);
|
|
||||||
SynMemoBody.Text := Create;
|
|
||||||
|
|
||||||
rx.Free;
|
|
||||||
|
|
||||||
end else begin
|
end else begin
|
||||||
editName.Text := '';
|
editName.Text := '';
|
||||||
end;
|
end;
|
||||||
@ -236,6 +166,7 @@ begin
|
|||||||
Modified := False;
|
Modified := False;
|
||||||
btnSave.Enabled := Modified;
|
btnSave.Enabled := Modified;
|
||||||
btnDiscard.Enabled := Modified;
|
btnDiscard.Enabled := Modified;
|
||||||
|
Mainform.actRunRoutines.Enabled := FEditObjectName <> '';
|
||||||
Mainform.ShowStatusMsg;
|
Mainform.ShowStatusMsg;
|
||||||
Screen.Cursor := crDefault;
|
Screen.Cursor := crDefault;
|
||||||
end;
|
end;
|
||||||
@ -280,8 +211,14 @@ end;
|
|||||||
|
|
||||||
|
|
||||||
procedure TfrmRoutineEditor.btnAddParamClick(Sender: TObject);
|
procedure TfrmRoutineEditor.btnAddParamClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
Param: TRoutineParam;
|
||||||
begin
|
begin
|
||||||
Parameters.Add('Param'+IntToStr(Parameters.Count+1)+DELIM+'INT'+DELIM+'IN');
|
Param := TRoutineParam.Create;
|
||||||
|
Param.Name := 'Param'+IntToStr(Parameters.Count+1);
|
||||||
|
Param.Datatype := 'INT';
|
||||||
|
Param.Context := 'IN';
|
||||||
|
Parameters.Add(Param);
|
||||||
// See List.OnPaint:
|
// See List.OnPaint:
|
||||||
listParameters.Repaint;
|
listParameters.Repaint;
|
||||||
Modification(Sender);
|
Modification(Sender);
|
||||||
@ -336,16 +273,19 @@ procedure TfrmRoutineEditor.listParametersGetText(Sender: TBaseVirtualTree;
|
|||||||
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
||||||
var CellText: String);
|
var CellText: String);
|
||||||
var
|
var
|
||||||
Values: TStringList;
|
Param: TRoutineParam;
|
||||||
begin
|
begin
|
||||||
if Column = 0 then
|
Param := Parameters[Node.Index];
|
||||||
CellText := IntToStr(Node.Index+1)
|
case Column of
|
||||||
else if (Column = 3) and (comboType.ItemIndex = 1) then
|
0: CellText := IntToStr(Node.Index+1);
|
||||||
CellText := 'IN' // A function can only have IN parameters
|
1: CellText := Param.Name;
|
||||||
else begin
|
2: CellText := Param.Datatype;
|
||||||
Values := explode(DELIM, Parameters[Node.Index]);
|
3: begin
|
||||||
CellText := Values[Column-1];
|
if comboType.ItemIndex = 1 then
|
||||||
FreeAndNil(Values);
|
CellText := 'IN' // A function can only have IN parameters
|
||||||
|
else
|
||||||
|
CellText := Param.Context;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -371,16 +311,14 @@ end;
|
|||||||
procedure TfrmRoutineEditor.listParametersNewText(Sender: TBaseVirtualTree;
|
procedure TfrmRoutineEditor.listParametersNewText(Sender: TBaseVirtualTree;
|
||||||
Node: PVirtualNode; Column: TColumnIndex; NewText: String);
|
Node: PVirtualNode; Column: TColumnIndex; NewText: String);
|
||||||
var
|
var
|
||||||
OldValues: TStringList;
|
Param: TRoutineParam;
|
||||||
new: String;
|
|
||||||
begin
|
begin
|
||||||
OldValues := explode(DELIM, Parameters[Node.Index]);
|
Param := Parameters[Node.Index];
|
||||||
case Column of
|
case Column of
|
||||||
1: new := NewText + DELIM + OldValues[1] + DELIM + OldValues[2];
|
1: Param.Name := NewText;
|
||||||
2: new := OldValues[0] + DELIM + NewText + DELIM + OldValues[2];
|
2: Param.Datatype := NewText;
|
||||||
3: new := OldValues[0] + DELIM + OldValues[1] + DELIM + NewText;
|
3: Param.Context := NewText;
|
||||||
end;
|
end;
|
||||||
Parameters[Node.Index] := new;
|
|
||||||
Modification(Sender);
|
Modification(Sender);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -469,6 +407,7 @@ var
|
|||||||
allRoutineNames: TStringList;
|
allRoutineNames: TStringList;
|
||||||
ProcOrFunc: String;
|
ProcOrFunc: String;
|
||||||
TargetExists: Boolean;
|
TargetExists: Boolean;
|
||||||
|
t: TListNodeType;
|
||||||
begin
|
begin
|
||||||
// Save changes
|
// Save changes
|
||||||
Result := mrOk;
|
Result := mrOk;
|
||||||
@ -514,10 +453,13 @@ begin
|
|||||||
FEditObjectName := editName.Text;
|
FEditObjectName := editName.Text;
|
||||||
FAlterRoutineType := UpperCase(GetFirstWord(comboType.Text));
|
FAlterRoutineType := UpperCase(GetFirstWord(comboType.Text));
|
||||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
if FAlterRoutineType = 'PROCEDURE' then t := lntProcedure
|
||||||
|
else t := lntFunction;
|
||||||
|
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, t);
|
||||||
Modified := False;
|
Modified := False;
|
||||||
btnSave.Enabled := Modified;
|
btnSave.Enabled := Modified;
|
||||||
btnDiscard.Enabled := Modified;
|
btnDiscard.Enabled := Modified;
|
||||||
|
Mainform.actRunRoutines.Enabled := True;
|
||||||
except
|
except
|
||||||
on E:Exception do begin
|
on E:Exception do begin
|
||||||
MessageDlg(E.Message, mtError, [mbOk], 0);
|
MessageDlg(E.Message, mtError, [mbOk], 0);
|
||||||
@ -530,16 +472,14 @@ end;
|
|||||||
function TfrmRoutineEditor.ComposeCreateStatement(NameOfObject: String): String;
|
function TfrmRoutineEditor.ComposeCreateStatement(NameOfObject: String): String;
|
||||||
var
|
var
|
||||||
ProcOrFunc: String;
|
ProcOrFunc: String;
|
||||||
par: TStringList;
|
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
ProcOrFunc := UpperCase(GetFirstWord(comboType.Text));
|
ProcOrFunc := UpperCase(GetFirstWord(comboType.Text));
|
||||||
Result := 'CREATE '+ProcOrFunc+' '+Mainform.mask(NameOfObject)+'(';
|
Result := 'CREATE '+ProcOrFunc+' '+Mainform.mask(NameOfObject)+'(';
|
||||||
for i:=0 to Parameters.Count-1 do begin
|
for i:=0 to Parameters.Count-1 do begin
|
||||||
par := explode(DELIM, Parameters[i]);
|
|
||||||
if ProcOrFunc = 'PROCEDURE' then
|
if ProcOrFunc = 'PROCEDURE' then
|
||||||
Result := Result + par[2] + ' ';
|
Result := Result + Parameters[i].Context + ' ';
|
||||||
Result := Result + Mainform.Mask(par[0]) + ' ' + par[1];
|
Result := Result + Mainform.Mask(Parameters[i].Name) + ' ' + Parameters[i].Datatype;
|
||||||
if i < Parameters.Count-1 then
|
if i < Parameters.Count-1 then
|
||||||
Result := Result + ', ';
|
Result := Result + ', ';
|
||||||
end;
|
end;
|
||||||
|
@ -400,7 +400,7 @@ begin
|
|||||||
FEditObjectName := editName.Text;
|
FEditObjectName := editName.Text;
|
||||||
tabALTERcode.TabVisible := FEditObjectName <> '';
|
tabALTERcode.TabVisible := FEditObjectName <> '';
|
||||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntTable);
|
||||||
Mainform.ParseSelectedTableStructure;
|
Mainform.ParseSelectedTableStructure;
|
||||||
ResetModificationFlags;
|
ResetModificationFlags;
|
||||||
AlterCodeValid := False;
|
AlterCodeValid := False;
|
||||||
|
@ -175,7 +175,7 @@ begin
|
|||||||
Mainform.Connection.Query(sql);
|
Mainform.Connection.Query(sql);
|
||||||
FEditObjectName := editName.Text;
|
FEditObjectName := editName.Text;
|
||||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntTrigger);
|
||||||
Modified := False;
|
Modified := False;
|
||||||
btnSave.Enabled := Modified;
|
btnSave.Enabled := Modified;
|
||||||
btnDiscard.Enabled := Modified;
|
btnDiscard.Enabled := Modified;
|
||||||
|
@ -181,7 +181,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
FEditObjectName := editName.Text;
|
FEditObjectName := editName.Text;
|
||||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntView);
|
||||||
Mainform.ParseSelectedTableStructure;
|
Mainform.ParseSelectedTableStructure;
|
||||||
Modified := False;
|
Modified := False;
|
||||||
btnSave.Enabled := Modified;
|
btnSave.Enabled := Modified;
|
||||||
|
Reference in New Issue
Block a user