Caché永続クラスのバージョン化されたデータストレージ

標準のCachéストアドクラスでは、レコードが変更されると、以前のプロパティ値は永久に消えます。 しかし、これは望ましくない場合もあります。「すべての動きを記録する必要があります」。 もちろん、まず第一に、経済的責任者向けのアプリケーションを開発する際に、このような要件が発生します。たとえば、誤ったアクションをキャンセルして所定の期間ドキュメントの状態を復元する、またはより重要なことには、攻撃者による「隠蔽」の試みでインシデントを調査する機会が重要です»データベース内。

この記事では、Cachéオブジェクトのバージョンストレージとリカバリを実装する方法を示します。



この機能は、%OnBeforeSave()メソッドとUpdateイベントのSQLトリガーを使用して永続クラスに追加できます。 永続クラスが使用されるほとんどの場合、プロパティの値(配列やリスト)を含むレコード全体は、^ ClassData(id)の形式のストレージグローバルノードに格納されるため、オブジェクトを上書きする前に、レコードの前のバージョンをある人里離れた場所に保存することができますWindowsのごみ箱のように。

この記事には、抽象PersHistクラス(必要なメソッドが追加される%Persistent継承クラス)およびTestPersHistデモクラスで構成される上記の機能の使用例が付属しています。TestPersHistデモクラスでは、PersHistの後継であって永続的。 オブジェクトとSQLアクセスの両方でレコードを作成および変更する方法とまったく同じですが、そこに上書きされたデータはトレースなしでは消えず、復元できます。



PersHistクラスのソース
///%Persistentクラスのこの祖先

///更新履歴の記録を有効にするトリガーが含まれています。

クラスPersHist Extends%Persistent [ Abstract ClassType = ""、 ProcedureBlock ]

