Move code from GetSQLSplitMarkers and SplitSQL into TSQLBatch class. Enables me to access details like string size of batch. Preparation for query history.

This commit is contained in:
Ansgar Becker
2012-03-06 21:52:57 +00:00
parent ccca1bf982
commit 4de1b3012d
4 changed files with 209 additions and 182 deletions

View File

@@ -50,11 +50,27 @@ type
GripRect: TRect;
end;
TSQLBatch = class;
TSQLSentence = class(TObject)
LeftOffset, RightOffset: Integer;
SQL: String;
private
FOwner: TSQLBatch;
function GetSize: Integer;
function GetSQL: String;
public
LeftOffset, RightOffset: Integer;
constructor Create(Owner: TSQLBatch);
property SQL: String read GetSQL;
property Size: Integer read GetSize;
end;
TSQLBatch = class(TObjectList<TSQLSentence>)
private
FSQL: String;
procedure SetSQL(Value: String);
function GetSize: Integer;
public
property Size: Integer read GetSize;
property SQL: String read FSQL write SetSQL;
end;
TSQLBatch = TObjectList<TSQLSentence>;
// Threading stuff
TQueryThread = class(TThread)
@@ -100,8 +116,6 @@ type
function Explode(Separator, Text: String) :TStringList;
procedure ExplodeQuotedList(Text: String; var List: TStringList);
function getEnumValues(str: String): String;
function GetSQLSplitMarkers(const SQL: String): TSQLBatch;
function SplitSQL(const SQL: String): TSQLBatch;
function sstr(str: String; len: Integer) : String;
function encrypt(str: String): String;
function decrypt(str: String): String;
@@ -289,109 +303,6 @@ begin
end;
function GetSQLSplitMarkers(const SQL: String): TSQLBatch;
var
i, AllLen, DelimLen, DelimStart, LastLeftOffset, RightOffset, LastNewLineOffset: Integer;
c, n, LastStringEncloser: Char;
Delim, DelimTest, QueryTest: String;
InString, InComment, InBigComment, InEscape: Boolean;
Marker: TSQLSentence;
rx: TRegExpr;
const
StringEnclosers = ['"', '''', '`'];
NewLines = [#13, #10];
WhiteSpaces = NewLines + [#9, ' '];
begin
// Scan SQL batch for delimiters and return a list with start + end offsets
AllLen := Length(SQL);
i := 0;
LastLeftOffset := 1;
Delim := Mainform.Delimiter;
InString := False; // Loop in "enclosed string" or `identifier`
InComment := False; // Loop in one-line comment (# or --)
InBigComment := False; // Loop in /* multi-line */ or /*! condictional comment */
InEscape := False; // Previous char was backslash
LastStringEncloser := #0;
DelimLen := Length(Delim);
Result := TSQLBatch.Create;
rx := TRegExpr.Create;
rx.Expression := '^\s*DELIMITER\s+(\S+)';
rx.ModifierG := True;
rx.ModifierI := True;
rx.ModifierM := False;
while i < AllLen do begin
Inc(i);
// Current and next char
c := SQL[i];
if i < AllLen then n := SQL[i+1]
else n := #0;
// Check for comment syntax and for enclosed literals, so a query delimiter can be ignored
if (not InComment) and (not InBigComment) and (not InString) and ((c + n = '--') or (c = '#')) then
InComment := True;
if (not InComment) and (not InBigComment) and (not InString) and (c + n = '/*') then
InBigComment := True;
if InBigComment and (not InComment) and (not InString) and (c + n = '*/') then
InBigComment := False;
if (not InEscape) and (not InComment) and (not InBigComment) and CharInSet(c, StringEnclosers) then begin
if (not InString) or (InString and (c = LastStringEncloser)) then begin
InString := not InString;
LastStringEncloser := c;
end;
end;
if (CharInSet(c, NewLines) and (not CharInSet(n, NewLines))) or (i = 1) then begin
if i > 1 then
InComment := False;
if (not InString) and (not InBigComment) and rx.Exec(copy(SQL, i, 100)) then begin
Delim := rx.Match[1];
DelimLen := rx.MatchLen[1];
Inc(i, rx.MatchLen[0]);
LastLeftOffset := i;
continue;
end;
end;
if not InEscape then
InEscape := c = '\'
else
InEscape := False;
// Prepare delimiter test string
if (not InComment) and (not InString) and (not InBigComment) then begin
DelimStart := Max(1, i+1-DelimLen);
DelimTest := Copy(SQL, DelimStart, i-Max(i-DelimLen, 0));
end else
DelimTest := '';
// End of query or batch reached. Add query markers to result list if sentence is not empty.
if (DelimTest = Delim) or (i = AllLen) then begin
RightOffset := i+1;
if DelimTest = Delim then
Dec(RightOffset, DelimLen);
QueryTest := Trim(Copy(SQL, LastLeftOffset, RightOffset-LastLeftOffset));
if (QueryTest <> '') and (QueryTest <> Delim) then begin
Marker := TSQLSentence.Create;
while CharInSet(SQL[LastLeftOffset], WhiteSpaces) do
Inc(LastLeftOffset);
Marker.LeftOffset := LastLeftOffset;
Marker.RightOffset := RightOffset;
Result.Add(Marker);
LastLeftOffset := i+1;
end;
end;
end;
end;
function SplitSQL(const SQL: String): TSQLBatch;
var
Query: TSQLSentence;
begin
// Return a list of queries tokenized from a big string. Replaces earlier parseSQL() method.
Result := GetSQLSplitMarkers(SQL);
for Query in Result do
Query.SQL := Copy(SQL, Query.LeftOffset, Query.RightOffset-Query.LeftOffset);
end;
{***
@@ -2813,6 +2724,135 @@ begin
end;
{ TSQLSentence }
constructor TSQLSentence.Create(Owner: TSQLBatch);
begin
// Use a back reference to the parent batch object, so we can extract SQL from it
FOwner := Owner;
end;
function TSQLSentence.GetSize: Integer;
begin
Result := RightOffset - LeftOffset;
end;
function TSQLSentence.GetSQL: String;
begin
Result := Copy(FOwner.SQL, LeftOffset, RightOffset-LeftOffset);
end;
{ TSQLBatch }
function TSQLBatch.GetSize: Integer;
var
Query: TSQLSentence;
begin
// Return overall string length of batch
Result := 0;
for Query in Self do
Inc(Result, Query.Size);
end;
procedure TSQLBatch.SetSQL(Value: String);
var
i, AllLen, DelimLen, DelimStart, LastLeftOffset, RightOffset, LastNewLineOffset: Integer;
c, n, LastStringEncloser: Char;
Delim, DelimTest, QueryTest: String;
InString, InComment, InBigComment, InEscape: Boolean;
Marker: TSQLSentence;
rx: TRegExpr;
const
StringEnclosers = ['"', '''', '`'];
NewLines = [#13, #10];
WhiteSpaces = NewLines + [#9, ' '];
begin
// Scan SQL batch for delimiters and store a list with start + end offsets
FSQL := Value;
Clear;
AllLen := Length(FSQL);
i := 0;
LastLeftOffset := 1;
Delim := Mainform.Delimiter;
InString := False; // Loop in "enclosed string" or `identifier`
InComment := False; // Loop in one-line comment (# or --)
InBigComment := False; // Loop in /* multi-line */ or /*! condictional comment */
InEscape := False; // Previous char was backslash
LastStringEncloser := #0;
DelimLen := Length(Delim);
rx := TRegExpr.Create;
rx.Expression := '^\s*DELIMITER\s+(\S+)';
rx.ModifierG := True;
rx.ModifierI := True;
rx.ModifierM := False;
while i < AllLen do begin
Inc(i);
// Current and next char
c := FSQL[i];
if i < AllLen then n := FSQL[i+1]
else n := #0;
// Check for comment syntax and for enclosed literals, so a query delimiter can be ignored
if (not InComment) and (not InBigComment) and (not InString) and ((c + n = '--') or (c = '#')) then
InComment := True;
if (not InComment) and (not InBigComment) and (not InString) and (c + n = '/*') then
InBigComment := True;
if InBigComment and (not InComment) and (not InString) and (c + n = '*/') then
InBigComment := False;
if (not InEscape) and (not InComment) and (not InBigComment) and CharInSet(c, StringEnclosers) then begin
if (not InString) or (InString and (c = LastStringEncloser)) then begin
InString := not InString;
LastStringEncloser := c;
end;
end;
if (CharInSet(c, NewLines) and (not CharInSet(n, NewLines))) or (i = 1) then begin
if i > 1 then
InComment := False;
if (not InString) and (not InBigComment) and rx.Exec(copy(FSQL, i, 100)) then begin
Delim := rx.Match[1];
DelimLen := rx.MatchLen[1];
Inc(i, rx.MatchLen[0]);
LastLeftOffset := i;
continue;
end;
end;
if not InEscape then
InEscape := c = '\'
else
InEscape := False;
// Prepare delimiter test string
if (not InComment) and (not InString) and (not InBigComment) then begin
DelimStart := Max(1, i+1-DelimLen);
DelimTest := Copy(FSQL, DelimStart, i-Max(i-DelimLen, 0));
end else
DelimTest := '';
// End of query or batch reached. Add query markers to result list if sentence is not empty.
if (DelimTest = Delim) or (i = AllLen) then begin
RightOffset := i+1;
if DelimTest = Delim then
Dec(RightOffset, DelimLen);
QueryTest := Trim(Copy(FSQL, LastLeftOffset, RightOffset-LastLeftOffset));
if (QueryTest <> '') and (QueryTest <> Delim) then begin
Marker := TSQLSentence.Create(Self);
while CharInSet(FSQL[LastLeftOffset], WhiteSpaces) do
Inc(LastLeftOffset);
Marker.LeftOffset := LastLeftOffset;
Marker.RightOffset := RightOffset;
Add(Marker);
LastLeftOffset := i+1;
end;
end;
end;
end;
end.

View File

@@ -2199,9 +2199,8 @@ end;
procedure TMainForm.actExecuteQueryExecute(Sender: TObject);
var
Query: TSQLSentence;
Text, LB: String;
ProfileNode: PVirtualNode;
Batch: TSQLBatch;
BatchAll, Batch: TSQLBatch;
Tab: TQueryTab;
begin
@@ -2210,32 +2209,25 @@ begin
OperationRunning(True);
ShowStatusMsg('Splitting SQL queries ...');
Batch := TSQLBatch.Create;
if Sender = actExecuteCurrentQuery then begin
Batch := GetSQLSplitMarkers(Tab.Memo.Text);
for Query in Batch do begin
BatchAll := TSQLBatch.Create;
BatchAll.SQL := Tab.Memo.Text;
for Query in BatchAll do begin
if (Tab.Memo.SelStart >= Query.LeftOffset-1) and (Tab.Memo.SelStart < Query.RightOffset) then begin
Text := Copy(Tab.Memo.Text, Query.LeftOffset, Query.RightOffset-Query.LeftOffset);
Batch.SQL := Query.SQL;
Tab.LeftOffsetInMemo := Query.LeftOffset;
break;
end;
end;
BatchAll.Free;
end else if Sender = actExecuteSelection then begin
Text := Tab.Memo.SelText;
Batch.SQL := Tab.Memo.SelText;
Tab.LeftOffsetInMemo := Tab.Memo.SelStart;
end else begin
Text := Tab.Memo.Text;
Batch.SQL := Tab.Memo.Text;
Tab.LeftOffsetInMemo := 0;
end;
// Give text back its original linebreaks if possible
case Tab.MemoLineBreaks of
lbsUnix: LB := LB_UNIX;
lbsMac: LB := LB_MAC;
lbsWide: LB := LB_WIDE;
end;
if LB <> '' then
Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]);
Batch := SplitSQL(Text);
Text := '';
EnableProgress(Batch.Count);
Tab.ResultTabs.Clear;
@@ -2985,10 +2977,10 @@ end;
}
function TMainform.InitConnection(Params: TConnectionParameters; ActivateMe: Boolean; var Connection: TDBConnection): Boolean;
var
i: Integer;
SessionExists, RestoreLastActiveDatabase: Boolean;
StartupScript, StartupSQL, LastActiveDatabase: String;
StartupScript, LastActiveDatabase: String;
StartupBatch: TSQLBatch;
Query: TSQLSentence;
SessionNode, DBNode: PVirtualNode;
begin
Connection := Params.CreateConnection(Self);
@@ -3051,10 +3043,10 @@ begin
if not FileExists(StartupScript) then
ErrorDialog('Startup script file not found: '+StartupScript)
else begin
StartupSQL := ReadTextfile(StartupScript, nil);
StartupBatch := SplitSQL(StartupSQL);
for i:=0 to StartupBatch.Count-1 do try
Connection.Query(StartupBatch[i].SQL);
StartupBatch := TSQLBatch.Create;
StartupBatch.SQL := ReadTextfile(StartupScript, nil);
for Query in StartupBatch do try
Connection.Query(Query.SQL);
except
// Suppress popup, errors get logged into SQL log
end;
@@ -4820,7 +4812,7 @@ var
Attri: TSynHighlighterAttributes;
Proposal: TSynCompletionProposal;
Editor: TCustomSynEdit;
QueryMarkers: TSQLBatch;
Queries: TSQLBatch;
Query: TSQLSentence;
Conn: TDBConnection;
RoutineEditor: TfrmRoutineEditor;
@@ -4921,14 +4913,15 @@ begin
sql := 'SELECT * FROM '+ActiveDbObj.QuotedName+' WHERE ' + Editor.Text;
end else begin
// In a query tab
QueryMarkers := GetSQLSplitMarkers(Editor.Text);
for Query in QueryMarkers do begin
Queries := TSQLBatch.Create;
Queries.SQL := Editor.Text;
for Query in Queries do begin
if (Query.LeftOffset <= Editor.SelStart) and (Editor.SelStart < Query.RightOffset) then begin
sql := Copy(Editor.Text, Query.LeftOffset, Query.RightOffset-Query.LeftOffset);
sql := Query.SQL;
break;
end;
end;
FreeAndNil(QueryMarkers);
Queries.Free;
end;
// 2. Parse FROM clause, detect relevant table/view, probably aliased

View File

@@ -48,15 +48,12 @@ uses
}
procedure TRunSQLFileForm.FormActivate(Sender: TObject);
var
Stream : TFileStream;
lines : String;
filesize,
querycount,
rowsaffected : Int64;
starttime : Cardinal;
SQL : TSQLBatch;
i : Integer;
lines_remaining : String;
Stream: TFileStream;
lines, lines_remaining: String;
filesize, querycount, rowsaffected: Int64;
starttime: Cardinal;
Queries: TSQLBatch;
i: Integer;
begin
if Running then
abort;
@@ -71,6 +68,7 @@ begin
rowsaffected := 0;
lines_remaining := '';
starttime := GetTickCount;
Queries := TSQLBatch.Create;
Screen.Cursor := crHourGlass;
@@ -100,17 +98,17 @@ begin
Repaint;
// Split buffer into single queries
SQL := SplitSQL( lines_remaining + lines );
Queries.SQL := lines_remaining + lines;
lines := '';
lines_remaining := '';
// Execute detected queries
for i := 0 to SQL.Count - 1 do
for i:=0 to Queries.Count-1 do
begin
// Last line has to be processed in next loop if end of file is not reached
if (i = SQL.Count-1) and (Stream.Position < Stream.Size) then
if (i = Queries.Count-1) and (Stream.Position < Stream.Size) then
begin
lines_remaining := SQL[i].SQL;
lines_remaining := Queries[i].SQL;
break;
end;
@@ -119,19 +117,19 @@ begin
lblQueryCountValue.Caption := FormatNumber( querycount );
// Display part of query
memoQueryValue.Text := sstr( SQL[i].SQL, 100 );
memoQueryValue.Text := sstr(Queries[i].SQL, 100 );
// Time
lblTimeValue.Caption := FormatTimeNumber((GetTickCount - starttime) DIV 1000, True);
// Execute single query and display affected rows
Mainform.ActiveConnection.Query(SQL[i].SQL);
Mainform.ActiveConnection.Query(Queries[i].SQL);
rowsaffected := rowsaffected + Mainform.ActiveConnection.RowsAffected;
lblAffectedRowsValue.Caption := FormatNumber( rowsaffected );
Repaint;
end;
SQL.Free;
Queries.Free;
end;
Stream.Free;

View File

@@ -454,8 +454,7 @@ end;
function TfrmTableEditor.ComposeAlterStatement: TSQLBatch;
var
Specs: TStringList;
ColSpec, IndexSQL: String;
Query: TSQLSentence;
ColSpec, IndexSQL, SQL: String;
i: Integer;
Results: TDBQuery;
Col, PreviousCol: PTableColumn;
@@ -465,19 +464,16 @@ var
procedure AddQuery;
begin
if Specs.Count > 0 then begin
Query := TSQLSentence.Create;
Query.SQL := 'ALTER TABLE '+DBObject.Connection.QuoteIdent(DBObject.Name) + CRLF + #9 + ImplodeStr(',' + CRLF + #9, Specs);
Query.SQL := Trim(Query.SQL);
Result.Add(Query);
SQL := SQL + Trim('ALTER TABLE '+DBObject.Connection.QuoteIdent(DBObject.Name) + CRLF + #9 + ImplodeStr(',' + CRLF + #9, Specs)) + ';' + CRLF;
Specs.Clear;
end;
end;
begin
// Compose ALTER query, called by buttons and for SQL code tab
Mainform.ShowStatusMsg('Composing ALTER statement ...');
Result := TSQLBatch.Create;
Screen.Cursor := crHourglass;
Specs := TStringList.Create;
SQL := '';
// Special case for altered foreign keys: These have to be dropped in a seperate query
// otherwise the server would return error 121 "Duplicate key on write or update"
@@ -631,6 +627,9 @@ begin
AddQuery;
Result := TSQLBatch.Create;
Result.SQL := SQL;
FreeAndNil(Specs);
Mainform.ShowStatusMsg;
MainForm.DisableProgress;
@@ -643,18 +642,14 @@ var
i, IndexCount: Integer;
Col: PTableColumn;
Node: PVirtualNode;
tmp: String;
Query: TSQLSentence;
tmp, SQL: String;
begin
// Compose CREATE query, called by buttons and for SQL code tab
Result := TSQLBatch.Create;
Query := TSQLSentence.Create;
Result.Add(Query);
Query.SQL := 'CREATE TABLE '+DBObject.Connection.QuoteIdent(editName.Text)+' ('+CRLF;
SQL := 'CREATE TABLE '+DBObject.Connection.QuoteIdent(editName.Text)+' ('+CRLF;
Node := listColumns.GetFirst;
while Assigned(Node) do begin
Col := listColumns.GetNodeData(Node);
Query.SQL := Query.SQL + #9 + Col.SQLCode + ','+CRLF;
SQL := SQL + #9 + Col.SQLCode + ','+CRLF;
Node := listColumns.GetNextSibling(Node);
end;
@@ -662,43 +657,44 @@ begin
for i:=0 to FKeys.Count-1 do begin
tmp := FKeys[i].SQLCode;
if tmp <> '' then begin
Query.SQL := Query.SQL + #9 + tmp + ','+CRLF;
SQL := SQL + #9 + tmp + ','+CRLF;
Inc(IndexCount);
end;
end;
for i:=0 to FForeignKeys.Count-1 do
Query.SQL := Query.SQL + #9 + FForeignKeys[i].SQLCode(True) + ','+CRLF;
SQL := SQL + #9 + FForeignKeys[i].SQLCode(True) + ','+CRLF;
if Integer(listColumns.RootNodeCount) + IndexCount + FForeignKeys.Count > 0 then
Delete(Query.SQL, Length(Query.SQL)-2, 3);
Delete(SQL, Length(SQL)-2, 3);
Query.SQL := Query.SQL + CRLF + ')' + CRLF;
SQL := SQL + CRLF + ')' + CRLF;
if memoComment.Text <> '' then
Query.SQL := Query.SQL + 'COMMENT='+esc(memoComment.Text) + CRLF;
SQL := SQL + 'COMMENT='+esc(memoComment.Text) + CRLF;
if comboCollation.Text <> '' then
Query.SQL := Query.SQL + 'COLLATE='+esc(comboCollation.Text) + CRLF;
SQL := SQL + 'COLLATE='+esc(comboCollation.Text) + CRLF;
if comboEngine.Text <> '' then begin
if DBObject.Connection.ServerVersionInt < 40018 then
Query.SQL := Query.SQL + 'TYPE='+comboEngine.Text + CRLF
SQL := SQL + 'TYPE='+comboEngine.Text + CRLF
else
Query.SQL := Query.SQL + 'ENGINE='+comboEngine.Text + CRLF;
SQL := SQL + 'ENGINE='+comboEngine.Text + CRLF;
end;
if comboRowFormat.Tag = ModifiedFlag then
Query.SQL := Query.SQL + 'ROW_FORMAT='+comboRowFormat.Text + CRLF;
SQL := SQL + 'ROW_FORMAT='+comboRowFormat.Text + CRLF;
if chkChecksum.Checked then
Query.SQL := Query.SQL + 'CHECKSUM='+IntToStr(Integer(chkChecksum.Checked)) + CRLF;
SQL := SQL + 'CHECKSUM='+IntToStr(Integer(chkChecksum.Checked)) + CRLF;
if editAutoInc.Text <> '' then
Query.SQL := Query.SQL + 'AUTO_INCREMENT='+editAutoInc.Text + CRLF;
SQL := SQL + 'AUTO_INCREMENT='+editAutoInc.Text + CRLF;
if editAvgRowLen.Text <> '' then
Query.SQL := Query.SQL + 'AVG_ROW_LENGTH='+editAvgRowLen.Text + CRLF;
SQL := SQL + 'AVG_ROW_LENGTH='+editAvgRowLen.Text + CRLF;
if editMaxRows.Text <> '' then
Query.SQL := Query.SQL + 'MAX_ROWS='+editMaxRows.Text + CRLF;
SQL := SQL + 'MAX_ROWS='+editMaxRows.Text + CRLF;
if memoUnionTables.Enabled and (memoUnionTables.Text <> '') then
Query.SQL := Query.SQL + 'UNION=('+memoUnionTables.Text+')' + CRLF;
SQL := SQL + 'UNION=('+memoUnionTables.Text+')' + CRLF;
if comboInsertMethod.Enabled and (comboInsertMethod.Text <> '') then
Query.SQL := Query.SQL + 'INSERT_METHOD='+comboInsertMethod.Text + CRLF;
Query.SQL := Trim(Query.SQL);
SQL := SQL + 'INSERT_METHOD='+comboInsertMethod.Text + CRLF;
Result := TSQLBatch.Create;
Result.SQL := Trim(SQL);
end;