Issue #2127: add view editor

This commit is contained in:
Ansgar Becker
2025-04-14 16:28:12 +02:00
parent 2bd7cb972f
commit a5cfcd77b1
5 changed files with 693 additions and 3 deletions

View File

@@ -280,6 +280,13 @@
<HasResources Value="True"/>
<ResourceBaseClass Value="Frame"/>
</Unit>
<Unit>
<Filename Value="source\view.pas"/>
<IsPartOfProject Value="True"/>
<ComponentName Value="frmView"/>
<HasResources Value="True"/>
<ResourceBaseClass Value="Frame"/>
</Unit>
</Units>
</ProjectOptions>
<CompilerOptions>

View File

@@ -20,7 +20,7 @@ uses
column_selection, loaddata, csv_detector, createdatabase, editvar, copytable,
exportgrid, usermanager, selectdbobject, reformatter, searchreplace,
connections, jsonregistry, sqlhelp, updatecheck, insertfiles, texteditor,
customize_highlighter, preferences, table_editor;
customize_highlighter, preferences, table_editor, view;
{$R *.res}
{.$R resources.rc}

View File

@@ -1421,7 +1421,7 @@ implementation
uses
FileInfo, winpeimagereader, elfreader, machoreader, About, data_sorting, column_selection, loaddata, editvar,
copytable, csv_detector, exportgrid, usermanager, selectdbobject, reformatter, connections, sqlhelp, updatecheck,
insertfiles, texteditor, preferences, table_editor;
insertfiles, texteditor, preferences, table_editor, view;
{$R *.lfm}
@@ -12190,7 +12190,7 @@ begin
FreeAndNil(ActiveObjectEditor);
case Obj.NodeType of
lntTable: EditorClass := TfrmTableEditor;
//lntView: EditorClass := TfrmView;
lntView: EditorClass := TfrmView;
//lntProcedure, lntFunction: EditorClass := TfrmRoutineEditor;
//lntTrigger: EditorClass := TfrmTriggerEditor;
//lntEvent: EditorClass := TfrmEventEditor;

423
source/view.lfm Normal file
View File

