mirror of
https://github.com/HeidiSQL/HeidiSQL.git
synced 2025-08-06 18:24:26 +08:00
The 3 procedures Parse(Table|View|Routine)Structure() already do some connection specific stuff, and now even more, so they're moved to TMySQLConnection now. In order to display the right collation even if only the character set was found in a CREATE TABLE code, the default collation per charset is detected via CollationTable. See http://www.heidisql.com/forum.php?t=6348 .
This commit is contained in:
@ -126,8 +126,8 @@ begin
|
|||||||
FKeys.Clear;
|
FKeys.Clear;
|
||||||
FForeignKeys.Clear;
|
FForeignKeys.Clear;
|
||||||
case FDBObj.NodeType of
|
case FDBObj.NodeType of
|
||||||
lntTable: ParseTableStructure(FDBObj.CreateCode, FColumns, FKeys, FForeignKeys);
|
lntTable: FDBObj.Connection.ParseTableStructure(FDBObj.CreateCode, FColumns, FKeys, FForeignKeys);
|
||||||
lntView: ParseViewStructure(FDBObj.CreateCode, FDBObj.Name, FColumns, Algorithm, CheckOption, SelectCode);
|
lntView: FDBObj.Connection.ParseViewStructure(FDBObj.CreateCode, FDBObj.Name, FColumns, Algorithm, CheckOption, SelectCode);
|
||||||
else raise Exception.Create('Neither table nor view: '+FDBObj.Name);
|
else raise Exception.Create('Neither table nor view: '+FDBObj.Name);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, GraphUtil, ClipBrd, Dialogs, Forms, Controls, ComCtrls, ShellApi, CheckLst,
|
Classes, SysUtils, Graphics, GraphUtil, ClipBrd, Dialogs, Forms, Controls, ComCtrls, ShellApi, CheckLst,
|
||||||
Windows, Contnrs, ShlObj, ActiveX, VirtualTrees, SynRegExpr, Messages, WideStrUtils, Math,
|
Windows, Contnrs, ShlObj, ActiveX, VirtualTrees, SynRegExpr, Messages, Math,
|
||||||
Registry, SynEditHighlighter, DateUtils, Generics.Collections, StrUtils, AnsiStrings, TlHelp32, Types,
|
Registry, SynEditHighlighter, DateUtils, Generics.Collections, StrUtils, AnsiStrings, TlHelp32, Types,
|
||||||
mysql_connection, mysql_structures;
|
mysql_connection, mysql_structures;
|
||||||
|
|
||||||
@ -141,10 +141,6 @@ type
|
|||||||
function DateBackFriendlyCaption(d: TDateTime): String;
|
function DateBackFriendlyCaption(d: TDateTime): String;
|
||||||
procedure InheritFont(AFont: TFont);
|
procedure InheritFont(AFont: TFont);
|
||||||
function GetLightness(AColor: TColor): Byte;
|
function GetLightness(AColor: TColor): Byte;
|
||||||
procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
|
||||||
procedure ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, CheckOption, SelectCode: String);
|
|
||||||
procedure ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
|
||||||
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
|
||||||
function ReformatSQL(SQL: String): String;
|
function ReformatSQL(SQL: String): String;
|
||||||
function ParamBlobToStr(lpData: Pointer): TStringlist;
|
function ParamBlobToStr(lpData: Pointer): TStringlist;
|
||||||
function ParamStrToBlob(out cbData: DWORD): Pointer;
|
function ParamStrToBlob(out cbData: DWORD): Pointer;
|
||||||
@ -2192,384 +2188,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
|
||||||
var
|
|
||||||
ColSpec: String;
|
|
||||||
rx, rxCol: TRegExpr;
|
|
||||||
i: Integer;
|
|
||||||
InLiteral: Boolean;
|
|
||||||
Col: TTableColumn;
|
|
||||||
Key: TTableKey;
|
|
||||||
ForeignKey: TForeignKey;
|
|
||||||
begin
|
|
||||||
if Assigned(Columns) then Columns.Clear;
|
|
||||||
if Assigned(Keys) then Keys.Clear;
|
|
||||||
if Assigned(ForeignKeys) then ForeignKeys.Clear;
|
|
||||||
if CreateTable = '' then
|
|
||||||
Exit;
|
|
||||||
rx := TRegExpr.Create;
|
|
||||||
rx.ModifierS := False;
|
|
||||||
rx.ModifierM := True;
|
|
||||||
rx.Expression := '^\s+[`"]([^`"]+)[`"]\s(\w+)';
|
|
||||||
rxCol := TRegExpr.Create;
|
|
||||||
rxCol.ModifierI := True;
|
|
||||||
if rx.Exec(CreateTable) then while true do begin
|
|
||||||
if not Assigned(Columns) then
|
|
||||||
break;
|
|
||||||
ColSpec := '';
|
|
||||||
for i:=rx.MatchPos[2]+rx.MatchLen[2] to Length(CreateTable) do begin
|
|
||||||
if CharInSet(CreateTable[i], [#13, #10]) then
|
|
||||||
break;
|
|
||||||
ColSpec := ColSpec + CreateTable[i];
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Strip trailing comma
|
|
||||||
if (ColSpec <> '') and (ColSpec[Length(ColSpec)] = ',') then
|
|
||||||
Delete(ColSpec, Length(ColSpec), 1);
|
|
||||||
|
|
||||||
Col := TTableColumn.Create;
|
|
||||||
Columns.Add(Col);
|
|
||||||
Col.Name := rx.Match[1];
|
|
||||||
Col.OldName := Col.Name;
|
|
||||||
Col.Status := esUntouched;
|
|
||||||
Col.LengthCustomized := True;
|
|
||||||
|
|
||||||
// Datatype
|
|
||||||
Col.DataType := GetDatatypeByName(UpperCase(rx.Match[2]));
|
|
||||||
|
|
||||||
// Length / Set
|
|
||||||
// Various datatypes, e.g. BLOBs, don't have any length property
|
|
||||||
InLiteral := False;
|
|
||||||
if (ColSpec <> '') and (ColSpec[1] = '(') then begin
|
|
||||||
for i:=2 to Length(ColSpec) do begin
|
|
||||||
if (ColSpec[i] = ')') and (not InLiteral) then
|
|
||||||
break;
|
|
||||||
if ColSpec[i] = '''' then
|
|
||||||
InLiteral := not InLiteral;
|
|
||||||
end;
|
|
||||||
Col.LengthSet := Copy(ColSpec, 2, i-2);
|
|
||||||
Delete(ColSpec, 1, i);
|
|
||||||
end;
|
|
||||||
ColSpec := Trim(ColSpec);
|
|
||||||
|
|
||||||
// Unsigned
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 8)) = 'UNSIGNED' then begin
|
|
||||||
Col.Unsigned := True;
|
|
||||||
Delete(ColSpec, 1, 9);
|
|
||||||
end else
|
|
||||||
Col.Unsigned := False;
|
|
||||||
|
|
||||||
// Zero fill
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 8)) = 'ZEROFILL' then begin
|
|
||||||
Col.ZeroFill := True;
|
|
||||||
Delete(ColSpec, 1, 9);
|
|
||||||
end else
|
|
||||||
Col.ZeroFill := False;
|
|
||||||
|
|
||||||
// Collation
|
|
||||||
rxCol.Expression := '^(CHARACTER SET \w+\s+)?COLLATE (\w+)\b';
|
|
||||||
if rxCol.Exec(ColSpec) then begin
|
|
||||||
Col.Collation := rxCol.Match[2];
|
|
||||||
Delete(ColSpec, 1, rxCol.MatchLen[0]+1);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Allow NULL
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 8)) = 'NOT NULL' then begin
|
|
||||||
Col.AllowNull := False;
|
|
||||||
Delete(ColSpec, 1, 9);
|
|
||||||
end else begin
|
|
||||||
Col.AllowNull := True;
|
|
||||||
// Sporadically there is a "NULL" found at this position.
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then
|
|
||||||
Delete(ColSpec, 1, 5);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Default value
|
|
||||||
Col.DefaultType := cdtNothing;
|
|
||||||
Col.DefaultText := '';
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 14)) = 'AUTO_INCREMENT' then begin
|
|
||||||
Col.DefaultType := cdtAutoInc;
|
|
||||||
Col.DefaultText := 'AUTO_INCREMENT';
|
|
||||||
Delete(ColSpec, 1, 15);
|
|
||||||
end else if UpperCase(Copy(ColSpec, 1, 8)) = 'DEFAULT ' then begin
|
|
||||||
Delete(ColSpec, 1, 8);
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then begin
|
|
||||||
Col.DefaultType := cdtNull;
|
|
||||||
Col.DefaultText := 'NULL';
|
|
||||||
Delete(ColSpec, 1, 5);
|
|
||||||
end else if UpperCase(Copy(ColSpec, 1, 17)) = 'CURRENT_TIMESTAMP' then begin
|
|
||||||
Col.DefaultType := cdtCurTS;
|
|
||||||
Col.DefaultText := 'CURRENT_TIMESTAMP';
|
|
||||||
Delete(ColSpec, 1, 18);
|
|
||||||
end else if ColSpec[1] = '''' then begin
|
|
||||||
InLiteral := True;
|
|
||||||
for i:=2 to Length(ColSpec) do begin
|
|
||||||
if ColSpec[i] = '''' then
|
|
||||||
InLiteral := not InLiteral
|
|
||||||
else if not InLiteral then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
Col.DefaultType := cdtText;
|
|
||||||
Col.DefaultText := Copy(ColSpec, 2, i-3);
|
|
||||||
// A single quote gets escaped by single quote - remove the escape char - escaping is done in Save action afterwards
|
|
||||||
Col.DefaultText := StringReplace(Col.DefaultText, '''''', '''', [rfReplaceAll]);
|
|
||||||
Delete(ColSpec, 1, i);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
if UpperCase(Copy(ColSpec, 1, 27)) = 'ON UPDATE CURRENT_TIMESTAMP' then begin
|
|
||||||
// Adjust default type
|
|
||||||
case Col.DefaultType of
|
|
||||||
cdtText: Col.DefaultType := cdtTextUpdateTS;
|
|
||||||
cdtNull: Col.DefaultType := cdtNullUpdateTS;
|
|
||||||
cdtCurTS: Col.DefaultType := cdtCurTSUpdateTS;
|
|
||||||
end;
|
|
||||||
Delete(ColSpec, 1, 28);
|
|
||||||
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;
|
|
||||||
|
|
||||||
if not rx.ExecNext then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Detect keys
|
|
||||||
// PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`), KEY `id_2` (`id`) USING BTREE,
|
|
||||||
// KEY `Text` (`Text`(100)), FULLTEXT KEY `Email` (`Email`,`Text`)
|
|
||||||
rx.Expression := '^\s+((\w+)\s+)?KEY\s+([`"]?([^`"]+)[`"]?\s+)?\((.+)\)(\s+USING\s+(\w+))?,?$';
|
|
||||||
if rx.Exec(CreateTable) then while true do begin
|
|
||||||
if not Assigned(Keys) then
|
|
||||||
break;
|
|
||||||
Key := TTableKey.Create;
|
|
||||||
Keys.Add(Key);
|
|
||||||
Key.Name := rx.Match[4];
|
|
||||||
if Key.Name = '' then Key.Name := rx.Match[2]; // PRIMARY
|
|
||||||
Key.OldName := Key.Name;
|
|
||||||
Key.IndexType := rx.Match[2];
|
|
||||||
Key.OldIndexType := Key.IndexType;
|
|
||||||
Key.Algorithm := rx.Match[7];
|
|
||||||
if Key.IndexType = '' then Key.IndexType := 'KEY'; // KEY
|
|
||||||
Key.Columns := Explode(',', rx.Match[5]);
|
|
||||||
for i:=0 to Key.Columns.Count-1 do begin
|
|
||||||
rxCol.Expression := '^[`"]?([^`"]+)[`"]?(\((\d+)\))?$';
|
|
||||||
if rxCol.Exec(Key.Columns[i]) then begin
|
|
||||||
Key.Columns[i] := rxCol.Match[1];
|
|
||||||
Key.SubParts.Add(rxCol.Match[3]);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
if not rx.ExecNext then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Detect foreign keys
|
|
||||||
// CONSTRAINT `FK1` FOREIGN KEY (`which`) REFERENCES `fk1` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
|
||||||
rx.Expression := '\s+CONSTRAINT\s+[`"]([^`"]+)[`"]\sFOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+[`"]([^\(]+)[`"]\s\(([^\)]+)\)(\s+ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?(\s+ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?';
|
|
||||||
if rx.Exec(CreateTable) then while true do begin
|
|
||||||
if not Assigned(ForeignKeys) then
|
|
||||||
break;
|
|
||||||
ForeignKey := TForeignKey.Create;
|
|
||||||
ForeignKeys.Add(ForeignKey);
|
|
||||||
ForeignKey.KeyName := rx.Match[1];
|
|
||||||
ForeignKey.OldKeyName := ForeignKey.KeyName;
|
|
||||||
ForeignKey.KeyNameWasCustomized := True;
|
|
||||||
ForeignKey.ReferenceTable := StringReplace(rx.Match[3], '`', '', [rfReplaceAll]);
|
|
||||||
ForeignKey.ReferenceTable := StringReplace(ForeignKey.ReferenceTable, '"', '', [rfReplaceAll]);
|
|
||||||
ExplodeQuotedList(rx.Match[2], ForeignKey.Columns);
|
|
||||||
ExplodeQuotedList(rx.Match[4], ForeignKey.ForeignColumns);
|
|
||||||
if rx.Match[6] <> '' then
|
|
||||||
ForeignKey.OnDelete := rx.Match[6];
|
|
||||||
if rx.Match[8] <> '' then
|
|
||||||
ForeignKey.OnUpdate := rx.Match[8];
|
|
||||||
if not rx.ExecNext then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
FreeAndNil(rxCol);
|
|
||||||
FreeAndNil(rx);
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, CheckOption, SelectCode: String);
|
|
||||||
var
|
|
||||||
rx: TRegExpr;
|
|
||||||
Col: TTableColumn;
|
|
||||||
Results: TMySQLQuery;
|
|
||||||
DbName, DbAndViewName: String;
|
|
||||||
begin
|
|
||||||
if CreateCode <> '' then begin
|
|
||||||
// CREATE
|
|
||||||
// [OR REPLACE]
|
|
||||||
// [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
|
|
||||||
// [DEFINER = { user | CURRENT_USER }]
|
|
||||||
// [SQL SECURITY { DEFINER | INVOKER }]
|
|
||||||
// VIEW view_name [(column_list)]
|
|
||||||
// AS select_statement
|
|
||||||
// [WITH [CASCADED | LOCAL] CHECK OPTION]
|
|
||||||
rx := TRegExpr.Create;
|
|
||||||
rx.ModifierG := False;
|
|
||||||
rx.ModifierI := True;
|
|
||||||
rx.Expression := '^CREATE\s+(OR\s+REPLACE\s+)?'+
|
|
||||||
'(ALGORITHM\s*=\s*(\w+)\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];
|
|
||||||
// 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];
|
|
||||||
end else
|
|
||||||
raise Exception.Create('Regular expression did not match the VIEW code in ParseViewStructure(): '+CRLF+CRLF+CreateCode);
|
|
||||||
rx.Free;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Views reveal their columns only with a SHOW COLUMNS query.
|
|
||||||
// No keys available in views - SHOW KEYS always returns an empty result
|
|
||||||
if Assigned(Columns) then begin
|
|
||||||
Columns.Clear;
|
|
||||||
rx := TRegExpr.Create;
|
|
||||||
rx.Expression := '^(\w+)(\((.+)\))?';
|
|
||||||
if DbName <> '' then
|
|
||||||
DbAndViewName := MainForm.mask(DbName)+'.';
|
|
||||||
DbAndViewName := DbAndViewName + MainForm.mask(ViewName);
|
|
||||||
Results := MainForm.ActiveConnection.GetResults('SHOW /*!32332 FULL */ COLUMNS FROM '+DbAndViewName);
|
|
||||||
while not Results.Eof do begin
|
|
||||||
Col := TTableColumn.Create;
|
|
||||||
Columns.Add(Col);
|
|
||||||
Col.Name := Results.Col('Field');
|
|
||||||
Col.AllowNull := Results.Col('Null') = 'YES';
|
|
||||||
if rx.Exec(Results.Col('Type')) then begin
|
|
||||||
Col.DataType := GetDatatypeByName(rx.Match[1]);
|
|
||||||
Col.LengthSet := rx.Match[3];
|
|
||||||
end;
|
|
||||||
Col.Unsigned := (Col.DataType.Category = dtcInteger) and (Pos('unsigned', Results.Col('Type')) > 0);
|
|
||||||
Col.AllowNull := UpperCase(Results.Col('Null')) = 'YES';
|
|
||||||
Col.Collation := Results.Col('Collation', True);
|
|
||||||
Col.Comment := Results.Col('Comment', True);
|
|
||||||
if Col.DataType.Category <> dtcTemporal then
|
|
||||||
Col.DefaultText := Results.Col('Default');
|
|
||||||
if Results.IsNull('Default') and Col.AllowNull then
|
|
||||||
Col.DefaultType := cdtNull
|
|
||||||
else if Col.DataType.Index = dtTimestamp then
|
|
||||||
Col.DefaultType := cdtCurTSUpdateTS
|
|
||||||
else if (Col.DefaultText = '') and Col.AllowNull then
|
|
||||||
Col.DefaultType := cdtNothing
|
|
||||||
else
|
|
||||||
Col.DefaultType := cdtText;
|
|
||||||
Results.Next;
|
|
||||||
end;
|
|
||||||
rx.Free;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
procedure ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
|
||||||
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
|
||||||
var
|
|
||||||
Params: String;
|
|
||||||
ParenthesesCount: Integer;
|
|
||||||
rx: TRegExpr;
|
|
||||||
i: Integer;
|
|
||||||
Param: TRoutineParam;
|
|
||||||
begin
|
|
||||||
// Parse CREATE code of stored function or procedure to detect parameters
|
|
||||||
rx := TRegExpr.Create;
|
|
||||||
rx.ModifierI := True;
|
|
||||||
rx.ModifierG := True;
|
|
||||||
// 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)
|
|
||||||
ParenthesesCount := 0;
|
|
||||||
Params := '';
|
|
||||||
for i:=1 to Length(CreateCode) do begin
|
|
||||||
if CreateCode[i] = ')' then begin
|
|
||||||
Dec(ParenthesesCount);
|
|
||||||
if ParenthesesCount = 0 then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
if ParenthesesCount >= 1 then
|
|
||||||
Params := Params + CreateCode[i];
|
|
||||||
if CreateCode[i] = '(' then
|
|
||||||
Inc(ParenthesesCount);
|
|
||||||
end;
|
|
||||||
rx.Expression := '(^|,)\s*((IN|OUT|INOUT)\s+)?(\S+)\s+([^\s,\(]+(\([^\)]*\))?[^,]*)';
|
|
||||||
if rx.Exec(Params) then while true do begin
|
|
||||||
Param := TRoutineParam.Create;
|
|
||||||
Param.Context := UpperCase(rx.Match[3]);
|
|
||||||
if Param.Context = '' then
|
|
||||||
Param.Context := 'IN';
|
|
||||||
Param.Name := WideDequotedStr(rx.Match[4], '`');
|
|
||||||
Param.Datatype := rx.Match[5];
|
|
||||||
Parameters.Add(Param);
|
|
||||||
if not rx.ExecNext then
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Cut left part including parameters, so it's easier to parse the rest
|
|
||||||
CreateCode := Copy(CreateCode, i+1, MaxInt);
|
|
||||||
// CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body
|
|
||||||
// CREATE FUNCTION sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body
|
|
||||||
// LANGUAGE SQL
|
|
||||||
// | [NOT] DETERMINISTIC // IS_DETERMINISTIC
|
|
||||||
// | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } // DATA_ACCESS
|
|
||||||
// | SQL SECURITY { DEFINER | INVOKER } // SECURITY_TYPE
|
|
||||||
// | COMMENT 'string' // COMMENT
|
|
||||||
|
|
||||||
rx.Expression := '\bLANGUAGE SQL\b';
|
|
||||||
if rx.Exec(CreateCode) then
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
rx.Expression := '\bRETURNS\s+(\w+(\([^\)]*\))?(\s+UNSIGNED)?)';
|
|
||||||
if rx.Exec(CreateCode) then begin
|
|
||||||
Returns := rx.Match[1];
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\b(NOT\s+)?DETERMINISTIC\b';
|
|
||||||
if rx.Exec(CreateCode) then begin
|
|
||||||
Deterministic := rx.MatchLen[1] = -1;
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\b(CONTAINS SQL|NO SQL|READS SQL DATA|MODIFIES SQL DATA)\b';
|
|
||||||
if rx.Exec(CreateCode) then begin
|
|
||||||
DataAccess := rx.Match[1];
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.Expression := '\bSQL\s+SECURITY\s+(DEFINER|INVOKER)\b';
|
|
||||||
if rx.Exec(CreateCode) then begin
|
|
||||||
Security := rx.Match[1];
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
|
||||||
end;
|
|
||||||
rx.ModifierG := False;
|
|
||||||
rx.Expression := '\bCOMMENT\s+''((.+)[^''])''[^'']';
|
|
||||||
if rx.Exec(CreateCode) then begin
|
|
||||||
Comment := StringReplace(rx.Match[1], '''''', '''', [rfReplaceAll]);
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
|
||||||
end;
|
|
||||||
rx.Expression := '^\s*CHARSET\s+[\w\d]+\s';
|
|
||||||
if rx.Exec(CreateCode) then
|
|
||||||
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
|
||||||
// Tata, remaining code is the routine body
|
|
||||||
Body := TrimLeft(CreateCode);
|
|
||||||
|
|
||||||
rx.Free;
|
|
||||||
end;
|
|
||||||
|
|
||||||
|
|
||||||
function ReformatSQL(SQL: String): String;
|
function ReformatSQL(SQL: String): String;
|
||||||
var
|
var
|
||||||
AllKeywords, ImportantKeywords, PairKeywords: TStringList;
|
AllKeywords, ImportantKeywords, PairKeywords: TStringList;
|
||||||
|
@ -234,8 +234,8 @@ begin
|
|||||||
for Obj in DBObjects do begin
|
for Obj in DBObjects do begin
|
||||||
if (Obj.Database=comboDatabase.Text) and (Obj.Name=comboTable.Text) then begin
|
if (Obj.Database=comboDatabase.Text) and (Obj.Name=comboTable.Text) then begin
|
||||||
case Obj.NodeType of
|
case Obj.NodeType of
|
||||||
lntTable: ParseTableStructure(Obj.CreateCode, Columns, nil, nil);
|
lntTable: Obj.Connection.ParseTableStructure(Obj.CreateCode, Columns, nil, nil);
|
||||||
lntView: ParseViewStructure(Obj.CreateCode, Obj.Name, Columns, Algorithm, CheckOption, SelectCode);
|
lntView: Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, Columns, Algorithm, CheckOption, SelectCode);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
@ -2968,7 +2968,7 @@ begin
|
|||||||
lntFunction: Query := 'SELECT ';
|
lntFunction: Query := 'SELECT ';
|
||||||
end;
|
end;
|
||||||
Parameters := TRoutineParamList.Create;
|
Parameters := TRoutineParamList.Create;
|
||||||
ParseRoutineStructure(Obj.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
Obj.Connection.ParseRoutineStructure(Obj.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
||||||
Query := Query + mask(Obj.Name);
|
Query := Query + mask(Obj.Name);
|
||||||
ParamInput := '';
|
ParamInput := '';
|
||||||
for i:=0 to Parameters.Count-1 do begin
|
for i:=0 to Parameters.Count-1 do begin
|
||||||
@ -4664,7 +4664,7 @@ begin
|
|||||||
for DbObj in DbObjects do begin
|
for DbObj in DbObjects do begin
|
||||||
if (CompareText(DbObj.Name, Identifier)=0) and (DbObj.NodeType in [lntFunction, lntProcedure]) then begin
|
if (CompareText(DbObj.Name, Identifier)=0) and (DbObj.NodeType in [lntFunction, lntProcedure]) then begin
|
||||||
Params := TRoutineParamList.Create(True);
|
Params := TRoutineParamList.Create(True);
|
||||||
ParseRoutineStructure(DbObj.CreateCode, Params, DummyBool, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr);
|
DbObj.Connection.ParseRoutineStructure(DbObj.CreateCode, Params, DummyBool, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr);
|
||||||
ItemText := '';
|
ItemText := '';
|
||||||
for i:=0 to Params.Count-1 do
|
for i:=0 to Params.Count-1 do
|
||||||
ItemText := ItemText + '"' + Params[i].Name + ': ' + Params[i].Datatype + '", ';
|
ItemText := ItemText + '"' + Params[i].Name + ': ' + Params[i].Datatype + '", ';
|
||||||
@ -6542,7 +6542,7 @@ begin
|
|||||||
lntTable, lntView:
|
lntTable, lntView:
|
||||||
if Assigned(SelectDBObjectForm) and (Sender=SelectDBObjectForm.TreeDBO) then begin
|
if Assigned(SelectDBObjectForm) and (Sender=SelectDBObjectForm.TreeDBO) then begin
|
||||||
Columns := TTableColumnList.Create(True);
|
Columns := TTableColumnList.Create(True);
|
||||||
ParseTableStructure(DBObj.CreateCode, Columns, nil, nil);
|
DBObj.Connection.ParseTableStructure(DBObj.CreateCode, Columns, nil, nil);
|
||||||
ChildCount := Columns.Count;
|
ChildCount := Columns.Count;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -6585,7 +6585,7 @@ begin
|
|||||||
Item.NodeType := lntColumn;
|
Item.NodeType := lntColumn;
|
||||||
ParentItem := Sender.GetNodeData(Node.Parent);
|
ParentItem := Sender.GetNodeData(Node.Parent);
|
||||||
Columns := TTableColumnList.Create(True);
|
Columns := TTableColumnList.Create(True);
|
||||||
ParseTableStructure(ParentItem.CreateCode, Columns, nil, nil);
|
ParentItem.Connection.ParseTableStructure(ParentItem.CreateCode, Columns, nil, nil);
|
||||||
Item.Name := Columns[Node.Index].Name;
|
Item.Name := Columns[Node.Index].Name;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -6760,9 +6760,9 @@ begin
|
|||||||
try
|
try
|
||||||
case ActiveDbObj.NodeType of
|
case ActiveDbObj.NodeType of
|
||||||
lntTable:
|
lntTable:
|
||||||
ParseTableStructure(ActiveDbObj.CreateCode, SelectedTableColumns, SelectedTableKeys, SelectedTableForeignKeys);
|
ActiveConnection.ParseTableStructure(ActiveDbObj.CreateCode, SelectedTableColumns, SelectedTableKeys, SelectedTableForeignKeys);
|
||||||
lntView:
|
lntView:
|
||||||
ParseViewStructure(ActiveDbObj.CreateCode, ActiveDbObj.Name, SelectedTableColumns, Algorithm, CheckOption, SelectCode);
|
ActiveConnection.ParseViewStructure(ActiveDbObj.CreateCode, ActiveDbObj.Name, SelectedTableColumns, Algorithm, CheckOption, SelectCode);
|
||||||
end;
|
end;
|
||||||
except on E:EDatabaseError do
|
except on E:EDatabaseError do
|
||||||
MessageDlg(E.Message, mtError, [mbOK], 0);
|
MessageDlg(E.Message, mtError, [mbOK], 0);
|
||||||
@ -7314,7 +7314,7 @@ begin
|
|||||||
Columns := TTableColumnList.Create;
|
Columns := TTableColumnList.Create;
|
||||||
Keys := nil;
|
Keys := nil;
|
||||||
ForeignKeys := nil;
|
ForeignKeys := nil;
|
||||||
ParseTableStructure(CreateTable, Columns, Keys, ForeignKeys);
|
ActiveConnection.ParseTableStructure(CreateTable, Columns, Keys, ForeignKeys);
|
||||||
TextCol := '';
|
TextCol := '';
|
||||||
for TblColumn in Columns do begin
|
for TblColumn in Columns do begin
|
||||||
if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin
|
if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin
|
||||||
|
@ -66,7 +66,7 @@ type
|
|||||||
Unsigned, AllowNull, ZeroFill, LengthCustomized: Boolean;
|
Unsigned, AllowNull, ZeroFill, LengthCustomized: Boolean;
|
||||||
DefaultType: TColumnDefaultType;
|
DefaultType: TColumnDefaultType;
|
||||||
DefaultText: String;
|
DefaultText: String;
|
||||||
Comment, Collation: String;
|
Comment, Charset, Collation: String;
|
||||||
FStatus: TEditingStatus;
|
FStatus: TEditingStatus;
|
||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
@ -273,6 +273,10 @@ type
|
|||||||
function GetLastResults: TMySQLQueryList;
|
function GetLastResults: TMySQLQueryList;
|
||||||
procedure ClearDbObjects(db: String);
|
procedure ClearDbObjects(db: String);
|
||||||
procedure ClearAllDbObjects;
|
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 ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
||||||
|
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
||||||
property SessionName: String read FSessionName write FSessionName;
|
property SessionName: String read FSessionName write FSessionName;
|
||||||
property Parameters: TConnectionParameters read FParameters write FParameters;
|
property Parameters: TConnectionParameters read FParameters write FParameters;
|
||||||
property ThreadId: Cardinal read GetThreadId;
|
property ThreadId: Cardinal read GetThreadId;
|
||||||
@ -1579,6 +1583,402 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMySQLConnection.ParseTableStructure(CreateTable: String; Columns: TTableColumnList; Keys: TTableKeyList; ForeignKeys: TForeignKeyList);
|
||||||
|
var
|
||||||
|
ColSpec: String;
|
||||||
|
rx, rxCol: TRegExpr;
|
||||||
|
i: Integer;
|
||||||
|
InLiteral: Boolean;
|
||||||
|
Col: TTableColumn;
|
||||||
|
Key: TTableKey;
|
||||||
|
ForeignKey: TForeignKey;
|
||||||
|
Collations: TMySQLQuery;
|
||||||
|
begin
|
||||||
|
if Assigned(Columns) then Columns.Clear;
|
||||||
|
if Assigned(Keys) then Keys.Clear;
|
||||||
|
if Assigned(ForeignKeys) then ForeignKeys.Clear;
|
||||||
|
if CreateTable = '' then
|
||||||
|
Exit;
|
||||||
|
rx := TRegExpr.Create;
|
||||||
|
rx.ModifierS := False;
|
||||||
|
rx.ModifierM := True;
|
||||||
|
rx.Expression := '^\s+[`"]([^`"]+)[`"]\s(\w+)';
|
||||||
|
rxCol := TRegExpr.Create;
|
||||||
|
rxCol.ModifierI := True;
|
||||||
|
if rx.Exec(CreateTable) then while true do begin
|
||||||
|
if not Assigned(Columns) then
|
||||||
|
break;
|
||||||
|
ColSpec := '';
|
||||||
|
for i:=rx.MatchPos[2]+rx.MatchLen[2] to Length(CreateTable) do begin
|
||||||
|
if CharInSet(CreateTable[i], [#13, #10]) then
|
||||||
|
break;
|
||||||
|
ColSpec := ColSpec + CreateTable[i];
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Strip trailing comma
|
||||||
|
if (ColSpec <> '') and (ColSpec[Length(ColSpec)] = ',') then
|
||||||
|
Delete(ColSpec, Length(ColSpec), 1);
|
||||||
|
|
||||||
|
Col := TTableColumn.Create;
|
||||||
|
Columns.Add(Col);
|
||||||
|
Col.Name := rx.Match[1];
|
||||||
|
Col.OldName := Col.Name;
|
||||||
|
Col.Status := esUntouched;
|
||||||
|
Col.LengthCustomized := True;
|
||||||
|
|
||||||
|
// Datatype
|
||||||
|
Col.DataType := GetDatatypeByName(UpperCase(rx.Match[2]));
|
||||||
|
|
||||||
|
// Length / Set
|
||||||
|
// Various datatypes, e.g. BLOBs, don't have any length property
|
||||||
|
InLiteral := False;
|
||||||
|
if (ColSpec <> '') and (ColSpec[1] = '(') then begin
|
||||||
|
for i:=2 to Length(ColSpec) do begin
|
||||||
|
if (ColSpec[i] = ')') and (not InLiteral) then
|
||||||
|
break;
|
||||||
|
if ColSpec[i] = '''' then
|
||||||
|
InLiteral := not InLiteral;
|
||||||
|
end;
|
||||||
|
Col.LengthSet := Copy(ColSpec, 2, i-2);
|
||||||
|
Delete(ColSpec, 1, i);
|
||||||
|
end;
|
||||||
|
ColSpec := Trim(ColSpec);
|
||||||
|
|
||||||
|
// Unsigned
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 8)) = 'UNSIGNED' then begin
|
||||||
|
Col.Unsigned := True;
|
||||||
|
Delete(ColSpec, 1, 9);
|
||||||
|
end else
|
||||||
|
Col.Unsigned := False;
|
||||||
|
|
||||||
|
// Zero fill
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 8)) = 'ZEROFILL' then begin
|
||||||
|
Col.ZeroFill := True;
|
||||||
|
Delete(ColSpec, 1, 9);
|
||||||
|
end else
|
||||||
|
Col.ZeroFill := False;
|
||||||
|
|
||||||
|
// Charset
|
||||||
|
rxCol.Expression := '^CHARACTER SET (\w+)\b\s*';
|
||||||
|
if rxCol.Exec(ColSpec) then begin
|
||||||
|
Col.Charset := rxCol.Match[1];
|
||||||
|
Delete(ColSpec, 1, rxCol.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Collation - probably not present when charset present
|
||||||
|
rxCol.Expression := '^COLLATE (\w+)\b\s*';
|
||||||
|
if rxCol.Exec(ColSpec) then begin
|
||||||
|
Col.Collation := rxCol.Match[1];
|
||||||
|
Delete(ColSpec, 1, rxCol.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
if Col.Collation = '' then begin
|
||||||
|
Collations := CollationTable;
|
||||||
|
if Assigned(Collations) then while not Collations.Eof do begin
|
||||||
|
if (Collations.Col('Charset') = Col.Charset) and (Collations.Col('Default') = 'Yes') then begin
|
||||||
|
Col.Collation := Collations.Col('Collation');
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
Collations.Next;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Allow NULL
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 8)) = 'NOT NULL' then begin
|
||||||
|
Col.AllowNull := False;
|
||||||
|
Delete(ColSpec, 1, 9);
|
||||||
|
end else begin
|
||||||
|
Col.AllowNull := True;
|
||||||
|
// Sporadically there is a "NULL" found at this position.
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then
|
||||||
|
Delete(ColSpec, 1, 5);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Default value
|
||||||
|
Col.DefaultType := cdtNothing;
|
||||||
|
Col.DefaultText := '';
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 14)) = 'AUTO_INCREMENT' then begin
|
||||||
|
Col.DefaultType := cdtAutoInc;
|
||||||
|
Col.DefaultText := 'AUTO_INCREMENT';
|
||||||
|
Delete(ColSpec, 1, 15);
|
||||||
|
end else if UpperCase(Copy(ColSpec, 1, 8)) = 'DEFAULT ' then begin
|
||||||
|
Delete(ColSpec, 1, 8);
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then begin
|
||||||
|
Col.DefaultType := cdtNull;
|
||||||
|
Col.DefaultText := 'NULL';
|
||||||
|
Delete(ColSpec, 1, 5);
|
||||||
|
end else if UpperCase(Copy(ColSpec, 1, 17)) = 'CURRENT_TIMESTAMP' then begin
|
||||||
|
Col.DefaultType := cdtCurTS;
|
||||||
|
Col.DefaultText := 'CURRENT_TIMESTAMP';
|
||||||
|
Delete(ColSpec, 1, 18);
|
||||||
|
end else if ColSpec[1] = '''' then begin
|
||||||
|
InLiteral := True;
|
||||||
|
for i:=2 to Length(ColSpec) do begin
|
||||||
|
if ColSpec[i] = '''' then
|
||||||
|
InLiteral := not InLiteral
|
||||||
|
else if not InLiteral then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
Col.DefaultType := cdtText;
|
||||||
|
Col.DefaultText := Copy(ColSpec, 2, i-3);
|
||||||
|
// A single quote gets escaped by single quote - remove the escape char - escaping is done in Save action afterwards
|
||||||
|
Col.DefaultText := StringReplace(Col.DefaultText, '''''', '''', [rfReplaceAll]);
|
||||||
|
Delete(ColSpec, 1, i);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if UpperCase(Copy(ColSpec, 1, 27)) = 'ON UPDATE CURRENT_TIMESTAMP' then begin
|
||||||
|
// Adjust default type
|
||||||
|
case Col.DefaultType of
|
||||||
|
cdtText: Col.DefaultType := cdtTextUpdateTS;
|
||||||
|
cdtNull: Col.DefaultType := cdtNullUpdateTS;
|
||||||
|
cdtCurTS: Col.DefaultType := cdtCurTSUpdateTS;
|
||||||
|
end;
|
||||||
|
Delete(ColSpec, 1, 28);
|
||||||
|
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;
|
||||||
|
|
||||||
|
if not rx.ExecNext then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Detect keys
|
||||||
|
// PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`), KEY `id_2` (`id`) USING BTREE,
|
||||||
|
// KEY `Text` (`Text`(100)), FULLTEXT KEY `Email` (`Email`,`Text`)
|
||||||
|
rx.Expression := '^\s+((\w+)\s+)?KEY\s+([`"]?([^`"]+)[`"]?\s+)?\((.+)\)(\s+USING\s+(\w+))?,?$';
|
||||||
|
if rx.Exec(CreateTable) then while true do begin
|
||||||
|
if not Assigned(Keys) then
|
||||||
|
break;
|
||||||
|
Key := TTableKey.Create;
|
||||||
|
Keys.Add(Key);
|
||||||
|
Key.Name := rx.Match[4];
|
||||||
|
if Key.Name = '' then Key.Name := rx.Match[2]; // PRIMARY
|
||||||
|
Key.OldName := Key.Name;
|
||||||
|
Key.IndexType := rx.Match[2];
|
||||||
|
Key.OldIndexType := Key.IndexType;
|
||||||
|
Key.Algorithm := rx.Match[7];
|
||||||
|
if Key.IndexType = '' then Key.IndexType := 'KEY'; // KEY
|
||||||
|
Key.Columns := Explode(',', rx.Match[5]);
|
||||||
|
for i:=0 to Key.Columns.Count-1 do begin
|
||||||
|
rxCol.Expression := '^[`"]?([^`"]+)[`"]?(\((\d+)\))?$';
|
||||||
|
if rxCol.Exec(Key.Columns[i]) then begin
|
||||||
|
Key.Columns[i] := rxCol.Match[1];
|
||||||
|
Key.SubParts.Add(rxCol.Match[3]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if not rx.ExecNext then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Detect foreign keys
|
||||||
|
// CONSTRAINT `FK1` FOREIGN KEY (`which`) REFERENCES `fk1` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
rx.Expression := '\s+CONSTRAINT\s+[`"]([^`"]+)[`"]\sFOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+[`"]([^\(]+)[`"]\s\(([^\)]+)\)(\s+ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?(\s+ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?';
|
||||||
|
if rx.Exec(CreateTable) then while true do begin
|
||||||
|
if not Assigned(ForeignKeys) then
|
||||||
|
break;
|
||||||
|
ForeignKey := TForeignKey.Create;
|
||||||
|
ForeignKeys.Add(ForeignKey);
|
||||||
|
ForeignKey.KeyName := rx.Match[1];
|
||||||
|
ForeignKey.OldKeyName := ForeignKey.KeyName;
|
||||||
|
ForeignKey.KeyNameWasCustomized := True;
|
||||||
|
ForeignKey.ReferenceTable := StringReplace(rx.Match[3], '`', '', [rfReplaceAll]);
|
||||||
|
ForeignKey.ReferenceTable := StringReplace(ForeignKey.ReferenceTable, '"', '', [rfReplaceAll]);
|
||||||
|
ExplodeQuotedList(rx.Match[2], ForeignKey.Columns);
|
||||||
|
ExplodeQuotedList(rx.Match[4], ForeignKey.ForeignColumns);
|
||||||
|
if rx.Match[6] <> '' then
|
||||||
|
ForeignKey.OnDelete := rx.Match[6];
|
||||||
|
if rx.Match[8] <> '' then
|
||||||
|
ForeignKey.OnUpdate := rx.Match[8];
|
||||||
|
if not rx.ExecNext then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
FreeAndNil(rxCol);
|
||||||
|
FreeAndNil(rx);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMySQLConnection.ParseViewStructure(CreateCode, ViewName: String; Columns: TTableColumnList; var Algorithm, CheckOption, SelectCode: String);
|
||||||
|
var
|
||||||
|
rx: TRegExpr;
|
||||||
|
Col: TTableColumn;
|
||||||
|
Results: TMySQLQuery;
|
||||||
|
DbName, DbAndViewName: String;
|
||||||
|
begin
|
||||||
|
if CreateCode <> '' then begin
|
||||||
|
// CREATE
|
||||||
|
// [OR REPLACE]
|
||||||
|
// [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
|
||||||
|
// [DEFINER = { user | CURRENT_USER }]
|
||||||
|
// [SQL SECURITY { DEFINER | INVOKER }]
|
||||||
|
// VIEW view_name [(column_list)]
|
||||||
|
// AS select_statement
|
||||||
|
// [WITH [CASCADED | LOCAL] CHECK OPTION]
|
||||||
|
rx := TRegExpr.Create;
|
||||||
|
rx.ModifierG := False;
|
||||||
|
rx.ModifierI := True;
|
||||||
|
rx.Expression := '^CREATE\s+(OR\s+REPLACE\s+)?'+
|
||||||
|
'(ALGORITHM\s*=\s*(\w+)\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];
|
||||||
|
// 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];
|
||||||
|
end else
|
||||||
|
raise Exception.Create('Regular expression did not match the VIEW code in ParseViewStructure(): '+CRLF+CRLF+CreateCode);
|
||||||
|
rx.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Views reveal their columns only with a SHOW COLUMNS query.
|
||||||
|
// No keys available in views - SHOW KEYS always returns an empty result
|
||||||
|
if Assigned(Columns) then begin
|
||||||
|
Columns.Clear;
|
||||||
|
rx := TRegExpr.Create;
|
||||||
|
rx.Expression := '^(\w+)(\((.+)\))?';
|
||||||
|
if DbName <> '' then
|
||||||
|
DbAndViewName := EscapeString(DbName)+'.';
|
||||||
|
DbAndViewName := DbAndViewName + EscapeString(ViewName);
|
||||||
|
Results := GetResults('SHOW /*!32332 FULL */ COLUMNS FROM '+DbAndViewName);
|
||||||
|
while not Results.Eof do begin
|
||||||
|
Col := TTableColumn.Create;
|
||||||
|
Columns.Add(Col);
|
||||||
|
Col.Name := Results.Col('Field');
|
||||||
|
Col.AllowNull := Results.Col('Null') = 'YES';
|
||||||
|
if rx.Exec(Results.Col('Type')) then begin
|
||||||
|
Col.DataType := GetDatatypeByName(rx.Match[1]);
|
||||||
|
Col.LengthSet := rx.Match[3];
|
||||||
|
end;
|
||||||
|
Col.Unsigned := (Col.DataType.Category = dtcInteger) and (Pos('unsigned', Results.Col('Type')) > 0);
|
||||||
|
Col.AllowNull := UpperCase(Results.Col('Null')) = 'YES';
|
||||||
|
Col.Collation := Results.Col('Collation', True);
|
||||||
|
Col.Comment := Results.Col('Comment', True);
|
||||||
|
if Col.DataType.Category <> dtcTemporal then
|
||||||
|
Col.DefaultText := Results.Col('Default');
|
||||||
|
if Results.IsNull('Default') and Col.AllowNull then
|
||||||
|
Col.DefaultType := cdtNull
|
||||||
|
else if Col.DataType.Index = dtTimestamp then
|
||||||
|
Col.DefaultType := cdtCurTSUpdateTS
|
||||||
|
else if (Col.DefaultText = '') and Col.AllowNull then
|
||||||
|
Col.DefaultType := cdtNothing
|
||||||
|
else
|
||||||
|
Col.DefaultType := cdtText;
|
||||||
|
Results.Next;
|
||||||
|
end;
|
||||||
|
rx.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure TMySQLConnection.ParseRoutineStructure(CreateCode: String; Parameters: TRoutineParamList;
|
||||||
|
var Deterministic: Boolean; var Returns, DataAccess, Security, Comment, Body: String);
|
||||||
|
var
|
||||||
|
Params: String;
|
||||||
|
ParenthesesCount: Integer;
|
||||||
|
rx: TRegExpr;
|
||||||
|
i: Integer;
|
||||||
|
Param: TRoutineParam;
|
||||||
|
begin
|
||||||
|
// Parse CREATE code of stored function or procedure to detect parameters
|
||||||
|
rx := TRegExpr.Create;
|
||||||
|
rx.ModifierI := True;
|
||||||
|
rx.ModifierG := True;
|
||||||
|
// 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)
|
||||||
|
ParenthesesCount := 0;
|
||||||
|
Params := '';
|
||||||
|
for i:=1 to Length(CreateCode) do begin
|
||||||
|
if CreateCode[i] = ')' then begin
|
||||||
|
Dec(ParenthesesCount);
|
||||||
|
if ParenthesesCount = 0 then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
if ParenthesesCount >= 1 then
|
||||||
|
Params := Params + CreateCode[i];
|
||||||
|
if CreateCode[i] = '(' then
|
||||||
|
Inc(ParenthesesCount);
|
||||||
|
end;
|
||||||
|
rx.Expression := '(^|,)\s*((IN|OUT|INOUT)\s+)?(\S+)\s+([^\s,\(]+(\([^\)]*\))?[^,]*)';
|
||||||
|
if rx.Exec(Params) then while true do begin
|
||||||
|
Param := TRoutineParam.Create;
|
||||||
|
Param.Context := UpperCase(rx.Match[3]);
|
||||||
|
if Param.Context = '' then
|
||||||
|
Param.Context := 'IN';
|
||||||
|
Param.Name := DeQuoteIdent(rx.Match[4]);
|
||||||
|
Param.Datatype := rx.Match[5];
|
||||||
|
Parameters.Add(Param);
|
||||||
|
if not rx.ExecNext then
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Cut left part including parameters, so it's easier to parse the rest
|
||||||
|
CreateCode := Copy(CreateCode, i+1, MaxInt);
|
||||||
|
// CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] routine_body
|
||||||
|
// CREATE FUNCTION sp_name ([func_parameter[,...]]) RETURNS type [characteristic ...] routine_body
|
||||||
|
// LANGUAGE SQL
|
||||||
|
// | [NOT] DETERMINISTIC // IS_DETERMINISTIC
|
||||||
|
// | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } // DATA_ACCESS
|
||||||
|
// | SQL SECURITY { DEFINER | INVOKER } // SECURITY_TYPE
|
||||||
|
// | COMMENT 'string' // COMMENT
|
||||||
|
|
||||||
|
rx.Expression := '\bLANGUAGE SQL\b';
|
||||||
|
if rx.Exec(CreateCode) then
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
rx.Expression := '\bRETURNS\s+(\w+(\([^\)]*\))?(\s+UNSIGNED)?)';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Returns := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\b(NOT\s+)?DETERMINISTIC\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Deterministic := rx.MatchLen[1] = -1;
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\b(CONTAINS SQL|NO SQL|READS SQL DATA|MODIFIES SQL DATA)\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
DataAccess := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.Expression := '\bSQL\s+SECURITY\s+(DEFINER|INVOKER)\b';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Security := rx.Match[1];
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]);
|
||||||
|
end;
|
||||||
|
rx.ModifierG := False;
|
||||||
|
rx.Expression := '\bCOMMENT\s+''((.+)[^''])''[^'']';
|
||||||
|
if rx.Exec(CreateCode) then begin
|
||||||
|
Comment := StringReplace(rx.Match[1], '''''', '''', [rfReplaceAll]);
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
||||||
|
end;
|
||||||
|
rx.Expression := '^\s*CHARSET\s+[\w\d]+\s';
|
||||||
|
if rx.Exec(CreateCode) then
|
||||||
|
Delete(CreateCode, rx.MatchPos[0], rx.MatchLen[0]-1);
|
||||||
|
// Tata, remaining code is the routine body
|
||||||
|
Body := TrimLeft(CreateCode);
|
||||||
|
|
||||||
|
rx.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{ TMySQLQuery }
|
{ TMySQLQuery }
|
||||||
|
|
||||||
@ -1957,9 +2357,9 @@ begin
|
|||||||
FForeignKeys := TForeignKeyList.Create;
|
FForeignKeys := TForeignKeyList.Create;
|
||||||
// This is probably a VIEW, so column names need to be fetched differently
|
// This is probably a VIEW, so column names need to be fetched differently
|
||||||
if UpperCase(Res.ColumnNames[0]) = 'TABLE' then
|
if UpperCase(Res.ColumnNames[0]) = 'TABLE' then
|
||||||
ParseTableStructure(CreateCode, FColumns, FKeys, FForeignKeys)
|
Connection.ParseTableStructure(CreateCode, FColumns, FKeys, FForeignKeys)
|
||||||
else
|
else
|
||||||
ParseViewStructure(CreateCode, TableName, FColumns, Algorithm, CheckOption, SelectCode);
|
Connection.ParseViewStructure(CreateCode, TableName, FColumns, Algorithm, CheckOption, SelectCode);
|
||||||
FreeAndNil(Res);
|
FreeAndNil(Res);
|
||||||
FreeAndNil(FUpdateData);
|
FreeAndNil(FUpdateData);
|
||||||
FUpdateData := TUpdateData.Create(True);
|
FUpdateData := TUpdateData.Create(True);
|
||||||
|
@ -147,7 +147,7 @@ begin
|
|||||||
if DBObject.Name <> '' then begin
|
if DBObject.Name <> '' then begin
|
||||||
// Editing existing routine
|
// Editing existing routine
|
||||||
comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b');
|
comboType.ItemIndex := ListIndexByRegExpr(comboType.Items, '^'+FAlterRoutineType+'\b');
|
||||||
ParseRoutineStructure(DBObject.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
DBObject.Connection.ParseRoutineStructure(DBObject.CreateCode, Parameters, Deterministic, Returns, DataAccess, Security, Comment, Body);
|
||||||
comboReturns.Text := Returns;
|
comboReturns.Text := Returns;
|
||||||
chkDeterministic.Checked := Deterministic;
|
chkDeterministic.Checked := Deterministic;
|
||||||
if DataAccess <> '' then
|
if DataAccess <> '' then
|
||||||
|
@ -326,7 +326,7 @@ begin
|
|||||||
memoComment.Lines.Text := MainForm.ActiveConnection.UnescapeString(rx.Match[1])
|
memoComment.Lines.Text := MainForm.ActiveConnection.UnescapeString(rx.Match[1])
|
||||||
else
|
else
|
||||||
memoComment.Lines.Clear;
|
memoComment.Lines.Clear;
|
||||||
ParseTableStructure(DBObject.CreateCode, FColumns, FKeys, FForeignKeys);
|
DBObject.Connection.ParseTableStructure(DBObject.CreateCode, FColumns, FKeys, FForeignKeys);
|
||||||
end;
|
end;
|
||||||
listColumns.RootNodeCount := FColumns.Count;
|
listColumns.RootNodeCount := FColumns.Count;
|
||||||
DeInitializeVTNodes(listColumns);
|
DeInitializeVTNodes(listColumns);
|
||||||
@ -2147,7 +2147,7 @@ var
|
|||||||
Col: TTableColumn;
|
Col: TTableColumn;
|
||||||
begin
|
begin
|
||||||
Columns := TTableColumnList.Create(False);
|
Columns := TTableColumnList.Create(False);
|
||||||
ParseTableStructure(Clipboard.AsText, Columns, nil, nil);
|
DBObject.Connection.ParseTableStructure(Clipboard.AsText, Columns, nil, nil);
|
||||||
Node := listColumns.FocusedNode;
|
Node := listColumns.FocusedNode;
|
||||||
if not Assigned(Node) then
|
if not Assigned(Node) then
|
||||||
Node := listColumns.GetLast;
|
Node := listColumns.GetLast;
|
||||||
|
@ -556,8 +556,8 @@ begin
|
|||||||
ResultSQL := '';
|
ResultSQL := '';
|
||||||
Columns := TTableColumnList.Create(True);
|
Columns := TTableColumnList.Create(True);
|
||||||
case DBObj.NodeType of
|
case DBObj.NodeType of
|
||||||
lntTable: ParseTableStructure(DBObj.CreateCode, Columns, nil, nil);
|
lntTable: DBObj.Connection.ParseTableStructure(DBObj.CreateCode, Columns, nil, nil);
|
||||||
lntView: ParseViewStructure(DBObj.CreateCode, DBObj.Name, Columns, Dummy, Dummy, Dummy);
|
lntView: DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, Columns, Dummy, Dummy, Dummy);
|
||||||
else AddNotes(DBObj.Database, DBObj.Name, STRSKIPPED+'a '+LowerCase(DBObj.ObjType)+' does not contain rows.', '');
|
else AddNotes(DBObj.Database, DBObj.Name, STRSKIPPED+'a '+LowerCase(DBObj.ObjType)+' does not contain rows.', '');
|
||||||
end;
|
end;
|
||||||
if Columns.Count > 0 then begin
|
if Columns.Count > 0 then begin
|
||||||
@ -1109,7 +1109,7 @@ begin
|
|||||||
if not FSecondExportPass then begin
|
if not FSecondExportPass then begin
|
||||||
// Create temporary VIEW replacement
|
// Create temporary VIEW replacement
|
||||||
ColumnList := TTableColumnList.Create(True);
|
ColumnList := TTableColumnList.Create(True);
|
||||||
ParseViewStructure(DBObj.CreateCode, DBObj.Name, ColumnList, Dummy, Dummy, Dummy);
|
DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj.Name, ColumnList, Dummy, Dummy, Dummy);
|
||||||
Struc := '# Creating temporary table to overcome VIEW dependency errors'+CRLF+
|
Struc := '# Creating temporary table to overcome VIEW dependency errors'+CRLF+
|
||||||
'CREATE TABLE ';
|
'CREATE TABLE ';
|
||||||
if ToDb then
|
if ToDb then
|
||||||
|
@ -66,7 +66,7 @@ begin
|
|||||||
if Obj.Name <> '' then begin
|
if Obj.Name <> '' then begin
|
||||||
// Edit mode
|
// Edit mode
|
||||||
editName.Text := Obj.Name;
|
editName.Text := Obj.Name;
|
||||||
ParseViewStructure(Obj.CreateCode, Obj.Name, nil, Algorithm, CheckOption, SelectCode);
|
Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj.Name, nil, Algorithm, CheckOption, SelectCode);
|
||||||
rgAlgorithm.ItemIndex := rgAlgorithm.Items.IndexOf(Algorithm);
|
rgAlgorithm.ItemIndex := rgAlgorithm.Items.IndexOf(Algorithm);
|
||||||
rgCheck.ItemIndex := rgCheck.Items.IndexOf(CheckOption);
|
rgCheck.ItemIndex := rgCheck.Items.IndexOf(CheckOption);
|
||||||
if rgCheck.ItemIndex = -1 then
|
if rgCheck.ItemIndex = -1 then
|
||||||
|
Reference in New Issue
Block a user