How to identify the lock acquired by another user on a record in progress openedge - progress-4gl

User A has read first record from a customer table. User B wishes to know the lock which is held by A on the record. How will B identify the lock held by User A programmatically.

You can use LOCKED function:
Returns a TRUE value if a record is not available to a prior FIND . .
. NO-WAIT statement because another user has locked a record.
Example of the documentation:
REPEAT:
PROMPT-FOR customer.cust-num.
FIND customer USING customer.cust-num NO-ERROR NO-WAIT.
IF NOT AVAILABLE customer THEN DO:
IF LOCKED customer
THEN MESSAGE "Customer record is locked".
ELSE MESSAGE "Customer record was not found".
NEXT.
END.
DISPLAY cust-num name city state.
END.
Other example:
FIND FIRST table
WHERE table.c1 = "foo"
EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
IF LOCKED(table)
THEN DO:
/* The record is blocked by another user */
END.
ELSE DO:
IF AVAILABLE table
THEN DO:
/* The record can be modified */
END.
END.

You can use the built in _Lock Virtual System Table.
This is a basic example:
DEFINE TEMP-TABLE ttLock
FIELD LockId LIKE _Lock._Lock-Id
FIELD LockUsr LIKE _Lock._Lock-Usr
FIELD LockName LIKE _Lock._Lock-Name
FIELD LockTable LIKE _Lock._Lock-Table
FIELD LockFlags LIKE _Lock._Lock-flags
INDEX LockIdx IS PRIMARY UNIQUE LockId.
FOR EACH _Lock NO-LOCK:
IF _Lock._Lock-Usr = ? THEN NEXT .
CREATE ttLock.
ASSIGN
LockId = _Lock._Lock-Id
LockUsr = _Lock._Lock-Usr
LockName = _Lock._Lock-Name
LockTable = _Lock._Lock-Table
lockFlags = _Lock._Lock-flags.
END.
FOR EACH ttlock:
FIND _Trans NO-LOCK WHERE _Trans._Trans-Usrnum = ttLock.LockUsr NO-ERROR.
FIND _File NO-LOCK WHERE _File-Number = ttLock.LockTable.
MESSAGE
"Transaction Id:~t" (IF AVAILABLE _Trans THEN _Trans._Trans-Id ELSE ?) "~n"
"User Number:~t" ttLock.LockUsr "~n"
"User Name~t" ttLock.LockName "~n"
"Table Number:~t" ttLock.LockTable "~n"
"Table Name:~t" _File-Name "~n"
"Flags:~t" ttLock.LockFlags
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END.
(Taken directly from this entry in the knowledgebase.)
In the Database Administration guide you can see (page 848) you can see what the flags in _Lock.LockFlags stand for.
Flags for the lockā€”the flags specify a share
lock (S), exclusive lock (X), a lock upgraded
from share to exclusive(U), a lock in limbo (L),
a queued lock(Q), a lock kept across
transaction end boundary (K), a lock is part of
a JTAtransaction (J), a lock is in create mode
for JTA (C), or a lockwait timeout has expired
on this queued lock (E)
Querying the _Lock table might be something you want to do in test environments only. Depending on your systems size there might be LOT of data there. Also, use VSTs for READ-ONLY operations!

