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
|
||||
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,
|
||||
mysql_connection, mysql_structures;
|
||||
|
||||
@ -132,6 +132,11 @@ type
|
||||
end;
|
||||
TForeignKeyList = TObjectList<TForeignKey>;
|
||||
|
||||
TRoutineParam = class(TObject)
|
||||
Name, Context, Datatype: String;
|
||||
end;
|
||||
TRoutineParamList = TObjectList<TRoutineParam>;
|
||||
|
||||
TDBObjectEditor = class(TFrame)
|
||||
private
|
||||
FModified: Boolean;
|
||||
@ -241,6 +246,8 @@ type
|
||||
function GetLightness(AColor: TColor): Byte;
|
||||
procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
||||
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 ParamBlobToStr(lpData: Pointer): TStringlist;
|
||||
function ParamStrToBlob(out cbData: DWORD): Pointer;
|
||||
@ -3031,6 +3038,97 @@ begin
|
||||
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;
|
||||
var
|
||||
AllKeywords, ImportantKeywords: TStringList;
|
||||
|
@ -2461,6 +2461,12 @@ object MainForm: TMainForm
|
||||
ShortCut = 49187
|
||||
OnExecute = actDataShowAllExecute
|
||||
end
|
||||
object actRunRoutines: TAction
|
||||
Category = 'Database'
|
||||
Caption = 'Run routine(s) ...'
|
||||
ImageIndex = 35
|
||||
OnExecute = actRunRoutinesExecute
|
||||
end
|
||||
end
|
||||
object SaveDialog2: TSaveDialog
|
||||
DefaultExt = 'reg'
|
||||
@ -7533,6 +7539,9 @@ object MainForm: TMainForm
|
||||
object menuEmptyTables: TMenuItem
|
||||
Action = actEmptyTables
|
||||
end
|
||||
object Runroutines1: TMenuItem
|
||||
Action = actRunRoutines
|
||||
end
|
||||
object menuCreateObject: TMenuItem
|
||||
Caption = 'Create new'
|
||||
ImageIndex = 130
|
||||
|
104
source/main.pas
104
source/main.pas
@ -470,6 +470,8 @@ type
|
||||
tabDatabases: TTabSheet;
|
||||
ListDatabases: TVirtualStringTree;
|
||||
menuFetchDBitems: TMenuItem;
|
||||
actRunRoutines: TAction;
|
||||
Runroutines1: TMenuItem;
|
||||
procedure actCreateDBObjectExecute(Sender: TObject);
|
||||
procedure menuConnectionsPopup(Sender: TObject);
|
||||
procedure actExitApplicationExecute(Sender: TObject);
|
||||
@ -762,6 +764,7 @@ type
|
||||
procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
|
||||
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
|
||||
procedure ListDatabasesDblClick(Sender: TObject);
|
||||
procedure actRunRoutinesExecute(Sender: TObject);
|
||||
private
|
||||
FDelimiter: String;
|
||||
FileNameSessionLog: String;
|
||||
@ -921,7 +924,7 @@ type
|
||||
function GetTreeNodeType(Tree: TBaseVirtualTree; Node: PVirtualNode): TListNodeType;
|
||||
function GetFocusedTreeNodeType: TListNodeType;
|
||||
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 GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
|
||||
function GridPostInsert(Sender: TBaseVirtualTree): Boolean;
|
||||
@ -2545,6 +2548,68 @@ begin
|
||||
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);
|
||||
begin
|
||||
debug('perf: new connection clicked.');
|
||||
@ -4706,6 +4771,7 @@ var
|
||||
L: Cardinal;
|
||||
HasFocus, InDBTree: Boolean;
|
||||
Obj: PDBObject;
|
||||
NodeType: TListNodeType;
|
||||
begin
|
||||
// DBtree and ListTables both use popupDB as menu. Find out which of them was rightclicked.
|
||||
if Sender is TPopupMenu then
|
||||
@ -4721,14 +4787,16 @@ begin
|
||||
L := DBtree.GetNodeLevel(DBtree.FocusedNode)
|
||||
else
|
||||
L := 0;
|
||||
NodeType := GetFocusedTreeNodeType;
|
||||
actCreateDatabase.Enabled := L = 0;
|
||||
actCreateTable.Enabled := L in [1,2];
|
||||
actCreateView.Enabled := L in [1,2];
|
||||
actCreateRoutine.Enabled := L in [1,2];
|
||||
actCreateTrigger.Enabled := L in [1,2];
|
||||
actDropObjects.Enabled := L in [1,2];
|
||||
actCopyTable.Enabled := HasFocus and (GetFocusedTreeNodeType in [lntTable, lntView]);
|
||||
actEmptyTables.Enabled := HasFocus and (GetFocusedTreeNodeType in [lntTable, lntView]);
|
||||
actCopyTable.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
|
||||
actEmptyTables.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
|
||||
actRunRoutines.Enabled := HasFocus and (NodeType in [lntProcedure, lntFunction]);
|
||||
actEditObject.Enabled := L > 0;
|
||||
// Show certain items which are valid only here
|
||||
menuTreeExpandAll.Visible := True;
|
||||
@ -4743,6 +4811,7 @@ begin
|
||||
actCreateRoutine.Enabled := True;
|
||||
actDropObjects.Enabled := ListTables.SelectedCount > 0;
|
||||
actEmptyTables.Enabled := False;
|
||||
actRunRoutines.Enabled := True;
|
||||
if HasFocus then begin
|
||||
Obj := ListTables.GetNodeData(ListTables.FocusedNode);
|
||||
actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView];
|
||||
@ -5167,7 +5236,6 @@ var
|
||||
SnippetsAccessible : Boolean;
|
||||
Files: TStringList;
|
||||
Col: TTableColumn;
|
||||
Param: String;
|
||||
begin
|
||||
ActiveQueryHelpers.Items.BeginUpdate;
|
||||
ActiveQueryHelpers.Items.Clear;
|
||||
@ -5200,10 +5268,8 @@ begin
|
||||
end;
|
||||
end;
|
||||
lntFunction, lntProcedure: if Assigned(RoutineEditor) then begin
|
||||
for i:=0 to RoutineEditor.Parameters.Count-1 do begin
|
||||
Param := Copy(RoutineEditor.Parameters[i], 1, Pos(DELIM, RoutineEditor.Parameters[i])-1);
|
||||
ActiveQueryHelpers.Items.Add(Param);
|
||||
end;
|
||||
for i:=0 to RoutineEditor.Parameters.Count-1 do
|
||||
ActiveQueryHelpers.Items.Add(RoutineEditor.Parameters[i].Name);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@ -6579,10 +6645,9 @@ end;
|
||||
{**
|
||||
Refresh one database node in the db tree
|
||||
}
|
||||
procedure TMainForm.RefreshTreeDB(db: String);
|
||||
procedure TMainForm.RefreshTreeDB(db: String; FocusObjectName: String=''; FocusObjectType: TListNodeType=lntNone);
|
||||
var
|
||||
oldActiveDatabase, oldSelectedTableName: String;
|
||||
oldSelectedTableType: TListNodeType;
|
||||
oldActiveDatabase: String;
|
||||
DBNode, FNode: PVirtualNode;
|
||||
TableHereHadFocus: Boolean;
|
||||
DBObjects: TDBObjectList;
|
||||
@ -6592,12 +6657,15 @@ var
|
||||
NewColumn: TColumnIndex; var Allowed: Boolean) of object;
|
||||
begin
|
||||
debug('RefreshTreeDB()');
|
||||
oldActiveDatabase := ActiveDatabase;
|
||||
oldSelectedTableName := SelectedTable.Name;
|
||||
oldSelectedTableType := SelectedTable.NodeType;
|
||||
DBNode := FindDBNode(db);
|
||||
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
|
||||
FocusChangingEvent := DBtree.OnFocusChanging;
|
||||
FocusChangeEvent := DBtree.OnFocusChanged;
|
||||
@ -6612,9 +6680,9 @@ begin
|
||||
DBObjects := Connection.GetDBObjects(db);
|
||||
for i:=0 to DBObjects.Count-1 do begin
|
||||
// Need to check if table was renamed, in which case oldSelectedTable is no longer available
|
||||
if (DBObjects[i].Name = oldSelectedTableName)
|
||||
and (DBObjects[i].NodeType = oldSelectedTableType) then begin
|
||||
SelectDBObject(oldSelectedTableName, oldSelectedTableType);
|
||||
if (DBObjects[i].Name = FocusObjectName)
|
||||
and (DBObjects[i].NodeType = FocusObjectType) then begin
|
||||
SelectDBObject(FocusObjectName, FocusObjectType);
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
|
@ -351,4 +351,14 @@ object frmRoutineEditor: TfrmRoutineEditor
|
||||
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
|
||||
|
@ -38,6 +38,7 @@ type
|
||||
btnRemoveParam: TToolButton;
|
||||
btnClearParams: TToolButton;
|
||||
SynMemoCREATEcode: TSynMemo;
|
||||
btnRunProc: TButton;
|
||||
procedure comboTypeSelect(Sender: TObject);
|
||||
procedure btnSaveClick(Sender: TObject);
|
||||
procedure btnHelpClick(Sender: TObject);
|
||||
@ -75,7 +76,7 @@ type
|
||||
function ComposeCreateStatement(NameOfObject: String): String;
|
||||
public
|
||||
{ Public declarations }
|
||||
Parameters: TStringList;
|
||||
Parameters: TRoutineParamList;
|
||||
constructor Create(AOwner: TComponent); override;
|
||||
destructor Destroy; override;
|
||||
procedure Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone); override;
|
||||
@ -111,7 +112,7 @@ begin
|
||||
Mainform.SynCompletionProposal.AddEditor(SynMemoBody);
|
||||
FixVT(listParameters);
|
||||
Mainform.RestoreListSetup(listParameters);
|
||||
Parameters := TStringList.Create;
|
||||
Parameters := TRoutineParamList.Create;
|
||||
editName.MaxLength := NAME_LEN;
|
||||
end;
|
||||
|
||||
@ -127,11 +128,8 @@ end;
|
||||
|
||||
procedure TfrmRoutineEditor.Init(ObjectName: String=''; ObjectType: TListNodeType=lntNone);
|
||||
var
|
||||
Create, Params: String;
|
||||
ParenthesesCount: Integer;
|
||||
Context: String;
|
||||
rx: TRegExpr;
|
||||
i: Integer;
|
||||
Create, Returns, DataAccess, Security, Comment, Body: String;
|
||||
Deterministic: Boolean;
|
||||
begin
|
||||
inherited;
|
||||
if ObjectType = lntProcedure then FAlterRoutineType := 'PROCEDURE'
|
||||
@ -149,84 +147,16 @@ begin
|
||||
if FEditObjectName <> '' then begin
|
||||
// Editing existing routine
|
||||
comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b');
|
||||
|
||||
Create := Mainform.Connection.GetVar('SHOW CREATE '+FAlterRoutineType+' '+Mainform.mask(editName.Text), 2);
|
||||
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;
|
||||
for i:=1 to Length(Create) do begin
|
||||
if Create[i] = ')' then begin
|
||||
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;
|
||||
|
||||
ParseRoutineStructure(Create, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
||||
comboReturns.Text := Returns;
|
||||
chkDeterministic.Checked := Deterministic;
|
||||
if DataAccess <> '' then
|
||||
comboDataAccess.ItemIndex := comboDataAccess.Items.IndexOf(DataAccess);
|
||||
if Security <> '' then
|
||||
comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(Security);
|
||||
editComment.Text := Comment;
|
||||
SynMemoBody.Text := Body;
|
||||
end else begin
|
||||
editName.Text := '';
|
||||
end;
|
||||
@ -236,6 +166,7 @@ begin
|
||||
Modified := False;
|
||||
btnSave.Enabled := Modified;
|
||||
btnDiscard.Enabled := Modified;
|
||||
Mainform.actRunRoutines.Enabled := FEditObjectName <> '';
|
||||
Mainform.ShowStatusMsg;
|
||||
Screen.Cursor := crDefault;
|
||||
end;
|
||||
@ -280,8 +211,14 @@ end;
|
||||
|
||||
|
||||
procedure TfrmRoutineEditor.btnAddParamClick(Sender: TObject);
|
||||
var
|
||||
Param: TRoutineParam;
|
||||
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:
|
||||
listParameters.Repaint;
|
||||
Modification(Sender);
|
||||
@ -336,16 +273,19 @@ procedure TfrmRoutineEditor.listParametersGetText(Sender: TBaseVirtualTree;
|
||||
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
|
||||
var CellText: String);
|
||||
var
|
||||
Values: TStringList;
|
||||
Param: TRoutineParam;
|
||||
begin
|
||||
if Column = 0 then
|
||||
CellText := IntToStr(Node.Index+1)
|
||||
else if (Column = 3) and (comboType.ItemIndex = 1) then
|
||||
Param := Parameters[Node.Index];
|
||||
case Column of
|
||||
0: CellText := IntToStr(Node.Index+1);
|
||||
1: CellText := Param.Name;
|
||||
2: CellText := Param.Datatype;
|
||||
3: begin
|
||||
if comboType.ItemIndex = 1 then
|
||||
CellText := 'IN' // A function can only have IN parameters
|
||||
else begin
|
||||
Values := explode(DELIM, Parameters[Node.Index]);
|
||||
CellText := Values[Column-1];
|
||||
FreeAndNil(Values);
|
||||
else
|
||||
CellText := Param.Context;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -371,16 +311,14 @@ end;
|
||||
procedure TfrmRoutineEditor.listParametersNewText(Sender: TBaseVirtualTree;
|
||||
Node: PVirtualNode; Column: TColumnIndex; NewText: String);
|
||||
var
|
||||
OldValues: TStringList;
|
||||
new: String;
|
||||
Param: TRoutineParam;
|
||||
begin
|
||||
OldValues := explode(DELIM, Parameters[Node.Index]);
|
||||
Param := Parameters[Node.Index];
|
||||
case Column of
|
||||
1: new := NewText + DELIM + OldValues[1] + DELIM + OldValues[2];
|
||||
2: new := OldValues[0] + DELIM + NewText + DELIM + OldValues[2];
|
||||
3: new := OldValues[0] + DELIM + OldValues[1] + DELIM + NewText;
|
||||
1: Param.Name := NewText;
|
||||
2: Param.Datatype := NewText;
|
||||
3: Param.Context := NewText;
|
||||
end;
|
||||
Parameters[Node.Index] := new;
|
||||
Modification(Sender);
|
||||
end;
|
||||
|
||||
@ -469,6 +407,7 @@ var
|
||||
allRoutineNames: TStringList;
|
||||
ProcOrFunc: String;
|
||||
TargetExists: Boolean;
|
||||
t: TListNodeType;
|
||||
begin
|
||||
// Save changes
|
||||
Result := mrOk;
|
||||
@ -514,10 +453,13 @@ begin
|
||||
FEditObjectName := editName.Text;
|
||||
FAlterRoutineType := UpperCase(GetFirstWord(comboType.Text));
|
||||
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;
|
||||
btnSave.Enabled := Modified;
|
||||
btnDiscard.Enabled := Modified;
|
||||
Mainform.actRunRoutines.Enabled := True;
|
||||
except
|
||||
on E:Exception do begin
|
||||
MessageDlg(E.Message, mtError, [mbOk], 0);
|
||||
@ -530,16 +472,14 @@ end;
|
||||
function TfrmRoutineEditor.ComposeCreateStatement(NameOfObject: String): String;
|
||||
var
|
||||
ProcOrFunc: String;
|
||||
par: TStringList;
|
||||
i: Integer;
|
||||
begin
|
||||
ProcOrFunc := UpperCase(GetFirstWord(comboType.Text));
|
||||
Result := 'CREATE '+ProcOrFunc+' '+Mainform.mask(NameOfObject)+'(';
|
||||
for i:=0 to Parameters.Count-1 do begin
|
||||
par := explode(DELIM, Parameters[i]);
|
||||
if ProcOrFunc = 'PROCEDURE' then
|
||||
Result := Result + par[2] + ' ';
|
||||
Result := Result + Mainform.Mask(par[0]) + ' ' + par[1];
|
||||
Result := Result + Parameters[i].Context + ' ';
|
||||
Result := Result + Mainform.Mask(Parameters[i].Name) + ' ' + Parameters[i].Datatype;
|
||||
if i < Parameters.Count-1 then
|
||||
Result := Result + ', ';
|
||||
end;
|
||||
|
@ -400,7 +400,7 @@ begin
|
||||
FEditObjectName := editName.Text;
|
||||
tabALTERcode.TabVisible := FEditObjectName <> '';
|
||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntTable);
|
||||
Mainform.ParseSelectedTableStructure;
|
||||
ResetModificationFlags;
|
||||
AlterCodeValid := False;
|
||||
|
@ -175,7 +175,7 @@ begin
|
||||
Mainform.Connection.Query(sql);
|
||||
FEditObjectName := editName.Text;
|
||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntTrigger);
|
||||
Modified := False;
|
||||
btnSave.Enabled := Modified;
|
||||
btnDiscard.Enabled := Modified;
|
||||
|
@ -181,7 +181,7 @@ begin
|
||||
end;
|
||||
FEditObjectName := editName.Text;
|
||||
Mainform.SetEditorTabCaption(Self, FEditObjectName);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase);
|
||||
Mainform.RefreshTreeDB(Mainform.ActiveDatabase, FEditObjectName, lntView);
|
||||
Mainform.ParseSelectedTableStructure;
|
||||
Modified := False;
|
||||
btnSave.Enabled := Modified;
|
||||
|
Reference in New Issue
Block a user