Home > Downloads > Papers > The Joy of Triggers |
By David Adams and Dan Beckett
Adapted from Programming 4th Dimension: The Ultimate Guide. You can download a PDF version of this article (968K) for easier reading and printing.
You can download an updated versionof this article in PDF format (944K).
To harness the full potential of 4D V6 you need to understand the topics covered in this presentation:
We discuss these subjects in plain language, and point out the common pitfalls to avoid. What you'll find is that these new features are opportunities to implement better systems more easily.
Whenever an invoice is deleted, all line items must be deleted, too.
Whenever the on-hand supply of a part drops below the reorder point, a purchase order reminder must be created.
Triggers are a centralized way of managing data integrity. Triggers are table methods used to control and validate actions that affect records or the table as a whole.
"Trigger" refers to two related concepts:
The code that runs in response to these events.
The Table Properties dialog lets you specify which events you want to run code in response to. In this case "trigger" means the events that cause your trigger code to run. These events are called "database events". An alternate term might be "table events" or "record events":
You can trap four distinct events:
Any time something happens that causes a database event, the trigger code for that table runs.
Triggers are great because they run whenever data changes. There's no way for something to "slip through the cracks". Triggers run when records are changed from any of these sources:
The standard rules you define for your tables are enforced no matter what. The logic resides on the server, and needs to be written only once. You don't need to write different code for different types of client software, and you don't have to worry about forgetting to write the necessary code. Triggers also give you a chance to validate a table's rules before committing the record. If there is any problem, you can halt the operation.
The easiest way to write triggers is to start with a piece of paper. Write down the rules for each table in your database narratively. Before you write any code answer these questions for each table:
We've included a simple Trigger Development Worksheet at the end of this handout. It provides reminders of the key pieces of information you need to define for each table database event.
If you built your database from a formal design you already have these rules documented on paper. If you built your database without documenting these rules, it's not too late. Putting these rules on paper provides these benefits:
Here's a sample of what a trigger looks like:
` [Customers] Trigger
C_LONGINT($0;$ErrorCode)
$ErrorCode:=0
Case of
: (Database event=Save Existing Record Event)
$ErrorCode:=Save_Customer
: (Database event=Save New Record Event)
$ErrorCode:=Save_Customer
: (Database event=Delete Record Event)
$ErrorCode:=Delete_Customer
: (Database event=Load Record Event)
` No rules in place
End case
$0:=$ErrorCode
Project methods are often the ideal place to put trigger code. In the example shown above each trigger event calls a project method instead of placing the code directly in the trigger. This provides three benefits:
Triggers are functions that return an error code. If you can't perform a necessary action inside a trigger--perhaps you can't delete a related record--then returning an error code halts the operation. You can use numbers from -15,000 to -32,000 for your custom error codes. The end user sees an error message just as if 4D generated the error. (The dialog does not appear on the server machine.)
There are restrictions on the commands you can use in a trigger. You must never use any command that changes the current record for the table the trigger belongs to. Doing so can cause crashes and/or data corruption. For example, you should never execute SAVE RECORD ([MyTable]) in the trigger for [MyTable]. These restrictions, however, do not apply to tables other than the table the trigger belongs to, so you can execute SAVE RECORD ([MyOtherTable]) in a trigger for [MyTable].
Do not invoke user interface objects from a trigger. A trigger may execute on a machine the user is not on--this is true under 4D Server V6, for Web connections, and for 4D Open clients. Do not use any command that opens a window, including ALERT , CONFIRM , DIALOG , MESSAGE , Open window , and Request .
Triggers run at a very low level of the database engine. For example, the On Load database event occurs once for each record displayed in an output form. Under 4D Server, the trigger runs on the server machine. So a long trigger, executed for each record at an output form on a single client machine, reduces the server processing time available to all other processes and clients. Because triggers can run frequently, it is essential that you make sure that only the events you use are turned on in the table properties dialog, and that your trigger code executes quickly and is error free.
Also, it is both good style and proper programming to use triggers only for enforcing table-level rules, and never for anything interface related.
Triggers execute on the machine where the database engine runs. This means that under 4D they run on the current machine, and under 4D Server they run on the server machine. The effect of this is that your code can behave very differently under 4D Server than under 4D. Here are the key differences:
Before you get too alarmed, you should know that a trigger shares the current record and current selection with the client machine. You don't need to do anything special there. Variables are the only area where there is a difference. You need to know how 4D Server V6 handles variables for code that runs on the server machine to make your triggers work as you want. The way variables work also affects database methods.
When a database is compiled, a "process variable table" and an "interprocess variable table" are created that describe all the variables in the database. All processes on a workstation share one copy of the interprocess variable table. Each global and local process on the client machine has its own copy of the process variable table. So, each process has the same variables defined, but they have different instances of the process variables. In other words, the process variable names and types are the same in each process, but the values are not. For example, the following table lists the value of a process variable in five different processes:
The names and types are constant among these processes, but the values are unrelated to one another.
In an interpreted database, variables are created and typed dynamically as they are used. Different processes end up with different variables defined. You can even give the same variable different data types in different processes. None of this is true in a compiled database. Each variable can have one and only one type and there is only one process variable table defined for the entire system no matter how many instances of the variable table exist. As you code, you should always remember these rules or your database will either not compile or will behave differently when compiled.
Duplicating the process variable table takes time and space. In order to optimize performance on the server machine, several server-executed routines share a single copy of the process variable table and therefore the same process variables. This is different from global processes under 4D or 4D Client where each process always has its own copy of each process variable to work with.
The following table shows the code that shares a single instance of a process variable table:
Share One Instance of Process Variable Table | Have Their Own Process Variable Table |
---|---|
All triggers
All server-executed local processes On Server Startup method On Server Open Connection method On Server Close Connection method On Server Shutdown method |
On Web Connection processes
Stored procedures |
All of the methods/processes listed on the left share a single process variable table. In other words, they have amongst them a single instance of any process variable. If, for example, you set a process variable in the trigger for one table, another table's trigger can overwrite that value. For this reason, process variables are not generally useful in these methods.
The processes listed on the right each have their own, distinct instance of the process variable table. So, like global processes, they have their own process variables.
Now that you know all about how variables are handled on 4D Server, here's what you need to remember when writing your triggers:
While we're on the subject of how code and variables are run on the server, let's review how 4D's database methods work.
4D and 4D Server V6 include seven database methods that give you the ability to perform special actions when the database starts up and shuts down, and when clients connect and disconnect:
These methods execute at specific times, in specific environments, and on specific machines. Some of these methods take parameters, have special restrictions, or include optional behavior. In this part of the handout we'll provide you with a lot of the nitty-gritty details.
Each of 4D V6's database methods execute on only one machine. Which machine depends on the method and whether you are using 4D or 4D Server. The type of process each method executes in also varies. Some execute in local, and some in global processes. Some of them execute in global processes, but share a single set of process variables with one another (and triggers). The following table summarizes the context in which each database method executes. The "*" indicates the global processes that share a common set of process variables.
Method
|
4D
|
4D Client | 4D Server |
---|---|---|---|
On Startup | Global | Global | |
On Server Open Connection | Global* | ||
On Server Close Connection | Global* | ||
On Exit | Local | Local | |
On Server Startup | Local | ||
On Server Shutdown | Global* | ||
On Web Connection | Global | Global |
The On Startup database method executes in the User/Custom Menus process when 4D starts or when a 4D Client connects. (It is the 4D V6 implementation of the Startup procedure from 4D 3.x.)
When you create a global process under 4D Client, one copy of the process starts on the client machine and a corresponding process starts on the server machine. The server-side connection process keeps track of data--like sets and selections--for the client and performs a lot of work on the server machine, avoiding sending data to the client needlessly. Each time a client process starts, the On Server Open Connection method runs on the server machine in the server-side instance of the global process. Each time a client process stops, the On Server Close Connection method runs on the server machine in the server-side instance of the global process. Because these methods execute on the server machine, it is prudent to keep the work they perform to a minimum because code executed on the server machine affects all users.
Each global process under 4D or 4D Client has its own set of process variables. Under 4D Server, all server-side connection processes share a single set of process variables with each other and with triggers. Because of this, you cannot rely on values stored in process variables in the server connection methods. You can, however, use interprocess variables, interprocess arrays, and records to store information.
4D V6 uses method parameters as a mechanism to allow you to uniquely identify and track processes. This enables you, for example, to log connection activity. 4D Server automatically provides On Server Open Connection and On Server Close Connection with three parameters. These parameters are longints and should be declared in the database methods themselves.
Parameter
|
Description
|
---|---|
User ID | Unique user ID used internally by 4D Server. This value does not correspond to the users and groups ID system. |
Connection ID | A unique connection ID supplied by the network component. |
Network Component ID | The ID of the network component the connection is established through. |
Both database methods receive the same parameters, and for any single process the values remain the same. If you open a connection you might see values like these:
Process
|
User
|
Connection
|
Network Component
|
---|---|---|---|
User/Custom Menus process | 40137148 | 40136076 | 4 |
Design process | 40137148 | 40135004 | 4 |
Customer_lookups | 40137148 | 40133932 | 4 |
For any particular client, the "user ID" remains the same, but each of their connection IDs is distinct. The combination of these three parameters is sure to be unique at any time. When a particular connection stops and On Server Close Connection runs, the parameter values are the same as when On Server Open Connection originally ran for this process. This mechanism gives you a way of identifying connection processes.
The On Server Open Connection method is a function that returns a longint result in $0. If no value is returned, or if you return a 0, the connection is allowed. If you return a non-zero value, the connection is rejected.
Accepting or rejecting a connection can only be done for the first connection attempt for a particular client. Once the first process has been accepted or rejected for a particular client, each subsequent process for that client follows suit no matter what number On Server Open Connection returns.
The On Exit database method executes on 4D or 4D Client when the user quits. This method executes in its own local process, so you cannot use it to modify data stored in 4D records. You can, however, send data to local disk files. If you are running under 4D Server and the database is quit by 4D Server, the On Exit method does not run on 4D Client. Under 4D Client/4D Server On Exit only executes when the client initiates the exit.
The On Server Startup and On Server Shutdown database methods execute as local processes on the server machine when a database is opened and closed, respectively. This is the ideal place to perform general housekeeping tasks and manage stored procedures. End users are not affected by these database methods because the methods have either already finished by the time the database is available on the network or not started until all clients have logged off.
Because these processes are local, they are restricted in which structure objects they can access.
The On Web Connection database method runs on the 4D or 4D Server machine for each new Web connection. For any particular web client, the On Web Connection method runs only the first time that client connects. Subsequent connections by the same web client do not cause the method to run again.
This method automatically receives two text parameters that should be declared in this method directly.
Parameter
|
Description
|
---|---|
URL | Portion of URL after machine address. |
HTTP Request Header | A unique connection ID supplied by the network component. |
These parameters supply information that allows you to take special action depending on the URL the user entered, or special attributes of the header. You can, for example, program your system to send different results depending on the user's browser.
Database Name |
|
Table Name |
|
Trigger ( Check one)
|
|
Method Name |
|
Action(s) |
|
Error Codes Include descriptions |
|
Products | Trainings | Downloads | Book Reviews | Links | About | |
Copyright © 1997-2004 Island Data/4DCompanion.com. Send comments and questions to dpadams@island-data.com or info@4DCompanion.com.