Ограничения
Ограничения в платформе определяют, какие значения могут иметь первичные свойства, а какие нет. В общем случае, ограничение задается как свойство, значение которого всегда должно быть NULL.
Фактически, ограничение это простое событие, в котором условием является ограничиваемое свойство, а обработкой - показ всех его не NULL значений (в режиме асинхронного сообщения) и отмена сделанных изменений. Так же как и для простого события необходимо задать базовое событие, которое определяет, когда будет проверяться заданное ограничение.
По сравнению с реализацией через простые события у ограничений есть ряд дополнительных преимуществ:
- Для ограничений существует процедура глобальной проверки на уже работающей БД (по аналогии с техникой "перерасчета" в простых событиях, которая однако здесь не применима, так как в обработке содержится оператор отмены изменений)
- Ограничение понятнее и читабельнее, так как подчеркивает статичный / декларативный характер такого правила (то есть независимость от момента во времени) в отличие от простого события.
- Существует возможность использовать созданное ограничение при вызове диалогов изменения свойств, которые используются в этом ограничении. В этом случае в диалоге будет устанавливаться дополнительный фильтр, таким образом, чтобы при изменении значения свойства на выбранное значение не нарушить созданное ограничение.
Отметим, что в некоторых случаях вместо выдачи сообщения пользователю и отмены транзакции, необходимо, например, автоматически устранить нарушенное ограничение. В этом случае рекомендуется использовать простые ограничения, или, в случае когда это невозможно сделать, все те же простые события.
Множество проверяемых строк определяется изменением ограничиваемого свойства, а не объектами, отредактированными в сессии: изменения отменяются, если в результате их применения ограничиваемое свойство стало не NULL хотя бы на одной строке. Изменение любого свойства, от которого оно зависит — в том числе свойства без параметров, например глобальной настройки-порога, — заставляет эту проверку сравнивать новое значение ограничиваемого свойства с прежним на всех строках, где определены остальные его операнды. Поэтому, если ограничиваемое свойство сравнивает большое хранимое свойство с таким порогом, применение изменения одного лишь порога вычисляет условие по всем строкам хранимого свойства, что на большой таблице может занять минуты. Если проверять нужно только строки, измененные в сессии, добавьте в условие явное условие изменения хранимого операнда: изменение одного порога тогда не вызывает проверку вовсе — но строки, нарушающие новый порог без правки в сессии, осознанно остаются непроверенными.
Как и любое условие события, ограничиваемое свойство вычисляется инкрементально по применяемым изменениям. Если оно содержит тяжелые группировки по большим таблицам (особенно вложенные нематериализованные), запрос, который строится при таком инкрементальном вычислении, может вырасти до непрактичных размеров — даже при заданных для используемых свойств хинтах вычисления. Для таких дорогих проверок используйте простое событие: сделайте его условием дешевый детектор соответствующих изменений, а в обработчике прочитайте тяжелые значения в локальные свойства, проверьте их и сами покажите сообщение и отмените изменения.
Показ сообщения
Для вывода всех не NULL значений платформа использует автоматически создаваемую форму, состоящую из:
- одной группы объектов, объекты которой соответствуют параметрам ограничиваемого свойства.
- свойств, подходящих по классам и либо принадлежащих группе свойств
System.id, либо явно указанных при создании ограничения. - фильтра равного ограничиваемому свойству.
- глобального сообщения, задаваемого разработчиком при создании ограничения.
Язык
Для создания ограничений используется инструкция CONSTRAINT.
Примеры
// остаток не меньше 0
CONSTRAINT balance(Sku s, Stock st) < 0 MESSAGE 'Остаток не может быть отрицательным для ' +
(GROUP CONCAT 'Товар: ' + name(Sku ss) + ' Склад: ' + name(Stock sst), '\n' IF SET(balance(ss, sst) < 0) ORDER sst);
limit 'Максимальный остаток' = DATA NUMERIC[16,3] ();
// проверяются только измененные в сессии строки: изменение одного limit() не перевычисляет все остатки
CONSTRAINT CHANGED(balance(Sku s, Stock st)) AND balance(s, st) > limit()
MESSAGE 'Остаток превышает допустимый максимум';
barcode = DATA STRING[15] (Sku);
// "эмуляция" политики безопасности
CONSTRAINT DROPCHANGED(barcode(Sku s)) AND name(currentUser()) != 'admin'
MESSAGE 'Изменять штрих-код для уже созданного товара разрешено только администратору';
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] // будет применяться фильтр по доступным sku при выборе товара для строки заказа
MESSAGE 'В заказе выбран недоступный пользователю товар для выбранного покупателя';