Fix and simplify delimiter customizing, now that the dropdown was moved to the mainform and didn't fire the OnExit event any longer for some reason. The new mechanism just uses a button (surely including a new icon, sigh...) which asks via InputQuery for the new value. Delimmiter "validation" is only done when setting the value per button, not in parseSql() any longer - avoids being bug-per-bug compatible with MySQL's SQL parser at least in the non interactive mode.

(Hope that code is ok with Franciscos original implementation. At least the old validation IF's are adapted)
This commit is contained in:
Ansgar Becker
2008-07-06 20:22:16 +00:00
parent 51f2342663
commit 1d3bfe6219
6 changed files with 89 additions and 218 deletions

View File

@ -103,8 +103,8 @@ const
REGNAME_SQLOUTHEIGHT = 'sqloutheight';
REGNAME_QUERYHELPERSWIDTH = 'queryhelperswidth';
REGNAME_SQLWHEREFILE = 'SQLWhereFile';
REGNAME_DELIMITERS = 'delimiters';
REGNAME_DELIMITERSELECTED = 'delimiterselected';
REGNAME_DELIMITER = 'Delimiter';
DEFAULT_DELIMITER = ';';
REGNAME_SQLHELPWINLEFT = 'SQLHelp_WindowLeft';
REGNAME_SQLHELPWINTOP = 'SQLHelp_WindowTop';
REGNAME_SQLHELPWINWIDTH = 'SQLHelp_WindowWidth';
@ -209,9 +209,6 @@ const
{TeraByte} NAME_TB = ' TB';
{PetaByte} NAME_PB = ' PB';
// See reference: mysql.cpp Ver 14.12 Distrib 5.0.45, for Win32 (ia32): Line 112
DEFAULT_DELIMITER = ';';
// Copied constants from [delphi11]\source\win32\rtl\win\ShlObj.pas to make them
// available in Delphi 10. We don't use the constants from ShlObj until delphi 10
// support is removed.

BIN
res/icons/delimiter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

View File

@ -516,7 +516,6 @@ type
function GetSelectedTable: string;
procedure SetSelectedDatabase(db: string);
procedure SetSelectedTable(table: string);
procedure ProcessClientSQL(command: WideString; parameter: WideString);
procedure SaveListSetup( List: TVirtualStringTree );
procedure RestoreListSetup( List: TVirtualStringTree );
procedure SetVisibleListColumns( List: TVirtualStringTree; Columns: TStringList );
@ -2184,7 +2183,7 @@ begin
MainForm.actQueryStopOnErrors.Enabled := InQueryTab;
MainForm.actQueryWordWrap.Enabled := InQueryTab;
Mainform.actClearQueryEditor.Enabled := InQueryTab and NotEmpty;
Mainform.ComboBoxQueryDelimiter.Enabled := InQueryTab;
Mainform.actSetDelimiter.Enabled := InQueryTab;
end;
@ -2435,22 +2434,9 @@ var
recordcount : Integer;
ds : TDataSet;
begin
if ( CurrentLine ) then
begin
// Run current line
SQL := parseSQL( SynMemoQuery.LineText, Mainform.Delimiter, ProcessClientSQL );
end
else
if ( Selection ) then
begin
// Run selection
SQL := parseSQL( SynMemoQuery.SelText, Mainform.Delimiter, ProcessClientSQL );
end
else
begin
// Run all
SQL := parseSQL( SynMemoQuery.Text, Mainform.Delimiter, ProcessClientSQL );
end;
if CurrentLine then SQL := parseSQL(SynMemoQuery.LineText, Mainform.Delimiter)
else if Selection then SQL := parseSQL(SynMemoQuery.SelText, Mainform.Delimiter)
else SQL := parseSQL(SynMemoQuery.Text, Mainform.Delimiter);
if ( SQL.Count = 0 ) then
begin
@ -5077,24 +5063,6 @@ begin
end;
{***
Callback procedure able to handle client-side SQL statements such as DELIMITER
@param command The command/option to be called
@param parameter The parameter of command
}
procedure TMDIChild.ProcessClientSQL(command: WideString; parameter: WideString);
begin
if command = 'DELIMITER' then
Mainform.ComboBoxQueryDelimiterAdd(parameter)
else if command = 'CLIENTSQL_ERROR' then begin
LogSQL( parameter, True );
if Mainform.actQueryStopOnErrors.Checked then
raise Exception.Create(parameter);
end;
end;
{**
Save setup of a VirtualStringTree to registry
}

View File

@ -44,9 +44,7 @@ type
function explode(separator, a: String) :TStringList;
procedure ensureValidIdentifier(name: String);
function getEnumValues(str: WideString): WideString;
function IsValidDelimiter(var s: WideString): WideString;
type TParseSQLProcessCommand = procedure(command: WideString; parameter: WideString) of object;
function parsesql(sql: WideString; delimiter: WideString; processcommand: TParseSQLProcessCommand = nil) : TWideStringList;
function parsesql(sql: WideString; delimiter: WideString) : TWideStringList;
function sstr(str: WideString; len: Integer) : WideString;
function encrypt(str: String): String;
function decrypt(str: String): String;
@ -355,41 +353,6 @@ end;
{***
Test that a delimiter looks reasonable.
@param s a string to be trimmed and tested.
@return s an error message if validation fails, a nil string if it succeeds.
}
function IsValidDelimiter(var s: WideString): WideString;
begin
result := '';
s := Trim(s);
// Test for empty delimiter.
if s = '' then result := 'DELIMITER must be followed by a non-comment character or string';
// Disallow backslash, because the MySQL CLI does so for some reason.
// Then again, is there any reason to be bug-per-bug compatible with some random SQL parser?
if Pos('\', s) > 0 then result := 'Backslash disallowed in DELIMITER (because the MySQL CLI does not accept it)';
// Disallow stuff which would be negated by the comment parsing logic.
if
(Pos('/*', s) > 0) or
(Pos('--', s) > 0) or
(Pos('#', s) > 0)
then result := 'Start-of-comment tokens disallowed in DELIMITER (because it would be ignored)';
// Disallow stuff which would be negated by the SQL parser (and could slightly confuse it, if at end-of-string).
if
(Pos('''', s) > 0) or
(Pos('`', s) > 0) or
(Pos('"', s) > 0)
then result := 'String literal markers disallowed in DELIMITER (because it would be ignored)';
if result <> '' then begin
result := WideFormat('Invalid delimiter %s: %s.', [s, result]);
end;
end;
{***
Return true if given character represents whitespace.
Limitations: only recognizes ANSI whitespace.
@ -459,10 +422,9 @@ end;
@param String (possibly large) bunch of SQL-statements, separated by semicolon
@param String SQL start delimiter
@param TParseSQLProcessCommand Method that execute actions relative to an object
@return TStringList Separated statements
}
function parsesql(sql: WideString; delimiter: WideString; processcommand: TParseSQLProcessCommand = nil) : TWideStringList;
function parsesql(sql: WideString; delimiter: WideString) : TWideStringList;
var
i, j, start, len : Integer;
tmp : WideString;
@ -472,26 +434,6 @@ var
delimiter_length : Integer;
encloser, secchar, thdchar : WideChar;
conditional : WideString;
msg : WideString;
{***
If a callback for processing client SQL etc was given, invoke it.
}
procedure CallProcessCommand(command: WideString; parameter: WideString);
begin
if Assigned(processcommand) then processcommand(command, parameter);
end;
{***
Updates the delimiter in the GUI from the one specified via pseudo-SQL.
}
procedure UpdateDelimiterData(execute_callback: Boolean = true);
begin
if (execute_callback) then CallProcessCommand('DELIMITER', delimiter);
// update the delimiter variables helper
delimiter_length := Length(delimiter);
end;
begin
result := TWideStringList.Create;
sql := trim(sql);
@ -507,8 +449,6 @@ begin
encloser := ' ';
conditional := '';
UpdateDelimiterData(false);
i := 0;
while i < len do begin
i := i + 1;
@ -589,14 +529,7 @@ begin
if indelimiter then begin
if (sql[i] in [WideChar(#13), WideChar(#10)]) or (i = len) then begin
if (i = len) then j := 1 else j := 0;
tmp := copy(sql, start + 10, i + j - (start + 10));
msg := IsValidDelimiter(tmp);
if msg = '' then begin
delimiter := tmp;
UpdateDelimiterData(true);
end else begin
CallProcessCommand('CLIENTSQL_ERROR', msg);
end;
delimiter := copy(sql, start + 10, i + j - (start + 10));
indelimiter := false;
start := i + 1;
end;
@ -648,6 +581,7 @@ begin
end;
// Add sql sentence.
delimiter_length := Length(delimiter);
if ((not instring) and (scanReverse(sql, i, delimiter, delimiter_length, false)) or (i = len)) then begin
if (i < len) then j := delimiter_length else begin
// end of string, add sql sentence but only remove delimiter if it's there

View File

@ -401,7 +401,7 @@ object MainForm: TMainForm
object ToolBarQuery: TToolBar
Left = 398
Top = 28
Width = 353
Width = 266
Height = 22
Align = alNone
AutoSize = True
@ -463,42 +463,10 @@ object MainForm: TMainForm
Top = 0
Action = actQueryWordWrap
end
object Panel1: TPanel
object btnSetDelimiter: TToolButton
Left = 243
Top = 0
Width = 110
Height = 22
BevelOuter = bvNone
UseDockManager = False
ParentBackground = False
TabOrder = 0
object LabelQueryDelimiter: TLabel
Left = 6
Top = 4
Width = 45
Height = 13
Caption = 'Delimiter:'
Font.Charset = DEFAULT_CHARSET
Font.Color = clBlack
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
end
object ComboBoxQueryDelimiter: TComboBox
Left = 55
Top = 0
Width = 45
Height = 21
Enabled = False
ItemHeight = 13
TabOrder = 0
OnExit = ComboBoxQueryDelimiterExit
Items.Strings = (
';'
';;'
'//')
end
Action = actSetDelimiter
end
end
end
@ -1279,6 +1247,14 @@ object MainForm: TMainForm
ShortCut = 16466
OnExecute = actQueryReplaceExecute
end
object actSetDelimiter: TAction
Category = 'SQL'
Caption = 'Set delimiter used in SQL execution'
Enabled = False
Hint = 'Set delimiter used in SQL execution'
ImageIndex = 106
OnExecute = actSetDelimiterExecute
end
end
object SaveDialog2: TSaveDialog
DefaultExt = 'reg'
@ -4413,6 +4389,19 @@ object MainForm: TMainForm
44AE426082}
Name = 'PngImage105'
Background = clWindow
end
item
PngImage.Data = {
89504E470D0A1A0A0000000D49484452000000100000001008060000001FF3FF
61000000017352474200AECE1CE90000001874455874536F6674776172650050
61696E742E4E45542076332E313072B225920000007D4944415478DA6364A010
30E2927861CE700B89BB40E224431B3906A842B9D50362401512F718D0800324
19407120526C00D00BEB80942894BB02E885A9A41A30F0B100F2820ED4901C92
BD0035640E904A066213A00167C931009C9C819AD570A9C1E785C3404A1C884B
80066C22C700504A9C0FD4FC1C9F2B294E480072902F11D735460A0000000049
454E44AE426082}
Name = 'PngImage106'
Background = clWindow
end>
PngOptions = [pngBlendOnDisabled, pngGrayscaleOnDisabled]
Left = 8

View File

@ -218,12 +218,11 @@ type
btnQueryReplace: TToolButton;
btnStopOnErrors: TToolButton;
btnQueryWordwrap: TToolButton;
Panel1: TPanel;
ComboBoxQueryDelimiter: TComboBox;
LabelQueryDelimiter: TLabel;
PopupQueryLoad: TPopupMenu;
btnEditView: TToolButton;
btnExecuteLine: TToolButton;
actSetDelimiter: TAction;
btnSetDelimiter: TToolButton;
procedure actCreateFieldExecute(Sender: TObject);
procedure actEditTablePropertiesExecute(Sender: TObject);
procedure actCreateTableExecute(Sender: TObject);
@ -284,10 +283,10 @@ type
procedure actRefreshExecute(Sender: TObject);
procedure actSaveSQLExecute(Sender: TObject);
procedure actSaveSQLSnippetExecute(Sender: TObject);
procedure actSetDelimiterExecute(Sender: TObject);
procedure actSQLhelpExecute(Sender: TObject);
procedure actUpdateCheckExecute(Sender: TObject);
procedure actWebbrowse(Sender: TObject);
procedure ComboBoxQueryDelimiterExit(Sender: TObject);
procedure EnsureConnected;
function ExecuteRemoteQuery(sender: THandle; query: string): TDataSet;
procedure ExecuteRemoteNonQuery(sender: THandle; query: string);
@ -302,6 +301,7 @@ type
function GetChildwin: TMDIChild;
function GetParamValue(const paramChar: Char; const paramName:
string; var curIdx: Byte; out paramValue: string): Boolean;
procedure UpdateDelimiterHint;
public
MaintenanceForm: TOptimize;
ViewForm: TfrmView;
@ -316,7 +316,6 @@ type
procedure popupQueryLoadClick( sender: TObject );
procedure FillPopupQueryLoad;
procedure PopupQueryLoadRemoveAbsentFiles( sender: TObject );
procedure ComboBoxQueryDelimiterAdd( delimiter: WideString );
function GetRegValue( valueName: String; defaultValue: Integer; Session: String = '' ) : Integer; Overload;
function GetRegValue( valueName: String; defaultValue: Boolean; Session: String = '' ) : Boolean; Overload;
function GetRegValue( valueName: String; defaultValue: String; Session: String = '' ) : String; Overload;
@ -487,9 +486,8 @@ begin
WriteInteger(REGNAME_TOOLBARQUERYLEFT, ToolBarQuery.Left);
WriteInteger(REGNAME_TOOLBARQUERYTOP, ToolBarQuery.Top);
// Save the delimiters
WriteString( REGNAME_DELIMITERS, ComboBoxQueryDelimiter.Items.Text );
WriteInteger( REGNAME_DELIMITERSELECTED, ComboBoxQueryDelimiter.ItemIndex );
// Save delimiter
WriteString( REGNAME_DELIMITER, Delimiter );
end;
CloseKey;
Free;
@ -523,7 +521,6 @@ procedure TMainForm.FormCreate(Sender: TObject);
var
ws : String;
Monitor: TMonitor;
delimiters: String;
const
MoveWinThreshold: Byte = 80;
begin
@ -563,14 +560,9 @@ begin
ToolBarQuery.Left := GetRegValue(REGNAME_TOOLBARQUERYLEFT, ToolBarQuery.Left);
ToolBarQuery.Top := GetRegValue(REGNAME_TOOLBARQUERYTOP, ToolBarQuery.Top);
// Delimiter stuff
delimiters := Trim( Mainform.GetRegValue(REGNAME_DELIMITERS, '') );
if delimiters <> '' then begin
ComboBoxQueryDelimiter.Items.Text := delimiters;
ComboBoxQueryDelimiter.ItemIndex := GetRegValue( REGNAME_DELIMITERSELECTED, 0 );
end else
ComboBoxQueryDelimiter.ItemIndex := ComboBoxQueryDelimiter.Items.IndexOf( DEFAULT_DELIMITER );
Delimiter := ComboBoxQueryDelimiter.Text;
// Delimiter
Delimiter := GetRegValue(REGNAME_DELIMITER, DEFAULT_DELIMITER);
UpdateDelimiterHint;
// Beautify AppRevision
if Pos('$Rev: WC', AppRevision) < 1 then
@ -1816,61 +1808,6 @@ begin
Childwin.SynMemoQuery.WordWrap := TAction(Sender).Checked;
end;
procedure TMainForm.ComboBoxQueryDelimiterExit(Sender: TObject);
begin
// a delimiter couldn't be empty
ComboBoxQueryDelimiter.Text := Trim(ComboBoxQueryDelimiter.Text);
// verify if the delimiter combobox isn't empty
if ComboBoxQueryDelimiter.Text = '' then begin
MessageDlg( 'A delimiter is needed.', mtWarning, [mbOK], 0);
ComboBoxQueryDelimiter.SetFocus;
end else begin
// add the new delimiter to combobox
ComboBoxQueryDelimiterAdd(ComboBoxQueryDelimiter.Text);
end;
end;
{***
Add a new query delimiter and select it
@param term The delimiter to add and/or select
}
procedure TMainform.ComboBoxQueryDelimiterAdd( delimiter: WideString );
var
index: Integer;
found: Boolean;
msg: String;
begin
// See reference: mysql.cpp Ver 14.12 Distrib 5.0.45, for Win32 (ia32): Line 824
// Check that delimiter does not contain a backslash
msg := IsValidDelimiter( delimiter );
if msg <> '' then begin
// rollback the delimiter
ComboBoxQueryDelimiter.Text := Delimiter;
// notify the user
raise Exception.Create( msg );
end else begin
// the delimiter is case-sensitive, following the implementation
// in the MySQL CLI, so we must locate it by hand
found := False;
for index := 0 to ComboBoxQueryDelimiter.Items.Count - 1 do begin
if ComboBoxQueryDelimiter.Items[index] = Delimiter then begin
ComboBoxQueryDelimiter.ItemIndex := index;
found := True;
break;
end;
end;
if not found then begin
ComboBoxQueryDelimiter.Items.Add( Delimiter );
ComboBoxQueryDelimiter.ItemIndex := ComboBoxQueryDelimiter.Items.Count - 1;
end;
Delimiter := ComboBoxQueryDelimiter.Text;
Childwin.LogSQL( Format( 'Delimiter changed to %s.', [Delimiter] ));
end;
end;
procedure TMainForm.FindDialogQueryFind(Sender: TObject);
var
@ -2055,4 +1992,50 @@ begin
end;
{**
Change default delimiter for SQL execution
}
procedure TMainForm.actSetDelimiterExecute(Sender: TObject);
var
newVal: String;
ok: Boolean;
begin
// Use a while loop to redisplay the input dialog after setting an invalid value
ok := False;
while not ok do begin
newVal := delimiter;
if InputQuery('Set delimiter', 'Delimiter used within SQL execution:', newVal) then begin
// Validate new value
newVal := Trim(newVal);
if (newVal = '')
or (Pos('\', newVal) > 0)
or (Pos('/*', newVal) > 0)
or (Pos('--', newVal) > 0)
or (Pos('#', newVal) > 0)
or (Pos('''', newVal) > 0)
or (Pos('`', newVal) > 0)
or (Pos('"', newVal) > 0)
then begin
MessageDlg('Invalid value: The delimiter must not be empty or contain comment or quoting characters.',
mtError, [mbOK], 0);
end else begin
Delimiter := newVal;
UpdateDelimiterHint;
ok := True;
end;
end else // Cancel clicked
ok := True;
end;
end;
{**
Sets the hint of the SetDelimiter TAction so it includes the delimiter itself
and the user has just to move the mouse over it to see it.
}
procedure TMainForm.UpdateDelimiterHint;
begin
actSetDelimiter.Hint := actSetDelimiter.Caption + ' (current value: '+delimiter+')';
end;
end.