diff --git a/packages/delphi11/heidisql.dpr b/packages/delphi11/heidisql.dpr index 30672cdb..725b8d38 100644 --- a/packages/delphi11/heidisql.dpr +++ b/packages/delphi11/heidisql.dpr @@ -35,7 +35,8 @@ uses table_editor in '..\..\source\table_editor.pas' {frmTableEditor}, mysql_api in '..\..\source\mysql_api.pas', mysql_connection in '..\..\source\mysql_connection.pas', - cUnicodeCodecs in '..\..\source\cUnicodeCodecs.pas'; + cUnicodeCodecs in '..\..\source\cUnicodeCodecs.pas', + trigger_editor in '..\..\source\trigger_editor.pas' {frmTriggerEditor: TFrame}; {$R ..\..\res\icon.RES} {$R ..\..\res\version.RES} diff --git a/packages/delphi11/heidisql.dproj b/packages/delphi11/heidisql.dproj index de502b12..7b73c879 100644 --- a/packages/delphi11/heidisql.dproj +++ b/packages/delphi11/heidisql.dproj @@ -180,6 +180,9 @@
frmTextEditor
+ +
frmTriggerEditor
+
frmUpdateCheck
diff --git a/res/icons/cog.png b/res/icons/cog.png new file mode 100644 index 00000000..67de2c6c Binary files /dev/null and b/res/icons/cog.png differ diff --git a/source/const.inc b/source/const.inc index 2ecae042..a8ffe178 100644 --- a/source/const.inc +++ b/source/const.inc @@ -247,6 +247,7 @@ const ICONINDEX_CRASHED_TABLE = -1; ICONINDEX_STOREDPROCEDURE = 119; ICONINDEX_STOREDFUNCTION = 35; + ICONINDEX_TRIGGER = 137; ICONINDEX_FUNCTION = 13; ICONINDEX_KEYWORD = 25; @@ -312,3 +313,5 @@ const FKEY = 'FULLTEXT'; SKEY = 'SPATIAL'; + SYNCOMPLETION_PATTERN: WideString = '\image{%d}\hspace{5}\color{clSilver}%s\column{}\color{clWindowText}%s'; + diff --git a/source/helpers.pas b/source/helpers.pas index c00299c5..2a7b7ad6 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -15,7 +15,7 @@ uses Classes, SysUtils, Graphics, GraphUtil, db, clipbrd, dialogs, type - TListNodeType = (lntNone, lntDb, lntTable, lntCrashedTable, lntView, lntFunction, lntProcedure, lntColumn); + TListNodeType = (lntNone, lntDb, lntTable, lntCrashedTable, lntView, lntFunction, lntProcedure, lntTrigger, lntColumn); TListNodeTypes = Set of TListNodeType; TListNode = record Text: WideString; @@ -2194,7 +2194,9 @@ begin else if t = 'FUNCTION' then Result := lntFunction else if t = 'PROCEDURE' then - Result := lntProcedure; + Result := lntProcedure + else if t = 'TRIGGER' then + Result := lntTrigger; end else begin if TableStatus.IsNull(1) and // Engine column is NULL for views diff --git a/source/main.dfm b/source/main.dfm index 0ba7235b..d1d1acaf 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -2241,6 +2241,13 @@ object MainForm: TMainForm ImageIndex = 19 OnExecute = actTableToolsExecute end + object actCreateTrigger: TAction + Category = 'Database' + Caption = 'Trigger' + Hint = 'Create a trigger' + ImageIndex = 137 + OnExecute = actCreateTriggerExecute + end end object SaveDialog2: TSaveDialog DefaultExt = 'reg' @@ -6039,6 +6046,29 @@ object MainForm: TMainForm 411C2FC06964B5EBFC0F200C40D1D6D566E00000000049454E44AE426082} Name = 'PngImage136' Background = clWindow + end + item + PngImage.Data = { + 89504E470D0A1A0A0000000D4948445200000010000000100804000000B5FA37 + EA0000000467414D410000AFC837058AE90000001974455874536F6674776172 + 650041646F626520496D616765526561647971C9653C000001C34944415478DA + 4D514D48545114FEEEC31C9A37FDBC11A79B4A61D1420884878B84C0160AAE5B + CF4A7117D1225AF8FCC1D171693B0986DC680BB111DCB431988DE84039D4A398 + 4D1158D230538EDADCF14DF7BE773B3D453D97CB8573BEFB9DEF9C8F699CC5FB + 6195F421971EBC3CCBB16380DBE5EFFA3555BAD1AA91AF44B98AC9F687C55380 + 9BF1875459D9AAD0C1353E97B42D0B32A116922321E0A3139BB1B0879F424539 + 0BF045ABFA4DB3031FF069EC699A00EFC6FF4E71E6E1020C4804F436E8F858D3 + DEE4DC74D862337D65D4A3D40176A8DC892602D6E1CE3E73A8457E5B2594C92D + 812A36B7C4440456AAA757C344AE2A852AB30DFF8EF11B7FE86711DF069CB740 + A63FBECE8921866B980B58CEBF6DECA386087E6067E01101E6FB5BD6DB207011 + 713C0FD8E2F6AF8465765B476886BB25272464CAEE95A468A55A13B21C8ACCA6 + AF8F2A9266E03B15DAE81E11436E76DE09F7F07A5C4E75B37D6268867732A687 + CB58D48DC917FFC77CE5F0995BC8E3AB30A27DB4A815CDEAF7CCBB58853B964D + 872D5299BD21518EDB970A835C2153F2ECA0504FC8853723A7663DE992BB576B + 91D2FD5681E58AE646ECB07DAD78CECDE3783C5C4936C096B2E7ECFE0748C2CE + 3B74ACC3980000000049454E44AE426082} + Name = 'PngImage137' + Background = clWindow end> PngOptions = [pngBlendOnDisabled, pngGrayscaleOnDisabled] Left = 104 @@ -6090,6 +6120,9 @@ object MainForm: TMainForm object menuCreateRoutine: TMenuItem Action = actCreateRoutine end + object menuCreateTrigger: TMenuItem + Action = actCreateTrigger + end end object N17: TMenuItem Caption = '-' diff --git a/source/main.pas b/source/main.pas index 40f5821e..8eafecdf 100644 --- a/source/main.pas +++ b/source/main.pas @@ -20,7 +20,7 @@ uses SynCompletionProposal, SynEditHighlighter, SynHighlighterSQL, TntStdCtrls, Tabs, SynUnicode, EditVar, helpers, createdatabase, table_editor, SynRegExpr, - WideStrUtils, ExtActns, CommCtrl, routine_editor, options, + WideStrUtils, ExtActns, CommCtrl, routine_editor, trigger_editor, options, Contnrs, PngSpeedButton, connections, SynEditKeyCmds, mysql_connection, mysql_api, insertfiles, TntComCtrls; @@ -444,6 +444,8 @@ type menuQueryHelpersGenerateInsert: TMenuItem; menuQueryHelpersGenerateUpdate: TMenuItem; menuQueryHelpersGenerateDelete: TMenuItem; + actCreateTrigger: TAction; + menuCreateTrigger: TMenuItem; procedure refreshMonitorConfig; procedure loadWindowConfig; procedure saveWindowConfig; @@ -703,6 +705,7 @@ type procedure TimerFilterVTTimer(Sender: TObject); procedure PageControlMainContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); procedure menuQueryHelpersGenerateStatementClick(Sender: TObject); + procedure actCreateTriggerExecute(Sender: TObject); private ReachedEOT : Boolean; FDelimiter: String; @@ -751,6 +754,7 @@ type SelectDBObjectForm: TfrmSelectDBObject; SQLHelpForm: TfrmSQLhelp; RoutineEditor: TfrmRoutineEditor; + TriggerEditor: TfrmTriggerEditor; OptionsForm: Toptionsform; SessionManager: TConnForm; AllDatabases, Databases: TWideStringList; @@ -1094,6 +1098,7 @@ begin FreeAndNil(OptionsForm); FreeAndNil(SessionManager); FreeAndNil(TableEditor); + FreeAndNil(TriggerEditor); FreeAndNil(CreateDatabaseForm); debug('mem: clearing query and browse data.'); @@ -2166,7 +2171,7 @@ end; procedure TMainForm.actDropObjectsExecute(Sender: TObject); var AllCount : Integer; - Tables, Views, Functions, Procedures: TWideStringList; + Tables, Views, Functions, Procedures, Triggers: TWideStringList; msg, activeDB : WideString; InDBTree: Boolean; Act: TAction; @@ -2203,6 +2208,7 @@ begin Views := TWideStringlist.Create; Procedures := TWideStringlist.Create; Functions := TWideStringlist.Create; + Triggers := TWideStringlist.Create; Act := Sender as TAction; InDBTree := (Act.ActionComponent is TMenuItem) @@ -2229,6 +2235,7 @@ begin lntView: Views.Add(SelectedTable.Text); lntProcedure: Procedures.Add(SelectedTable.Text); lntFunction: Functions.Add(SelectedTable.Text); + lntTrigger: Triggers.Add(SelectedTable.Text); end; end else begin // Invoked from database tab @@ -2237,12 +2244,13 @@ begin Views := GetVTCaptions(ListTables, True, 0, [lntView]); Procedures := GetVTCaptions(ListTables, True, 0, [lntProcedure]); Functions := GetVTCaptions(ListTables, True, 0, [lntFunction]); + Triggers := GetVTCaptions(ListTables, True, 0, [lntTrigger]); end; // Fix actions temporarily enabled for popup menu. ValidateControls(Sender); - AllCount := Tables.Count + Views.Count + Procedures.Count + Functions.Count; + AllCount := Tables.Count + Views.Count + Procedures.Count + Functions.Count + Triggers.Count; // Safety stop to avoid firing DROP TABLE without tablenames if (AllCount = 0) then @@ -2255,6 +2263,7 @@ begin if Views.Count > 0 then msg := msg + CRLF + 'View(s): ' + ImplodeStr(', ', Views); if Procedures.Count > 0 then msg := msg + CRLF + 'Procedure(s): ' + ImplodeStr(', ', Procedures); if Functions.Count > 0 then msg := msg + CRLF + 'Function(s): ' + ImplodeStr(', ', Functions); + if Triggers.Count > 0 then msg := msg + CRLF + 'Trigger(s): ' + ImplodeStr(', ', Triggers); if MessageDlg(msg, mtConfirmation, [mbok,mbcancel], 0) <> mrok then Exit; @@ -2264,6 +2273,7 @@ begin DoDrop('VIEW', Views, True); DoDrop('PROCEDURE', Procedures, False); DoDrop('FUNCTION', Functions, False); + DoDrop('TRIGGER', Triggers, False); // Refresh ListTables + dbtree so the dropped tables are gone: actRefresh.Execute; except @@ -3580,6 +3590,30 @@ begin ' FROM '+mask(DBNAME_INFORMATION_SCHEMA)+'.ROUTINES ' + 'WHERE ROUTINE_SCHEMA = '+esc(db)); end; + if Connection.InformationSchemaObjects.IndexOf('TRIGGERS') > -1 then begin + // Stored routines + Unions.Add('SELECT TRIGGER_NAME AS '+mask(DBO_NAME)+ + ', ''TRIGGER'' AS '+mask(DBO_TYPE)+ + ', NULL AS '+mask(DBO_ENGINE)+ + ', NULL AS '+mask(DBO_VERSION)+ + ', NULL AS '+mask(DBO_ROWFORMAT)+ + ', NULL AS '+mask(DBO_ROWS)+ + ', NULL AS '+mask(DBO_AVGROWLEN)+ + ', NULL AS '+mask(DBO_DATALEN)+ + ', NULL AS '+mask(DBO_MAXDATALEN)+ + ', NULL AS '+mask(DBO_INDEXLEN)+ + ', NULL AS '+mask(DBO_DATAFREE)+ + ', NULL AS '+mask(DBO_AUTOINC)+ + ', CREATED AS '+mask(DBO_CREATED)+ + ', NULL AS '+mask(DBO_UPDATED)+ + ', NULL AS '+mask(DBO_CHECKED)+ + ', DATABASE_COLLATION AS '+mask(DBO_COLLATION)+ + ', NULL AS '+mask(DBO_CHECKSUM)+ + ', NULL AS '+mask(DBO_CROPTIONS)+ + ', NULL AS '+mask(DBO_COMMENT)+ + ' FROM '+mask(DBNAME_INFORMATION_SCHEMA)+'.TRIGGERS ' + + 'WHERE TRIGGER_SCHEMA = '+esc(db)); + end; case Unions.Count of 0: ListObjectsSQL := 'SHOW TABLE STATUS FROM ' + mask(db); 1: ListObjectsSQL := Unions[0] + ' ORDER BY `Name`'; @@ -3722,6 +3756,7 @@ begin lntView: img := ICONINDEX_VIEW; lntProcedure: img := ICONINDEX_STOREDPROCEDURE; lntFunction: img := ICONINDEX_STOREDFUNCTION; + lntTrigger: img := ICONINDEX_TRIGGER; else img := -1; end; VTRowDataListTables[i].ImageIndex := img; @@ -4061,8 +4096,6 @@ var Proposal : TSynCompletionProposal; Editor : TCustomSynEdit; Queries : TWideStringList; -const - ItemPattern: WideString = '\image{%d}\hspace{5}\color{clSilver}%s\column{}\color{clWindowText}%s'; procedure addTable(Results: TMySQLQuery); var ObjName, ObjType: WideString; Icon: Integer; @@ -4077,10 +4110,11 @@ const lntFunction: Icon := ICONINDEX_STOREDFUNCTION; lntProcedure: Icon := ICONINDEX_STOREDPROCEDURE; lntView: Icon := ICONINDEX_VIEW; + lntTrigger: Icon := ICONINDEX_TRIGGER; else Icon := -1; end; Proposal.InsertList.Add( ObjName ); - Proposal.ItemList.Add( WideFormat(ItemPattern, [Icon, ObjType, ObjName]) ); + Proposal.ItemList.Add( WideFormat(SYNCOMPLETION_PATTERN, [Icon, ObjType, ObjName]) ); end; procedure addColumns( tablename: WideString ); @@ -4105,7 +4139,7 @@ const end; while not Columns.Eof do begin Proposal.InsertList.Add(Columns.Col('Field')); - Proposal.ItemList.Add(WideFormat(ItemPattern, [ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field')]) ); + Proposal.ItemList.Add(WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field')]) ); Columns.Next; end; FreeAndNil(Columns); @@ -4143,7 +4177,7 @@ begin Results := Connection.GetResults('SHOW '+UpperCase(rx.Match[1])+' VARIABLES'); while not Results.Eof do begin Proposal.InsertList.Add(Results.Col(0)); - Proposal.ItemList.Add(WideFormat(ItemPattern, [ICONINDEX_PRIMARYKEY, 'variable', Results.Col(0)+' \color{clSilver}= '+WideStringReplace(Results.Col(1), '\', '\\', [rfReplaceAll])] ) ); + Proposal.ItemList.Add(WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_PRIMARYKEY, 'variable', Results.Col(0)+' \color{clSilver}= '+WideStringReplace(Results.Col(1), '\', '\\', [rfReplaceAll])] ) ); Results.Next; end; except @@ -4234,7 +4268,7 @@ begin // Add databases for i := 0 to Databases.Count - 1 do begin Proposal.InsertList.Add(Databases[i]); - Proposal.ItemList.Add(WideFormat(ItemPattern, [ICONINDEX_DB, 'database', Databases[i]])); + Proposal.ItemList.Add(WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_DB, 'database', Databases[i]])); end; if ActiveDatabase <> '' then begin @@ -4254,13 +4288,13 @@ begin if MySqlFunctions[i].Version > Connection.ServerVersionInt then continue; Proposal.InsertList.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration ); - Proposal.ItemList.Add( WideFormat(ItemPattern, [ICONINDEX_FUNCTION, 'function', MySQLFunctions[i].Name + '\color{clSilver}' + MySQLFunctions[i].Declaration] ) ); + Proposal.ItemList.Add( WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_FUNCTION, 'function', MySQLFunctions[i].Name + '\color{clSilver}' + MySQLFunctions[i].Declaration] ) ); end; // Add keywords for i := 0 to MySQLKeywords.Count - 1 do begin Proposal.InsertList.Add( MySQLKeywords[i] ); - Proposal.ItemList.Add( WideFormat(ItemPattern, [ICONINDEX_KEYWORD, 'keyword', MySQLKeywords[i]] ) ); + Proposal.ItemList.Add( WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_KEYWORD, 'keyword', MySQLKeywords[i]] ) ); end; end; @@ -6007,6 +6041,8 @@ begin ImageIndex := ICONINDEX_STOREDPROCEDURE; lntFunction: ImageIndex := ICONINDEX_STOREDFUNCTION; + lntTrigger: + ImageIndex := ICONINDEX_TRIGGER; end; end; end; @@ -8049,6 +8085,15 @@ begin end; +procedure TMainForm.actCreateTriggerExecute(Sender: TObject); +begin + tabEditor.TabVisible := True; + PagecontrolMain.ActivePage := tabEditor; + PlaceObjectEditor(lntTrigger); + TriggerEditor.Init; +end; + + procedure TMainForm.DataGridScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer); var query: String; @@ -8117,6 +8162,8 @@ begin FreeAndNil(ViewEditor); if (not (Which in [lntProcedure, lntFunction])) and Assigned(RoutineEditor) then FreeAndNil(RoutineEditor); + if (Which <> lntTrigger) and Assigned(TriggerEditor) then + FreeAndNil(TriggerEditor); if Which in [lntTable, lntCrashedTable] then begin if not Assigned(TableEditor) then TableEditor := TfrmTableEditor.Create(tabEditor); @@ -8129,6 +8176,10 @@ begin if not Assigned(RoutineEditor) then RoutineEditor := TfrmRoutineEditor.Create(tabEditor); frm := RoutineEditor; + end else if Which = lntTrigger then begin + if not Assigned(TriggerEditor) then + TriggerEditor := TfrmTriggerEditor.Create(tabEditor); + frm := TriggerEditor; end else Exit; frm.Parent := tabEditor; @@ -8149,6 +8200,9 @@ begin end else if Editor = RoutineEditor then begin ObjType := 'Routine'; IconIndex := ICONINDEX_STOREDPROCEDURE; + end else if Editor = TriggerEditor then begin + ObjType := 'Trigger'; + IconIndex := ICONINDEX_Trigger; end else Exit; tabEditor.ImageIndex := IconIndex; @@ -8212,6 +8266,11 @@ begin RoutineEditor.Init(SelectedTable.Text, RoutineType); end; + lntTrigger: begin + PlaceObjectEditor(SelectedTable.NodeType); + TriggerEditor.Init(SelectedTable.Text); + end; + end; end; @@ -8824,6 +8883,8 @@ begin Editors.Add(ViewEditor.SynMemoSelect); if Assigned(RoutineEditor) then Editors.Add(RoutineEditor.SynMemoBody); + if Assigned(TriggerEditor) then + Editors.Add(TriggerEditor.SynMemoStatement); if Assigned(CreateDatabaseForm) then Editors.Add(CreateDatabaseForm.SynMemoPreview); if Assigned(OptionsForm) then @@ -8843,10 +8904,16 @@ begin Editor.Font.Size := FontSize; Editor.Gutter.Font.Name := FontName; Editor.Gutter.Font.Size := FontSize; + Editor.Gutter.AutoSize := BaseEditor.Gutter.AutoSize; + Editor.Gutter.DigitCount := BaseEditor.Gutter.DigitCount; + Editor.Gutter.LeftOffset := BaseEditor.Gutter.LeftOffset; + Editor.Gutter.RightOffset := BaseEditor.Gutter.RightOffset; + Editor.Gutter.ShowLineNumbers := BaseEditor.Gutter.ShowLineNumbers; Editor.ActiveLineColor := ActiveLineColor; Editor.Options := BaseEditor.Options; Editor.TabWidth := TabWidth; Editor.MaxScrollWidth := BaseEditor.MaxScrollWidth; + Editor.WantTabs := BaseEditor.WantTabs; // Shortcuts if Editor = BaseEditor then for j:=0 to Editor.Keystrokes.Count-1 do begin KeyStroke := Editor.Keystrokes[j]; diff --git a/source/trigger_editor.dfm b/source/trigger_editor.dfm new file mode 100644 index 00000000..4dbb759a --- /dev/null +++ b/source/trigger_editor.dfm @@ -0,0 +1,160 @@ +object frmTriggerEditor: TfrmTriggerEditor + Left = 0 + Top = 0 + Width = 477 + Height = 332 + TabOrder = 0 + DesignSize = ( + 477 + 332) + object lblName: TLabel + Left = 3 + Top = 6 + Width = 31 + Height = 13 + Caption = 'Name:' + FocusControl = editName + end + object lblTable: TLabel + Left = 3 + Top = 33 + Width = 45 + Height = 13 + Caption = 'On table:' + end + object lblBody: TLabel + Left = 3 + Top = 87 + Width = 327 + Height = 13 + Caption = 'Trigger statement: (e.g. "SET NEW.columnA = TRIM(OLD.columnA")' + end + object lblEvent: TLabel + Left = 3 + Top = 59 + Width = 32 + Height = 13 + Caption = 'Event:' + end + object editName: TTntEdit + Left = 96 + Top = 3 + Width = 378 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 0 + Text = 'editName' + OnChange = Modification + end + object SynMemoStatement: TSynMemo + Left = 3 + Top = 106 + Width = 471 + Height = 192 + SingleLineMode = False + Anchors = [akLeft, akTop, akRight, akBottom] + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = 'Courier New' + Font.Style = [] + TabOrder = 2 + Gutter.Font.Charset = DEFAULT_CHARSET + Gutter.Font.Color = clWindowText + Gutter.Font.Height = -11 + Gutter.Font.Name = 'Courier New' + Gutter.Font.Style = [] + Lines.UnicodeStrings = 'SynMemoStatement' + OnChange = Modification + end + object btnHelp: TButton + Left = 3 + Top = 304 + Width = 75 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = 'Help' + TabOrder = 3 + OnClick = btnHelpClick + end + object btnDiscard: TButton + Left = 84 + Top = 304 + Width = 75 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = 'Discard' + TabOrder = 4 + OnClick = btnDiscardClick + end + object btnSave: TButton + Left = 165 + Top = 304 + Width = 75 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = 'Save' + Default = True + TabOrder = 5 + OnClick = btnSaveClick + end + object comboTable: TTntComboBox + Left = 96 + Top = 30 + Width = 378 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + ItemHeight = 13 + TabOrder = 1 + OnChange = Modification + end + object comboTiming: TComboBox + Left = 96 + Top = 56 + Width = 145 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + TabOrder = 6 + OnChange = Modification + end + object comboEvent: TComboBox + Left = 247 + Top = 56 + Width = 145 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + TabOrder = 7 + OnChange = Modification + end + object SynCompletionProposalStatement: TSynCompletionProposal + Options = [scoLimitToMatchedText, scoUseInsertList, scoUsePrettyText, scoEndCharCompletion, scoCompleteWithTab, scoCompleteWithEnter] + EndOfTokenChr = '()[]. ' + TriggerChars = '.' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'MS Sans Serif' + Font.Style = [] + TitleFont.Charset = DEFAULT_CHARSET + TitleFont.Color = clBtnText + TitleFont.Height = -11 + TitleFont.Name = 'MS Sans Serif' + TitleFont.Style = [fsBold] + Columns = < + item + BiggestWord = 'CONSTRUCTOR' + BiggestWordW = 'CONSTRUCTOR' + end> + Images = MainForm.PngImageListMain + OnExecute = SynCompletionProposalStatementExecute + ShortCut = 16416 + Editor = SynMemoStatement + Left = 264 + Top = 304 + EndOfTokenChrW = '()[]. ' + TriggerCharsW = '.' + end +end diff --git a/source/trigger_editor.pas b/source/trigger_editor.pas new file mode 100644 index 00000000..315cec18 --- /dev/null +++ b/source/trigger_editor.pas @@ -0,0 +1,197 @@ +unit trigger_editor; + +interface + +uses + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, StdCtrls, TntStdCtrls, SynEdit, SynMemo, ExtCtrls, mysql_connection, mysql_api, + SynCompletionProposal, VirtualTrees; + +type + TfrmTriggerEditor = class(TFrame) + lblName: TLabel; + editName: TTntEdit; + SynMemoStatement: TSynMemo; + btnHelp: TButton; + btnDiscard: TButton; + btnSave: TButton; + comboTable: TTntComboBox; + lblTable: TLabel; + lblBody: TLabel; + SynCompletionProposalStatement: TSynCompletionProposal; + lblEvent: TLabel; + comboTiming: TComboBox; + comboEvent: TComboBox; + procedure btnHelpClick(Sender: TObject); + procedure btnDiscardClick(Sender: TObject); + procedure Modification(Sender: TObject); + procedure btnSaveClick(Sender: TObject); + procedure SynCompletionProposalStatementExecute(Kind: SynCompletionType; Sender: TObject; + var CurrentInput: WideString; var x, y: Integer; var CanExecute: Boolean); + private + { Private declarations } + FEditTriggerName: WideString; + public + { Public declarations } + constructor Create(AOwner: TComponent); override; + procedure Init(EditTriggerName: WideString=''); + end; + +implementation + +uses main, helpers; + +{$R *.dfm} + + +{** + Create: Restore GUI setup +} +constructor TfrmTriggerEditor.Create(AOwner: TComponent); +var + col: TProposalColumn; + i: Integer; +begin + inherited Create(AOwner); + Align := alClient; + SynMemoStatement.Highlighter := Mainform.SynSQLSyn1; + InheritFont(Font); + editName.MaxLength := NAME_LEN; + comboTiming.Items.Text := 'BEFORE'+CRLF+'AFTER'; + comboEvent.Items.Text := 'INSERT'+CRLF+'UPDATE'+CRLF+'DELETE'; + for i:=0 to Mainform.SynCompletionProposal.Columns.Count-1 do begin + col := SynCompletionProposalStatement.Columns.Add; + col.BiggestWord := Mainform.SynCompletionProposal.Columns[i].BiggestWord; + end; + SynCompletionProposalStatement.NbLinesInWindow := Mainform.SynCompletionProposal.NbLinesInWindow; + SynCompletionProposalStatement.Width := Mainform.SynCompletionProposal.Width; + SynCompletionProposalStatement.Options := Mainform.SynCompletionProposal.Options; + SynCompletionProposalStatement.TimerInterval := Mainform.SynCompletionProposal.TimerInterval; + SynCompletionProposalStatement.ItemHeight := Mainform.SynCompletionProposal.ItemHeight; + SynCompletionProposalStatement.Margin := Mainform.SynCompletionProposal.Margin; + SynCompletionProposalStatement.Font := Font; +end; + + +procedure TfrmTriggerEditor.Init(EditTriggerName: WideString=''); +var + Definition, TableList: TMySQLQuery; +begin + Mainform.SetupSynEditors; + FEditTriggerName := EditTriggerName; + editName.Text := ''; + SynMemoStatement.Text := ''; + comboEvent.ItemIndex := 0; + comboTiming.ItemIndex := 0; + TableList := Mainform.FetchActiveDbTableList; + comboTable.Items.Clear; + while not TableList.Eof do begin + if GetDBObjectType(TableList) in [lntTable, lntCrashedTable] then + comboTable.Items.Add(TableList.Col(DBO_NAME)); + TableList.Next; + end; + if comboTable.Items.Count > 0 then + comboTable.ItemIndex := 0; + if FEditTriggerName <> '' then begin + // Edit mode + Mainform.SetEditorTabCaption(Self, FEditTriggerName); + editName.Text := FEditTriggerName; + Definition := Mainform.Connection.GetResults('SELECT '+ + 'EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_STATEMENT, ACTION_TIMING '+ + 'FROM information_schema.TRIGGERS '+ + 'WHERE TRIGGER_SCHEMA='+esc(Mainform.ActiveDatabase)+' AND TRIGGER_NAME='+esc(FEditTriggerName)); + comboTable.ItemIndex := comboTable.Items.IndexOf(Definition.Col('EVENT_OBJECT_TABLE')); + comboTiming.ItemIndex := comboTiming.Items.IndexOf(UpperCase(Definition.Col('ACTION_TIMING'))); + comboEvent.ItemIndex := comboEvent.Items.IndexOf(UpperCase(Definition.Col('EVENT_MANIPULATION'))); + SynMemoStatement.Text := Definition.Col('ACTION_STATEMENT'); + end else begin + Mainform.SetEditorTabCaption(Self, ''); + editName.Text := 'Enter trigger name'; + end; + btnSave.Enabled := False; + btnDiscard.Enabled := False; +end; + + +procedure TfrmTriggerEditor.Modification(Sender: TObject); +begin + // Enable buttons if anything has changed + btnSave.Enabled := (editName.Text <> '') and (comboTable.ItemIndex > -1) + and (comboTiming.ItemIndex > -1) and (comboEvent.ItemIndex > -1) + and (SynMemoStatement.Text <> ''); + btnDiscard.Enabled := True; +end; + + +procedure TfrmTriggerEditor.btnDiscardClick(Sender: TObject); +begin + // Reinit editor, discarding changes + Init(FEditTriggerName); +end; + + +procedure TfrmTriggerEditor.btnSaveClick(Sender: TObject); +var + sql: WideString; + FocusChangeEvent: procedure(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex) of object; +begin + // Edit mode means we drop the trigger and recreate it, as there is no ALTER TRIGGER. + try + if FEditTriggerName <> '' then + Mainform.Connection.Query('DROP TRIGGER '+Mainform.mask(FEditTriggerName)); + // CREATE + // [DEFINER = { user | CURRENT_USER }] + // TRIGGER trigger_name trigger_time trigger_event + // ON tbl_name FOR EACH ROW trigger_stmt + sql := 'CREATE TRIGGER '+Mainform.mask(editName.Text)+' '+ + comboTiming.Items[comboTiming.ItemIndex]+' '+comboEvent.Items[comboEvent.ItemIndex]+ + ' ON '+Mainform.mask(comboTable.Text)+ + ' FOR EACH ROW '+SynMemoStatement.Text; + Mainform.Connection.Query(sql); + FocusChangeEvent := Mainform.DBtree.OnFocusChanged; + Mainform.DBtree.OnFocusChanged := nil; + Mainform.RefreshTreeDB(Mainform.ActiveDatabase); + Mainform.DBtree.OnFocusChanged := FocusChangeEvent; + Mainform.SelectDBObject(editName.Text, lntTrigger); + Init(editName.Text); + except on E:Exception do + MessageDlg(E.Message, mtError, [mbOK], 0); + end; +end; + + +procedure TfrmTriggerEditor.SynCompletionProposalStatementExecute(Kind: SynCompletionType; Sender: TObject; + var CurrentInput: WideString; var x, y: Integer; var CanExecute: Boolean); +var + Proposal: TSynCompletionProposal; + Token: WideString; + Columns: TMySQLQuery; +begin + // Propose column names from referencing table + Proposal := Sender as TSynCompletionProposal; + Token := UpperCase(Proposal.PreviousToken); + Proposal.InsertList.Clear; + Proposal.ItemList.Clear; + if (Token = 'NEW') or (Token = 'OLD') then begin + if comboTable.Text = '' then + CanExecute := False + else try + Columns := Mainform.Connection.GetResults('SHOW COLUMNS FROM '+Mainform.mask(comboTable.Text)); + while not Columns.Eof do begin + Proposal.InsertList.Add(Columns.Col('Field')); + Proposal.ItemList.Add(WideFormat(SYNCOMPLETION_PATTERN, [ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field')]) ); + Columns.Next; + end; + except + end; + end else + Mainform.SynCompletionProposalExecute(Kind, Sender, CurrentInput, x, y, CanExecute); +end; + + +procedure TfrmTriggerEditor.btnHelpClick(Sender: TObject); +begin + Mainform.CallSQLHelpWithKeyword('TRIGGERS'); +end; + +end.