- PVSM.RU - https://www.pvsm.ru -
В стандартных хранимых классах Caché при модификации записи прежние значения свойств исчезают безвозвратно. Но бывают случаи, когда это нежелательно, когда «все ходы должны быть записаны». В первую очередь, конечно, такое требование возникает при разработке приложений для материально ответственных лиц, для которых критична возможность, например, отменить ошибочное действие и восстановить состояние документа на заданное время, или, что ещё важнее, провести расследование инцидента с попыткой злоумышленника «замести следы» в базе.
В этой статье демонстрируется, как реализовать хранение и восстановление версий для объектов Caché.
Данный функционал можно добавить к Persistent-классам с помощью метода %OnBeforeSave() и SQL-триггера на событие Update. Поскольку в подавляющем большинстве случаев использования Persistent-классов вся запись, в том числе значения свойств — массивов и списков, хранится в узле глобала хранения вида ^ClassData(id), перед перезаписью объекта есть возможность сохранить предыдущую версию записи в каком-нибудь укромном месте — наподобие «корзины» в Windows.
К данной статье прилагается пример использования вышеописанной возможности, состоящий из абстрактного класса PersHist — наследника %Persistent, к которому добавлены необходимые методы, и демонстрационного класса TestPersHist, в котором нет ровно ничего необычного, кроме того, что он — наследник PersHist, а не напрямую %Persistent. Ровно так же в нём можно создавать и модифицировать записи, как объектным, так и SQL-доступом, но перезаписанные данные там не исчезают бесследно и могут быть восстановлены.
}
}
Вкратце прокомментирую методы, добавленные в класс PersHist.
Классовый метод GetDataLocation() — читает описание класса-наследника и возвращает имя глобала хранения.
Классовый метод SaveLastRevision(id) — центральная фигура нашего класса. По-хорошему, его следовало бы сделать приватным, но в таком случае его нельзя будет вызвать из SQL-триггера. Данный метод копирует запись из узла глобала ^ClassData(id) в ^ClassData(«History»,id,id_сохранённой_версии,$ZTIMESTAMP). Естественно, вместо ^ClassData используется реальное имя глобала хранения, возвращённое методом GetDataLocation().
Приватный метод %OnBeforeSave() — вызывает SaveLastRevision() только когда метод %Save() будет выполнять реальную модификацию уже хранящейся в базе записи.
То же самое делает и SQL-триггер OnBeforeSaveForSQL().
И, наконец, для восстановления данных к состоянию, актуальному на заданный момент времени в формате $ZTIMESTAMP (точнее, $ZU(188) — пересчитанного на местное время $ZTIMESTAMP) предназначен метод MakeActual(timestamp). Данный метод смотрит, обновлялась ли запись точно в указанное время либо после него, и, если перезаписывалась, сохраняет текущую версию записи и восстанавливает ранее сохранённую. Например,
do oref.MakeActual(($h-7)_","_(12.5*3600))
вернёт запись к состоянию, в котором она была неделю назад в 12:30, а копию состояния этой записи на момент обращения, разумеется, сохранит.
Если же требуется полный список сохранений данного объекта, то предлагается следующая последовательность вызовов.
s ids="" f s ids=oref.OrderIdSave(ids) q:ids="" w ids,!
выдаст все идентификаторы сохранённых версий. А метод
write oref.GetTimeStampByIdSave(ids)
вернёт точное время сохранения заинтересовавшей версии. Впрочем, восстановить её можно и непосредственно по идентификатору:
do oref.MakeActualByIdSave(ids)
Но было бы нелогично сохранять все изменения при модификации записи, и при этом позволить безвозвратно лишиться этой записи в случае её удаления. От этой неприятности страхуют приватный метод %OnDelete() и SQL-триггер OnBeforeDeleteForSQL, которые позволят при необходимости восстановить удалённую запись с помощью классового метода UndeleteId(id).
Как вычислить идентификатор записи, подлежащей восстановлению — вопрос нетривиальный, и решаться должен, видимо, уже по усмотрению разработчика конечного приложения. Но в любом случае необходима возможность навигации по удалённым записям. И эту возможность предоставляет метод OrderDeletedId(id,dir), который, аналогично всем известной функции $ORDER(), возвращает ближайший следующий или предыдущий (при dir=-1) идентификатор записи, удалённой, но сохранившейся в истории.
Демонстрационный класс TestPersHist, унаследованный от PersHist, не содержит ничего, кроме свойств в формате обычного поля, списка и массива. Предлагается произвольно создавать и модифицировать записи этого класса (пример — в комментариях к классу), после чего прямым просмотром глобала хранения ^User.TestPersHistD штатными средствами Caché проследить за изменениями содержимого глобала хранения и воочию убедиться, что все редакции всех свойств сохраняются в ветке ^User.TestPersHistD(«History»).
Код классов PersHist и TestPersHist в виде, готовом для импорта в Caché, доступен для скачивания по данной ссылке [1]: в формате CDL для Caché версии 5.0 и более ранних, в формате XML для современных.
Автор: dmart4
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/32507
Ссылки в тексте:
[1] данной ссылке: http://yadi.sk/d/Bn_4ZTOH4AFqD
[2] Источник: http://habrahabr.ru/post/174655/
Нажмите здесь для печати.