mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-26 11:17:57 +08:00
Try a new approach in TDBConnection.ParseRoutineStructure(). Should fix stripped backslashes from routine body, issue #3107.
* Use SHOW CREATE PROCEDURE/FUNCTION result again, instead of code from IS.ROUTINES * Remove every known CREATE PROCEDURE/FUNCTION clause and use remaining text as routine body. * Respect MS SQL function options, taken from http://msdn.microsoft.com/en-us/library/ms186755.aspx * Introduce helpers.ExtractComment() for usage in ParseRoutineStructure() and ParseTableStructure()
This commit is contained in:
@ -3340,18 +3340,7 @@ begin
|
||||
end;
|
||||
|
||||
// Comment
|
||||
if UpperCase(Copy(ColSpec, 1, 9)) = 'COMMENT ''' then begin
|
||||
InLiteral := True;
|
||||
for i:=10 to Length(ColSpec) do begin
|
||||
if ColSpec[i] = '''' then
|
||||
InLiteral := not InLiteral
|
||||
else if not InLiteral then
|
||||
break;
|
||||
end;
|
||||
Col.Comment := Copy(ColSpec, 10, i-11);
|
||||
Col.Comment := StringReplace(Col.Comment, '''''', '''', [rfReplaceAll]);
|
||||
Delete(ColSpec, 1, i);
|
||||
end;
|
||||
Col.Comment := ExtractComment(ColSpec);
|
||||
|
||||
if not rx.ExecNext then
|
||||
break;
|
||||
@ -3501,12 +3490,11 @@ end;
|
||||
|
||||
procedure TDBConnection.ParseRoutineStructure(Obj: TDBObject; Parameters: TRoutineParamList);
|
||||
var
|
||||
CreateCode, Params: String;
|
||||
CreateCode, Params, Body, Match: String;
|
||||
ParenthesesCount: Integer;
|
||||
rx: TRegExpr;
|
||||
i: Integer;
|
||||
Param: TRoutineParam;
|
||||
FromIS: TDBQuery;
|
||||
begin
|
||||
// Parse CREATE code of stored function or procedure to detect parameters
|
||||
rx := TRegExpr.Create;
|
||||
@ -3517,8 +3505,15 @@ begin
|
||||
// CREATE DEFINER=`root`@`localhost` PROCEDURE `test3`(IN `Param1` int(1) unsigned)
|
||||
// MSSQL: CREATE FUNCTION dbo.ConvertToInt(@string nvarchar(255), @maxValue int, @defValue int) RETURNS int
|
||||
|
||||
// Parse parameter list
|
||||
CreateCode := Obj.CreateCode;
|
||||
|
||||
rx.Expression := '\bDEFINER\s*=\s*(\S+)\s';
|
||||
if rx.Exec(CreateCode) then
|
||||
Obj.Definer := DequoteIdent(rx.Match[1], '@')
|
||||
else
|
||||
Obj.Definer := '';
|
||||
|
||||
// Parse parameter list
|
||||
ParenthesesCount := 0;
|
||||
Params := '';
|
||||
for i:=1 to Length(CreateCode) do begin
|
||||
@ -3532,7 +3527,8 @@ begin
|
||||
if CreateCode[i] = '(' then
|
||||
Inc(ParenthesesCount);
|
||||
end;
|
||||
log(lcinfo, params);
|
||||
|
||||
// Extract parameters from left part
|
||||
rx.Expression := '(^|,)\s*((IN|OUT|INOUT)\s+)?(\S+)\s+([^\s,\(]+(\([^\)]*\))?[^,]*)';
|
||||
if rx.Exec(Params) then while true do begin
|
||||
Param := TRoutineParam.Create;
|
||||
@ -3545,40 +3541,54 @@ begin
|
||||
if not rx.ExecNext then
|
||||
break;
|
||||
end;
|
||||
rx.Free;
|
||||
|
||||
if Obj.Body = '' then begin
|
||||
// Get everything else from information_schema.
|
||||
// See http://www.heidisql.com/forum.php?t=12075
|
||||
// See issue #3114
|
||||
// See http://www.heidisql.com/forum.php?t=12435
|
||||
FromIS := GetResults('SELECT * FROM information_schema.'+QuoteIdent('ROUTINES')+
|
||||
' WHERE '+
|
||||
' ('+QuoteIdent('ROUTINE_SCHEMA')+'='+EscapeString(Obj.Database)+
|
||||
' OR '+QuoteIdent('ROUTINE_CATALOG')+'='+EscapeString(Obj.Database)+')'+
|
||||
' AND '+QuoteIdent('ROUTINE_NAME')+'='+EscapeString(Obj.Name)+
|
||||
' AND '+QuoteIdent('ROUTINE_TYPE')+'='+EscapeString(UpperCase(Obj.ObjType))
|
||||
);
|
||||
Obj.Body := FromIS.Col('ROUTINE_DEFINITION');
|
||||
Obj.Definer := FromIS.Col('DEFINER', True);
|
||||
Obj.Returns := FromIS.Col('DATA_TYPE', True);
|
||||
if FromIS.Col('CHARACTER_MAXIMUM_LENGTH', True) <> '' then
|
||||
Obj.Returns := Obj.Returns + '(' + FromIS.Col('CHARACTER_MAXIMUM_LENGTH', True) + ')';
|
||||
Obj.Deterministic := FromIS.Col('IS_DETERMINISTIC', True) = 'YES';
|
||||
Obj.DataAccess := FromIS.Col('SQL_DATA_ACCESS', True);
|
||||
Obj.Security := FromIS.Col('SECURITY_TYPE', True);
|
||||
Obj.Comment := FromIS.Col('ROUTINE_COMMENT', True);
|
||||
if Self.Parameters.NetTypeGroup = ngMSSQL then begin
|
||||
// MSSQL includes the CREATE ... clause in the definition
|
||||
rx := TRegExpr.Create;
|
||||
rx.ModifierI := True;
|
||||
rx.ModifierG := True;
|
||||
rx.Expression := '\s+AS\s+BEGIN\s+(.*)\sEND\s*$';
|
||||
if rx.Exec(CreateCode) then
|
||||
Obj.Body := rx.Match[1];
|
||||
rx.Free;
|
||||
end;
|
||||
// Right part contains routine body
|
||||
Body := Copy(CreateCode, i+1, Length(CreateCode));
|
||||
// Remove "RETURNS x" and routine characteristics from body
|
||||
// LANGUAGE SQL
|
||||
// | [NOT] DETERMINISTIC
|
||||
// | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
|
||||
// | SQL SECURITY { DEFINER | INVOKER }
|
||||
// | COMMENT 'string'
|
||||
rx.Expression := '^\s*('+
|
||||
'RETURNS\s+(\S+)(\s+CHARSET\s+\S+)?(\s+COLLATE\s\S+)?|'+
|
||||
// MySQL function characteristics - see http://dev.mysql.com/doc/refman/5.1/de/create-procedure.html
|
||||
'LANGUAGE\s+SQL|'+
|
||||
'(NOT\s+)?DETERMINISTIC|'+
|
||||
'CONTAINS\s+SQL|'+
|
||||
'NO\s+SQL|'+
|
||||
'READS\s+SQL\s+DATA|'+
|
||||
'MODIFIES\s+SQL\s+DATA|'+
|
||||
'SQL\s+SECURITY\s+(DEFINER|INVOKER)|'+
|
||||
// MS SQL function options - see http://msdn.microsoft.com/en-us/library/ms186755.aspx
|
||||
'AS|'+
|
||||
'WITH\s+ENCRYPTION|'+
|
||||
'WITH\s+SCHEMABINDING|'+
|
||||
'WITH\s+RETURNS\s+NULL\s+ON\s+NULL\s+INPUT|'+
|
||||
'WITH\s+CALLED\s+ON\s+NULL\s+INPUT|'+
|
||||
'WITH\s+EXECUTE_AS_Clause'+
|
||||
')\s';
|
||||
if rx.Exec(Body) then while true do begin
|
||||
Match := UpperCase(rx.Match[1]);
|
||||
if Pos('RETURNS', Match) = 1 then
|
||||
Obj.Returns := rx.Match[2]
|
||||
else if Pos('DETERMINISTIC', Match) = 1 then
|
||||
Obj.Deterministic := True
|
||||
else if Pos('NOT DETERMINISTIC', Match) = 1 then
|
||||
Obj.Deterministic := False
|
||||
else if (Pos('CONTAINS SQL', Match) = 1) or (Pos('NO SQL', Match) = 1) or (Pos('READS SQL DATA', Match) = 1) or (Pos('MODIFIES SQL DATA', Match) = 1) then
|
||||
Obj.DataAccess := rx.Match[1]
|
||||
else if Pos('SQL SECURITY', Match) = 1 then
|
||||
Obj.Security := rx.Match[6];
|
||||
|
||||
|
||||
Delete(Body, 1, rx.MatchLen[0]);
|
||||
if not rx.Exec(Body) then
|
||||
break;
|
||||
end;
|
||||
Obj.Comment := ExtractComment(Body);
|
||||
Obj.Body := TrimLeft(Body);
|
||||
rx.Free;
|
||||
end;
|
||||
|
||||
|
||||
|
@ -257,6 +257,7 @@ type
|
||||
function ScanLineBreaks(Text: String): TLineBreaks;
|
||||
function RemoveNulChars(Text: String): String;
|
||||
function fixNewlines(txt: String): String;
|
||||
function ExtractComment(var SQL: String): String;
|
||||
function GetShellFolder(CSIDL: integer): string;
|
||||
// Common directories
|
||||
function DirnameCommonAppData: String;
|
||||
@ -741,6 +742,33 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
function ExtractComment(var SQL: String): String;
|
||||
var
|
||||
i, LitStart: Integer;
|
||||
InLiteral: Boolean;
|
||||
rx: TRegExpr;
|
||||
begin
|
||||
// Return comment from SQL and remove it from the original string
|
||||
// Single quotes are escaped by a second single quote
|
||||
rx := TRegExpr.Create;
|
||||
rx.Expression := '^\s*COMMENT\s+''';
|
||||
rx.ModifierI := True;
|
||||
if rx.Exec(SQL) then begin
|
||||
LitStart := rx.MatchLen[0]+1;
|
||||
InLiteral := True;
|
||||
for i:=LitStart to Length(SQL) do begin
|
||||
if SQL[i] = '''' then
|
||||
InLiteral := not InLiteral
|
||||
else if not InLiteral then
|
||||
break;
|
||||
end;
|
||||
Result := Copy(SQL, LitStart, i-LitStart-1);
|
||||
Result := StringReplace(Result, '''''', '''', [rfReplaceAll]);
|
||||
Delete(SQL, 1, i);
|
||||
end;
|
||||
rx.Free;
|
||||
end;
|
||||
|
||||
|
||||
{***
|
||||
Get the path of a Windows(r)-shellfolder, specified by an integer or a constant
|
||||
|
Reference in New Issue
Block a user