Search code examples
loopsdelphiiterationfiredacdelphi-10.2-tokyo

Is there a way to iterate over FireDAC's TFDParams using Delphi's for .. in .. do?


With Delphi 10.2 (Tokyo), I just want to do something as simple as:

function ShowFinalSQL(const qry: TFDQuery): String;
var
  cSQL: String;
  oParam: TFDParam;
begin
  cSQL := qry.SQL.Text;
  for oParam in qry.Params do
    cSQL := cSQL.Replace(oParam.Name, oParam.Value);
  Result := cSQL;
end;

But I always get the error message:

[dcc32 Error] DTUtilBD.pas(3115): E2010 Incompatible types: 'TFDParam' and 'TCollectionItem'

Is there a way of doing it?


Solution

  • TFDQuery.Params is a TFDParams, which can be iterated with a for..in loop as it has a public GetEnumerator() method. However, that method is inherited from TCollection to iterate TCollectionItem items, so it is not specialized for TFDParam items (feel free to file a bug report about that oversight).

    As such, when the loop iteration tries to assign the enumerator's Current property to your oParam variable, it fails to compile because a TCollectionItem cannot be assigned to a TFDParam. Which is exactly what the compiler error is complaining about.

    Your code basically gets compiled as-if it had been written like this:

    function ShowFinalSQL(const qry: TFDQuery): String;
    var
      cSQL: String;
      oParam: TFDParam;
      cEnum: TCollectionEnumerator;
    begin
      cSQL := qry.SQL.Text;
      //for oParam in qry.Params do
      cEnum := qry.Params.GetEnumerator;
      while cEnum.MoveNext do
      begin
        oParam := cEnum.Current; // <-- ERROR HERE - cEnum.Current is TCollectionItem!
        cSQL := cSQL.Replace(oParam.Name, oParam.Value);
      end;
      Result := cSQL;
    end;
    

    To fix this, you need to change your oParam variable to be a TCollectionItem instead of a TFDParam. You will just have to type-cast it when you want to access any TFDParam-specific members, eg:

    function ShowFinalSQL(const qry: TFDQuery): String;
    var
      cSQL: String;
      oParam: TCollectionItem;
    begin
      cSQL := qry.SQL.Text;
      for oParam in qry.Params do
        cSQL := cSQL.Replace(TFDParam(oParam).Name, TFDParam(oParam).Value);
      Result := cSQL;
    end;
    

    Alternatively:

    function ShowFinalSQL(const qry: TFDQuery): String;
    var
      cSQL: String;
      oItem: TCollectionItem;
      oParam: TFDParam;
    begin
      cSQL := qry.SQL.Text;
      for oItem in qry.Params do
      begin
        oParam := TFDParam(oItem);
        cSQL := cSQL.Replace(oParam.Name, oParam.Value);
      end;
      Result := cSQL;
    end;
    

    UPDATE: Alternatively, if you want to avoid type-casting manually, you can use absolute:

    function ShowFinalSQL(const qry: TFDQuery): String;
    var
      cSQL: String;
      oItem: TCollectionItem;
      oParam: TFDParam absolute oItem;
    begin
      cSQL := qry.SQL.Text;
      for oItem in qry.Params do
      begin
        cSQL := cSQL.Replace(oParam.Name, oParam.Value);
      end;
      Result := cSQL;
    end;