Jump to content
Rafael Dipold

Error on read-only transaction

Recommended Posts

Hello friends,

 

Would anyone have a tip why the read-only error happens when I run the FireDAC commands in this order, like the MRE below?

 

    //READ ONLY TRANSACTION
    LTransactionRO := TFDTransaction.Create(nil);
    LQueryRO := TFDQuery.Create(nil);
    try
      LTransactionRO.Connection := FDManager.AcquireConnection('MY_DB', '');
      LTransactionRO.Options.ReadOnly := True;

      LQueryRO.Connection := FDManager.AcquireConnection('MY_DB', '');
      LQueryRO.Transaction := LTransactionRO;
      LQueryRO.Open('SELECT * FROM mY_TABLE'); //OK
    finally
      LQueryRO.Free;
      LTransactionRO.Free;
    end;

    //READ WRITE TRANSACTION
    LTransactionRW := TFDTransaction.Create(nil);
    LQueryRW := TFDQuery.Create(nil);
    try
      LTransactionRW.Connection := FDManager.AcquireConnection('MY_DB', '');
      LTransactionRW.Options.ReadOnly := False;

      LQueryRW.Connection := FDManager.AcquireConnection('MY_DB', '');
      LQueryRW.Transaction := LTransactionRW;

      //ERROR: cannot execute UPDATE in a read-only transaction.
      LQueryRW.ExecSQL('UPDATE MY_TABLE SET COLUM1 = COLUM1 WHERE 1 = 1');
    finally
      LQueryRW.Free;
      LTransactionRW.Free;
    end;

if I comment the line `//LTransactionRO.Options.ReadOnly := True;`, the SQL UPDATE run without errors.

 

Thanks for any tips!

Share this post


Link to post

Have you tried to explicitly commit or rollback the first transaction before you free the object?

Share this post


Link to post

Yes, same thing.

 

In my project transactions are always started and finished, I removed everything I could in the example above for simplification.

Share this post


Link to post

Um, in that case, it looks like

  • there's a typo somewhere, or
  • there's an unintentional transaction substitution

Personally, I would check the code.

Share this post


Link to post

 

Here is a verifiable example, just enter a valid database and table.

 

I don't see anything that could be wrong here, it seems to be a Firedac bug:

 

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  FireDAC.Comp.Client,
  FireDAC.DApt,
  FireDAC.Phys.PG,
  FireDAC.Phys.PGDef,
  FireDAC.Stan.Async,
  FireDAC.Stan.Def,
  FireDAC.Stan.Intf,
  System.SysUtils;

var
  LConnectionDef: IFDStanConnectionDef;
  LQueryRO: TFDQuery;
  LQueryRW: TFDQuery;
  LTransactionRO: TFDTransaction;
  LTransactionRW: TFDTransaction;
begin
  try
    FDManager.DriverDefFileAutoLoad := False;
    FDManager.ConnectionDefs.AutoLoad := False;
    FDManager.ConnectionDefs.Clear;

    FDManager.ConnectionDefs.BeginWrite;
    try
      LConnectionDef := FDManager.ConnectionDefs.AddConnectionDef;
      LConnectionDef.Name := 'MY_DB';

      with TFDPhysPGConnectionDefParams(LConnectionDef.Params) do
      begin
        DriverID := 'PG';
        CharacterSet := TFDPGCharacterSet.csUTF8;
        Server := '127.0.0.1';
        Database := 'MY_DB';
        Port  := 11786;
        UserName := 'myuser';
        Password := '123456';
      end;
    finally
      FDManager.ConnectionDefs.EndWrite;
    end;

    LTransactionRO := TFDTransaction.Create(nil);
    LQueryRO := TFDQuery.Create(nil);
    try
      LTransactionRO.Connection := FDManager.AcquireConnection('MY_DB', '');
      LTransactionRO.Options.ReadOnly := True;

      LQueryRO.Connection := FDManager.AcquireConnection('MY_DB', '');
      LQueryRO.Transaction := LTransactionRO;

      LQueryRO.Open('SELECT * FROM MY_TABLE');
    finally
      LQueryRO.Free;
      LTransactionRO.Free;
    end;

    LTransactionRW := TFDTransaction.Create(nil);
    LQueryRW := TFDQuery.Create(nil);
    try
      LTransactionRW.Connection := FDManager.AcquireConnection('MY_DB', '');
      LTransactionRW.Options.ReadOnly := False;

      LQueryRW.Connection := FDManager.AcquireConnection('MY_DB', '');
      LQueryRW.Transaction := LTransactionRW;

      //ERROR: cannot execute UPDATE in a read-only transaction.
      LQueryRW.ExecSQL('UPDATE MY_TABLE SET COLUMN_1 = COLUMN_1 WHERE 1=1');
    finally
      LQueryRW.Free;
      LTransactionRW.Free;
    end;

    Writeln('OK');
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

 

Share this post


Link to post

Hi!

Maybe I'm wrong but I think that you get two "different" connections when the AcquireConnection method is call.

 

I would do this:

 

LTransactionRW.Connection := FDManager.AcquireConnection('MY_DB', '');
LTransactionRW.Options.ReadOnly := False;

LQueryRW.Connection := LTransactionRW.Connection;
LQueryRW.Transaction := LTransactionRW;

 

Share this post


Link to post

I didn't even know about TFDManager. There is no answer here:

image.thumb.png.d283445b5bd7da6d1c5deecbb80a3602.png

Personally, I always work with a single Connection (in the DataModule) with a single transaction that I store in a variable.

Share this post


Link to post

Hi, 

 

I can consider now this problem solved.

As you can read here: Currently, explicit transaction object assignment is supported only for InterBase and Firebird.

I have always worked with Firebird and I was guided by the same logic to manage PostgreSQL transactions. And now I see that I will have to redesign my connection layer.

Thanks

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×