Расширение Qbim, пример реализации битемпоральной модели данных

Примеры:


Предпосылки

Модель представления данных близка к шестой нормальной форме, но без избыточной нормализации.

Шестая нормальная форма введена Кристофером Дейтом как обобщение пятой нормальной формы для хронологической базы данных в своей книге Введение в системы баз данных.

Расширение Qbim представляет собой конструктор для разработки ERM на темпоральной основе, то есть, моделируемые объекты представляются временными срезами, последовательностями своих состояний во времени. Основное внимание и акцент в данном расширении сделан не на "декомпозиции до конца", а на поддержании темпоральности данных. Расширение реализует краткий набор логики хранения (создания, изменения) объектов в реляционной СУБД (на примере qhb), реализованный в слое хранимых процедур.


Модель данных

В рамках расширения Qbim рассматривается пример реализации модели хранения данных с указанием интервалов времени их действия. В отличие от нехронологических баз данных, хранящих только текущие данные, такая модель предполагает хранение данных, относящихся к прошлым, а также будущим периодам времени.

В предлагаемой реализации наряду с идентификатором кортежа данных указывается еще и интервал времени, в течение которого эти данные считаются актуальными. Эти интервалы могут относиться или пересекаться с настоящим временем, находиться в прошлом или описывать будущие значения. Интервалы хранятся в двух специально выделенных для этого полях типа timestamp, образующих полузакрытый интервал диапазонного типа tsrange.

Серия данных с одним идентификатором (далее объект), может образовывать непересекающиеся отрезки, или «версии», на временной оси «действия» этих данных (business time).

Дополнительная временная ось (system time) фиксирует, когда эти версии были созданы или изменены.


Реализация модели

Терминология

ТерминОпределение
ОбъектНекоторая оцифрованная, смоделированная из реального мира сущность. Обладает свойствами (атрибутами) и операциями над ними (методами). В определённые временные интервалы имеет фиксированный набор атрибутов. Но во времени, от интервала к интервалу, может менять набор атрибутов. В базе данных набор Объектов представляется сущностью/таблицей, его атрибуты представлены полями таблицы, методы реализованы хранимыми процедурами и функциями.
Реализация объектаОдин из представителей объекта, выделяемый среди других с помощью идентификатора. В базе данных представляет собой серию записей одной таблицы/сущности.
Версия реализации объектаОдин из наборов атрибутов объекта, постоянный в течении определенного временного интервала. В базе данных - одна из записей таблицы/сущности, принадлежащая объекту (по идентификатору), с определенным интервалом действия.
Идентификатор реализации объектаИдентификатор (id - обычно целое число, но может быть UUID-ом), однозначно определяющий реализацию объекта среди других объектов сущности. Все версии одного и того же объекта имеют один и тот же идентификатор.
Идентификатор версии реализации объектаКортеж из идентификатора и интервала действия версии ( <id[, sd, ed]> ), однозначно определяющий объект и версию.

Ограничения и умолчания

Зарезервированные поля

Для идентификатора и границ интервала действия в версионированных таблицах резервируются три поля:

Резервируемое полеТипНазначение
idbigint или uuidидентификатор
sdtimestampstart date, начало периода действия
edtimestampend date, окончание периода действия

Далее для bigint может использоваться термин «локальный идентификатор», а для uuid — «глобальный идентификатор».

Любой версионированный объект представляется набором своих версий реализации, связанных уникальным идентификатором реализации (id) и набором атрибутов / свойств. Каждая его версия - кортежем id и периодом действия этой версии (период представляется интервалом [sd, ed) - включается левая граница и не включается правая, типы границ - timestamp).

Первичный ключ версии объекта - составной: <id,[sd,ed)>.

Версии не пересекаются и не содержат разрывов

Интервалы действия версий образуются как tsrange(sd,ed,'[)') — полуоткрытый интервал. Отсутствие пересечений интервалов реализуется ограничением вида:

constraint ... exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))

Следует заметить, что при таких условиях допустимыми являются версии, в которых sd == ed.

Это допущение сделано намеренно, поскольку:

  • такие версии имеют нулевую длину, не пересекаются с другими версиями, не влияют на многие сценарии выбора версий и серий версий (непустых);
  • при наличии фиксации времени изменения (system time) не пропадает и не требует изменения привязка к версии события (поле action.link_dt), поскольку привязка осуществляется именно к значению поля sd версионированной таблицы.

Описание версионируемых таблиц

Все версионируемые таблицы и их поля описываются в специально выделяемых таблицах obj и obj_item.

Таблица obj:

ПолеТипНазначение
idbigserialИдентификатор
tabtextНазвание таблицы, где хранится объект. Если требуется, вместе с именем схемы
dsctextОписание
parentbigintСсылка на ID верхнего в иерархии объекта
is_temporalboolПризнак темпоральности объекта
is_loggingboolПризнак логирования действий пользователя для объекта
is_globalboolПризнак глобального идентификатора у объекта. Для глобальных идентификаторов используется тип UUID, для локальных — bigint

Таблица obj_item:

ПолеТипНазначение
idbigserialИдентификатор
upbigintСсылка на объект (obj.id)
ordbigintПорядковый номер атрибута
nametextИмя
r_clsbigintЕсли имеется словарное поле, номер словаря (@CLS.ID)
r_objbigintЕсли имеется ссылка на объект, номер объекта
optionjsonbДополнительные параметры. Например, указание о замене типа на массив для методов
dsctextОписание атрибута