Querying _LOCK in older releases is OK, but you have to use the appropriate code for the respective version:
_LOCK always has the full number of entries given in -L, regardless how many locks currently exist.
In pre-11.4 releases the fields are not indexed, but all used locks are at the beginning of the table, so you can use
IF _Lock._Lock-Usr = ? THEN LEAVE.
in the for each loop (_Lock._Lock-Name = ? is also fine). See http://knowledgebase.progress.com/articles/Article/P161995
In 11.4 and 11.5 the populated entries are no longer at the beginning so the old code will give wrong results (see http://knowledgebase.progress.com/articles/Article/000056304, this is fixed in 11.5.1). Fortunately scanning the lock table is now much faster so you can use
FOR EACH _Lock NO-LOCK WHERE _Lock-Recid <> ?:
mentioned in the same article. Technically this is not implemented with indices. (An index wouldn't work with the <> operator.)
In 11.5 and 11.6 both variants should work, but the newer variant with a where phrase should be faster.

Related

Keeping two fields in sync when one of them is updated

The application I'm working on is a character based application. there is a field adfc.unme-code in a table and another field arbu.unit-code-shu. These two fields are shown in different windows but they must be in sync. When I update unme-code, unit-code-shu must be updated too.
Is a simple assign statement would be enough or should I do something else? Also, should I use the actual fields or a buffer?
You can use the ASSIGN statement in your code to assign both values at the same time. If there's a possibility of some other process changing those fields, you can create a database trigger procedures on each field to copy the value over to the other field. In the Data Dictionary, view the field properties and click on the "Triggers..." button to do that.
Yes, ASSIGN is used to set the value of a buffers/records field. However: certain critera needs to be met:
The buffer/record needs to be available.
You must have the "lock" of the buffer/record.
If you have the record available and locked you can simply do:
ASSIGN arbu.unit-code-shu = adfc.unme-code.
To make this code "safer" you can simply make sure that arbu is available and not locked by any other user. And finally it will handle if your assign fails because you have no lock at all.
IF AVAILABLE arbu AND NOT LOCKED arbu THEN DO:
ASSIGN arbu.unit-code-shu = adfc.unme-code NO-ERROR.
IF ERROR-STATUS:GET-NUMBER(1) = 396 THEN DO:
MESSAGE "Apparently the record is locked." VIEW-AS ALERT-BOX ERROR.
END.
END.
If you do not have the record (or the right locking) you need to look into your application and see how you can add this feature. What identifies the right record of the second table? A single id? Something else? Are there always a 1 to 1 relation between arbu and adfc etc?
Perhaps your application has a simple way of setting the value of a field. If there is a architecture at place you should try to stick to it...

Code to assign an ID when button is clicked

I have designed a simple database to keep track of company contacts. Now, I am building a form to allow employees to add contacts to the database.
On the form itself, I have all the columns except the primary key (contactID) tied to a text box. I would like the contactID value to be (the total number of entered contacts + 1) when the Add button is clicked. Basically, the first contact entered will have a contactID of 1 (0 + 1 = 1). Maybe the COUNT command factors in?
So, I am looking for assistance with what code I should place in the .Click event. Perhaps it would help to know how similar FoxPro is to SQL.
Thanks
The method you recommend for assigning ContactIDs is not a good idea. If two people are using the application at the same time, they could each create a record with the same ContactID.
My recommendation is that you use VFP's AutoIncrementing Integer capability. That is, set the relevant column to be Integer (AutoInc) in the Table Designer. Then, each new row gets the next available value, but you don't have to do any work to make it happen.
There are various ways to do this. Probably the simplest is to attempt to lock the table with flock() when saving, and if successful do:
calc max id_field to lnMax
Then when inserting your new record use lnMax+1 as the id_field value. Don't forget to
unlock all
... after saving. You'll want to ensure that 'id_field' has an index tag on it, and that you handle the case where someone else might have the table locked.
You can also do it more 'automagically' with a stored procedure.

Get next available auto_increment ID in PostgreSQL - A better approach?

I'm new to postgreSQL, so would really appreciate any pointers from the community.
I am updating some functionality in the CMS of a pretty old site I've just inherited. Basically, I need the ID of an article before it is inserted into the database. Is there anyway anyway to check the next value that will be used by a sequence before a database session (insert) has begun?
At first I thought I could use SELECT max(id) from tbl_name, however as the id is auto incremented from a sequence and articles are often deleted, it obviously won't return a correct id for the next value in the sequence.
As the article isn't in the database yet, and a database session hasn't started, it seems I can't use the currval() functionality of postgreSQL. Furthermore if I use nextval() it auto increments the sequence before the data is inserted (the insert also auto-incrementing the sequence ending up with the sequence being doubly incremented).
The way I am getting around it at the moment is as follows:
function get_next_id()
{
$SQL = "select nextval('table_id_seq')";
$response = $this->db_query($SQL);
$arr = pg_fetch_array($query_response, NULL, PGSQL_ASSOC);
$id = (empty($arr['nextval'])) ? 'NULL' : intval($arr['nextval']);
$new_id = $id-1;
$SQL = "select setval('table_id_seq', {$new_id})";
$this->db_query($SQL);
return $id;
}
I use SELECT nextval('table_id_seq') to get the next ID in the sequence. As this increments the sequence I then immediately use SELECT setval('table_id_seq',$id) to set the sequence back to it's original value. That way when the user submits the data and the code finally hits the INSERT statement, it auto increments and the ID before the insert and after the insert are identical.
While this works for me, I'm not too hot on postgreSQL and wonder if it could cause any problems down the line, or if their isn't a better method? Is there no way to check the next value of a sequence without auto-incrementing it?
If it helps I'm using postgresql 7.2
Folks - there are reasons to get the ID before inserting a record. For example, I have an application that stores the ID as part of the text that is inserted into another field. There are only two ways to do this.
1) Regardless of the method, get the ID before inserting to include in my INSERT statement
2) INSERT, get the the ID (again, regardless of how (SELECT ... or from INSERT ... RETURNING id;)), update the record's text field that includes the ID
Many of the comments and answers assumed the OP was doing something wrong... which is... wrong. The OP clearly stated "Basically, I need the ID of an article before it is inserted into the database". It should not matter why the OP wants/needs to do this - just answer the question.
My solution opted to get the ID up front; so I do nextval() and setval() as necessary to achieve my needed result.
Disclaimer: Not sure about 7.2 as I have never used that.
Apparently your ID column is defined to get its default value from the sequence (probably because it's defined as serial although I don't know if that was available in 7.x).
If you remove the default but keep the sequence, then you can retrieve the next ID using nextval() before inserting the new row.
Removing the default value for the column will require you to always provide an ID during insert (by retrieving it from the sequence). If you are doing that anyway, then I don't see a problem. If you want to cater for both scenarios, create a before insert trigger (does 7.x have them?) that checks if the ID column has a value, if not retrieve a new value from the sequence otherwise leave it alone.
The real question though is: why do you need the ID before insert. You could simply send the row to the server and then get the generated id by calling curval()
But again: you should really (I mean really) talk to the customer to upgrade to a recent version of Postgres

