Перейти к основному содержимому
Версия: 7.0

Brief: physical model

Исполнение

Хранимые данные и материализации

В базе данных хранятся первичные (DATA) свойства — все, кроме локальных, которые хранят значения только в рамках сессии, — и вычисляемые свойства, помеченные опцией MATERIALIZED: их значения лежат в поле таблицы, автоматически обновляются при изменении данных, от которых свойство зависит, и читаются прямо из базы. Отдельными полями хранятся принадлежность объекта классу (_CLASS_ИмяТаблицы) и признак полноты таблицы (_FULL_ИмяТаблицы) — последний только там, где полнота не обеспечена уже самим содержимым таблицы: таблица, хранящая принадлежность классу для всех потомков класса своего ключа, помечается полной сама и такого поля не получает.

Материализовать свойство можно тогда и только тогда, когда наборов объектов с не NULL значением у него конечное число. Материализация переносит стоимость с чтения на запись: значение не вычисляется при каждом обращении, но обновляется при каждом изменении исходных данных. Механизм — материализации.

sum = GROUP SUM sum(OrderDetail od) BY order(od) MATERIALIZED;

Аналогия: материализованное представление базы данных, обновляемое сразу при изменении исходных данных.

Таблицы и имена в БД

Таблица объявляется инструкцией TABLE:

TABLE name [dbName] (className1, ..., classNameN) [FULL | NODEFAULT];

Классы задают ключевые поля key0, ..., key(N-1) — по одному на класс, с нумерацией от нуля, — остальные поля — значения свойств. Таблицу свойства задаёт опция TABLE; без неё свойство попадает в ближайшую по классам ключей таблицу, NODEFAULT исключает таблицу из выбора, а без подходящей создаётся таблица _auto_.... FULL означает, что таблица содержит все объекты классов своих ключей, и влияет только на выполнение запросов. Механизм — таблицы.

Имена зависят от политики именования (db.namingPolicy):

ПолитикаТаблицаПоле
С сигнатурой (умолчание)Пространство_ТаблицаПространство_Свойство_Класс1_..._КлассN
Без сигнатурыПространство_ТаблицаПространство_Свойство
КраткаяТаблицаСвойство

Индексы

Индекс по одному свойству создаётся опцией INDEXED, индекс по произвольному списку полей одной таблицы — инструкцией INDEX:

INDEX [dbName] [indexType] field1, ..., fieldN;

Индексировать можно только материализованные свойства. В составной индекс входят и материализованные свойства, и параметры, указывающие на ключевые поля; он должен содержать хотя бы одно материализованное свойство, а все свойства в нём — храниться в одной таблице и использовать один и тот же набор параметров. Типы LIKE и MATCH сохраняют обычный индекс и пытаются добавить специализированные — LIKE добавляет индекс LIKE, а MATCH на строковом поле добавляет сразу MATCH и LIKE, — что на строковых полях происходит только если у текущего адаптера БД включена поддержка триграмм / полнотекстового поиска, а на одиночном поле TSVECTOR MATCH создаёт только специализированный GIN-индекс. Уникальный индекс по всем ключам таблицы и индексы по суффиксам ключей keyK, ..., keyN создаются автоматически. Механизм — индексы.

orderDate = DATA DATE (Order) INDEXED;
INDEX supplier(Sku s, DATE d), s, price(s, d), d;

Пересчёт материализаций

Оператор RECALCULATE создаёт действие, которое заново вычисляет хранимые значения материализованного свойства из его определения:

RECALCULATE [CLASSES | NOCLASSES] propertyId(expr1, ..., exprN) [WHERE whereExpr]

К пересчёту обращаются, когда хранимые значения могли разойтись с определением, — после изменения определения свойства или прямого исправления данных в базе. WHERE ограничивает пересчитываемые наборы аргументов, CLASSES пересчитывает только данные о классах, NOCLASSES — только значения.

recalculateSum() {
RECALCULATE sum(Order o);
}

Модули

Модули и порядок

Модуль — функционально законченная часть проекта: объявления классов, свойств, действий, форм, событий, ограничений (модули). Один модуль — один файл .lsf, начинающийся с заголовка модуля: MODULE, REQUIRE, PRIORITY, NAMESPACE.

REQUIRE перечисляет модули, от которых текущий зависит. Зависимость транзитивна, циклы не допускаются, и по ней строится порядок инициализации: модуль инициализируется после всех своих зависимостей. От модуля System зависит любой модуль. Зависимость определяет и видимость: элемент можно найти по имени только в модуле-зависимости, поэтому расширять чужой функционал позволяет техника расширений, см. Brief: расширения.

Проект — совокупность модулей и сопутствующих файлов; по умолчанию модулями считаются все файлы .lsf в classpath сервера приложений, а параметры запуска logics.includePaths, logics.topModule, logics.orderDependencies сужают этот набор и переопределяют порядок.

Аналогия: пакет или сборка.

MODULE Sale;
REQUIRE System, Utils, Item;
NAMESPACE Sale;

