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:
Ansgar Becker
2010-04-04 22:33:12 +00:00
parent c0533a2f78
commit 45ba97b13f
8 changed files with 253 additions and 128 deletions

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;