3

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?

1 Answers1

5

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you very much @remy-lebeau! Your explanation made it simple to understand too! – Fábio Amorim Jun 01 '20 at 20:01
  • 3
    Another alternartive: _var oParam: TFDParam ABSOLUTE oItem;_ Then you don't need the _oParam:=TFDParam(oItem);_ line... – HeartWare Jun 02 '20 at 06:36
  • @HeartWare, I didn't know the `ABSOLUTE` keyword... Could you tell me more about it? – Fábio Amorim Jun 02 '20 at 11:45
  • 1
    _VAR Y : ABSOLUTE X_ defines a variable Y of type that occupies the same (starting) memory address as X. You're responsible for ensuring that it makes sense. F.ex. _VAR X : BYTE ; VAR Y : LONGINT ABSOLUTE X;_ doesn't make sense, as X only is 1 byte in size, whereas Y is 4, and you don't know what the three bytes after X is. If you want to overlay two variables, make sure that you define the smaller one ABSOLUTE the larger one. If both variables are CLASSes or POINTERs they always occupy the same amount of bytes, so they can be overlaid on top of each other without restriction. – HeartWare Jun 02 '20 at 12:01
  • 1
    One use I often do is in Event handlers. Sender is usually a simple TObject, but if I _know_ that I always call it from an event of a - say - TButton, I usually declare _VAR BTN : TButton ABSOLUTE Sender;_ so I can access the Sender under its proper type (I then usually do an _ASSERT(Sender IS TButton);_ at the entry of the event handler, to make sure...) @FábioAmorim – HeartWare Jun 02 '20 at 12:04
  • How was possible for me to pass about 3 years developing in Delphi without knowing about this?? Thank you very **very** much @HeartWare! – Fábio Amorim Jun 02 '20 at 12:48
  • @HeartWare, you should add your comment as an answer too. So I could mark it as correct as well. After all, it was I used in my code. – Fábio Amorim Jun 02 '20 at 14:53
  • @HeartWare the `is` operator performs an RTTI type check at runtime. So, if you are going to invoke that overhead anyway, you may as well just skip using `absolute` and use the `as` operator instead, which performs the same check and converts at the same time: `var Btn: TButton; Btn := Sender as TButton;` That will raise an exception if the conversion fails. – Remy Lebeau Jun 02 '20 at 16:55
  • 1
    @FábioAmorim only 1 answer can be marked as accepted at a time. I have added an `absolute` example to my answer – Remy Lebeau Jun 02 '20 at 16:55
  • @RemyLebeau: Yes, the IS operator performs RTTI check - but as I use it in ASSERT statement, it is NULL'ed out in RELEASE builds (or if you turn off assertions). That's why I do it this way and does not use AS. – HeartWare Jun 03 '20 at 09:46
  • @RemyLebeau Looks like `TFDParams.Items[]` also uses casting to provide a TFDParam item. From unit `FireDAC.Stan.Param` > `TFDParams.GetItem()`: `Result := TFDParam(inherited Items[Index]);` (Delphi 10.4.2) – emno Jun 09 '21 at 11:00