Системные модули

Системные модули поставляются вместе с платформой — это стандартная библиотека, которую проект подключает через REQUIRE и не переопределяет (системные модули). Двенадцать из них платформа загружает сама — System, Service, Reflection, Authentication, Security, SystemEvents, Email, Icon, Scheduler, Time, Utils, UserEvents, — но загруженность не означает зависимость: неявной зависимостью каждого модуля является только System, а объявление из любого другого требует REQUIRE.

МодульДля чего
Systemкорневые типы, базовые классы, инфраструктура
Utilsвспомогательные свойства и действия общего назначения
Timeсвойства и операции над датой и временем
Authenticationпользователи, контакты, вход в систему
Securityроли и политики доступа
Serviceсервисные действия и настройки сервера
SystemEventsсобытия жизненного цикла сервера
UserEventsпрограммный доступ к фильтрам и сортировкам формы
Reflectionметаданные о навигаторе, формах, свойствах, таблицах
Schedulerзапуск действий по расписанию
Emailотправка и приём почты
Iconкаталог иконок интерфейса

Прикладные дополнения: Backup, Chat, Eval, Excel, Document / Word, Image / OpenCV, I18n, Integration, MasterData, Numerator, Hierarchy, Historizable, Geo, Printer / QZTray / Sound / Com, ProcessMonitor / Profiler, RabbitMQ / WebSocket, Messenger со своими Telegram / Slack / Viber / Whatsapp / Skype, SQLUtils, DefaultData, Schedule.

Всё это — обычные объявления в .lsf, а не примитивы языка: lpad, currentDate, currentUser — свойства, искать их следует в ветке paradigm.

Метапрограммирование

META и вызов метакода (@)

Метакод — блок кода lsFusion с параметрами, порождающий при использовании другой код. Объявляется инструкцией META — имя, список параметров, последовательность инструкций, завершающее END; используется инструкцией @, которая называет метакод и передаёт аргументы: каждый параметр метакода заменяется на переданный аргумент во всех местах использования.

Кроме подстановки, в метакоде есть операции над лексемами: ## объединяет две соседние лексемы в одну, ### делает то же, переводя первый символ второй лексемы в верхний регистр, кроме случая, когда все слева оказалось пустым, а сам токен не начинается с ###: тогда капитализация пропускается, и prefix###name при пустом prefix даёт name, а не Name. Ими собираются имена порождаемых элементов. Механизм — метапрограммирование.

Аналогия: макрос с текстовой подстановкой параметров.

META objectProperties(object, caption)
object##Name 'Имя '##caption = DATA BPSTRING[100](object);
object##Value 'Стоимость '##caption = DATA INTEGER (object);
END

@objectProperties(Document, 'документа'); // DocumentName 'Имя документа' = ...

Порождаемый код и аргументы вызова

Тело метакода состоит из инструкций уровня модуля, и сама инструкция @ пишется на уровне модуля: метакод порождает объявления элементов системы, а не операторы внутри тела действия.

Отсюда два типовых применения: семейство однотипных объявлений для переданного класса или префикса и набор элементов, добавляемый на произвольную форму через EXTEND FORM с именем формы в параметре (Brief: расширения).

Аргументом вызова может быть составной ID, идентификатор класса, литерал или пустой параметр — но не значение свойства: выбор поведения по данным делается не метакодом, а абстрактными свойствами и действиями.

Среда разработки дописывает после вызова @ порождённый код в фигурных скобках, чтобы работали навигация и анализ; платформа этот блок игнорирует и формирует код заново, поэтому править его вручную нельзя.

Расширения

Расширение классов

Инструкция EXTEND CLASS донаследует уже объявленный класс от новых родительских классов и добавляет ему новые статические объекты.

Структурно это выносит связи между классами в отдельный модуль: базовый модуль объявляет класс, зависящий от него модуль добавляет родителя, ничего в базовом не меняя. Механизм — расширение классов.

CLASS Box : Shape;
CLASS Quadrilateral;
EXTEND CLASS Box : Quadrilateral; // добавляем наследование

EXTEND CLASS ShapeType { // добавляем статический объект
circle 'Окружность'
}

Расширение свойств и действий

Абстрактное свойство или действие — объявленная точка расширения: базовый модуль оператором ABSTRACT задаёт контракт — классы параметров, вид выбора реализации и класс результата, который у свойства есть всегда, а у действия — только если оператор его назвал, — а другие модули добавляют реализации инструкцией += для свойств и инструкцией ACTION+ для действий. Виды выбора и опции — в Brief: свойства.

Структурно это отложенная сборка обычного оператора: для свойства — оператора выбора, для действия — оператора ветвления или последовательности. Реализации хранятся упорядоченным списком, и новая добавляется в его начало или конец.

Отсюда основной приём модульности: базовый модуль объявляет точку расширения, зависящие от него модули добавляют поведение, и обратной зависимости не возникает.

Расширение форм

