如何在 Delphi XE5 中使用 Firedac FDConnection 组件调用带有 void return 的函数?

How to call a function with void return using Firedac FDConnection Component in Delphi XE5?

我最近开始使用 Delphi XE5 中 FDConnection 组件的 [ExecSQLScalar]1 and [ExecSQL]2 方法。不需要构建 Dataset 对象非常方便,例如 FDQuery 只是为了简单的查询或执行。 但是,在执行带有 void return 的函数时,我遇到了一个奇怪的问题,该函数具有可以生成异常的内部验证。我正在使用 Postgres 数据库。

CREATE FUNCTION can_be_exception()
  RETURNS void AS
$$
BEGIN
    RAISE EXCEPTION E'fail';
END;
$$
  LANGUAGE plpgsql STABLE;

在delphi中,我调用了ExecSQLScalar函数...

FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');

首先 运行,我收到以下错误:

Project TFDConnectionDEMO.exe raised exception class EPgNativeException with message '[FireDAC][Phys][PG][libpq] ERROR: fail'.

在第二个 运行 上,我收到违规访问错误:

Project TFDConnectionDEMO.exe raised exception class $C0000005 with message 'access violation at 0x00000000: read of address 0x00000000'.

显然错误发生在单元 FireDAC.Comp.Client

的下面一行
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
  const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
  oCmd: IFDPhysCommand;
begin
  oCmd := BaseCreateSQL;
  try
    if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
      FDFree(FExecSQLTab);

...

忽略之前的错误并重试,显示另一个错误...

Project TZConnectionDEMO.exe raised exception class EFDException with message '[FireDAC][DatS]-24. Row is not nested'.

搜索,我没有找到对此错误的响应。我认为我的错误是使用 FDConnection 组件的 ExecSQLScalar 函数调用银行 raise_exception 函数。所以我尝试使用 FDConnection.ExecSQL 并且正如我想象的那样,如果参数中有 SELECT 子句,则不能使用它。

有没有更好的方法用FDConnection.ExecSQL调用void return函数? BUG会在组件中吗?还是说这样的电话不对?

使用 ExecSQLScalar is fine in this case. This is certainly a bug (which was already fixed, at least in Delphi 10.2.3). As you've correctly pointed out, the problem is in releasing a table storage object instance held by the FExecSQLTab field by using FDFree 程序。

我没有 Delphi XE5 源代码,但也许你可以在里面看到类似的东西(关于发生的事情的评论是我添加的):

if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
  FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
  FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;

问题是,当 SQL 命令执行在存储 table 定义阶段(oCmd.Define)引发异常时,引用之前销毁的存储 table 对象实例(通过 FDFree)仍然存储在 FExecSQLTab 字段中(作为悬挂指针)。

然后,当以这种方式执行不同的命令时,FDFree 过程仅为该悬挂指针调用。因此访问冲突。

更正此问题的方法是更换行,例如通过:

FDFree(FExecSQLTab);

作者:

FDFreeAndNil(FExecSQLTab);

这是在后来的 Delphi 版本中完成的。