4th Dimension and 4D Server/4D Client automatically manage databases by preventing multi-user or multi-process conflicts. Two users or two processes cannot modify the same record or object at the same time. However, the second user or process can have read-only access to the record or object at the same time.
There are several reasons for using the multi-user commands:
Modifying records by using the language.
Using a custom user interface for multi-user operations.
Saving related modifications inside a transaction.
There are three important concepts to be aware of when using commands in a multi-processing database:
Each table is in either a read-only or a read/write state.
Records become locked when they are loaded and unlocked when they are unloaded.
A locked record cannot be modified.
As a convention in the following sections, the person performing an operation on the multi-user database is referred to as the local user. Other people using the database are referred to as the other users. The discussion is from the perspective of the local user. Also, from a multi-process perspective, the process executing an operation on the database is the current process. Any other executing process is referred to as other processes. The discussion is from the point of view of the current process.
A locked record cannot be modified by the local user or the current process. A locked record can be loaded, but cannot be modified. A record is locked when one of the other users or processes has successfully loaded the record for modification. Only the user who is modifying the record sees that record as unlocked. All other users and processes see the record as locked, and therefore unavailable for modification. A table must be in a read/write state for a record to be loaded unlocked.
Read-Only and Read/Write States
Each table in a database is in either a read/write or a read-only state for each user and process of the database. Read-only means that records for the table can be loaded but not modified. Read/write means that records for the table can be loaded and modified if no other user has locked the record first.
Note that if you change the status of a table, the change takes effect for the next record loaded. If there is a record currently loaded when you change the table's status, that record is not affected by the status change.
When a table is read-only and a record is loaded, the record is always locked. In other words, the record can be displayed, printed, and otherwise used, but it cannot be modified.
Note that read-only status applies only to editing existing records. Read-only status does not affect the creation of new records. You can add records to a read-only table using CREATE RECORD and ADD RECORD or the New Record menu command from the User environment's Enter menu.
4th Dimension automatically sets a table to read-only for commands that do not require write access to records. These commands are:
SELECTION TO ARRAY
SELECTION RANGE TO ARRAY
You can find out the state of a table at any time using the Read only state function.
Before executing any of these commands, 4th Dimension saves the current state of the table (read-only or read/write) for the current process. After the command has executed, the state is restored.
When a table is read/write and a record is loaded, the record will become unlocked if no other user has locked the record first. If the record is locked by another user, the record is loaded as a locked record that cannot be modified by the local user.
A table must be set to read/write and the record loaded for it to become unlocked and thus modifiable.
If a user loads a record from a table in read/write mode, no other users can load that record for modification. However, other users can add records to the table, either through the CREATE RECORD or ADD RECORD commands or manually in the User environment.
Read/write is the default state for all tables when a database is opened and a new process is started.
Changing the Status of a Table
You can use the READ ONLY and READ WRITE commands to change the state of a table. If you want to change the state of a table in order to make a record read-only or read/write, you must execute the command before the record is loaded. Any record that is already loaded is not affected by the
READ ONLY and READ WRITE commands.
Each process has its own state (read-only or read/write) for each table in the database.
Loading, Modifying and Unloading Records
Before the local user can modify a record, the table must be in the read/write state and the record must be loaded and unlocked.
Any of the commands that loads a current record (if there is one) such as NEXT RECORD, QUERY, ORDER BY, RELATE ONE, etc. sets the record as locked or unlocked. The record is loaded according to the current state of its table (read-only or read/write) and its availability. A record may also be loaded for a related table by any of the commands that cause an automatic relation to be established.
If a table is in the read-only state, then a record loaded from that table is locked. A locked record cannot be saved or deleted. Read-only is the preferred state, because it allows other users to load, modify, and then save the record.
If a table is in the read/write state, then a record that is loaded from that table is unlocked only if no other users have locked the record first. An unlocked record can be modified and saved. A table should be put into the read/write state before a record needs to be loaded, modified, and then saved.
If the record is to be modified, you use the Locked function to test whether or not a record is locked by another user. If a record is locked (Locked returns True), load the record with the LOAD RECORD command and again test whether or not the record is locked. This sequence must be continued until the record becomes unlocked (Locked returns False).
When modifications to be made to a record are finished, the record must be released (and therefore unlocked for the other users) with UNLOAD RECORD. If a record is not unloaded, it will remain locked for all other users until a different current record is selected. Changing the current record of a table automatically unlocks the previous current record. You need to explicitly call UNLOAD RECORD if you do not change the current record. This discussion applies to existing records. When a new record is created, it can be saved regardless of the state of the table to which it belongs.
Note: When it is used in a transaction, the UNLOAD RECORD command unloads the current record only for the process that manages the transaction. For other processes, the record stays locked as long as the transaction has not been validated (or cancelled).
Use the LOCKED ATTRIBUTES command to see which user and/or process have locked a record.
Loops to Load Unlocked Records
The following example shows the simplest loop with which to load an unlocked record:
READ WRITE ([Customers]) ` Set the table's state to read/write Repeat ` Loop until the record is unlocked LOAD RECORD ([Customers]) ` Load record and set locked status Until (Not (Locked([Customers]))) ` Do something to the record here READ ONLY ([Customers]) ` Set the table's state to read-only
The loop continues until the record is unlocked.
A loop like this is used only if the record is unlikely to be locked by anyone else, since the user would have to wait for the loop to terminate. Thus, it is unlikely that the loop would be used as is unless the record could only be modified by means of a method.
The following example uses the previous loop to load an unlocked record and modify the record:
READ WRITE([Inventory]) Repeat ` Loop until the record is unlocked LOAD RECORD([Inventory]) ` Load record and set it to locked Until (Not (Locked([Inventory]))) [Inventory]Part Qty := [Inventory]Part Qty 1 ` Modify the record SAVE RECORD ([Inventory]) ` Save the record UNLOAD RECORD ([Inventory]) ` Let other users modfiy it READ ONLY([Inventory])
The MODIFY RECORD command automatically notifies the user if a record is locked, and prevents the record from being modified. The following example avoids this automatic notification by first testing the record with the Locked function. If the record is locked, the user can cancel.
This example efficiently checks to see if the current record is locked for the table [Commands]. If it is locked, the process is delayed by the procedure for one second. This technique can be used both in a multi-user or multi-process situation:
Repeat READ ONLY([Commands]) ` You do not need read/write right now QUERY([Commands]) ` If the search was completed and some records were returned If ((OK=1) & (Records in selection([Commands])>0)) READ WRITE([Commands]) ` Set the table to read/write state LOAD RECORD([Commands]) While (Locked([Commands]) & (OK=1)) `If the record is locked, ` loop until the record is unlocked ` Who is the record locked by? LOCKED ATTRIBUTES([Commands];$Process;$User;$Machine;$Name) If ($Process=-1) ` Has the record been deleted? ALERT("The record has been deleted in the meantime.") OK:=0 Else If ($User="") ` Are you in single-user mode $User:="you" End if CONFIRM("The record is already used by "+$User+" in the "+$Name+" Process.") If (OK=1) ` If you want to wait for a few seconds DELAY PROCESS(Current process;120) ` Wait for a few seconds LOAD RECORD([Commands])` Try to load the record End if End if End while If (OK=1) ` The record is unlocked MODIFY RECORD([Commands]) ` You can modify the record UNLOAD RECORD([Commands]) End if READ ONLY([Commands]) ` Switch back to read-only OK:=1 End if Until (OK=0)
Using Commands in Multi-user or Multi-process Environment
A number of commands in the language perform specific actions when they encounter a locked record. They behave normally if they do not encounter a locked record.
Here is a list of these commands and their actions when a locked record is encountered.
MODIFY RECORD: Displays a dialog box stating that the record is in use. The record is not displayed, therefore the user cannot modify the record. In the User environment, the record is shown in read-only state.
MODIFY SELECTION: Behaves normally except when the user double-clicks a record to modify it. MODIFY SELECTION displays dialog box stating that the record is in use and then allows read-only access to the record.
APPLY TO SELECTION: Loads a locked record, but does not modify it. APPLY TO SELECTION can be used to read information from the table without special care. If the command encounters a locked record, the record is put into the LockedSet system set.
DELETE SELECTION: Does not delete any locked records; it skips them. If the command encounters a locked record, the record is put into the LockedSet system set.
DELETE RECORD: This command is ignored if the record is locked. No error is returned. You must test that the record is unlocked before executing this command.
SAVE RECORD: This command is ignored if the record is locked. No error is returned. You must test that the record is unlocked before executing this command.
ARRAY TO SELECTION: Does not save any locked records. If the command encounters a locked record, the record is put into the LockedSet system set.
GOTO RECORD: Records in a multi-user/multi-process database may be deleted and added by other users, therefore the record numbers may change. Use caution when directly referencing a record by number in a multi-user database.
Sets: Take special care with sets, as the information that the set was based on may be changed by another user or process.
LOAD RECORD, Locked, LOCKED ATTRIBUTES, Methods, READ ONLY, Read only state, READ WRITE, UNLOAD RECORD, Variables.