Skip to main content
Version: 7.0

Constraints

Constraints in the platform determine which values the data properties can have and which cannot. In general, a constraint is defined as a property which value should always be NULL.

In fact, a constraint is a simple event, where condition is the constrained property and handling is showing all the non-NULL values (in the asynchronous message mode) and canceling all the changes. As well as for a simple event, you need to specify a base event, which determines when the defined constraint is checked.

Compared to implementation via simple events, constraints have a set of additional advantages:

  • There is a global checking procedure upon a working database for constraints (similarly to "recalculation" technique in simple events which is not applicable here as long as the handler contains the cancel changes operator)
  • Constraints are more understandable and readable since, unlike simple events, they emphasize the static/declarative nature of these rules, i.e. their independence from the moment in time.
  • You can use the created constraint when showing dialogs for changing properties used in this constraint. In this case, an additional filter will be set in the dialog so that, when the property value changes to the selected one, the constraint is not violated.

Note that in some cases, instead of showing a message to the user and canceling the transaction, it is necessary, for example, to automatically resolve the violated constraint. In that case, it is recommended to use simple constraints, or, if it is impossible, simple events.

The set of rows checked is determined by the change of the constrained property, not by the objects edited in the session: the changes are canceled when the constrained property became non-NULL on at least one row as a result of the applied changes. A change of any property it depends on — including a property with no parameters, such as a global threshold setting — forces this check to compare the new value of the constrained property with the pre-change one on all rows where its other operands are defined. So when the constrained property compares a large stored property with such a threshold, applying a change of the threshold alone evaluates the condition over all rows of the stored property, which on a large table can take minutes. When only the rows changed in the session must be checked, add to the condition an explicit change condition on the stored operand: a change of the threshold alone then triggers no check at all — but rows that violate the new threshold without being edited in the session are deliberately left unchecked.

The changes that trigger the check also include object deletion: on deletion the data properties of the object are reset to NULL; its class-membership check and the properties calculated over it that require that membership also return NULL. So a constraint whose condition combines a change condition on a property of the object, one that covers the transition of the value to NULL, with the negation of another of its properties that returns NULL after deletion fires on the deletion of every object whose changed property had a non-NULL previous value: the change condition holds because of the reset to NULL, and the negation of NULL is true. When the constraint is not meant to prohibit deletion, add the object's class-membership check to the condition: for the deleted object it returns NULL, and the constraint does not fire. All of the above also applies to a class change that moves the object out of its previous class: it acts in the same way as deletion.

Like any event condition, the constrained property is computed incrementally over the changes being applied. If it contains heavy aggregations over large tables (especially nested non-materialized ones), the query built by this incremental computation can grow impractically large — even with computation hints on the properties involved. For such expensive checks, use a simple event instead: make its condition a cheap detector of the relevant changes, and in its handler read the heavy values into local properties, check them, and show the message and cancel the changes explicitly.

Show message

For any non-NULL value output the platform uses an automatically generated form, consisting of:

  • one group of objects with the objects corresponding to the parameters of the constrained property.
  • properties with the matching classes and either belonging to property group System.id or explicitly specified when creating the constraint.
  • a filter equal to the constrained property.
  • a global message defined by the developer when creating the constraint.

Language

Constraints are created using the CONSTRAINT statement.

Examples

// balance not less than 0
CONSTRAINT balance(Sku s, Stock st) < 0 MESSAGE 'The balance cannot be negative for ' +
(GROUP CONCAT 'Product: ' + name(Sku ss) + ' Warehouse: ' + name(Stock sst), '\n' IF SET(balance(ss, sst) < 0) ORDER sst);

limit 'Maximum balance' = DATA NUMERIC[16,3] ();
// check only the rows changed in the session: changing limit() alone does not re-evaluate all balances
CONSTRAINT CHANGED(balance(Sku s, Stock st)) AND balance(s, st) > limit()
MESSAGE 'The balance exceeds the allowed maximum';

barcode = DATA STRING[15] (Sku);
// "emulation" security policy
CONSTRAINT DROPCHANGED(barcode(Sku s)) AND name(currentUser()) != 'admin'
MESSAGE 'Only the administrator is allowed to change the barcode for an already created product';

sku = DATA Sku (OrderDetail);
in = DATA BOOLEAN (Sku, Customer);

CONSTRAINT sku(OrderDetail d) AND NOT in(sku(d), customer(order(d)))
CHECKED BY sku[OrderDetail] // a filter by available sku when selecting an item for an order line will be applied
MESSAGE 'In the order, a product unavailable to the user is selected for the selected customer';