W0lfw00ds
W0lfw00ds

Reputation: 2096

Update buffer and reset the locking-mode back to original one

Lets say we have a procedure which takes in a buffer-parameter:

myprocedure.p:

DEF PARAM BUFFER bufferParameter FOR DatabaseTable.

/* Get the same buffer in EXCLUSIVE-LOCK for updating */
FIND CURRENT bufferParameter EXCLUSIVE-LOCK.

/* Update the value */
ASSIGN bufferParameter.MyField = "new value".

/* Reset the locking mode back to the original one? */
FIND CURRENT bufferParameter NO-LOCK.

The problem with this approach is that it is possibly changing the original locking-mode of the passed buffer parameter. It is possible that the buffer was in EXCLUSIVE-LOCK or f.ex in NO-LOCK. By changing the locking-mode, this procedure could invoke updating errors in some other programs using this same buffer.

Another solution would be to create a new temporary buffer for the same table, and then update the database thru that buffer (without touching the passed parameter buffer). The con of this approach is that the original passed buffer parameter will become 'out-of-date'. It would require a new database query to update it's 'MyField' value to match the one in the database.

How I could reset the buffer's locking-mode back to the original one after updating it's fields?

Upvotes: 1

Views: 1727

Answers (5)

W0lfw00ds
W0lfw00ds

Reputation: 2096

So I figured out that I was wrong when I thought that the original buffer was not updated when another buffer with that same record has updated the same record.

So a good way to update a passed buffer is to get the same record by ROWID and update it in another buffer separately:

DEF BUFFER myFirstBuffer FOR MyTable.
DEF BUFFER mySecondBuffer FOR MyTable.

FIND FIRST myFirstBuffer WHERE myFirstBuffer.MyField = "myvalue" NO-LOCK.

/* At this moment myFirstBuffer.MyField is "myvalue" */

FIND FIRST mySecondBuffer WHERE ROWID(mySecondBuffer) = ROWID(myFirstBuffer) EXCLUSIVE-LOCK.
ASSIGN mySecondBuffer.MyField = "mynewvalue".

/* Now both 'myFirstBuffer.MyField' and 'mySecondBuffer.MyField' match ("mynewvalue")! */

The Progress seems to handle all of the data changes and updates the buffers accordingly. However, I believe that this doesn't work if there are 2 separate programs running, f.ex myprogram1.p and myprogram2.p and they both update the same record at a different time (though this is another story...). Earlier we had some problems with TRIGGERs when the trigger affected fields were not updated on the buffer.

But in a nutshell, I think it's best to query the record separately with the ROWID and update it that way. I do believe that the developer shouldn't use FIND CURRENT myBuffer NO-LOCK. as that will change the locking-mode of the record and could break logics using myBuffer later.

Upvotes: 0

idspispopd
idspispopd

Reputation: 404

I think your second solution would be the one to use. Define a local buffer (no need to call it a temporary buffer which is not a clearly defined term in OpenEdge IMO), load the record of the parameter into this buffer using rowid (basically a free operation, if the parameter buffer was locked already) and change your local buffer. Don't worry about anything getting out of date. Quoting http://knowledgebase.progress.com/articles/Article/P4548 (please read the complete article)

Only a single copy of a record is allowed to exist at any one time in the client record pool. This means that in the following case shows two buffers pointing at the same physical copy of customer 1. This means after executing the following code

DEFINE BUFFER cust FOR customer.
DEFINE BUFFER cust1 FOR customer.
FIND FIRST cust no-lock.
FIND cust1 where rowid(cust1) = rowid(cust) exclusive-lock.
cust1.name = 'xyz':U.

cust.name would also have the value 'xyz'. I just tried the code to be sure. Indeed I also confirmed that the lock is not on the buffer but on the record. I could also assign cust.name even though the find to cust uses no-lock. If there is not already a transaction outside your program/procedure then the lock will be released when leaving the procedure. Definitely don't try to work with _lock unless it's necessary to show the user who is locking the record he wants to change (or similar purposes).

Upvotes: 0

Tom Bascom
Tom Bascom

Reputation: 14020

This thread might be useful background - it talks about how to figure out the current lock status.

http://stackoverflow.com/questions/26136842/progress-4gl-display-buffer-lock-type?rq=1

Ultimately I think this is what you really need:

    /* lightly tested psuedo code...
     */

    procedure updateMyField:

      define input parameter myRowid  as rowid.

      define buffer updMyTable for myTable.  /* this limits the scope of the buffer to this procedure */

      do for updMyTable transaction:  /* FOR "strong scopes" the buffer to this block */
        find updMyTable exclusive-lock where rowid( updMyTable ) = myRowid no-error.  /* the EXCLUSIVE-LOCK is scoped the FOR block -- when we leave the block it is released */
        if available updMyTable then
          assign
            updMyTable.myField = "new value"
          .
         else
          do:
            message "could not get an exclusive-lock on myTable".
            /* or whatever error handling you need... */
          end.
      end.  /* the transaction is committed and the lock is released -- no need to change its status */

      return.

    end.

    /* main block
     */

    find first myTable no-lock.
    display myTable.myField.
    pause.

    run updateMyField ( rowid( myTable )).

    find first myTable no-lock.
    display myTable.myField.
    pause.

Upvotes: 2

TheDrooper
TheDrooper

Reputation: 1217

You can't actually unlock a record like that when you're in a transaction. If you try, the exclusive lock becomes a limbo lock until the transaction ends and the changes are committed to the database. This is to prevent data integrity problems. If you're looking at the _Lock-Flags field, the limbo lock will be an "L". You can also use Promon to check the lock table status.

The transaction may be scoped to different procedures, depending on how you lock and update records. You can see if you're in a transaction by sticking this line in your code:

MESSAGE TRANSACTION VIEW-AS ALERT-BOX.

Here's the OpenEdge documentation on record locking and transaction scoping: https://documentation.progress.com/output/ua/OpenEdge_latest/index.html#page/gsabl/handling-data-and-locking-records.html#

Upvotes: 1

Screwtape
Screwtape

Reputation: 1367

It's bizarre that lock type is not an attribute of the buffer handle. It would seem to be an obvious necessity.

You can find the _lock records for the recid, but you need to find the table ID as well, which would mean looking it up in _file from the handle:table.

So, assuming you've only one database connected, you'd have something like:

FIND _file NO-LOCK WHERE _file-name = bufHandle:TABLE.
FIND _lock NO-LOCK WHERE _lock-name = USERID()
    AND _lock-table = _file-number
    AND _lock-recid = bufHandle:RECID.

If the record exists, you know you have a lock. The _lock_flags will tell you what kind of locks you have. I think S is share and E is exclusive and think if there is a Q then you are waiting for that lock, which I wouldn't expect if you already have a lock on the record.

You could then re-lock the record in the appropriate state if it wasn't already exclusively locked when your function began.

Upvotes: 1

Related Questions