{



///このコールバックメソッドは、<METHOD>%Save </ METHOD>メソッドによって呼び出され、

///オブジェクトが保存されていることを通知します。 前に呼ばれます

///データはすべてディスクに書き込まれます。

///

/// <P> <VAR> insert </ VAR>は、このオブジェクトが初めて保存される場合、1に設定されます。

///

/// <P>このメソッドがエラーを返した場合、<METHOD>%Save </ METHOD>の呼び出しは失敗します。

メソッド %OnBeforeSave( insert As%Boolean As%Status [ Final Private ]

{

q $$$ OKを 挿入

q : '.. %ObjectModified () $$$ OK

q .. SaveLastRevision (.. %Id ())

}



///このコールバックメソッドは、<METHOD>%Delete </ METHOD>メソッドによって呼び出され、

/// <VAR> oid </ VAR>で指定されたオブジェクトが削除されていることを通知します。

///

/// <P>このメソッドがエラーを返した場合、オブジェクトは削除されません。

ClassMethod %OnDelete( oid As%ObjectIdentity As%ステータス [ 最終 プライベート ]

{

s id = $ lg oid q : ' id 0

d .. SaveLastRevision id

q $$$ OK

}



ClassMethod GetDataLocation() As%String

{

ix = ##クラス %ClassDefinition )。 %OpenId (.. %ClassName ())

q ix ストレージ カウント () '= 1 ""; 洗練されたストレージはサポートされていません

q ix ストレージ GetAt (1)。 データの場所

}



///保存されたリビジョンの次または前のID($ o()と同様)を返します

メソッド OrderIdSave( ids As%String = "" Direction As%String = 1 As%String

{

s dl = .. GetDataLocation () q dl = "" ""

s id = .. %Id () q : ' id ""

q $ o (@ dl @( "History" id ids )、 Direction

}



///保存されたリビジョンのタイムスタンプを返します

メソッド GetTimeStampByIdSave( ids As%String As%String

{

s dl = .. GetDataLocation () q dl = "" ""

s id = .. %Id () q : ' id ""

q $ d (@ dl @( "History" id ids ))<10 ""

q $ o (@ dl @( "History" id ids "" ))

}



///指定した時刻以降にリビジョンを実際に保存し直します

///そしてそれをロードします。 保存されていない変更はすべて失われます。

メソッド MakeActual( タイムスタンプ As%StringTimeStamp As%ステータス

{

s dl = .. GetDataLocation () q dl = "" 0 ; サポートされていないストレージ

s id = .. %Id () q : ' id 0 ; 決して保存しなかった

s zts = タイムスタンプ

; 保存されたリビジョンをzts> = timestampで検索

i $ d (@ dl @( "History" "ZI" id zts )) s ids = $ o (@ dl @( "History" "ZI" id zts "" )、-1)

e s zts = $ o (@ dl @( "History" "ZI" id "" )) q zts = "" 0 s ids = $ o (@ dl @( "History" "ZI" id zts "" ))

q $ s ids :.. MakeActualByIdSave ids )、1:0)

}



///保存されたリビジョンを再度実際に作成し、idsaveでロードします。

///未保存の変更はすべて失われます。

メソッド MakeActualByIdSave( ids As%String As%Status

{

s dl = .. GetDataLocation () q dl = "" 0 ; サポートされていないストレージ

s id = .. %Id () q : ' id 0 ; 決して保存しなかった

q $ d (@ dl @( "History" id ids ))<10 0

s zts = $ o (@ dl @( "History" id ids "" )) q zts = "" 0

d .. SaveLastRevision id

k @ dl @( id m @ dl @( id )= @ dl @( "History" id ids zts

d .. %Reload () d .. %SetModified (1)

q .. %Save () ; インデックスを正しくするためにもう一度保存します

}



///削除されたレコードの次または前のID($ o()と同様)を返します

ClassMethod OrderDeletedId( id As%String = "" Direction As%String = 1 As%String [ 最終 ]

{

s dl = .. GetDataLocation () q dl = "" 0

f s id = $ o (@ dl @( "History" id )、 Direction q id = "" q : ' $ d (@ dl @( id ))

q id

}



ClassMethod SaveLastRevision( id As%ステータス [ 最終 ]

{

s id = $ g id q : ' id $$$ OK ; 保存する前のリビジョンはありません

s dl = .. GetDataLocation () q dl = "" $$$ OK

s ids = $ i (@ dl @( "History" id ))、 zts = $ zu (188) ; ローカルタイムゾーンの$ ZTIMESTAMP

m @ dl @( "History" id ids zts )= @ dl @( id

s @ dl @( "History" "ZI" id zts ids )= ""

q $$$ OK

}



ClassMethod UnDeleteId( id As%String As%ステータス [ 最終 ]

{

s dl = .. GetDataLocation () q dl = "" 0

q : ' $ g id )0

q $ d (@ dl @( id ))0 ; 削除されていません

q $ d (@ dl @( "History" id )) '> 1 0 ; 保存されたリビジョンはありません

; 最新リビジョンの検索

s ids = $ o (@ dl @( "History" id "" )、-1) q : ' ids 0

s zts = $ o (@ dl @( "History" id ids "" )、-1) q zts = "" 0

m @ dl @( id )= @ dl @( "History" id ids zts

s ix = .. %OpenId id d ix %SetModified (1) s res = ix %保存 () k ix

q res

}



OnBeforeDeleteForSQLの トリガー [ イベント = DELETE]

{

n id ix s id = {ID} q : ' id

d .. SaveLastRevision id

q

}



OnBeforeSaveForSQLの トリガー [ イベント =更新]

{

n id ix s id = {ID} q : ' id

d .. SaveLastRevision id

q

}



}


クラスソースTestPersHist
///テスト方法

///

/// //インスタンスを作成して初期化します:

///

/// s ix = ## class(TestPersHist)。%New()

/// s ix.TestVal = $ H

/// w ix.TestArray.SetAt( "Abra"、1)

/// w ix.TestArray.SetAt( "Shvabra"、2)

/// w ix.TestList.InsertAt( "Cadabra"、1)

/// s id = ix。%Id()

/// w ix。%Save()k ix

///

/// //更新してみてください

///

/// s ix = ## class(TestPersHist)。%OpenId(id)

/// s ix.TestVal = $ J

/// w ix.TestArray.SetAt( "Swim"、3)

///

/// w ix。%Save()k ix

///

/// //グローバル^ User.TestPersHistDを見てください。 すべての変更が記録されました!

クラスTestPersHistはPersHistを拡張します [ ClassType = persistent、 ProcedureBlock ]

{



プロパティ TestArray As%Stringの配列 MAXLEN = 255 ); // [コレクション=配列];



プロパティ TestList As %% Stringのリスト // [コレクション=リスト];



プロパティ TestVal As%String MAXLEN = 255 )[ 必須 ];



}


PersHistクラスに追加されたメソッドについて簡単にコメントします。

クラスメソッドGetDataLocation()-下位クラスの説明を読み取り、ストレージグローバルの名前を返します。

クラスメソッドSaveLastRevision(id)は、クラスの中心的な図です。 良い方法では、プライベートにする必要がありますが、この場合、SQLトリガーから呼び出すことはできません。 このメソッドは、グローバルノード^ ClassData(id)から^ ClassData( "History"、id、idのsaved_version、$ ZTIMESTAMP)にレコードをコピーします。 当然、^ ClassDataの代わりに、GetDataLocation()によって返されるストレージグローバルの実際の名前が使用されます。

プライベートメソッド%OnBeforeSave()-%Save()メソッドがデータベースに既に保存されているレコードの実際の変更を実行する場合にのみSaveLastRevision()を呼び出します。

OnBeforeSaveForSQL()SQLトリガーも同じことを行います。

最後に、MakeActual(タイムスタンプ)メソッドを使用して、$ ZTIMESTAMP形式(より正確には、$ ZU(188)-ローカライズされた$ ZTIMESTAMP)で、特定の時点での現在の状態にデータを復元します。 このメソッドは、指定された時刻またはそれ以降にレコードが正確に更新されたかどうかを確認し、上書きされた場合、レコードの現在のバージョンを保存し、以前に保存されたバージョンを復元します。 例えば



orefを行い ます。 MakeActual (( $ h -7)_ "、" _(12.5 * 3600))



1週間前の12:30の状態にレコードを戻します。もちろん、控訴の時点でこのレコードの状態のコピーを保存します。

このオブジェクトの保存の完全なリストが必要な場合は、次の一連の呼び出しが提案されます。



s ids = "" f s ids = oref OrderIdSave ids q ids = "" w ids!



保存されたバージョンのすべての識別子を提供します。 メソッド



orefを 書き ます GetTimeStampByIdSave ids



興味のあるバージョンを保存する正確な時間を返します。 ただし、識別子によって直接復元できます。



orefを行い ます。 MakeActualByIdSave ids



しかし、レコードが変更されたときにすべての変更を保存し、同時にこのレコードが削除された場合に永久に失わせることは非論理的です。 UndeleteId(id)クラスメソッドを使用して削除されたレコードを復元できるprivate%OnDelete()メソッドとOnBeforeDeleteForSQL SQLトリガーは、この迷惑に対して保証されています。

復元するレコードの識別子を計算する方法は簡単な質問であり、最終アプリケーションの開発者の裁量ですでに決定されているようです。 ただし、いずれにしても、削除されたレコードをナビゲートできる必要があります。 また、この機会はOrderDeletedId(id、dir)メソッドによって提供されます。このメソッドは、よく知られている$ ORDER()関数と同様に、削除されたが履歴に保存されたレコードの最も近い次または前(dir = -1)の識別子を返します。



PersHistから継承したデモクラスTestPersHistには、通常のフィールド、リスト、および配列の形式のプロパティのみが含まれています。 このクラスのレコードを任意に作成および変更することをお勧めします(たとえば、クラスのコメントを参照)。その後、ストレージグローバルを直接表示します。 .TestPersHistD(「履歴」)。



CachéにインポートできるフォームのPersHistおよびTestPersHistクラスのコードは、 このリンクからダウンロードできます。Cachéバージョン5.0以前の場合はCDL形式、最新の場合はXML形式です。



All Articles