From 45ba97b13fe09475cc2b265999656cced4055c4c Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Sun, 4 Apr 2010 22:33:12 +0000 Subject: [PATCH] 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. --- source/helpers.pas | 100 ++++++++++++++++++++++++- source/main.dfm | 9 +++ source/main.pas | 104 +++++++++++++++++++++----- source/routine_editor.dfm | 10 +++ source/routine_editor.pas | 152 ++++++++++++-------------------------- source/table_editor.pas | 2 +- source/trigger_editor.pas | 2 +- source/view.pas | 2 +- 8 files changed, 253 insertions(+), 128 deletions(-) diff --git a/source/helpers.pas b/source/helpers.pas index 157299b4..64cb0a30 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -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; + TRoutineParam = class(TObject) + Name, Context, Datatype: String; + end; + TRoutineParamList = TObjectList; + 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; diff --git a/source/main.dfm b/source/main.dfm index f7e7c5df..0b6c4c0f 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -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 diff --git a/source/main.pas b/source/main.pas index 32dcebfc..8f0697cb 100644 --- a/source/main.pas +++ b/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; diff --git a/source/routine_editor.dfm b/source/routine_editor.dfm index 6f4899f2..c6072e46 100644 --- a/source/routine_editor.dfm +++ b/source/routine_editor.dfm @@ -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 diff --git a/source/routine_editor.pas b/source/routine_editor.pas index 7051678c..062d7a67 100644 --- a/source/routine_editor.pas +++ b/source/routine_editor.pas @@ -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 - CellText := 'IN' // A function can only have IN parameters - else begin - Values := explode(DELIM, Parameters[Node.Index]); - CellText := Values[Column-1]; - FreeAndNil(Values); + 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 + 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; diff --git a/source/table_editor.pas b/source/table_editor.pas index 17124dab..3b1ac6dd 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -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; diff --git a/source/trigger_editor.pas b/source/trigger_editor.pas index f102db15..2aef6c7e 100644 --- a/source/trigger_editor.pas +++ b/source/trigger_editor.pas @@ -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; diff --git a/source/view.pas b/source/view.pas index 0be0d169..eec0a966 100644 --- a/source/view.pas +++ b/source/view.pas @@ -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;