@@ -0,0 +1,423 @@
object frmView: TfrmView
Left = 0
Height = 625
Top = 0
Width = 875
ClientHeight = 625
ClientWidth = 875
DesignTimePPI = 120
ParentFont = False
TabOrder = 0
object lblSelect: TLabel
Left = 4
Height = 20
Top = 186
Width = 113
BorderSpacing.Around = 6
Caption = 'Select statement:'
end
object lblDisabledWhy: TLabel
AnchorSideLeft.Control = btnSave
AnchorSideLeft.Side = asrBottom
AnchorSideRight.Control = Owner
AnchorSideRight.Side = asrBottom
AnchorSideBottom.Control = Owner
AnchorSideBottom.Side = asrBottom
Left = 306
Height = 31
Top = 588
Width = 563
Anchors = [akLeft, akRight, akBottom]
AutoSize = False
BorderSpacing.Around = 6
Caption = 'You need the SHOW VIEW privilege in order to edit a view.'
Layout = tlCenter
Visible = False
end
inline SynMemoBody: TSynEdit
AnchorSideLeft.Control = Owner
AnchorSideTop.Control = PageControlMain
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = Owner
AnchorSideRight.Side = asrBottom
AnchorSideBottom.Control = btnSave
Left = 6
Height = 349
Top = 233
Width = 863
BorderSpacing.Around = 6
Anchors = [akTop, akLeft, akRight, akBottom]
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Courier New'
Font.Pitch = fpFixed
Font.Quality = fqNonAntialiased
ParentColor = False
ParentFont = False
TabOrder = 1
Gutter.LeftOffset = 2
Gutter.Width = 70
Gutter.MouseActions = <>
RightGutter.Width = 0
RightGutter.MouseActions = <>
Keystrokes = <>
MouseActions = <>
MouseTextActions = <>
MouseSelActions = <>
Options = [eoAutoIndent, eoGroupUndo, eoShowScrollHint]
VisibleSpecialChars = [vscSpace, vscTabAtLast]
RightEdge = 0
SelectedColor.BackPriority = 50
SelectedColor.ForePriority = 50
SelectedColor.FramePriority = 50
SelectedColor.BoldPriority = 50
SelectedColor.ItalicPriority = 50
SelectedColor.UnderlinePriority = 50
SelectedColor.StrikeOutPriority = 50
BracketHighlightStyle = sbhsBoth
BracketMatchColor.Background = clNone
BracketMatchColor.Foreground = clNone
BracketMatchColor.Style = [fsBold]
FoldedCodeColor.Background = clNone
FoldedCodeColor.Foreground = clGray
FoldedCodeColor.FrameColor = clGray
MouseLinkColor.Background = clNone
MouseLinkColor.Foreground = clBlue
LineHighlightColor.Background = clNone
LineHighlightColor.Foreground = clNone
OnChange = Modification
inline SynLeftGutterPartList1: TSynGutterPartList
object SynGutterMarks1: TSynGutterMarks
Width = 30
MouseActions = <>
end
object SynGutterLineNumber1: TSynGutterLineNumber
Width = 17
MouseActions = <>
MarkupInfo.Background = clBtnFace
MarkupInfo.Foreground = clNone
DigitCount = 2
ShowOnlyLineNumbersMultiplesOf = 1
ZeroStart = False
LeadingZeros = False
end
object SynGutterChanges1: TSynGutterChanges
Width = 5
MouseActions = <>
ModifiedColor = 59900
SavedColor = clGreen
end
object SynGutterSeparator1: TSynGutterSeparator
Width = 3
MouseActions = <>
MarkupInfo.Background = clWhite
MarkupInfo.Foreground = clGray
end
object SynGutterCodeFolding1: TSynGutterCodeFolding
Width = 13
MouseActions = <>
MarkupInfo.Background = clNone
MarkupInfo.Foreground = clGray
MouseActionsExpanded = <>
MouseActionsCollapsed = <>
end
end
end
object btnDiscard: TButton
AnchorSideLeft.Control = btnHelp
AnchorSideLeft.Side = asrBottom
AnchorSideBottom.Control = Owner
AnchorSideBottom.Side = asrBottom
Left = 106
Height = 31
Top = 588
Width = 94
Anchors = [akLeft, akBottom]
BorderSpacing.Around = 6
Caption = 'Discard'
TabOrder = 3
OnClick = btnDiscardClick
end
object btnSave: TButton
AnchorSideLeft.Control = btnDiscard
AnchorSideLeft.Side = asrBottom
AnchorSideBottom.Control = Owner
AnchorSideBottom.Side = asrBottom
Left = 206
Height = 31
Top = 588
Width = 94
Anchors = [akLeft, akBottom]
BorderSpacing.Around = 6
Caption = 'Save'
Default = True
TabOrder = 4
OnClick = btnSaveClick
end
object btnHelp: TButton
AnchorSideLeft.Control = Owner
AnchorSideBottom.Control = Owner
AnchorSideBottom.Side = asrBottom
Left = 6
Height = 31
Top = 588
Width = 94
Anchors = [akLeft, akBottom]
BorderSpacing.Around = 6
Caption = 'Help'
TabOrder = 2
OnClick = btnHelpClick
end
object PageControlMain: TPageControl
AnchorSideLeft.Control = Owner
AnchorSideTop.Control = Owner
AnchorSideRight.Control = Owner
AnchorSideRight.Side = asrBottom
Left = 6
Height = 221
Top = 6
Width = 863
ActivePage = tabOptions
Align = alTop
BorderSpacing.Around = 6
TabIndex = 0
TabOrder = 0
OnChange = PageControlMainChange
object tabOptions: TTabSheet
Caption = 'Options'
ClientHeight = 188
ClientWidth = 855
ImageIndex = 14
object lblName: TLabel
AnchorSideLeft.Control = tabOptions
AnchorSideTop.Control = tabOptions
Left = 6
Height = 20
Top = 6
Width = 43
BorderSpacing.Around = 6
Caption = 'Name:'
end
object lblDefiner: TLabel
AnchorSideLeft.Control = editName
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = tabOptions
Left = 499
Height = 20
Top = 6
Width = 52
BorderSpacing.Around = 6
Caption = 'Definer:'
end
object lblSecurity: TLabel
AnchorSideLeft.Control = rgAlgorithm
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = comboDefiner
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = tabOptions
AnchorSideRight.Side = asrBottom
Left = 501
Height = 20
Top = 40
Width = 83
BorderSpacing.Around = 6
Caption = 'SQL security:'
end
object rgAlgorithm: TRadioGroup
AnchorSideLeft.Control = tabOptions
AnchorSideTop.Control = editName
AnchorSideTop.Side = asrBottom
AnchorSideRight.Side = asrBottom
AnchorSideBottom.Control = tabOptions
AnchorSideBottom.Side = asrBottom
Left = 6
Height = 142
Top = 40
Width = 489
Anchors = [akTop, akLeft, akBottom]
AutoFill = True
BorderSpacing.Around = 6
Caption = 'Algorithm'
ChildSizing.LeftRightSpacing = 6
ChildSizing.EnlargeHorizontal = crsHomogenousChildResize
ChildSizing.EnlargeVertical = crsHomogenousChildResize
ChildSizing.ShrinkHorizontal = crsScaleChilds
ChildSizing.ShrinkVertical = crsScaleChilds
ChildSizing.Layout = cclLeftToRightThenTopToBottom
ChildSizing.ControlsPerLine = 1
ClientHeight = 117
ClientWidth = 485
ItemIndex = 0
Items.Strings = (
'UNDEFINED'
'MERGE'
'TEMPTABLE'
)
OnClick = Modification
TabOrder = 3
end
object editName: TEdit
AnchorSideTop.Control = tabOptions
Left = 105
Height = 28
Top = 6
Width = 388
BorderSpacing.Around = 6
TabOrder = 0
Text = 'editName'
TextHint = 'Enter view name'
OnChange = Modification
end
object comboDefiner: TComboBox
AnchorSideTop.Control = tabOptions
AnchorSideRight.Control = tabOptions
AnchorSideRight.Side = asrBottom
Left = 606
Height = 28
Top = 6
Width = 243
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Around = 6
ItemHeight = 20
TabOrder = 1
Text = 'comboDefiner'
OnChange = Modification
OnDropDown = comboDefinerDropDown
end
object comboSecurity: TComboBox
AnchorSideRight.Control = tabOptions
AnchorSideRight.Side = asrBottom
Left = 606
Height = 28
Top = 38
Width = 243
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Around = 6
ItemHeight = 20
Style = csDropDownList
TabOrder = 2
OnChange = Modification
end
object rgCheck: TRadioGroup
AnchorSideLeft.Control = rgAlgorithm
AnchorSideLeft.Side = asrBottom
AnchorSideTop.Control = comboSecurity
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = tabOptions
AnchorSideRight.Side = asrBottom
AnchorSideBottom.Control = tabOptions
AnchorSideBottom.Side = asrBottom
Left = 501
Height = 108
Top = 72
Width = 348
Anchors = [akTop, akLeft, akRight]
AutoFill = True
BorderSpacing.Around = 6
Caption = 'Check option for updates'
ChildSizing.LeftRightSpacing = 6
ChildSizing.EnlargeHorizontal = crsHomogenousChildResize
ChildSizing.EnlargeVertical = crsHomogenousChildResize
ChildSizing.ShrinkHorizontal = crsScaleChilds
ChildSizing.ShrinkVertical = crsScaleChilds
ChildSizing.Layout = cclLeftToRightThenTopToBottom
ChildSizing.ControlsPerLine = 1
ClientHeight = 83
ClientWidth = 344
ItemIndex = 0
Items.Strings = (
'None'
'CASCADED'
'LOCAL'
)
OnClick = Modification
TabOrder = 4
end
end
object tabCreateCode: TTabSheet
Caption = 'CREATE code'
ClientHeight = 188
ClientWidth = 855
ImageIndex = 39
inline SynMemoCreateCode: TSynEdit
Left = 0
Height = 188
Top = 0
Width = 855
Align = alClient
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Courier New'
Font.Pitch = fpFixed
Font.Quality = fqNonAntialiased
ParentColor = False
ParentFont = False
TabOrder = 0
Gutter.Width = 68
Gutter.MouseActions = <>
RightGutter.Width = 0
RightGutter.MouseActions = <>
Keystrokes = <>
MouseActions = <>
MouseTextActions = <>
MouseSelActions = <>
VisibleSpecialChars = [vscSpace, vscTabAtLast]
ReadOnly = True
SelectedColor.BackPriority = 50
SelectedColor.ForePriority = 50
SelectedColor.FramePriority = 50
SelectedColor.BoldPriority = 50
SelectedColor.ItalicPriority = 50
SelectedColor.UnderlinePriority = 50
SelectedColor.StrikeOutPriority = 50
BracketHighlightStyle = sbhsBoth
BracketMatchColor.Background = clNone
BracketMatchColor.Foreground = clNone
BracketMatchColor.Style = [fsBold]
FoldedCodeColor.Background = clNone
FoldedCodeColor.Foreground = clGray
FoldedCodeColor.FrameColor = clGray
MouseLinkColor.Background = clNone
MouseLinkColor.Foreground = clBlue
LineHighlightColor.Background = clNone
LineHighlightColor.Foreground = clNone
inline SynLeftGutterPartList1: TSynGutterPartList
object SynGutterMarks1: TSynGutterMarks
Width = 30
MouseActions = <>
end
object SynGutterLineNumber1: TSynGutterLineNumber
Width = 17
MouseActions = <>
MarkupInfo.Background = clBtnFace
MarkupInfo.Foreground = clNone
DigitCount = 2
ShowOnlyLineNumbersMultiplesOf = 1
ZeroStart = False
LeadingZeros = False
end
object SynGutterChanges1: TSynGutterChanges
Width = 5
MouseActions = <>
ModifiedColor = 59900
SavedColor = clGreen
end
object SynGutterSeparator1: TSynGutterSeparator
Width = 3
MouseActions = <>
MarkupInfo.Background = clWhite
MarkupInfo.Foreground = clGray
end
object SynGutterCodeFolding1: TSynGutterCodeFolding
Width = 13
MouseActions = <>
MarkupInfo.Background = clNone
MarkupInfo.Foreground = clGray
MouseActionsExpanded = <>
MouseActionsCollapsed = <>
end
end
end
end
end
end