Таблица action:

ПолеТипНазначение
idbigserialИдентификатор
r_objecturefСсылка на изменяемый объект и его идентификатор
lnk_dttimestampДата привязки к версии реализации события (SD)
fct_dttimestampРеальная дата версии (по началу транзакции)
actionbigintПроизведенное действие. Словарь
databasetextБаза данных, где расположен измененный объект
schematextСхема, где расположен измененный объект
dbusertextПользователь базы данных, совершивший действие
pidtextИдентификатор процесса (Process ID), в рамках работы которого произведено изменение
useriduuidUUID пользователя, вошедшего в систему и совершившего действие

В таблицу action, если указано в obj.is_logging, записываются основания действий с объектами.

«Действие» (поле action) имеет следующую кодировку:

КодДействие
1Создание
2Изменение (при создании новой версии)
3Изменение (при сокращении периода действия)
4Закрытие версии

ВАЖНО!
Для функционирования приложения достаточно важно, чтобы записи в эту таблицу записывались максимально быстро. Для этого есть возможность создавать таблицу как unlogged, а при включенном TARQ — также ... using append_only.


Методы работы с объектами

Для создания, изменения и закрытия объектов предлагаются методы, которые корректно отслеживают ограничения модели.

Методы выборки оставляются на выбор пользователя или приложения. Возможно выбрать конкретную версию, действующую в указанный момент времени, или серии версий за указанный период вместе со схлопнувшимися версиями (промежуточные версии с нулевой длительностью, sd == ed) или без них. Также возможны различные выборки совместно со связанными объектами.


Методы создания объекта

Для создания объекта (начальной версии объекта) предоставляются методы qbim_create():

CREATE FUNCTION qbim_create(
  obj_id int8, tab text,
  id int8,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','create_local_reg' language c volatile security definer;

CREATE FUNCTION qbim_create(
  obj_id int8, tab text,
  id uuid,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns uuid as 'MODULE_PATHNAME','create_global_reg' language c volatile security definer;

В одном из первых двух параметрах метода указывается идентификатор создаваемого объекта: по идентификатору в таблице obj описателя или по имени объекта (таблицы); возможно указание и идентификатора, и имени, но они должны соответствовать описателю.

В зависимости от типа идентификатора, используемого в объекте, третий параметр может иметь тип bigint или uuid; его пропуск (указание null::bigint или null::uuid) требует автоматической генерации идентификатора. В случае bigint значение может выбираться из соответствующей последовательности, а в случае uuid — генерируется случайное значение uuid.

Следующая пара параметров задает интервал действия версии, при пропуске (указании null:timestamp) используются значения по умолчанию, указанные при создании таблицы. Как правило, для sd это текущее время (CURRENT_TIMESTAMP), а для ed — «бесконечность» (infintity::timestamp).

В параметре userid можно указать идентификатор конечного пользователя, производящего операцию создания объекта, если таковой используется в приложении. Если не используется, можно пропустить (указать null::uuid).

Далее в переменном списке параметров (variadic "any") следует передавать параметры парами — имя и значение, или не передавать вовсе. В любом случае количество переменных должно быть четным. Атрибуты объекта, пропущенные в этом списке, получат значения NULL или будут установлены в соответствии с умолчаниями, заданными при создании таблицы.

При создании объекта, если это указано в obj, будет зафиксировано действие в таблице action.


Методы изменения объекта (создания версии)

Для изменения объекта предоставляются методы qbim_change(). Создание версии возможно в любой период, пересекающий периоды имеющихся у объекта версий (не должно получаться разрывов).

Параметры аналогичны методам qbim_create(). Умолчания для sd и ed — текущее время и «бесконечность» соответственно.

Новая версия может дополнять (с любой стороны) имеющиеся, пересекать любое количество имеющихся версий, покрывать полностью или частично уже имеющиеся версии.

В случае, если в переменном списке параметров опущено какое-то из полей, оно будет подставлено из соответствующей более ранней версии объекта, подходящей по времени.

Если в результате изменения какие-то из имеющихся версий будут полностью перекрыты новой версией, они будут считаться схлопнувшимися (sd == ed).

Методы qbim_change():

CREATE FUNCTION qbim_change(
  obj_id int8, tab text,
  id int8,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','change_local_reg' language c volatile security definer;

CREATE FUNCTION qbim_change(
  obj_id int8, tab text,
  id uuid,
  sd timestamp, ed timestamp,
  userid uuid,
  variadic others_args "any")
returns int8 as 'MODULE_PATHNAME','change_global_reg' language c volatile security definer;

При изменении объекта, если это указано в obj, будет зафиксировано действие в таблице action.


Методы закрытия объекта (версии)

Пара методов, закрывающих версии объекта текущей или указанной датой:

CREATE FUNCTION qbim_close(
  obj_id int8, tab text,
  id int8,
  ed timestamp,
  userid uuid)
returns int8 as 'MODULE_PATHNAME','close_local' language c volatile security definer;

CREATE FUNCTION qbim_close(
  obj_id int8, tab text,
  id uuid,
  ed timestamp,
  userid uuid)
returns int8 as 'MODULE_PATHNAME','close_global' language c volatile security definer;

В параметре ed указывается необходимое время для закрытия версий. Если пропущено (указано null::timestamp), то устанавливается текущее время. Остальные параметры аналогичны параметрам в методах qbim_create и qbim_change.

При закрытии объекта, если это указано в obj, будет зафиксировано действие в таблице action.