In patch 10.3.02.057, we made some changes to how the Uniface compiler handles component variations that overlay modelled triggers. We have learned that our earlier change to the inheritance model has been complicating customer migrations too much and it has made us rethink our solution.
In essence, we have revived the Uniface 9 trigger behaviour, enabling default trigger behaviour for all triggers that have default behaviour. For customers migrating from Uniface 9 to Uniface 10, this will make the migration smoother. For those of you who have already migrated to Uniface 10, this may result in some different behaviour. Here, we'll explain how Uniface 10 deals with your triggers and why we have chosen this behaviour.
Previous Uniface 10 Trigger behaviour
Previously, in Uniface 10, a trigger that had default behaviour and was empty was treated by the compiler as if the trigger was undeclared, but triggers with no default behaviour were not handled like this. Even worse, some triggers with default behaviour were already reverted to the Uniface 9 way of compiling them while others were not. The Read, Lock, and Validate (Entity) triggers have now been corrected, to align ALL triggers.
For example, an empty Read trigger (for an inner Entity) behaves differently to an undeclared Read trigger; a done statement is executed and data is retrieved from the database, even when no read statement is defined. If a Read trigger is undeclared, no code is executed and no data is read from the database, which is the expected behaviour.
The advantage of this approach is that it makes behaviour explicit, so if a trigger fires a read statement, you actually see that read statement in code. The disadvantage is that this behaviour is different from Uniface 9, where an empty trigger always means: do nothing, no default behaviour. This implies that in Uniface 10, all empty triggers that are migrated from Uniface 9 applications would have to be undeclared to get the same runtime behaviour, which can be a cumbersome task.
It is important to note that we are referring to compiler behaviour and how it enables the default behaviour of triggers. This is different from the way the runtime engine determines (or executes) the default behaviour of a trigger. If the compiler disables the default behaviour, the runtime engine just executes an end statement if the trigger is empty. If the compiler enables the default behaviour, the runtime decides what should happen—this can be nothing, or some implicit behaviour, depending on the trigger involved.
For example, if the operation Exec (which actually was a trigger in Uniface 9), is empty (or undeclared), the runtime will behave as if an edit statement is defined. Since edit is only executed for Form and DSP type components, where a USP would issue a webget and webgen, this is actually a good example of the runtime implementing the actual execution.
New Uniface 10 Trigger behaviour
To resolve this, we have enabled the default trigger behaviour for all triggers that have default behaviour so that it works as it did in Uniface 9. We expect this will make migrations to Uniface 10 considerably smoother and that it will have minimal impact on applications where development started in Uniface 10. Yes, there can be a different behaviour so please be aware.
Why Didn't We Do This in the Beginning?
The underlying idea was to remove the magic within Uniface so that if a trigger fires a read statement, you actually see that read statement. If the exec operation (trigger) fires an edit, you would see it in code. Magic and default behaviour are nice and can be very powerful, but they make it difficult to explain and understand, especially if you are coming from a non-Uniface world. And we do want to grow the Uniface business and attract more developers.
However, we value our existing customers and want to keep their applications compatible at a low cost. In addition, we want to keep migrations as simple as we can. If the balance is wrong, we rethink and adapt which is what we've done in this case.