Search code examples
delphifirebirdfirebird-3.0delphi-10.1-berlin

How to use a transaction parameter block with the Firebird 3 OO Api


I've been playing with the new Firebird.pas interface included with Firebird 3. I'm having an issue when I try to use a custom transaction parameter block. I seem to always get the error "invalid format for transaction parameter block" if I add any tags to the block. The only example I've seen of how to do this is the "Using_OO_API.html" document included with Firebird 3. Here's the code to reproduce the error. Any suggestions appreciated!

procedure TForm1.Connect2ButtonClick(Sender: TObject);
var
  Master: IMaster;
  Status: IStatus;
  Dispatcher: IProvider;
  Util: IUtil;
  dpb: IXpbBuilder;
  tpb: IXpbBuilder;
  Attachment: IAttachment;
  Transaction: ITransaction;
  Statement: IStatement;
  ErrorString: AnsiString;
  StatusVector: NativeIntPtr;
  UseCustomTransaction: Boolean;
begin
  // Connect to Firebird 3 and use a custom transaction object.
  try
    Master := fb_get_master_interface();
    Status := Master.getStatus();

    Dispatcher := master.getDispatcher();
    Util := Master.getUtilInterface();
    dpb := Util.getXpbBuilder(status, IXpbBuilder.DPB, nil, 0);
    dpb.insertString(status, isc_dpb_user_name, 'SYSDBA');
    dpb.insertString(status, isc_dpb_password, 'sillypw');
    Attachment := Dispatcher.attachDatabase(status, PAnsiChar('myserver:testdb'), dpb.getBufferLength(status), dpb.getBuffer(status));

    UseCustomTransaction := True;
    if UseCustomTransaction then
    begin
      // Transaction := attachment.startTransaction(status, 0, nil);
      tpb := Util.getXpbBuilder(status, IXpbBuilder.TPB, nil, 0);
      tpb.insertTag(status, isc_tpb_version3);
      tpb.insertTag(status, isc_tpb_write);
      tpb.insertTag(status, isc_tpb_read_committed);
      tpb.insertTag(status, isc_tpb_nowait);
      tpb.insertTag(status, isc_tpb_rec_version);

      // This always seems to error with "invalid format for transaction parameter block"
      Transaction := attachment.startTransaction(status, tpb.getBufferLength(status), tpb.getBuffer(status));
    end
    else
    begin
      // Creating default transaction works fine. As an aside, what are the default transaction properties?
      Transaction := attachment.startTransaction(status, 0, nil);
    end;

    Statement := attachment.prepare(status, transaction, 0,
          'select rdb$relation_id relid, rdb$relation_name csname ' +
          '  from rdb$relations ' +
          '  where rdb$relation_id < ?',
             3, 0);

    Memo1.Lines.Add('Simple Plan: ' + Statement.getPlan(status, false));
    Memo1.Lines.Add('Detailed Plan: ' + Statement.getPlan(status, true));
    Memo1.Lines.Add('');

        transaction.rollback(status);

        Statement.free(status);
        attachment.detach(status);

    dpb.dispose;
    if UseCustomTransaction then
      tpb.dispose;

  except
    on E: FbException do
    begin
      SetLength(ErrorString, 2000);
      StatusVector := E.getStatus().getErrors();
      // Note that fb_interpret does not seem to appear in firebird.pas so we added it by hand.
      //   function fb_interpret(s: PAnsiChar; n: Cardinal; var statusVector: NativeIntPtr): Integer; cdecl;    external 'fbclient';
      SetLength(ErrorString, fb_interpret(PAnsiChar(ErrorString), 2000, StatusVector));
      ShowMessage(String(ErrorString));
    end
  end;

end;

Solution

  • There appears to be a problem with the IXpbBuilder (perhaps specific to TPB or to InsertTag) such that it creates an invalid transaction parameter buffer. This can be worked around by creating the buffer by hand as in the following code:

    //  var TransParamBuffer: TBytes
    SetLength(TransParamBuffer, 5);
    TransParamBuffer[0] := isc_tpb_version3;
    TransParamBuffer[1] := isc_tpb_write;
    TransParamBuffer[2] := isc_tpb_read_committed;
    TransParamBuffer[3] := isc_tpb_nowait;
    TransParamBuffer[4] := isc_tpb_rec_version;
    Transaction := attachment.startTransaction(status, Length(TransParamBuffer), @TransParamBuffer[0]);