-
-
Notifications
You must be signed in to change notification settings - Fork 563
Expand file tree
/
Copy pathtrigger_editor.pas
More file actions
319 lines (282 loc) · 11.1 KB
/
trigger_editor.pas
File metadata and controls
319 lines (282 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
unit trigger_editor;
interface
uses
Winapi.Windows, System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Menus, SynEdit, SynMemo,
SynCompletionProposal, SynRegExpr,
dbconnection, dbstructures, dbstructures.mysql, apphelpers, gnugettext, Vcl.ComCtrls, extra_controls;
type
TFrame = TDBObjectEditor;
TfrmTriggerEditor = class(TFrame)
SynMemoBody: TSynMemo;
btnHelp: TButton;
btnDiscard: TButton;
btnSave: TButton;
lblBody: TLabel;
SynCompletionProposalStatement: TSynCompletionProposal;
PageControlMain: TPageControl;
tabOptions: TTabSheet;
tabCreateCode: TTabSheet;
comboDefiner: TComboBox;
lblDefiner: TLabel;
editName: TEdit;
lblName: TLabel;
lblTable: TLabel;
comboTable: TComboBox;
lblEvent: TLabel;
comboTiming: TComboBox;
comboEvent: TComboBox;
SynMemoCreateCode: TSynMemo;
procedure btnHelpClick(Sender: TObject);
procedure btnDiscardClick(Sender: TObject);
procedure Modification(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
procedure SynCompletionProposalStatementExecute(Kind: SynCompletionType; Sender: TObject;
var CurrentInput: String; var x, y: Integer; var CanExecute: Boolean);
procedure comboDefinerDropDown(Sender: TObject);
procedure comboChange(Sender: TObject);
procedure PageControlMainChange(Sender: TObject);
private
{ Private declarations }
function ComposeCreateStatement: String;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
procedure Init(Obj: TDBObject); override;
function ApplyModifications: TModalResult; override;
end;
implementation
uses main;
{$R *.dfm}
{**
Create: Restore GUI setup
}
constructor TfrmTriggerEditor.Create(AOwner: TComponent);
var
col: TProposalColumn;
i: Integer;
begin
inherited;
SynMemoBody.Highlighter := Mainform.SynSQLSynUsed;
editName.MaxLength := NAME_LEN;
for i:=0 to Mainform.SynCompletionProposal.Columns.Count-1 do begin
col := SynCompletionProposalStatement.Columns.Add;
col.ColumnWidth := Mainform.SynCompletionProposal.Columns[i].ColumnWidth;
end;
SynCompletionProposalStatement.NbLinesInWindow := Mainform.SynCompletionProposal.NbLinesInWindow;
SynCompletionProposalStatement.Width := Mainform.SynCompletionProposal.Width;
SynCompletionProposalStatement.Options := Mainform.SynCompletionProposal.Options;
SynCompletionProposalStatement.TimerInterval := Mainform.SynCompletionProposal.TimerInterval;
SynCompletionProposalStatement.Margin := Mainform.SynCompletionProposal.Margin;
FMainSynMemo := SynMemoBody;
btnSave.Hint := ShortCutToText(MainForm.actSaveSQL.ShortCut);
end;
procedure TfrmTriggerEditor.Init(Obj: TDBObject);
var
Definitions: TDBQuery;
DBObjects: TDBObjectList;
i: Integer;
Found: Boolean;
Body, QuoteCharsRx, QuotedWordRx: String;
rx: TRegExpr;
begin
inherited;
editName.Text := '';
comboDefiner.Text := '';
comboDefiner.TextHint := f_('Current user (%s)', [Obj.Connection.CurrentUserHostCombination]);
comboDefiner.Hint := f_('Leave empty for current user (%s)', [Obj.Connection.CurrentUserHostCombination]);
SynMemoBody.Text := 'BEGIN'+CRLF+CRLF+'END';
comboEvent.Items.Text := 'INSERT'+CRLF+'UPDATE'+CRLF+'DELETE';
comboEvent.ItemIndex := 0;
case Obj.Connection.Parameters.NetTypeGroup of
ngSQLite:
comboTiming.Items.Text := 'BEFORE' + sLineBreak + 'AFTER' + sLineBreak + 'INSTEAD OF';
else
comboTiming.Items.Text := 'BEFORE' + sLineBreak + 'AFTER';
end;
comboTiming.ItemIndex := 0;
DBObjects := MainForm.ActiveConnection.GetDBObjects(Mainform.ActiveDatabase);
comboTable.Items.Clear;
for i:=0 to DBObjects.Count-1 do begin
if DBObjects[i].NodeType in [lntTable] then
comboTable.Items.Add(DBObjects[i].Name);
end;
if comboTable.Items.Count > 0 then
comboTable.ItemIndex := 0;
if ObjectExists then begin
// Edit mode
editName.Text := DBObject.Name;
// MariaDB: CREATE DEFINER=`root`@`localhost` TRIGGER `trg` BEFORE INSERT ON `tbl` FOR EACH ROW BEGIN .. END
// SQLite: CREATE TRIGGER "test_delete" AFTER INSERT ON "albums" FOR EACH ROW BEGIN .. END
rx := TRegExpr.Create;
rx.ModifierI := True;
QuoteCharsRx := QuoteRegExprMetaChars(DBObject.Connection.QuoteChars);
QuotedWordRx := '['+QuoteCharsRx+']?[^'+QuoteCharsRx+']+['+QuoteCharsRx+']?';
rx.Expression := '(\sDEFINER=('+QuotedWordRx+'@'+QuotedWordRx+'))?' +
'\s+TRIGGER\s+'+QuotedWordRx +
'\s+('+Implode('|', comboTiming.Items)+')' +
'\s+('+Implode('|', comboEvent.Items)+')' +
'\s+ON\s+('+QuotedWordRx+')' +
'\s+FOR\s+EACH\s+ROW\s+(.+)$';
try
Body := DBObject.Connection.GetCreateCode(DBObject);
if rx.Exec(Body) then begin
comboDefiner.Text := DBObject.Connection.DeQuoteIdent(rx.Match[2], '@');
comboTiming.ItemIndex := comboTiming.Items.IndexOf(UpperCase(rx.Match[3]));
comboEvent.ItemIndex := comboEvent.Items.IndexOf(UpperCase(rx.Match[4]));
comboTable.ItemIndex := comboTable.Items.IndexOf(DBObject.Connection.DeQuoteIdent(rx.Match[5]));
Body := rx.Match[6];
end
else
raise EDbError.CreateFmt(_('Result from previous query does not contain expected pattern: %s'), [rx.Expression]);
except
on E:EDbError do begin
DBObject.Connection.Log(lcError, E.Message);
Body := '';
end;
end;
SynMemoBody.Text := Body;
SynMemoBody.TopLine := FMainSynMemoPreviousTopLine;
end else begin
editName.Text := '';
if MainForm.FocusedTables.Count > 0 then begin
for i:=0 to comboTable.Items.Count-1 do begin
if comboTable.Items[i] = MainForm.FocusedTables[0].Name then begin
comboTable.ItemIndex := i;
comboChange(comboTable);
Break;
end;
end;
end;
end;
// Buttons are randomly moved, since VirtualTree update, see #440
btnSave.Top := Height - btnSave.Height - 3;
btnHelp.Top := btnSave.Top;
btnDiscard.Top := btnSave.Top;
Modification(Self);
Modified := False;
btnSave.Enabled := Modified;
btnDiscard.Enabled := Modified;
Mainform.ShowStatusMsg;
TExtForm.PageControlTabHighlight(PageControlMain);
Screen.Cursor := crDefault;
end;
procedure TfrmTriggerEditor.Modification(Sender: TObject);
begin
// Enable buttons if anything has changed
Modified := True;
btnSave.Enabled := Modified
and (editName.Text <> '') and (comboTable.ItemIndex > -1)
and (comboTiming.ItemIndex > -1) and (comboEvent.ItemIndex > -1)
and (SynMemoBody.Text <> '');
btnDiscard.Enabled := Modified;
SynMemoCreateCode.Text := ComposeCreateStatement;
end;
procedure TfrmTriggerEditor.PageControlMainChange(Sender: TObject);
begin
TExtForm.PageControlTabHighlight(PageControlMain);
end;
procedure TfrmTriggerEditor.btnDiscardClick(Sender: TObject);
begin
// Reinit editor, discarding changes
Modified := False;
Init(DBObject);
end;
procedure TfrmTriggerEditor.btnSaveClick(Sender: TObject);
begin
ApplyModifications;
end;
procedure TfrmTriggerEditor.comboChange(Sender: TObject);
begin
// Auto generate trigger name as long as it was not user-edited. See issue #3477.
if (not ObjectExists) and (not editName.Modified) then
editName.Text := comboTable.Text+'_'+LowerCase(comboTiming.Text)+'_'+LowerCase(comboEvent.Text);
Modification(Sender);
end;
procedure TfrmTriggerEditor.comboDefinerDropDown(Sender: TObject);
begin
// Populate definers from mysql.user
(Sender as TComboBox).Items.Assign(DBObject.Connection.AllUserHostCombinations);
end;
function TfrmTriggerEditor.ApplyModifications: TModalResult;
var
OldCreateCode: String;
begin
// Edit mode means we drop the trigger and recreate it, as there is no ALTER TRIGGER.
Result := mrOk;
try
// In edit mode we could create a temporary trigger, but that would only cause an error a la
// "This version of MySQL doesn't yet support multiple triggers with the same action time and event for one table"
// So, we take the risk of loosing the trigger for cases in which the user has SQL errors in
// his statement. The user must fix such errors and re-press "Save" while we have them in memory,
// otherwise the trigger attributes are lost forever.
OldCreateCode := '';
if ObjectExists then try
OldCreateCode := DBObject.CreateCode;
DBObject.Connection.Query('DROP TRIGGER '+DBObject.Connection.QuoteIdent(DBObject.Name));
except
end;
MainForm.ActiveConnection.Query(ComposeCreateStatement);
DBObject.Name := editName.Text;
DBObject.UnloadDetails;
Mainform.UpdateEditorTab;
Mainform.RefreshTree(DBObject);
Modified := False;
btnSave.Enabled := Modified;
btnDiscard.Enabled := Modified;
except
on E:EDbError do begin
ErrorDialog(E.Message);
Result := mrAbort;
if not OldCreateCode.IsEmpty then
DBObject.Connection.Query(OldCreateCode);
end;
end;
end;
procedure TfrmTriggerEditor.SynCompletionProposalStatementExecute(Kind: SynCompletionType; Sender: TObject;
var CurrentInput: String; var x, y: Integer; var CanExecute: Boolean);
var
Proposal: TSynCompletionProposal;
Token, DisplayText: String;
Columns: TDBQuery;
begin
// Propose column names from referencing table
Proposal := Sender as TSynCompletionProposal;
Proposal.Font.Assign(Font);
Proposal.ItemHeight := TExtForm.ScaleSize(PROPOSAL_ITEM_HEIGHT, Self);
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 := DBObject.Connection.GetResults('SHOW COLUMNS FROM '+DBObject.Connection.QuoteIdent(comboTable.Text));
while not Columns.Eof do begin
DisplayText := SynCompletionProposalPrettyText(ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field'), '');
Proposal.AddItem(DisplayText, 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
Help(Self, 'createtrigger');
end;
function TfrmTriggerEditor.ComposeCreateStatement: String;
begin
// CREATE
// [DEFINER = { user | CURRENT_USER }]
// TRIGGER trigger_name trigger_time trigger_event
// ON tbl_name FOR EACH ROW trigger_stmt
Result := 'CREATE ';
if comboDefiner.Text <> '' then
Result := Result + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, True, '@')+' ';
Result := Result + 'TRIGGER '+DBObject.Connection.QuoteIdent(editName.Text)+' '+
comboTiming.Items[comboTiming.ItemIndex]+' '+comboEvent.Items[comboEvent.ItemIndex]+
' ON '+DBObject.Connection.QuoteIdent(comboTable.Text)+
' FOR EACH ROW '+SynMemoBody.Text;
end;
end.