diff --git a/source/helpers.pas b/source/helpers.pas index 7c69f2df..cd2d527e 100644 --- a/source/helpers.pas +++ b/source/helpers.pas @@ -70,6 +70,12 @@ type GripRect: TRect; end; + TSQLSentence = class(TObject) + LeftOffset, RightOffset: Integer; + SQL: String; + end; + TSQLBatch = TObjectList; + {$I const.inc} @@ -81,7 +87,8 @@ type function IsWhitespace(const c: Char): Boolean; function IsLetter(const c: Char): Boolean; function IsNumber(const c: Char): Boolean; - function parsesql(sql: String) : TStringList; + 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; @@ -357,28 +364,6 @@ begin end; - -{*** - Add a non-empty value to a Stringlist - - @param TStringList - @param string to add - @param string to enclose added string in (use %s) - @return void -} -procedure addResult(list: TStringList; s: String; enclose: String = ''); -begin - s := trim(s); - if length(s) > 0 then begin - if enclose <> '' then s := Format(enclose, [s]); - list.Add(s); - end; - // Avoid memory leak - s := ''; -end; - - - {*** Return true if given character represents whitespace. Limitations: only recognizes ANSI whitespace. @@ -442,189 +427,92 @@ begin end; - -{*** - Tokenize sql-script and return a TStringList with sql-statements - - @param String (possibly large) bunch of SQL-statements, separated by semicolon - @param String SQL start delimiter - @return TStringList Separated statements -} -function parsesql(sql: String) : TStringList; +function GetSQLSplitMarkers(const SQL: String): TSQLBatch; var - i, j, start, len : Integer; - tmp : String; - instring, backslash, incomment : Boolean; - inconditional, condterminated : Boolean; - inbigcomment, indelimiter : Boolean; - delimiter_length : Integer; - encloser, secchar, thdchar : Char; - conditional : String; + i, AllLen, DelimLen, DelimStart, LastLeftOffset, RightOffset, LastNewLineOffset: Integer; + c, n: Char; + Delim, DelimTest, QueryTest: String; + InString, InComment, InBigComment: Boolean; + Marker: TSQLSentence; + rx: TRegExpr; +const + StringEnclosers = ['"', '''', '`']; + NewLines = [#13, #10]; begin - result := TStringList.Create; - sql := trim(sql); - instring := false; - start := 1; - len := length(sql); - backslash := false; - incomment := false; - inbigcomment := false; - inconditional := false; - condterminated := false; - indelimiter := false; - encloser := ' '; - conditional := ''; - + // Scan SQL batch for delimiters and return a list with start + end offsets + AllLen := Length(SQL); i := 0; - while i < len do begin - i := i + 1; + 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 */ + DelimLen := Length(Delim); + Result := TSQLBatch.Create; + rx := TRegExpr.Create; + rx.Expression := '^\s*DELIMITER\s+(\S+)\s*$'; + rx.ModifierI := True; + rx.ModifierM := True; + 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; - // Helpers for multi-character tests, avoids testing for string length. - secchar := '+'; - thdchar := '+'; - if i < length(sql) then secchar := sql[i + 1]; - if i + 1 < length(sql) then thdchar := sql[i + 2]; - - // Turn comments into whitespace. - if (sql[i] = '#') and (not instring) and (not inbigcomment) then begin - incomment := true; - end; - if (sql[i] + secchar = '--') and (not instring) and (not inbigcomment) then begin - incomment := true; - sql[i] := ' '; - if start = i then start := start + 1; - i := i + 1; - end; - if (sql[i] + secchar = '/*') and (not (thdchar = '!')) and (not instring) and (not incomment) then begin - inbigcomment := true; - incomment := true; - sql[i] := ' '; - if start = i then start := start + 1; - i := i + 1; - end; - if incomment and (not inbigcomment) and CharInSet(sql[i], [#13, #10]) then begin - incomment := false; - end; - if inbigcomment and (sql[i] + secchar = '*/') then begin - inbigcomment := false; - incomment := false; - sql[i] := ' '; - if start = i then start := start + 1; - i := i + 1; - sql[i] := ' '; - end; - if incomment or inbigcomment then begin - sql[i] := ' '; - end; - - // Skip whitespace immediately if at start of sentence. - if (start = i) and IsWhitespace(sql[i]) then begin - start := start + 1; - if i < len then continue; - end; - - // Avoid parsing stuff inside string literals. - if CharInSet(sql[i], ['''', '"', '`']) and (not (backslash and instring)) and (not incomment) and (not indelimiter) then begin - if instring and (sql[i] = encloser) then begin - if secchar = encloser then - i := i + 1 // encoded encloser-char - else - instring := not instring // string closed - end - else if not instring then begin // string is following - instring := true; - encloser := sql[i]; // remember enclosing-character - end; - if i < len then continue; - end; - - if (instring and (sql[i] = '\')) or backslash then - backslash := not backslash; - - // Allow a DELIMITER command in middle of SQL, like the MySQL CLI does. - if (not instring) and (not incomment) and (not inconditional) and (not indelimiter) and (start + 8 = i) and scanReverse(sql, i, 'delimiter', 9, true) then begin - // The allowed DELIMITER format is: - // - if IsWhitespace(secchar) then begin - indelimiter := true; - i := i + 1; - if i < len then continue; + // 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 CharInSet(c, StringEnclosers) then + InString := not InString; + if (CharInSet(c, NewLines) and (not CharInSet(n, NewLines))) or (i = 1) then begin + InComment := False; + if (not InString) and (not InBigComment) and rx.Exec(copy(SQL, LastLeftOffset, 100)) then begin + Delim := rx.Match[1]; + DelimLen := Length(Delim); + Inc(i, rx.MatchLen[0]); + LastLeftOffset := i; + continue; end; end; - if indelimiter then begin - if CharInSet(sql[i], [#13, #10]) or (i = len) then begin - if (i = len) then j := 1 else j := 0; - Mainform.Delimiter := copy(sql, start + 10, i + j - (start + 10)); - indelimiter := false; - start := i + 1; - end; - if i < len then continue; - end; + // 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 := ''; - // Handle conditional comments. - if (not instring) and (not incomment) and (sql[i] + secchar + thdchar = '/*!') then begin - inconditional := true; - condterminated := false; - tmp := ''; - conditional := ''; - i := i + 2; - if i < len then continue; - end; - - if inconditional and (conditional = '') then begin - if not IsNumber(sql[i]) then begin - conditional := tmp; - // note: - // we do not trim the start of the SQL inside conditional - // comments like we do on non-commented sql. - if i < len then continue; - end else tmp := tmp + sql[i]; - end; - - if inconditional and (not instring) and (not incomment) and (sql[i] + secchar = '*/') then begin - inconditional := false; - if condterminated then begin - // at least one statement was terminated inside the conditional. - // if the conditional had no more contents after that statement, - // clear the end marker. otherwise, add a new start marker. - if trim(copy(sql, start, i - start)) = '' then begin - sql[i] := ' '; - i := i + 1; - sql[i] := ' '; - start := i + 1; - end else begin - tmp := '/*!' + conditional + ' '; - move(tmp[1], sql[start - length(tmp)], length(tmp)); - start := start - length(tmp); - end; - condterminated := false; - end else begin - if start = i then start := start + 1; - i := i + 1; + // 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; + Marker.LeftOffset := LastLeftOffset; + Marker.RightOffset := RightOffset; + Result.Add(Marker); + LastLeftOffset := i+1; end; - if i < len then continue; - end; - - // Add sql sentence. - delimiter_length := Length(Mainform.Delimiter); - if ((not instring) and (scanReverse(sql, i, Mainform.Delimiter, delimiter_length, false)) or (i = len)) then begin - if (i < len) then j := delimiter_length else begin - // end of string, add sql sentence but only remove delimiter if it's there - if scanReverse(sql, i, Mainform.Delimiter, delimiter_length, false) then j := delimiter_length else j := 0; - end; - if inconditional then begin - addResult(result, copy(sql, start, i - start - j + 1), '%s */'); - condterminated := true; - end else begin - addResult(result, copy(sql, start, i - start - j + 1)); - end; - start := i + 1; end; end; +end; - // Avoid memory leak - sql := ''; + +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; diff --git a/source/main.dfm b/source/main.dfm index 5bc84e42..8a73772a 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -1947,16 +1947,16 @@ object MainForm: TMainForm Hint = 'Execute selected SQL...|Execute selected SQL-query/queries...' ImageIndex = 104 ShortCut = 16504 - OnExecute = actExecuteSelectionExecute + OnExecute = actExecuteQueryExecute end - object actExecuteLine: TAction + object actExecuteCurrentQuery: TAction Category = 'SQL' - Caption = 'Run current line' + Caption = 'Run current query' Enabled = False - Hint = 'Execute Line|Executes the current line of SQL' + Hint = 'Run current query|Run currently focused SQL query' ImageIndex = 105 ShortCut = 24696 - OnExecute = actExecuteLineExecute + OnExecute = actExecuteQueryExecute end object actImageView: TAction Category = 'Export/Import' @@ -8121,7 +8121,7 @@ object MainForm: TMainForm Action = actExecuteSelection end object MenuRunLine: TMenuItem - Action = actExecuteLine + Action = actExecuteCurrentQuery end object MenuItem1: TMenuItem Caption = '-' diff --git a/source/main.pas b/source/main.pas index 63362c12..f6549fdb 100644 --- a/source/main.pas +++ b/source/main.pas @@ -115,7 +115,7 @@ type actExportData: TAction; Exportdata1: TMenuItem; CopyasXMLdata1: TMenuItem; - actExecuteLine: TAction; + actExecuteCurrentQuery: TAction; actImageView: TAction; actInsertFiles: TAction; InsertfilesintoBLOBfields1: TMenuItem; @@ -501,12 +501,10 @@ type procedure ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); function mask(str: String) : String; procedure actExecuteQueryExecute(Sender: TObject); - procedure actExecuteSelectionExecute(Sender: TObject); procedure actCopyAsXMLExecute(Sender: TObject); procedure actCreateDatabaseExecute(Sender: TObject); procedure actDataCancelChangesExecute(Sender: TObject); procedure actExportDataExecute(Sender: TObject); - procedure actExecuteLineExecute(Sender: TObject); procedure actImageViewExecute(Sender: TObject); procedure actInsertFilesExecute(Sender: TObject); procedure actDataDeleteExecute(Sender: TObject); @@ -562,8 +560,6 @@ type TargetCanvas: TCanvas); procedure LogSQL(Msg: String; Category: TMySQLLogCategory=lcInfo); procedure KillProcess(Sender: TObject); - procedure ExecSQLClick(Sender: TObject; Selection: Boolean = false; - CurrentLine: Boolean=false); procedure SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatusChanges); procedure TimerHostUptimeTimer(Sender: TObject); procedure ListTablesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; @@ -1563,7 +1559,8 @@ procedure TMainForm.DoAfterConnect; var i, j: Integer; lastUsedDB, StartupScript, StartupSQL: String; - functioncats, StartupBatch: TStringList; + functioncats: TStringList; + StartupBatch: TSQLBatch; miGroup, miFilterGroup, miFunction, @@ -1587,9 +1584,9 @@ begin MessageDlg('Error: Startup script file not found: '+StartupScript, mtError, [mbOK], 0) else begin StartupSQL := ReadTextfile(StartupScript); - StartupBatch := ParseSQL(StartupSQL); + StartupBatch := SplitSQL(StartupSQL); for i:=0 to StartupBatch.Count-1 do try - Connection.Query(StartupBatch[i]); + Connection.Query(StartupBatch[i].SQL); except // Suppress popup, errors get logged into SQL log end; @@ -1989,18 +1986,137 @@ begin end; procedure TMainForm.actExecuteQueryExecute(Sender: TObject); +var + SQLBatch: TSQLBatch; + Query: TSQLSentence; + i, QueryCount: Integer; + SQLTime, SQLNetTime: Cardinal; + Results: TMySQLQuery; + ColName, Text, LB: String; + col: TVirtualTreeColumn; + ResultLabel: TLabel; + cap: String; + Grid: TVirtualStringTree; + Memo: TSynMemo; begin - ExecSqlClick(sender, false); -end; + Screen.Cursor := crHourglass; + ResultLabel := ActiveQueryTab.LabelResultInfo; + Memo := ActiveQueryMemo; -procedure TMainForm.actExecuteSelectionExecute(Sender: TObject); -begin - ExecSqlClick(sender, true); -end; + ShowStatusMsg('Splitting SQL queries ...'); + if Sender = actExecuteCurrentQuery then begin + SQLBatch := GetSQLSplitMarkers(Memo.Text); + for Query in SQLBatch do begin + if (Query.LeftOffset <= Memo.SelStart) and (Memo.SelStart <= Query.RightOffset) then begin + Text := Copy(Memo.Text, Query.LeftOffset, Query.RightOffset-Query.LeftOffset); + break; + end; + end; + end else if Sender = actExecuteSelection then + Text := Memo.SelText + else + Text := Memo.Text; + // Give text back its original linebreaks if possible + case ActiveQueryTab.MemoLineBreaks of + lbsUnix: LB := LB_UNIX; + lbsMac: LB := LB_MAC; + lbsWide: LB := LB_WIDE; + end; + if LB <> '' then + Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]); + SQLBatch := SplitSQL(Text); -procedure TMainForm.actExecuteLineExecute(Sender: TObject); -begin - ExecSqlClick(sender, false, true); + ResultLabel.Caption := ''; + EnableProgressBar(SQLBatch.Count); + SQLtime := 0; + SQLNetTime := 0; + QueryCount := 0; + Results := TMySQLQuery.Create(Self); + Results.Connection := Connection; + Results.LogCategory := lcUserFiredSQL; + for i:=0 to SQLBatch.Count-1 do begin + ShowStatusMsg('Executing query #'+FormatNumber(i)+' of '+FormatNumber(SQLBatch.Count)+' ...'); + ProgressBarStatus.StepIt; + ProgressBarStatus.Repaint; + Results.SQL := SQLBatch[i].SQL; + // Immediately free results for all but last query + Results.StoreResult := i = SQLBatch.Count-1; + try + Results.Execute; + Inc(SQLtime, Connection.LastQueryDuration); + Inc(SQLNetTime, Connection.LastQueryNetworkDuration); + Inc(QueryCount); + if Results.StoreResult and Results.HasResult then + ResultLabel.Caption := FormatNumber(Results.ColumnCount) +' column(s) x '+FormatNumber(Results.RecordCount) +' row(s) in last result set.' + else + ResultLabel.Caption := FormatNumber(Connection.RowsAffected) +' row(s) affected by last query.'; + except + on E:EDatabaseError do begin + if actQueryStopOnErrors.Checked or (i = SQLBatch.Count - 1) then begin + Screen.Cursor := crDefault; + MessageDlg( E.Message, mtError, [mbOK], 0 ); + Break; + end; + end; + end; + end; + ProgressBarStatus.Hide; + + if QueryCount > 0 then begin + cap := ' Duration for '; + cap := cap + IntToStr(QueryCount); + if QueryCount < SQLBatch.Count then + cap := cap + ' of ' + IntToStr(SQLBatch.Count); + if SQLBatch.Count = 1 then + cap := cap + ' query' + else + cap := cap + ' queries'; + cap := cap + ': '+FormatNumber(SQLTime/1000, 3) +' sec.'; + if SQLNetTime > 0 then + cap := cap + ' (+ '+FormatNumber(SQLNetTime/1000, 3) +' sec. network)'; + ResultLabel.Caption := ResultLabel.Caption + cap; + end; + + if Assigned(Results) and Results.HasResult then begin + editFilterVT.Clear; + TimerFilterVT.OnTimer(Sender); + // Reset filter if filter panel was disabled + UpdateFilterPanel(Sender); + + ShowStatusMsg('Setting up result grid ...'); + Grid := ActiveGrid; + Grid.Header.Options := Grid.Header.Options + [hoVisible]; + Grid.Header.Columns.BeginUpdate; + Grid.Header.Columns.Clear; + for i:=0 to Results.ColumnCount-1 do begin + ColName := Results.ColumnNames[i]; + col := Grid.Header.Columns.Add; + col.Text := ColName; + if Results.DataType(i).Category in [dtcInteger, dtcReal] then + col.Alignment := taRightJustify; + if Results.ColIsPrimaryKeyPart(i) then + col.ImageIndex := ICONINDEX_PRIMARYKEY + else if Results.ColIsUniqueKeyPart(i) then + col.ImageIndex := ICONINDEX_UNIQUEKEY + else if Results.ColIsKeyPart(i) then + col.ImageIndex := ICONINDEX_INDEXKEY; + end; + Grid.Header.Columns.EndUpdate; + + Grid.BeginUpdate; + Grid.Clear; + FreeAndNil(ActiveQueryTab.Results); + ActiveQueryTab.Results := Results; + Grid.RootNodeCount := Results.RecordCount; + Grid.OffsetXY := Point(0, 0); + for i:=0 to Grid.Header.Columns.Count-1 do + AutoCalcColWidth(Grid, i); + Grid.EndUpdate; + end; + // Ensure controls are in a valid state + ValidateControls(Sender); + Screen.Cursor := crDefault; + ShowStatusMsg( STATUS_MSG_READY ); end; @@ -3878,7 +3994,7 @@ begin HasSelection := InQueryTab and Tab.Memo.SelAvail; actExecuteQuery.Enabled := InQueryTab and NotEmpty; actExecuteSelection.Enabled := InQueryTab and HasSelection; - actExecuteLine.Enabled := InQueryTab and (Tab.Memo.LineText <> ''); + actExecuteCurrentQuery.Enabled := actExecuteQuery.Enabled; actSaveSQLAs.Enabled := InQueryTab and NotEmpty; actSaveSQL.Enabled := actSaveSQLAs.Enabled and Tab.Memo.Modified; actSaveSQLselection.Enabled := InQueryTab and HasSelection; @@ -3936,130 +4052,6 @@ begin end; -procedure TMainForm.ExecSQLClick(Sender: TObject; Selection: Boolean=false; CurrentLine: Boolean=false); -var - SQL: TStringList; - i, QueryCount: Integer; - SQLTime, SQLNetTime: Cardinal; - Results: TMySQLQuery; - ColName, Text, LB: String; - col: TVirtualTreeColumn; - ResultLabel: TLabel; - cap: String; - Grid: TVirtualStringTree; -begin - Screen.Cursor := crHourglass; - ResultLabel := ActiveQueryTab.LabelResultInfo; - if CurrentLine then Text := ActiveQueryMemo.LineText - else if Selection then Text := ActiveQueryMemo.SelText - else Text := ActiveQueryMemo.Text; - // Give text back its original linebreaks if possible - case ActiveQueryTab.MemoLineBreaks of - lbsUnix: LB := LB_UNIX; - lbsMac: LB := LB_MAC; - lbsWide: LB := LB_WIDE; - end; - if LB <> '' then - Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]); - - ResultLabel.Caption := ''; - ShowStatusMsg('Splitting SQL queries ...'); - SQL := parseSQL(Text); - actExecuteQuery.Enabled := false; - actExecuteSelection.Enabled := false; - EnableProgressBar(SQL.Count); - SQLtime := 0; - SQLNetTime := 0; - QueryCount := 0; - Results := TMySQLQuery.Create(Self); - Results.Connection := Connection; - Results.LogCategory := lcUserFiredSQL; - for i:=0 to SQL.Count-1 do begin - ShowStatusMsg('Executing query #'+FormatNumber(i)+' of '+FormatNumber(SQL.Count)+' ...'); - ProgressBarStatus.StepIt; - ProgressBarStatus.Repaint; - Results.SQL := SQL[i]; - // Immediately free results for all but last query - Results.StoreResult := i = SQL.Count-1; - try - Results.Execute; - Inc(SQLtime, Connection.LastQueryDuration); - Inc(SQLNetTime, Connection.LastQueryNetworkDuration); - Inc(QueryCount); - if Results.StoreResult and Results.HasResult then - ResultLabel.Caption := FormatNumber(Results.ColumnCount) +' column(s) x '+FormatNumber(Results.RecordCount) +' row(s) in last result set.' - else - ResultLabel.Caption := FormatNumber(Connection.RowsAffected) +' row(s) affected by last query.'; - except - on E:EDatabaseError do begin - if actQueryStopOnErrors.Checked or (i = SQL.Count - 1) then begin - Screen.Cursor := crDefault; - MessageDlg( E.Message, mtError, [mbOK], 0 ); - Break; - end; - end; - end; - end; - ProgressBarStatus.Hide; - - if QueryCount > 0 then begin - cap := ' Duration for '; - cap := cap + IntToStr(QueryCount); - if QueryCount < SQL.Count then - cap := cap + ' of ' + IntToStr(SQL.Count); - if SQL.Count = 1 then - cap := cap + ' query' - else - cap := cap + ' queries'; - cap := cap + ': '+FormatNumber(SQLTime/1000, 3) +' sec.'; - if SQLNetTime > 0 then - cap := cap + ' (+ '+FormatNumber(SQLNetTime/1000, 3) +' sec. network)'; - ResultLabel.Caption := ResultLabel.Caption + cap; - end; - - if Assigned(Results) and Results.HasResult then begin - editFilterVT.Clear; - TimerFilterVT.OnTimer(Sender); - // Reset filter if filter panel was disabled - UpdateFilterPanel(Sender); - - ShowStatusMsg('Setting up result grid ...'); - Grid := ActiveGrid; - Grid.Header.Options := Grid.Header.Options + [hoVisible]; - Grid.Header.Columns.BeginUpdate; - Grid.Header.Columns.Clear; - for i:=0 to Results.ColumnCount-1 do begin - ColName := Results.ColumnNames[i]; - col := Grid.Header.Columns.Add; - col.Text := ColName; - if Results.DataType(i).Category in [dtcInteger, dtcReal] then - col.Alignment := taRightJustify; - if Results.ColIsPrimaryKeyPart(i) then - col.ImageIndex := ICONINDEX_PRIMARYKEY - else if Results.ColIsUniqueKeyPart(i) then - col.ImageIndex := ICONINDEX_UNIQUEKEY - else if Results.ColIsKeyPart(i) then - col.ImageIndex := ICONINDEX_INDEXKEY; - end; - Grid.Header.Columns.EndUpdate; - - Grid.BeginUpdate; - Grid.Clear; - FreeAndNil(ActiveQueryTab.Results); - ActiveQueryTab.Results := Results; - Grid.RootNodeCount := Results.RecordCount; - Grid.OffsetXY := Point(0, 0); - for i:=0 to Grid.Header.Columns.Count-1 do - AutoCalcColWidth(Grid, i); - Grid.EndUpdate; - end; - // Ensure controls are in a valid state - ValidateControls(Sender); - Screen.Cursor := crDefault; - ShowStatusMsg( STATUS_MSG_READY ); -end; - - { Proposal about to insert a String into synmemo } procedure TMainForm.SynCompletionProposalCodeCompletion(Sender: TObject; var Value: String; Shift: TShiftState; Index: Integer; EndToken: Char); @@ -4080,22 +4072,18 @@ procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; Sender: TObject; var CurrentInput: String; var x, y: Integer; var CanExecute: Boolean); var - i,j : Integer; - Results : TMySQLQuery; - DBObjects : TDBObjectList; - sql, TableClauses: String; - Tables : TStringList; - tablename : String; - rx : TRegExpr; - PrevShortToken, - PrevLongToken, - Token : UnicodeString; - Start, - TokenTypeInt : Integer; - Attri : TSynHighlighterAttributes; - Proposal : TSynCompletionProposal; - Editor : TCustomSynEdit; - Queries : TStringList; + i,j: Integer; + Results: TMySQLQuery; + DBObjects: TDBObjectList; + sql, TableClauses, tablename, PrevShortToken, PrevLongToken, Token: String; + Tables: TStringList; + rx: TRegExpr; + Start, TokenTypeInt: Integer; + Attri: TSynHighlighterAttributes; + Proposal: TSynCompletionProposal; + Editor: TCustomSynEdit; + QueryMarkers: TSQLBatch; + Query: TSQLSentence; procedure addTable(Obj: TDBObject); begin @@ -4188,16 +4176,14 @@ begin sql := 'SELECT * FROM `'+SelectedTable.Name+'` WHERE ' + Editor.Text; end else begin // Proposal in one of the query tabs - Queries := parsesql(Editor.Text); - j := 0; - for i:=0 to Queries.Count-1 do begin - Inc(j, Length(Queries[i])+1); - if (j >= Editor.SelStart) or (i = Queries.Count-1) then begin - sql := Queries[i]; + QueryMarkers := GetSQLSplitMarkers(Editor.Text); + for Query in QueryMarkers do begin + if (Query.LeftOffset <= Editor.SelStart) and (Editor.SelStart <= Query.RightOffset) then begin + sql := Copy(Editor.Text, Query.LeftOffset, Query.RightOffset-Query.LeftOffset); break; end; end; - FreeAndNil(Queries); + FreeAndNil(QueryMarkers); end; // 2. Parse FROM clause to detect relevant table/view, probably aliased diff --git a/source/runsqlfile.pas b/source/runsqlfile.pas index 6371db45..22091a9a 100644 --- a/source/runsqlfile.pas +++ b/source/runsqlfile.pas @@ -54,7 +54,7 @@ var querycount, rowsaffected : Int64; starttime : Cardinal; - SQL : TStringList; + SQL : TSQLBatch; i : Integer; lines_remaining : String; begin @@ -100,7 +100,7 @@ begin Repaint; // Split buffer into single queries - SQL := parseSQL( lines_remaining + lines ); + SQL := SplitSQL( lines_remaining + lines ); lines := ''; lines_remaining := ''; @@ -110,7 +110,7 @@ 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 begin - lines_remaining := SQL[i]; + lines_remaining := SQL[i].SQL; break; end; @@ -119,13 +119,13 @@ begin lblQueryCountValue.Caption := FormatNumber( querycount ); // Display part of query - memoQueryValue.Text := sstr( SQL[i], 100 ); + memoQueryValue.Text := sstr( SQL[i].SQL, 100 ); // Time lblTimeValue.Caption := FormatTimeNumber( (GetTickCount - starttime) DIV 1000 ); // Execute single query and display affected rows - Mainform.Connection.Query(SQL[i]); + Mainform.Connection.Query(SQL[i].SQL); rowsaffected := rowsaffected + Mainform.Connection.RowsAffected; lblAffectedRowsValue.Caption := FormatNumber( rowsaffected );