260
source/view.pas Normal file
View File

@@ -0,0 +1,260 @@
unit view;
{$mode delphi}{$H+}
interface
uses
SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, SynEdit,
ExtCtrls, Menus, LCLProc,
dbconnection, dbstructures, dbstructures.mysql, apphelpers, ComCtrls, extra_controls;
type
TFrame = TDBObjectEditor;
TfrmView = class(TFrame)
SynMemoBody: TSynEdit;
lblSelect: TLabel;
btnDiscard: TButton;
btnSave: TButton;
btnHelp: TButton;
lblDisabledWhy: TLabel;
PageControlMain: TPageControl;
tabOptions: TTabSheet;
tabCreateCode: TTabSheet;
rgAlgorithm: TRadioGroup;
lblName: TLabel;
editName: TEdit;
lblDefiner: TLabel;
comboDefiner: TComboBox;
comboSecurity: TComboBox;
lblSecurity: TLabel;
rgCheck: TRadioGroup;
SynMemoCreateCode: TSynEdit;
procedure btnHelpClick(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
procedure btnDiscardClick(Sender: TObject);
procedure Modification(Sender: TObject);
procedure comboDefinerDropDown(Sender: TObject);
procedure PageControlMainChange(Sender: TObject);
private
{ Private declarations }
function ComposeCreateStatement: TSQLBatch;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
procedure Init(Obj: TDBObject); override;
function ApplyModifications: TModalResult; override;
end;
implementation
uses main;
{$R *.lfm}
{**
Create: Restore GUI setup
}
constructor TfrmView.Create(AOwner: TComponent);
begin
inherited;
SynMemoBody.Highlighter := Mainform.SynSQLSynUsed;
Mainform.SynCompletionProposal.AddEditor(SynMemoBody);
SynMemoCreateCode.Highlighter := Mainform.SynSQLSynUsed;
Mainform.SynCompletionProposal.AddEditor(SynMemoCreateCode);
editName.MaxLength := NAME_LEN;
comboSecurity.Items.Add('Definer');
comboSecurity.Items.Add('Invoker');
FMainSynMemo := SynMemoBody;
btnSave.Hint := ShortCutToText(MainForm.actSaveSQL.ShortCut);
end;
{**
FormShow: Fill controls with content in edit mode
}
procedure TfrmView.Init(Obj: TDBObject);
var
Algorithm, CheckOption, SelectCode, Definer, SQLSecurity: String;
i: Integer;
begin
inherited;
lblDisabledWhy.Font.Color := clRed;
comboDefiner.Text := '';
comboDefiner.TextHint := f_('Current user (%s)', [Obj.Connection.CurrentUserHostCombination]);
comboDefiner.Hint := f_('Leave empty for current user (%s)', [Obj.Connection.CurrentUserHostCombination]);
if Obj.Name <> '' then begin
// Edit mode
editName.Text := Obj.Name;
Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj, Algorithm, Definer, SQLSecurity, CheckOption, SelectCode);
comboDefiner.Text := Definer;
rgAlgorithm.ItemIndex := rgAlgorithm.Items.IndexOf(Algorithm);
rgCheck.ItemIndex := rgCheck.Items.IndexOf(CheckOption);
if rgCheck.ItemIndex = -1 then
rgCheck.ItemIndex := 0;
for i:=0 to comboSecurity.Items.Count-1 do begin
if LowerCase(SQLSecurity) = LowerCase(comboSecurity.Items[i]) then begin
comboSecurity.ItemIndex := i;
Break;
end;
end;
SynMemoBody.Text := SelectCode;
// User may not be allowed to run SHOW CREATE VIEW, in which case we have an empty CreateCode.
// Disable editor in this case.
lblDisabledWhy.Visible := SelectCode = '';
editName.Enabled := not lblDisabledWhy.Visible;
rgAlgorithm.Enabled := editName.Enabled;
rgCheck.Enabled := rgAlgorithm.Enabled;
SynMemoBody.Enabled := rgAlgorithm.Enabled;
SynMemoBody.TopLine := FMainSynMemoPreviousTopLine;
end else begin
// Create mode
editName.Text := '';
rgAlgorithm.Enabled := True;
rgAlgorithm.ItemIndex := 0;
rgCheck.Enabled := True;
rgCheck.ItemIndex := 0;
comboSecurity.ItemIndex := 0;
SynMemoBody.Text := 'SELECT ';
lblDisabledWhy.Hide;
end;
// Most clauses only supported by MySQL
comboDefiner.Enabled := comboDefiner.Enabled and Obj.Connection.Parameters.IsAnyMySQL;
lblDefiner.Enabled := comboDefiner.Enabled;
comboSecurity.Enabled := comboSecurity.Enabled and Obj.Connection.Parameters.IsAnyMySQL;
lblSecurity.Enabled := comboSecurity.Enabled;
rgAlgorithm.Enabled := rgAlgorithm.Enabled and Obj.Connection.Parameters.IsAnyMySQL;
rgCheck.Enabled := rgCheck.Enabled and Obj.Connection.Parameters.IsAnyMySQL;
// Update create code tab
Modification(Self);
Modified := False;
btnSave.Enabled := Modified;
btnDiscard.Enabled := Modified;
Mainform.ShowStatusMsg;
TExtForm.PageControlTabHighlight(PageControlMain);
Screen.Cursor := crDefault;
end;
procedure TfrmView.comboDefinerDropDown(Sender: TObject);
begin
// Populate definers from mysql.user
(Sender as TComboBox).Items.Assign(DBObject.Connection.AllUserHostCombinations);
end;
{**
Lookup "Create|Alter View" in SQL help
}
procedure TfrmView.btnHelpClick(Sender: TObject);
begin
Help(Self, 'createview');
end;
procedure TfrmView.btnDiscardClick(Sender: TObject);
begin
// Reinit editor, discarding changes
Modified := False;
Init(DBObject);
end;
{**
Apply changes
}
procedure TfrmView.btnSaveClick(Sender: TObject);
begin
ApplyModifications;
end;
function TfrmView.ApplyModifications: TModalResult;
var
Batch: TSQLBatch;
Query: TSQLSentence;
begin
// Save changes
Result := mrOk;
try
Batch := ComposeCreateStatement;
for Query in Batch do begin
DBObject.Connection.Query(Query.SQL);
end;
Batch.Free;
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;
end;
end;
end;
procedure TfrmView.Modification(Sender: TObject);
var
Batch: TSQLBatch;
begin
Modified := True;
btnSave.Enabled := Modified and (editName.Text <> '');
btnDiscard.Enabled := Modified;
// Update create code
Batch := ComposeCreateStatement;
SynMemoCreateCode.Text := Batch.SQL;
Batch.Free;
end;
procedure TfrmView.PageControlMainChange(Sender: TObject);
begin
TExtForm.PageControlTabHighlight(PageControlMain);
end;
function TfrmView.ComposeCreateStatement: TSQLBatch;
var
sql, ViewName, RenameView: String;
begin
// Create or Alter code
if not ObjectExists then begin
sql := 'CREATE ';
ViewName := editName.Text;
end else begin
sql := 'ALTER ';
ViewName := DBObject.Name;
end;
ViewName := DBObject.Connection.QuoteIdent(ViewName);
if rgAlgorithm.Enabled and (rgAlgorithm.ItemIndex > -1) then
sql := sql + 'ALGORITHM = '+Uppercase(rgAlgorithm.Items[rgAlgorithm.ItemIndex])+' ';
if comboDefiner.Enabled and (comboDefiner.Text <> '') then
sql := sql + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, True, '@')+' ';
if comboSecurity.Enabled and (comboSecurity.Text <> '') then
sql := sql + 'SQL SECURITY ' + UpperCase(comboSecurity.Text)+' ';
sql := sql + 'VIEW ' + ViewName+' AS '+SynMemoBody.Text+' ';
if rgCheck.Enabled and (rgCheck.ItemIndex > 0) then
sql := sql + 'WITH '+Uppercase(rgCheck.Items[rgCheck.ItemIndex])+' CHECK OPTION';
sql := sql + ';' + sLineBreak;
if ObjectExists and (DBObject.Name <> editName.Text) then begin
RenameView := DBObject.Connection.QuoteIdent(editName.Text);
sql := sql + 'RENAME TABLE '+ViewName + ' TO '+RenameView + ';' + sLineBreak;
end;
Result := TSQLBatch.Create;
Result.SQL := Trim(SQL);
end;
end.