APEX - Creating a page with multiple forms linked to multiple related tables... that all submit with one button?

I have two tables in APEX that are linked by their primary key. One table (APEX_MAIN) holds the basic metadata of a document in our system and the other (APEX_DATES) holds important dates related to that document's processing.
For my team I have created a contrl panel where they can interact with all of this data. The issue is that right now they alter the information in APEX_MAIN on a page then they alter APEX_DATES on another. I would really like to be able to have these forms on the same page and submit updates to their respective tables & rows with a single submit button. I have set this up currently using two different regions on the same page but I am getting errors both with the initial fetching of the rows (Which ever row is fetched 2nd seems to work but then the page items in the form that was fetched 1st are empty?) and with submitting (It give some error about information in the DB having been altered since the update request was sent). Can anyone help me?
It is a limitation of the built-in Apex forms that you can only have one automated row fetch process per page, unfortunately. You can have more than one form region per page, but you have to code all the fetch and submit processing yourself if you do (not that difficult really, but you need to take care of optimistic locking etc. yourself too).
Splitting one table's form over several regions is perfectly possible, even using the built-in form functionality, because the region itself is just a layout object, it has no functionality associated with it.
Building forms manually is quite straight-forward but a bit more work.
Items
These should have the source set to "Static Text" rather than database column.
Buttons
You will need button like Create, Apply Changes, Delete that submit the page. These need unique request values so that you know which table is being processed, e.g. CREATE_EMP. You can make the buttons display conditionally, e.g. Create only when PK item is null.
Row Fetch Process
This will be a simple PL/SQL process like:
select ename, job, sal
into :p1_ename, :p1_job, :p1_sal
from emp
where empno = :p1_empno;
It will need to be conditional so that it only fires on entry to the form and not after every page load - otherwise if there are validation errors any edits will be lost. This can be controlled by a hidden item that is initially null but set to a non-null value on page load. Only fetch the row if the hidden item is null.
Submit Process(es)
You could have 3 separate processes for insert, update, delete associated with the buttons, or a single process that looks at the :request value to see what needs doing. Either way the processes will contain simple DML like:
insert into emp (empno, ename, job, sal)
values (:p1_empno, :p1_ename, :p1_job, :p1_sal);
Optimistic Locking
I omitted this above for simplicity, but one thing the built-in forms do for you is handle "optimistic locking" to prevent 2 users updating the same record simultaneously, with one's update overwriting the other's. There are various methods you can use to do this. A common one is to use OWA_OPT_LOCK.CHECKSUM to compare the record as it was when selected with as it is at the point of committing the update.
In fetch process:
select ename, job, sal, owa_opt_lock.checksum('SCOTT','EMP',ROWID)
into :p1_ename, :p1_job, :p1_sal, :p1_checksum
from emp
where empno = :p1_empno;
In submit process for update:
update emp
set job = :p1_job, sal = :p1_sal
where empno = :p1_empno
and owa_opt_lock.checksum('SCOTT','EMP',ROWID) = :p1_checksum;
if sql%rowcount = 0 then
-- handle fact that update failed e.g. raise_application_error
end if;
Another, easier solution for the fetching part is creating a view with all the feilds that you need.
The weak point is it that you later need to alter the "submit" code to insert to the tables that are the source for the view data

