diff --git a/source/copytable.pas b/source/copytable.pas index 87c518c7..2e1718c6 100644 --- a/source/copytable.pas +++ b/source/copytable.pas @@ -88,7 +88,7 @@ end; procedure TCopyTableForm.FormShow(Sender: TObject); var Table, Filter: String; - Algorithm, CheckOption, SelectCode: String; + Dummy: String; DBObjects: TDBObjectList; Obj: TDBObject; Values: TStringList; @@ -127,7 +127,7 @@ begin FForeignKeys.Clear; case FDBObj.NodeType of lntTable: FDBObj.Connection.ParseTableStructure(FDBObj.CreateCode, FColumns, FKeys, FForeignKeys); - lntView: FDBObj.Connection.ParseViewStructure(FDBObj.CreateCode, FDBObj.Name, FColumns, Algorithm, CheckOption, SelectCode); + lntView: FDBObj.Connection.ParseViewStructure(FDBObj.CreateCode, FDBObj.Name, FColumns, Dummy, Dummy, Dummy, Dummy); else raise Exception.Create('Neither table nor view: '+FDBObj.Name); end; diff --git a/source/createdatabase.dfm b/source/createdatabase.dfm index ccee72c4..cdaa32e4 100644 --- a/source/createdatabase.dfm +++ b/source/createdatabase.dfm @@ -61,7 +61,7 @@ object CreateDatabaseForm: TCreateDatabaseForm Anchors = [akLeft, akTop, akRight] TabOrder = 0 TextHint = 'Enter database name' - OnChange = editDBNameChange + OnChange = Modified end object comboCharset: TComboBox Left = 88 @@ -82,7 +82,6 @@ object CreateDatabaseForm: TCreateDatabaseForm Anchors = [akRight, akBottom] Caption = 'OK' Default = True - Enabled = False TabOrder = 3 OnClick = btnOKClick end diff --git a/source/createdatabase.pas b/source/createdatabase.pas index 905da01d..6cef8a5f 100644 --- a/source/createdatabase.pas +++ b/source/createdatabase.pas @@ -21,7 +21,6 @@ type procedure btnOKClick(Sender: TObject); procedure comboCharsetChange(Sender: TObject); procedure Modified(Sender: TObject); - procedure editDBNameChange(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); @@ -172,31 +171,6 @@ begin end; -{** - User writes something into editDBName -} -procedure TCreateDatabaseForm.editDBNameChange(Sender: TObject); -begin - editDBName.Font.Color := clWindowText; - editDBName.Color := clWindow; - // Enable "OK"-Button by default - btnOK.Enabled := True; - try - ensureValidIdentifier( editDBName.Text ); - except - // Invalid database name - if editDBName.Text <> '' then begin - editDBName.Font.Color := clRed; - editDBName.Color := clYellow; - end; - btnOK.Enabled := False; - end; - - // Invoke SQL preview - Modified(Sender); -end; - - {** Create the database } diff --git a/source/event_editor.pas b/source/event_editor.pas index efd65955..9b7039b7 100644 --- a/source/event_editor.pas +++ b/source/event_editor.pas @@ -196,7 +196,7 @@ end; procedure TfrmEventEditor.Modification(Sender: TObject); begin Modified := True; - btnSave.Enabled := Modified; + btnSave.Enabled := Modified and (editName.Text <> ''); btnDiscard.Enabled := Modified; CreateCodeValid := False; AlterCodeValid := False; diff --git a/source/helpers.pas b/source/helpers.pas index 526fffc9..cc237073 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -40,6 +40,7 @@ type TDBObjectEditor = class(TFrame) private FModified: Boolean; + FDefiners: TStringList; procedure SetModified(Value: Boolean); protected public @@ -48,6 +49,7 @@ type destructor Destroy; override; procedure Init(Obj: TDBObject); virtual; function DeInit: TModalResult; + function GetDefiners: TStringList; property Modified: Boolean read FModified write SetModified; function ApplyModifications: TModalResult; virtual; abstract; end; @@ -75,7 +77,6 @@ type function implodestr(seperator: String; a: TStrings) :String; function Explode(Separator, Text: String) :TStringList; procedure ExplodeQuotedList(Text: String; var List: TStringList); - procedure ensureValidIdentifier(name: String); function getEnumValues(str: String): String; function GetSQLSplitMarkers(const SQL: String): TSQLBatch; function SplitSQL(const SQL: String): TSQLBatch; @@ -238,81 +239,6 @@ begin end; - -{*** - Check for valid identifier (table-/db-/column-name) ? - - @param string Identifier - @return boolean Name is valid? - @note rosenfield, 2007-02-01: - Those certain characters are standard filesystem wildcards * ?, - pipe redirection characters | < >, standard path separators / \, - Windows mount point identifiers :, DBMS security / container separator - characters . and so on. In other words, characters that may or may - not be allowed by MySQL and the underlying filesystem, but which are - really, really, really stupid to use in a table name, since you'll - get into trouble once trying to use the table/db in a query or move it - to a different filesystem, or what not. - @note ansgarbecker, 2007-02-01: - Since mysql 5.1.6 those problematic characters are encoded in - a hexadecimal manner if they apply to a file (table) or folder (database) - But after testing that by renaming a table to a name with a dot - I still get an error, so we currently should be careful also on a 5.1.6+ - @see http://dev.mysql.com/doc/refman/5.1/en/identifier-mapping.html -} -procedure ensureValidIdentifier( name: String ); -var - i : Integer; - invalidChars, invalidCharsShown : String; - isToolong, hasInvalidChars : Boolean; - msgStr : String; -begin - isToolong := false; - hasInvalidChars := false; - - // Check length - if (length(name) < 1) or (length(name) > 64) then - isToolong := true; - - // Check for invalid chars - invalidChars := '\/:*?"<>|.'; - for i:=1 to length(name) do - begin - if (pos( name[i], invalidChars ) > 0 ) then - begin - hasInvalidChars := true; - break; - end; - end; - - // Raise exception which explains what's wrong - if isTooLong or hasInvalidChars then - begin - if hasInvalidChars then - begin - // Add space between chars for better readability - invalidCharsShown := ''; - for i:=1 to length(invalidChars) do - begin - invalidCharsShown := invalidCharsShown + invalidChars[i] + ' '; - end; - msgStr := 'The name "%s" contains some invalid characters.'+ - CRLF+CRLF + 'An identifier must not contain the following characters:'+CRLF+invalidCharsShown; - end - else if isToolong then - begin - msgStr := 'The name "%s" has '+IntToStr(Length(name)) - +' characters and exceeds the maximum length of 64 characters.'; - end; - - Raise Exception.CreateFmt(msgStr, [name]); - end; - - -end; - - - {*** Get values from an enum- or set-typed column definition @@ -2347,6 +2273,26 @@ begin end; +function TDBObjectEditor.GetDefiners: TStringList; + function q(s: String): String; + begin + Result := DBObject.Connection.QuoteIdent(s); + end; +begin + // For populating combobox items + if not Assigned(FDefiners) then begin + try + FDefiners := DBObject.Connection.GetCol('SELECT CONCAT('+q('User')+', '+esc('@')+', '+q('Host')+') FROM '+ + q('mysql')+'.'+q('user')+' WHERE '+q('User')+'!='+esc('')+' ORDER BY '+q('User')+', '+q('Host')); + except on E:EDatabaseError do + FDefiners := TStringList.Create; + end; + end; + Result := FDefiners; +end; + + + // Following code taken from OneInst.pas, http://assarbad.net/de/stuff/!import/nico.old/ // Slightly modified to better integrate that into our code, comments translated from german. diff --git a/source/loaddata.pas b/source/loaddata.pas index 1c99b88d..3274faa9 100644 --- a/source/loaddata.pas +++ b/source/loaddata.pas @@ -220,7 +220,7 @@ end; procedure Tloaddataform.comboTableChange(Sender: TObject); var - Algorithm, CheckOption, SelectCode: String; + DummyStr: String; Col: TTableColumn; DBObjects: TDBObjectList; Obj: TDBObject; @@ -235,7 +235,7 @@ begin if (Obj.Database=comboDatabase.Text) and (Obj.Name=comboTable.Text) then begin case Obj.NodeType of lntTable: Obj.Connection.ParseTableStructure(Obj.CreateCode, Columns, nil, nil); - lntView: Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, Columns, Algorithm, CheckOption, SelectCode); + lntView: Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, Columns, DummyStr, DummyStr, DummyStr, DummyStr); end; end; end; diff --git a/source/main.pas b/source/main.pas index 4cc5eea5..a1cbc53d 100644 --- a/source/main.pas +++ b/source/main.pas @@ -527,7 +527,7 @@ type procedure actPrintListExecute(Sender: TObject); procedure actCopyTableExecute(Sender: TObject); procedure ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); - function mask(str: String; HasMultiSegments: Boolean=False) : String; + function mask(str: String; Glue: Char=#0) : String; procedure actExecuteQueryExecute(Sender: TObject); procedure actCreateDatabaseExecute(Sender: TObject); procedure actDataCancelChangesExecute(Sender: TObject); @@ -2104,9 +2104,9 @@ end; // Escape database, table, field, index or key name. -function TMainform.mask(str: String; HasMultiSegments: Boolean=False) : String; +function TMainform.mask(str: String; Glue: Char=#0) : String; begin - result := ActiveConnection.QuoteIdent(str, HasMultiSegments); + result := ActiveConnection.QuoteIdent(str, Glue); end; @@ -2935,8 +2935,8 @@ procedure TMainForm.actRunRoutinesExecute(Sender: TObject); var Tab: TQueryTab; Query, ParamInput, - Returns, DataAccess, Security, Comment, Body: String; - Deterministic: Boolean; + DummyStr: String; + DummyBool: Boolean; i: Integer; pObj: PDBObject; Obj: TDBObject; @@ -2968,7 +2968,7 @@ begin lntFunction: Query := 'SELECT '; end; Parameters := TRoutineParamList.Create; - Obj.Connection.ParseRoutineStructure(Obj.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body); + Obj.Connection.ParseRoutineStructure(Obj.CreateCode, Parameters, DummyBool, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr); Query := Query + mask(Obj.Name); ParamInput := ''; for i:=0 to Parameters.Count-1 do begin @@ -4664,7 +4664,7 @@ begin for DbObj in DbObjects do begin if (CompareText(DbObj.Name, Identifier)=0) and (DbObj.NodeType in [lntFunction, lntProcedure]) then begin Params := TRoutineParamList.Create(True); - DbObj.Connection.ParseRoutineStructure(DbObj.CreateCode, Params, DummyBool, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr); + DbObj.Connection.ParseRoutineStructure(DbObj.CreateCode, Params, DummyBool, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr); ItemText := ''; for i:=0 to Params.Count-1 do ItemText := ItemText + '"' + Params[i].Name + ': ' + Params[i].Datatype + '", '; @@ -4733,7 +4733,6 @@ begin // Try to rename, on any error abort and don't rename ListItem try - ensureValidIdentifier( NewText ); // rename table ActiveConnection.Query('RENAME TABLE ' + mask(Obj.Name) + ' TO ' + mask(NewText)); @@ -6751,7 +6750,7 @@ end; procedure TMainForm.ParseSelectedTableStructure; var - Algorithm, CheckOption, SelectCode: String; + DummyStr: String; begin SelectedTableColumns.Clear; SelectedTableKeys.Clear; @@ -6762,7 +6761,7 @@ begin lntTable: ActiveConnection.ParseTableStructure(ActiveDbObj.CreateCode, SelectedTableColumns, SelectedTableKeys, SelectedTableForeignKeys); lntView: - ActiveConnection.ParseViewStructure(ActiveDbObj.CreateCode, ActiveDbObj.Name, SelectedTableColumns, Algorithm, CheckOption, SelectCode); + ActiveConnection.ParseViewStructure(ActiveDbObj.CreateCode, ActiveDbObj.Name, SelectedTableColumns, DummyStr, DummyStr, DummyStr, DummyStr); end; except on E:EDatabaseError do MessageDlg(E.Message, mtError, [mbOK], 0); @@ -7310,7 +7309,7 @@ begin idx := ForeignKey.Columns.IndexOf(DataGrid.Header.Columns[Column].Text); if idx > -1 then begin // Find the first text column if available and use that for displaying in the pulldown instead of using meaningless id numbers - CreateTable := ActiveConnection.GetVar('SHOW CREATE TABLE '+Mask(ForeignKey.ReferenceTable, True), 1); + CreateTable := ActiveConnection.GetVar('SHOW CREATE TABLE '+Mask(ForeignKey.ReferenceTable, '.'), 1); Columns := TTableColumnList.Create; Keys := nil; ForeignKeys := nil; @@ -7326,7 +7325,7 @@ begin KeyCol := Mask(ForeignKey.ForeignColumns[idx]); SQL := 'SELECT '+KeyCol; if TextCol <> '' then SQL := SQL + ', LEFT(' + Mask(TextCol) + ', 256)'; - SQL := SQL + ' FROM '+Mask(ForeignKey.ReferenceTable, True)+' GROUP BY '+KeyCol+' ORDER BY '; + SQL := SQL + ' FROM '+Mask(ForeignKey.ReferenceTable, '.')+' GROUP BY '+KeyCol+' ORDER BY '; if TextCol <> '' then SQL := SQL + Mask(TextCol) else SQL := SQL + KeyCol; SQL := SQL + ' LIMIT 1000'; diff --git a/source/mysql_connection.pas b/source/mysql_connection.pas index 14cf49f6..2f97720f 100644 --- a/source/mysql_connection.pas +++ b/source/mysql_connection.pas @@ -227,6 +227,7 @@ type FPlinkProcInfo: TProcessInformation; FLastResults: Array of PMYSQL_RES; FResultCount: Integer; + FCurrentUserHostCombination: String; procedure SetActive(Value: Boolean); procedure ClosePlink; procedure SetDatabase(Value: String); @@ -245,6 +246,7 @@ type function GetInformationSchemaObjects: TStringList; function GetConnectionUptime: Integer; function GetServerUptime: Integer; + function GetCurrentUserHostCombination: String; function DecodeAPIString(a: AnsiString): String; procedure Log(Category: TMySQLLogCategory; Msg: String); procedure ClearCache; @@ -256,8 +258,8 @@ type function EscapeString(Text: String; ProcessJokerChars: Boolean=False): String; function escChars(const Text: String; EscChar, Char1, Char2, Char3, Char4: Char): String; function UnescapeString(Text: String): String; - class function QuoteIdent(Identifier: String; HasMultiSegments: Boolean=False): String; - function DeQuoteIdent(Identifier: String): String; + class function QuoteIdent(Identifier: String; Glue: Char=#0): String; + function DeQuoteIdent(Identifier: String; Glue: Char=#0): String; function ConvertServerVersion(Version: Integer): String; function GetResults(SQL: String): TMySQLQuery; function GetCol(SQL: String; Column: Integer=0): TStringList; @@ -274,9 +276,9 @@ type procedure ClearDbObjects(db: String); procedure ClearAllDbObjects; procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList); - procedure ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, CheckOption, SelectCode: String); + procedure ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, Definer, CheckOption, SelectCode: String); procedure ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList; - var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String); + var Deterministic: Boolean; var Definer, Returns, DataAccess, Security, Comment, Body: String); property SessionName: String read FSessionName write FSessionName; property Parameters: TConnectionParameters read FParameters write FParameters; property ThreadId: Cardinal read GetThreadId; @@ -303,6 +305,7 @@ type property InformationSchemaObjects: TStringList read GetInformationSchemaObjects; property ObjectNamesInSelectedDB: TStrings read FObjectNamesInSelectedDB write FObjectNamesInSelectedDB; property ResultCount: Integer read FResultCount; + property CurrentUserHostCombination: String read GetCurrentUserHostCombination; published property Active: Boolean read FActive write SetActive default False; property Database: String read FDatabase write SetDatabase; @@ -436,6 +439,7 @@ begin FIsUnicode := False; FDatabases := TDatabaseList.Create(True); FLoginPromptDone := False; + FCurrentUserHostCombination := ''; end; @@ -1040,21 +1044,23 @@ end; Add backticks to identifier Todo: Support ANSI style } -class function TMySQLConnection.QuoteIdent(Identifier: String; HasMultiSegments: Boolean=False): String; +class function TMySQLConnection.QuoteIdent(Identifier: String; Glue: Char=#0): String; begin Result := Identifier; Result := StringReplace(Result, '`', '``', [rfReplaceAll]); - if HasMultiSegments then - Result := StringReplace(Result, '.', '`.`', [rfReplaceAll]); + if Glue <> #0 then + Result := StringReplace(Result, Glue, '`'+Glue+'`', [rfReplaceAll]); Result := '`' + Result + '`'; end; -function TMySQLConnection.DeQuoteIdent(Identifier: String): String; +function TMySQLConnection.DeQuoteIdent(Identifier: String; Glue: Char=#0): String; begin Result := Identifier; if (Result[1] = '`') and (Result[Length(Identifier)] = '`') then Result := Copy(Result, 2, Length(Result)-2); + if Glue <> #0 then + Result := StringReplace(Result, '`'+Glue+'`', Glue, [rfReplaceAll]); end; @@ -1231,6 +1237,15 @@ begin end; +function TMySQLConnection.GetCurrentUserHostCombination: String; +begin + // Return current user@host combination, used by various object editors for DEFINER clauses + if FCurrentUserHostCombination = '' then + FCurrentUserHostCombination := GetVar('SELECT CURRENT_USER()'); + Result := FCurrentUserHostCombination; +end; + + procedure TMySQLConnection.ClearCache; begin // Free cached lists and results. Called when the connection was closed and/or destroyed @@ -1240,6 +1255,7 @@ begin FreeAndNil(FInformationSchemaObjects); ClearAllDbObjects; FTableEngineDefault := ''; + FCurrentUserHostCombination := ''; end; @@ -1809,7 +1825,7 @@ begin end; -procedure TMySQLConnection.ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, CheckOption, SelectCode: String); +procedure TMySQLConnection.ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, Definer, CheckOption, SelectCode: String); var rx: TRegExpr; Col: TTableColumn; @@ -1830,19 +1846,20 @@ begin rx.ModifierI := True; rx.Expression := '^CREATE\s+(OR\s+REPLACE\s+)?'+ '(ALGORITHM\s*=\s*(\w+)\s+)?'+ - '(DEFINER\s*=\s*\S+\s+)?'+ + '(DEFINER\s*=\s*(\S+)\s+)?'+ '(SQL\s+SECURITY\s+\w+\s+)?'+ 'VIEW\s+(`?(\w[\w\s]*)`?\.)?(`?(\w[\w\s]*)`?)?\s+'+ '(\([^\)]\)\s+)?'+ 'AS\s+(.+)(\s+WITH\s+(\w+\s+)?CHECK\s+OPTION\s*)?$'; if rx.Exec(CreateCode) then begin Algorithm := rx.Match[3]; + Definer := DeQuoteIdent(rx.Match[5], '@'); // When exporting a view we need the db name for the below SHOW COLUMNS query, // if the connection is on a different db currently - DbName := rx.Match[7]; - ViewName := rx.Match[9]; - CheckOption := Trim(rx.Match[13]); - SelectCode := rx.Match[11]; + DbName := rx.Match[8]; + ViewName := rx.Match[10]; + CheckOption := Trim(rx.Match[14]); + SelectCode := rx.Match[12]; end else raise Exception.Create('Regular expression did not match the VIEW code in ParseViewStructure(): '+CRLF+CRLF+CreateCode); rx.Free; @@ -1889,7 +1906,7 @@ end; procedure TMySQLConnection.ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList; - var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String); + var Deterministic: Boolean; var Definer, Returns, DataAccess, Security, Comment, Body: String); var Params: String; ParenthesesCount: Integer; @@ -1904,6 +1921,14 @@ begin // 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) + + rx.Expression := '\bDEFINER\s*=\s*(\S+)\s'; + if rx.Exec(CreateCode) then + Definer := DequoteIdent(rx.Match[1], '@') + else + Definer := ''; + + // Parse parameter list ParenthesesCount := 0; Params := ''; for i:=1 to Length(CreateCode) do begin @@ -2345,7 +2370,7 @@ end; procedure TMySQLQuery.PrepareEditing; var Res: TMySQLQuery; - CreateCode, Algorithm, CheckOption, SelectCode: String; + CreateCode, Dummy: String; begin // Try to fetch column names and keys if FEditingPrepared then @@ -2359,7 +2384,7 @@ begin if UpperCase(Res.ColumnNames[0]) = 'TABLE' then Connection.ParseTableStructure(CreateCode, FColumns, FKeys, FForeignKeys) else - Connection.ParseViewStructure(CreateCode, TableName, FColumns, Algorithm, CheckOption, SelectCode); + Connection.ParseViewStructure(CreateCode, TableName, FColumns, Dummy, Dummy, Dummy, Dummy); FreeAndNil(Res); FreeAndNil(FUpdateData); FUpdateData := TUpdateData.Create(True); @@ -3079,7 +3104,7 @@ begin for i:=0 to Columns.Count-1 do Result := Result + TMySQLConnection.QuoteIdent(Columns[i]) + ', '; if Columns.Count > 0 then Delete(Result, Length(Result)-1, 2); - Result := Result + ') REFERENCES ' + TMySQLConnection.QuoteIdent(ReferenceTable, True) + ' ('; + Result := Result + ') REFERENCES ' + TMySQLConnection.QuoteIdent(ReferenceTable, '.') + ' ('; for i:=0 to ForeignColumns.Count-1 do Result := Result + TMySQLConnection.QuoteIdent(ForeignColumns[i]) + ', '; if ForeignColumns.Count > 0 then Delete(Result, Length(Result)-1, 2); diff --git a/source/routine_editor.dfm b/source/routine_editor.dfm index c6072e46..7efdd092 100644 --- a/source/routine_editor.dfm +++ b/source/routine_editor.dfm @@ -25,7 +25,7 @@ object frmRoutineEditor: TfrmRoutineEditor Anchors = [akLeft, akBottom] Caption = 'Save' Default = True - TabOrder = 3 + TabOrder = 4 OnClick = btnSaveClick end object btnDiscard: TButton @@ -36,7 +36,7 @@ object frmRoutineEditor: TfrmRoutineEditor Anchors = [akLeft, akBottom] Caption = 'Discard' ModalResult = 2 - TabOrder = 2 + TabOrder = 3 OnClick = btnDiscardClick end object btnHelp: TButton @@ -46,7 +46,7 @@ object frmRoutineEditor: TfrmRoutineEditor Height = 25 Anchors = [akLeft, akBottom] Caption = 'Help' - TabOrder = 1 + TabOrder = 2 OnClick = btnHelpClick end object SynMemoBody: TSynMemo @@ -63,7 +63,7 @@ object frmRoutineEditor: TfrmRoutineEditor Font.Height = -13 Font.Name = 'Courier New' Font.Style = [] - TabOrder = 0 + TabOrder = 1 OnDragDrop = SynMemoBodyDragDrop OnDragOver = SynMemoBodyDragOver Gutter.AutoSize = True @@ -102,7 +102,7 @@ object frmRoutineEditor: TfrmRoutineEditor ActivePage = tabOptions Align = alTop Images = MainForm.ImageListMain - TabOrder = 4 + TabOrder = 0 object tabOptions: TTabSheet Caption = 'Options' ImageIndex = 39 @@ -157,6 +157,13 @@ object frmRoutineEditor: TfrmRoutineEditor Caption = '&Comment:' FocusControl = editComment end + object lblDefiner: TLabel + Left = 408 + Top = 11 + Width = 39 + Height = 13 + Caption = 'De&finer:' + end object chkDeterministic: TCheckBox Left = 84 Top = 114 @@ -164,7 +171,7 @@ object frmRoutineEditor: TfrmRoutineEditor Height = 17 Anchors = [akLeft, akTop, akRight] Caption = '&Deterministic' - TabOrder = 0 + TabOrder = 7 OnClick = Modification end object editComment: TEdit @@ -173,7 +180,7 @@ object frmRoutineEditor: TfrmRoutineEditor Width = 505 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 1 + TabOrder = 2 Text = 'editComment' OnChange = Modification end @@ -184,7 +191,7 @@ object frmRoutineEditor: TfrmRoutineEditor Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 2 + TabOrder = 6 OnChange = Modification end object comboDataAccess: TComboBox @@ -194,7 +201,7 @@ object frmRoutineEditor: TfrmRoutineEditor Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 3 + TabOrder = 5 OnChange = Modification end object comboReturns: TComboBox @@ -212,19 +219,29 @@ object frmRoutineEditor: TfrmRoutineEditor Width = 310 Height = 21 Style = csDropDownList - TabOrder = 5 + TabOrder = 3 OnSelect = comboTypeSelect end object editName: TEdit Left = 84 Top = 8 - Width = 505 + Width = 310 Height = 21 - Anchors = [akLeft, akTop, akRight] - TabOrder = 6 + TabOrder = 0 Text = 'editName' TextHint = 'Enter routine name' - OnChange = editNameChange + OnChange = Modification + end + object comboDefiner: TComboBox + Left = 489 + Top = 8 + Width = 100 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 1 + Text = 'comboDefiner' + OnChange = Modification + OnDropDown = comboDefinerDropDown end end object tabParameters: TTabSheet diff --git a/source/routine_editor.pas b/source/routine_editor.pas index 6c79f80d..215f5e44 100644 --- a/source/routine_editor.pas +++ b/source/routine_editor.pas @@ -39,10 +39,11 @@ type btnClearParams: TToolButton; SynMemoCREATEcode: TSynMemo; btnRunProc: TButton; + lblDefiner: TLabel; + comboDefiner: TComboBox; procedure comboTypeSelect(Sender: TObject); procedure btnSaveClick(Sender: TObject); procedure btnHelpClick(Sender: TObject); - procedure editNameChange(Sender: TObject); procedure btnAddParamClick(Sender: TObject); procedure listParametersGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; @@ -70,6 +71,7 @@ type const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); procedure btnDiscardClick(Sender: TObject); + procedure comboDefinerDropDown(Sender: TObject); private { Private declarations } FAlterRoutineType: String; @@ -128,7 +130,7 @@ end; procedure TfrmRoutineEditor.Init(Obj: TDBObject); var - Returns, DataAccess, Security, Comment, Body: String; + Definer, Returns, DataAccess, Security, Comment, Body: String; Deterministic: Boolean; begin inherited; @@ -143,11 +145,14 @@ begin comboDataAccess.ItemIndex := 0; comboSecurity.ItemIndex := 0; editComment.Clear; + comboDefiner.Text := ''; + comboDefiner.TextHint := 'Current user ('+Obj.Connection.CurrentUserHostCombination+')'; + comboDefiner.Hint := 'Leave empty for current user ('+Obj.Connection.CurrentUserHostCombination+')'; SynMemoBody.Text := 'BEGIN'+CRLF+CRLF+'END'; if DBObject.Name <> '' then begin // Editing existing routine comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b'); - DBObject.Connection.ParseRoutineStructure(DBObject.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body); + DBObject.Connection.ParseRoutineStructure(Obj.CreateCode, Parameters, Deterministic, Definer, Returns, DataAccess, Security, Comment, Body); comboReturns.Text := Returns; chkDeterministic.Checked := Deterministic; if DataAccess <> '' then @@ -155,11 +160,11 @@ begin if Security <> '' then comboSecurity.ItemIndex := comboSecurity.Items.IndexOf(Security); editComment.Text := Comment; + comboDefiner.Text := Definer; SynMemoBody.Text := Body; end else begin editName.Text := ''; end; - editNameChange(Self); comboTypeSelect(comboType); btnRemoveParam.Enabled := Assigned(listParameters.FocusedNode); Modified := False; @@ -171,27 +176,10 @@ begin end; -procedure TfrmRoutineEditor.editNameChange(Sender: TObject); -begin - editName.Font.Color := clWindowText; - editName.Color := clWindow; - try - ensureValidIdentifier( editName.Text ); - except - // Invalid name - if editName.Text <> '' then begin - editName.Font.Color := clRed; - editName.Color := clYellow; - end; - end; - Modification(Sender); -end; - - procedure TfrmRoutineEditor.Modification(Sender: TObject); begin Modified := True; - btnSave.Enabled := Modified; + btnSave.Enabled := Modified and (editName.Text <> ''); btnDiscard.Enabled := Modified; SynMemoCreateCode.Text := ComposeCreateStatement(editName.Text); end; @@ -209,6 +197,13 @@ begin end; +procedure TfrmRoutineEditor.comboDefinerDropDown(Sender: TObject); +begin + // Populate definers from mysql.user + (Sender as TComboBox).Items.Assign(GetDefiners); +end; + + procedure TfrmRoutineEditor.btnAddParamClick(Sender: TObject); var Param: TRoutineParam; @@ -474,7 +469,10 @@ var i: Integer; begin ProcOrFunc := UpperCase(GetFirstWord(comboType.Text)); - Result := 'CREATE '+ProcOrFunc+' '+Mainform.mask(NameOfObject)+'('; + Result := 'CREATE '; + if comboDefiner.Text <> '' then + Result := Result + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, '@')+' '; + Result := Result + ProcOrFunc+' '+Mainform.mask(NameOfObject)+'('; for i:=0 to Parameters.Count-1 do begin if ProcOrFunc = 'PROCEDURE' then Result := Result + Parameters[i].Context + ' '; diff --git a/source/table_editor.dfm b/source/table_editor.dfm index 6b88b496..3a07fb5b 100644 --- a/source/table_editor.dfm +++ b/source/table_editor.dfm @@ -199,7 +199,7 @@ object frmTableEditor: TfrmTableEditor TabOrder = 0 Text = 'editName' TextHint = 'Enter table name' - OnChange = editNameChange + OnChange = Modification end object memoComment: TMemo Left = 72 diff --git a/source/table_editor.pas b/source/table_editor.pas index e6374a67..5b246c23 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -88,7 +88,6 @@ type listForeignKeys: TVirtualStringTree; menuCopyColumns: TMenuItem; menuPasteColumns: TMenuItem; - procedure editNameChange(Sender: TObject); procedure Modification(Sender: TObject); procedure btnAddColumnClick(Sender: TObject); procedure btnRemoveColumnClick(Sender: TObject); @@ -660,24 +659,6 @@ begin end; -procedure TfrmTableEditor.editNameChange(Sender: TObject); -begin - // Name edited - editName.Font.Color := clWindowText; - editName.Color := clWindow; - try - ensureValidIdentifier( editName.Text ); - except - // Invalid name - if editName.Text <> '' then begin - editName.Font.Color := clRed; - editName.Color := clYellow; - end; - end; - Modification(Sender); -end; - - procedure TfrmTableEditor.Modification(Sender: TObject); begin // Memorize modified status @@ -685,7 +666,7 @@ begin if Sender is TComponent then TComponent(Sender).Tag := ModifiedFlag; Modified := True; - btnSave.Enabled := Modified; + btnSave.Enabled := Modified and (editName.Text <> ''); btnDiscard.Enabled := Modified; CreateCodeValid := False; AlterCodeValid := False; @@ -1947,7 +1928,7 @@ begin MessageDlg('Please select a reference table before selecting foreign columns.', mtError, [mbOk], 0) else begin try - MainForm.ActiveConnection.GetVar('SELECT 1 FROM '+Mainform.Mask(Key.ReferenceTable, True)); + MainForm.ActiveConnection.GetVar('SELECT 1 FROM '+Mainform.Mask(Key.ReferenceTable, '.')); Allowed := True; except // Leave Allowed = False @@ -1999,7 +1980,7 @@ begin 3: begin Key := FForeignKeys[Node.Index]; SetEditor := TSetEditorLink.Create(VT); - SetEditor.ValueList := MainForm.ActiveConnection.GetCol('SHOW COLUMNS FROM '+Mainform.Mask(Key.ReferenceTable, True)); + SetEditor.ValueList := MainForm.ActiveConnection.GetCol('SHOW COLUMNS FROM '+Mainform.Mask(Key.ReferenceTable, '.')); EditLink := SetEditor; end; 4, 5: begin diff --git a/source/tabletools.pas b/source/tabletools.pas index 459d8fe1..c9a13a8a 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -557,7 +557,7 @@ begin Columns := TTableColumnList.Create(True); case DBObj.NodeType of lntTable: DBObj.Connection.ParseTableStructure(DBObj.CreateCode, Columns, nil, nil); - lntView: DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, Columns, Dummy, Dummy, Dummy); + lntView: DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, Columns, Dummy, Dummy, Dummy, Dummy); else AddNotes(DBObj.Database, DBObj.Name, STRSKIPPED+'a '+LowerCase(DBObj.ObjType)+' does not contain rows.', ''); end; if Columns.Count > 0 then begin @@ -1109,7 +1109,7 @@ begin if not FSecondExportPass then begin // Create temporary VIEW replacement ColumnList := TTableColumnList.Create(True); - DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, ColumnList, Dummy, Dummy, Dummy); + DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, ColumnList, Dummy, Dummy, Dummy, Dummy); Struc := '# Creating temporary table to overcome VIEW dependency errors'+CRLF+ 'CREATE TABLE '; if ToDb then diff --git a/source/trigger_editor.dfm b/source/trigger_editor.dfm index 65607f8b..5b2eeb6a 100644 --- a/source/trigger_editor.dfm +++ b/source/trigger_editor.dfm @@ -36,12 +36,18 @@ object frmTriggerEditor: TfrmTriggerEditor Height = 13 Caption = 'Event:' end + object lblDefiner: TLabel + Left = 247 + Top = 6 + Width = 39 + Height = 13 + Caption = 'Definer:' + end object editName: TEdit - Left = 96 + Left = 84 Top = 3 - Width = 378 + Width = 157 Height = 21 - Anchors = [akLeft, akTop, akRight] TabOrder = 0 Text = 'editName' TextHint = 'Enter trigger name' @@ -59,7 +65,7 @@ object frmTriggerEditor: TfrmTriggerEditor Font.Height = -13 Font.Name = 'Courier New' Font.Style = [] - TabOrder = 2 + TabOrder = 4 Gutter.Font.Charset = DEFAULT_CHARSET Gutter.Font.Color = clWindowText Gutter.Font.Height = -11 @@ -76,7 +82,7 @@ object frmTriggerEditor: TfrmTriggerEditor Height = 25 Anchors = [akLeft, akBottom] Caption = 'Help' - TabOrder = 3 + TabOrder = 5 OnClick = btnHelpClick end object btnDiscard: TButton @@ -86,7 +92,7 @@ object frmTriggerEditor: TfrmTriggerEditor Height = 25 Anchors = [akLeft, akBottom] Caption = 'Discard' - TabOrder = 4 + TabOrder = 6 OnClick = btnDiscardClick end object btnSave: TButton @@ -97,13 +103,13 @@ object frmTriggerEditor: TfrmTriggerEditor Anchors = [akLeft, akBottom] Caption = 'Save' Default = True - TabOrder = 5 + TabOrder = 7 OnClick = btnSaveClick end object comboTable: TComboBox - Left = 96 + Left = 84 Top = 30 - Width = 378 + Width = 390 Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] @@ -111,12 +117,12 @@ object frmTriggerEditor: TfrmTriggerEditor OnChange = Modification end object comboTiming: TComboBox - Left = 96 + Left = 84 Top = 56 - Width = 145 + Width = 157 Height = 21 Style = csDropDownList - TabOrder = 6 + TabOrder = 2 OnChange = Modification end object comboEvent: TComboBox @@ -125,9 +131,20 @@ object frmTriggerEditor: TfrmTriggerEditor Width = 145 Height = 21 Style = csDropDownList - TabOrder = 7 + TabOrder = 3 OnChange = Modification end + object comboDefiner: TComboBox + Left = 304 + Top = 3 + Width = 170 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 8 + Text = 'comboDefiner' + OnChange = Modification + OnDropDown = comboDefinerDropDown + end object SynCompletionProposalStatement: TSynCompletionProposal Options = [scoLimitToMatchedText, scoUseInsertList, scoUsePrettyText, scoEndCharCompletion, scoCompleteWithTab, scoCompleteWithEnter] EndOfTokenChr = '()[]. ' diff --git a/source/trigger_editor.pas b/source/trigger_editor.pas index ba7ff81d..2f098444 100644 --- a/source/trigger_editor.pas +++ b/source/trigger_editor.pas @@ -23,12 +23,15 @@ type lblEvent: TLabel; comboTiming: TComboBox; comboEvent: TComboBox; + lblDefiner: TLabel; + comboDefiner: 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: String; var x, y: Integer; var CanExecute: Boolean); + procedure comboDefinerDropDown(Sender: TObject); private { Private declarations } public @@ -81,6 +84,9 @@ var begin inherited; editName.Text := ''; + comboDefiner.Text := ''; + comboDefiner.TextHint := 'Current user ('+Obj.Connection.CurrentUserHostCombination+')'; + comboDefiner.Hint := 'Leave empty for current user ('+Obj.Connection.CurrentUserHostCombination+')'; SynMemoStatement.Text := 'BEGIN'+CRLF+CRLF+'END'; comboEvent.ItemIndex := 0; comboTiming.ItemIndex := 0; @@ -99,6 +105,7 @@ begin Found := False; while not Definitions.Eof do begin if Definitions.Col('Trigger') = DBObject.Name then begin + comboDefiner.Text := Definitions.Col('Definer'); comboTable.ItemIndex := comboTable.Items.IndexOf(Definitions.Col('Table')); comboTiming.ItemIndex := comboTiming.Items.IndexOf(UpperCase(Definitions.Col('Timing'))); comboEvent.ItemIndex := comboEvent.Items.IndexOf(UpperCase(Definitions.Col('Event'))); @@ -125,11 +132,12 @@ end; procedure TfrmTriggerEditor.Modification(Sender: TObject); begin // Enable buttons if anything has changed - btnSave.Enabled := (editName.Text <> '') and (comboTable.ItemIndex > -1) + Modified := True; + btnSave.Enabled := Modified + and (editName.Text <> '') and (comboTable.ItemIndex > -1) and (comboTiming.ItemIndex > -1) and (comboEvent.ItemIndex > -1) and (SynMemoStatement.Text <> ''); - btnDiscard.Enabled := True; - Modified := True; + btnDiscard.Enabled := Modified; end; @@ -147,6 +155,13 @@ begin end; +procedure TfrmTriggerEditor.comboDefinerDropDown(Sender: TObject); +begin + // Populate definers from mysql.user + (Sender as TComboBox).Items.Assign(GetDefiners); +end; + + function TfrmTriggerEditor.ApplyModifications: TModalResult; var sql: String; @@ -167,7 +182,10 @@ begin // [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)+' '+ + sql := 'CREATE '; + if comboDefiner.Text <> '' then + sql := sql + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, '@')+' '; + sql := sql + 'TRIGGER '+Mainform.mask(editName.Text)+' '+ comboTiming.Items[comboTiming.ItemIndex]+' '+comboEvent.Items[comboEvent.ItemIndex]+ ' ON '+Mainform.mask(comboTable.Text)+ ' FOR EACH ROW '+SynMemoStatement.Text; diff --git a/source/view.dfm b/source/view.dfm index d7f49a6f..bce9a8ae 100644 --- a/source/view.dfm +++ b/source/view.dfm @@ -32,16 +32,22 @@ object frmView: TfrmView Layout = tlCenter Visible = False end + object lblDefiner: TLabel + Left = 215 + Top = 6 + Width = 39 + Height = 13 + Caption = 'Definer:' + end object editName: TEdit Left = 42 Top = 3 - Width = 405 + Width = 167 Height = 21 - Anchors = [akLeft, akTop, akRight] TabOrder = 0 Text = 'editName' TextHint = 'Enter view name' - OnChange = editNameChange + OnChange = Modification end object rgAlgorithm: TRadioGroup Left = 3 @@ -54,7 +60,7 @@ object frmView: TfrmView 'UNDEFINED' 'MERGE' 'TEMPTABLE') - TabOrder = 1 + TabOrder = 2 OnClick = Modification end object SynMemoSelect: TSynMemo @@ -69,7 +75,7 @@ object frmView: TfrmView Font.Height = -13 Font.Name = 'Courier New' Font.Style = [] - TabOrder = 2 + TabOrder = 4 Gutter.AutoSize = True Gutter.DigitCount = 2 Gutter.Font.Charset = DEFAULT_CHARSET @@ -102,7 +108,7 @@ object frmView: TfrmView Height = 25 Anchors = [akLeft, akBottom] Caption = 'Discard' - TabOrder = 3 + TabOrder = 6 OnClick = btnDiscardClick end object btnSave: TButton @@ -113,7 +119,7 @@ object frmView: TfrmView Anchors = [akLeft, akBottom] Caption = 'Save' Default = True - TabOrder = 4 + TabOrder = 7 OnClick = btnSaveClick end object rgCheck: TRadioGroup @@ -128,7 +134,7 @@ object frmView: TfrmView 'None' 'CASCADED' 'LOCAL') - TabOrder = 5 + TabOrder = 3 OnClick = Modification end object btnHelp: TButton @@ -138,7 +144,18 @@ object frmView: TfrmView Height = 25 Anchors = [akLeft, akBottom] Caption = 'Help' - TabOrder = 6 + TabOrder = 5 OnClick = btnHelpClick end + object comboDefiner: TComboBox + Left = 265 + Top = 3 + Width = 182 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 1 + Text = 'comboDefiner' + OnChange = Modification + OnDropDown = comboDefinerDropDown + end end diff --git a/source/view.pas b/source/view.pas index 22bbfbe7..d35e8e6c 100644 --- a/source/view.pas +++ b/source/view.pas @@ -20,11 +20,13 @@ type rgCheck: TRadioGroup; btnHelp: TButton; lblDisabledWhy: TLabel; + lblDefiner: TLabel; + comboDefiner: TComboBox; procedure btnHelpClick(Sender: TObject); procedure btnSaveClick(Sender: TObject); - procedure editNameChange(Sender: TObject); procedure btnDiscardClick(Sender: TObject); procedure Modification(Sender: TObject); + procedure comboDefinerDropDown(Sender: TObject); private { Private declarations } public @@ -59,14 +61,18 @@ end; } procedure TfrmView.Init(Obj: TDBObject); var - Algorithm, CheckOption, SelectCode: String; + Algorithm, CheckOption, SelectCode, Definer: String; begin inherited; lblDisabledWhy.Font.Color := clRed; + comboDefiner.Text := ''; + comboDefiner.TextHint := 'Current user ('+Obj.Connection.CurrentUserHostCombination+')'; + comboDefiner.Hint := 'Leave empty for current user ('+Obj.Connection.CurrentUserHostCombination+')'; if Obj.Name <> '' then begin // Edit mode editName.Text := Obj.Name; - Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, nil, Algorithm, CheckOption, SelectCode); + Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, nil, Algorithm, Definer, CheckOption, SelectCode); + comboDefiner.Text := Definer; rgAlgorithm.ItemIndex := rgAlgorithm.Items.IndexOf(Algorithm); rgCheck.ItemIndex := rgCheck.Items.IndexOf(CheckOption); if rgCheck.ItemIndex = -1 then @@ -89,8 +95,6 @@ begin SynMemoSelect.Text := 'SELECT '; lblDisabledWhy.Hide; end; - // Ensure name is validated - editNameChange(Self); Modified := False; btnSave.Enabled := Modified; btnDiscard.Enabled := Modified; @@ -99,20 +103,10 @@ begin end; -{** - View name has changed: Check for valid naming -} -procedure TfrmView.editNameChange(Sender: TObject); +procedure TfrmView.comboDefinerDropDown(Sender: TObject); begin - try - ensureValidIdentifier( editName.Text ); - editName.Font.Color := clWindowText; - editName.Color := clWindow; - except - editName.Font.Color := clRed; - editName.Color := clYellow; - end; - Modification(Sender); + // Populate definers from mysql.user + (Sender as TComboBox).Items.Assign(GetDefiners); end; @@ -164,6 +158,8 @@ begin viewname := Mainform.mask(viewname); if rgAlgorithm.Enabled and (rgAlgorithm.ItemIndex > -1) then sql := sql + 'ALGORITHM = '+Uppercase(rgAlgorithm.Items[rgAlgorithm.ItemIndex])+' '; + if comboDefiner.Text <> '' then + sql := sql + 'DEFINER='+DBObject.Connection.QuoteIdent(comboDefiner.Text, '@')+' '; sql := sql + 'VIEW ' + viewname+' AS '+SynMemoSelect.Text+' '; if rgCheck.Enabled and (rgCheck.ItemIndex > 0) then sql := sql + 'WITH '+Uppercase(rgCheck.Items[rgCheck.ItemIndex])+' CHECK OPTION'; @@ -195,7 +191,7 @@ end; procedure TfrmView.Modification(Sender: TObject); begin Modified := True; - btnSave.Enabled := Modified; + btnSave.Enabled := Modified and (editName.Text <> ''); btnDiscard.Enabled := Modified; end;