Инструкция EXTEND FORM дополняет форму, объявленную в другом модуле, — теми же блоками, что и объявление FORM (см. Brief: формы): объекты, отображаемые свойства и действия, фильтры, сортировки. Отдельные блоки расширения меняют элементы, уже добавленные на форму; дизайн формы тоже настраивается извне.

Добавляемый элемент можно поставить до или после конкретного элемента формы либо в начало или в конец; для объектов эта позиция задаёт их место в порядке групп объектов, от которого зависят группа отображения свойства и группа объектов для фильтра. Механизм — расширение форм.

EXTEND FORM items
PROPERTIES(i) NEWSESSION DELETE // кнопка удаления
OBJECTS g = ItemGroup BEFORE i // группа товаров перед товаром
PROPERTIES(g) READONLY name
FILTERS itemGroup(i) = g
;

Отдельной логики расширения для навигатора и дизайна формы нет: эти конструкции расширяемы по определению (расширения).

Идентификация элементов

Идентификаторы и пространства имён

Именованные элементы системы — свойства, действия, пользовательские классы, формы, элементы навигатора, группы свойств и действий, окна, таблицы, метакоды: к каждому обращаются по имени (идентификация элементов).

Элемент создаётся в пространстве имён, которое задаёт модуль инструкцией NAMESPACE (по умолчанию — имя модуля), а инструкция PRIORITY перечисляет дополнительные пространства имён, приоритетные при поиске; обе — в заголовке модуля.

Обращение записывается простым идентификаторомname — или составным, с явным пространством имён — Sale.Document. Строка <пространство имён>.<имя>полное имя элемента (именование).

Аналогия: пакеты и импорты, только приоритетное пространство задаётся один раз в заголовке модуля.

MODULE Sale;
REQUIRE System, Utils;
NAMESPACE Sale;

CLASS Document 'Документ'; // полное имя — Sale.Document

Именование

Именование рекомендует начинать имя элемента системы с маленькой буквы, имя класса — с большой, каждое следующее слово в имени — с большой: myFirstName, MySuperClass; сама грамматика идентификатора регистр не ограничивает. Уникальность требуется среди элементов одного типа, а не по всей системе: класс и форма могут носить одно и то же полное имя, и свойство с действием тоже. Внутри одного типа есть свои исключения — метакоды могут делить полное имя, если принимают разное количество параметров, свойства и действия — если различаются сигнатурой, — так что полное имя свойства само по себе не уникально.

Каноническое имя — строка, уникально определяющая элемент среди элементов того же типа. Для классов, групп свойств, элементов навигатора, окон и таблиц это полное имя; для свойств и действий к нему добавляется сигнатура — канонические имена классов параметров в квадратных скобках.

Sale.Document                  // класс
Item.gender[Item.Article] // свойство: полное имя и сигнатура

Каноническое имя связывает элемент с настройками политики безопасности и данными рефлексии. Для свойства, хранимого в базе данных, оно же задаёт имя поля по умолчанию — разработчик может указать это имя явно.

Поиск элементов

По короткому имени элемент находит алгоритм поиска, учитывающий: само имя (регистрозависимо), зависимость модулей — кандидат должен быть объявлен в модуле, от которого зависит модуль поиска, — явно указанное пространство имён, а при его отсутствии приоритет собственного пространства модуля и пространств из PRIORITY.

Для свойств и действий к этому добавляются классы параметров: подходят кандидаты, чьей сигнатуре соответствуют классы аргументов, из них выбирается наиболее конкретный. Сигнатуру можно указать в самом обращении: name[AClass](b).

Если подходящих кандидатов несколько или ни одного, платформа выдаёт ошибку при запуске.

Переименование и migration.script

Переименование элемента или перенос в другое пространство имён меняет каноническое имя, а значит разрывает связь элемента с уже сохранёнными данными и настройками. Соответствие старого и нового имён описывает файл migration.script в CLASSPATH сервера приложений: блоки V<номер версии> { ... }, из которых при старте применяются блоки с версией выше сохранённой в базе. Механизм — миграция.

Тип измененияЧто сохраняет
PROPERTYнастройки политики безопасности и рефлексии для свойства или действия
STORED PROPERTYто же плюс переименование поля в базе, то есть данные первичного свойства
FORM PROPERTYнастройки таблицы для свойства или действия на форме
CLASSобъекты пользовательского класса и связанные с ними данные — но не канонические имена первичных свойств, у которых класс входит в сигнатуру: они меняются вместе с ним, автоматически не отслеживаются и требуют собственных записей STORED PROPERTY, иначе сервер переименует осиротевшую колонку в _DELETED_ плюс ее прежнее имя в базе — а если колонка с таким именем уже есть, просто удалит ее, — и новое свойство начнет с пустого
OBJECTданные, связанные со статическим объектом
TABLEпереименование таблицы вместо переноса записей
NAVIGATORнастройки политики безопасности для элемента навигатора
V0.3.1 {
STORED PROPERTY Item.gender[Item.Article] -> Item.dataGender[Item.Article]
CLASS Date.DateInterval -> Date.Interval
}