Is it possible to keep the user's SQL formatting in the text editor while the query hasn't been modified in the visual query builder?
Last modified:
It's possible, but a little bit tricky.
Below is a piece of the code of a form that contains a Query Builder and a Text Editor controls on it. The Query Builder gets updated only if the syntax of SQL query typed in the Text Editor is correct, and it has been really changed (changing the SQL text formatting hasn't taken into account).
If a parsing error occurred, the user could be guided to the erroneous token in the query text to correct the error (actGoToErrorPosExecute), or get back to the SQL that was parsed fine (actReturnGoodParseExecute). If the user is not satisfied with getting back to the last parsed query, he can get back to the erroneous query (actReturnToPreviousStateExecute).
unit uQueryBuilder; interface TfQueryBuilder = class(TForm) // ... private fSQLChanged: boolean; lastParsedSQL: string; previousParsedSQL: string; previousStateSQL: string; fLastErrorPos: TacPos; acQueryBuilder: TacQueryBuilder; function ParseQuery(const aSQL: string): Boolean; procedure ShowParsingError(ASQLError: EacSQLError); // ... end; implementation // ... procedure TfQueryBuilder.seSQLChange(Sender: TObject); begin fChangedSQL := true; end; procedure TfQueryBuilder.seSQLExit(Sender: TObject); function SqlTextHasBeenChanged: Boolean; begin Result := acQueryBuilder.SQL <> lastParsedSQL; end; function QueryBuilderHasBeenChanged: Boolean; begin Result := acQueryBuilder.SQL <> previousParsedSQL; end; begin Assert(Sender is TacSqlTextEditor); if not fSQLChanged or (csDestroying in ComponentState) then Exit; // Save the Text Editor state here if not ParseQuery(TacSqlTextEditor(Sender).Text) then Abort; if SqlHasBeenChanged then begin acQueryBuilder.SQL := lastParsedSQL; if not QueryBuilderHasBeenChanged then begin fTextEditor.Text := previousStateSQL; // Restore the Text Editor state here end; end; end; function TfQueryBuilder.ParseQuery(const aSQL: string): Boolean; function ParseAndGenerateSQL(const ASQL: string): string; var q: TSQLSubQueryExpression; lCTE, lFromObj: TObjectList; begin q := acQueryBuilder.SQLContext.ParseSelect(ASQL) try // that's magic ;) lCTE := TObjectList.Create(false); try lFromObj := TObjectList.Create(false); try q.PrepareAndFixupRecursive(lCTE, lFromObj); finally lFromObj.Free; end; finally lCTE.Free; end; Result := q.SimpleSQL(acSQLBuilderPlainText.SQLBuilder); finally q.Free; end; end; begin previousStateSQL := aSQL; if Trim(aSQL) = '' then begin Result := True; Exit; end; previousParsedSQL := acQueryBuilder.SQL; try lastParsedSQL := ParseAndGenerateSQL(aSQL); result := True; except on E: EacSQLError do begin result := false; fLastErrorPos := ASQLError.ErrorPos; ShowParsingErrorHint(E); end else Raise; end; end; procedure TfQueryBuilder.ShowParsingError(ASQLError: EacSQLError); begin ShowMessage(ASQLError.Message); // display the error the way you like end; procedure TfQueryBuilder.actReturnToPreviousStateExecute(Sender: TObject); begin seSQL.Text := previousStateSQL; end; procedure TfQueryBuilder.actGoToErrorPosExecute(Sender: TObject); begin seSQL.ScrollToPosition(fLastErrorPos.line - 1, fLastErrorPos.col); seSQL.Caret.TextPosition := TextLocation(fLastErrorPos.line - 1, fLastErrorPos.col); seSQL.SetFocus; end; procedure TfQueryBuilder.actReturnGoodParseExecute(Sender: TObject); begin seSQL.Text := lastGoodSQL; end;