Sybase select variable logic

Ok, I have a question relating to an issue I've previously had. I know how to fix it, but we are having problems trying to reproduce the error.
We have a series of procedures that create records based on other records. The records are linked to the primary record by way of a link_id. In a procedure that grabs this link_id, the query is
select #p_link_id = id --of the parent
from table
where thingy_id = (blah)
Now, there are multiple rows in the table for the activity. Some can be cancelled. The code I have doesn't disinclude cancelled rows in the select statement, so if there are previously cancelled rows, those ids will appear in the select. There is always going to be one 'open' record that is selected if I disinclude cancelled rows. (append where status != 'C')
This solves this issue. However, I need to be able to reproduce the issue in our development environment.
I've gone through a process where I've entered a whole heap of data, opening, cancelling, etc to try and get this select statement to return an invalid id. However, whenever I run the select, the ids are in order (sequence generated), but in the case where this error occured, the select statement returned what seems to be the first value into the variable.
For example.
ID Status
1 Cancelled
2 Cancelled
3 Cancelled
4 Open
Given the above, if I do a select for the ID I want, I want to get '4'. In the error, the result is 1. However, even if I enter in 10 cancelled records, I still get the last one in the select.
In oracle, I know that if you select into a variable and more than one record is returned, you get an error (I think). Sybase apparently can assign multiple values into a variable without erroring.
I'm thinking that there's either something to do with how the data is selected from the table, where the id's without a sort order don't return in ascending order, or there's a dboption where a select into a variable will save the first or last value queried.
Edit: it looks like we can reproduce this error by rolling back stored procedure changes. However, the procs don't go anywhere near this link_id column. Is it possible that changes to the database architecture could break an index or something?
If more than one row is returned, the value that is stored will be the last value in the list, according to this.
If you haven't specified an order for retrieval via ORDER BY, then the order returned will be at the convenience of the database engine. It may very well vary by the database instance. It may be in the order created, or even appear "random" because of where the data is placed within the database block structure.
The moral of the story:
Always make singleton SELECTs return a single row
When #1 can't be done, use an ORDER BY to make sure the one you care about comes last