Skip to main content

This is documentation for Caché & Ensemble. See the InterSystems IRIS version of this content.Opens in a new tab

For information on migrating to InterSystems IRISOpens in a new tab, see Why Migrate to InterSystems IRIS?

多次元ストレージの使用法 (グローバル)

この章では、多次元ストレージ (グローバル変数) を使用して実行できるさまざまな処理について説明します。ここでは、以下のトピックについて説明します。

グローバルへのデータ格納

グローバル・ノードへのデータの格納は単純で、グローバルを他の変数のように処理するだけです。違いは、グローバルに対する処理が自動的にデータベースに書き込まれる点です。

グローバルの生成

新しいグローバルを生成するための設定作業は一切必要ありません。 グローバルにデータを設定するだけで、自動的に新しいグローバル構造が生成されます。 グローバル (またはグローバル添え字) を生成し、1 回の演算でそこにデータを置くことができます。グローバル (または添え字) を生成し、NULL 文字列を設定すれば空のグローバルのままとなります。ObjectScript では、これらの処理は SET コマンドを使用して実行します。

以下の例は、Color という名のグローバルが存在しなければそれを定義し、値 “Red” と関連付けます。Color という名前のグローバルが既に存在する場合は、新規の情報を格納できるようにそのグローバルを修正します。

Caché Basic では、以下のとおりです。

^Color = "Red"

ObjectScript では以下のようになります。

 SET ^Color = "Red"
Note:

アプリケーション内でダイレクト・グローバル・アクセスを使用するとき、名前付け規約を作成し、アプリケーションの異なる部分同士でお互いの名前が“衝突”しないように規約に従ってください。これは、クラス、メソッド、他の変数に名前付け規約を作成するときと同様です。また、Caché が使用する特定のグローバル名との衝突を避けてください。それらのリストについては、"Caché プログラミング入門ガイド" の付録 “識別子のルールとガイドライン” の “回避する必要があるグローバル変数名” セクションを参照してください。

グローバル・ノードに格納するデータ

グローバル添え字ノードに値を格納するには、他の変数配列と同様に、グローバル・ノードの値を設定します。指定したノードが存在していない場合は、そのノードを生成します。既に存在している場合、既存の値を新しい値に置き換えます。

式を使用して、グローバルにノードを指定します (グローバル参照)。グローバル参照は、キャレット文字 (^)、グローバル名、および必要に応じて 1 つ以上の添え字値で構成します。添え字は括弧 “( )” で囲み、コンマで区切ります。各添え字値は、それ自身がリテラル値、変数、論理式、グローバル参照などの式です。

グローバル・ノードの値の設定は、アトミック処理です。これは、正常に実行できることが保証されており、整合性を確保するためのロックを使用する必要もありません。

以下は、すべて有効なグローバル参照です。

Caché Basic では、以下のとおりです。

^Data = 2
^Data("Color") = "Red"
^Data(1,1) = 100
^Data(^Data) = 10     ' The value of ^Data is the subscript
^Data(a,b) = 50       ' The values of local variables a and b are subscripts
^Data(a + 10) = 50    

ObjectScript では以下のようになります。

   SET ^Data = 2
   SET ^Data("Color")="Red"
   SET ^Data(1,1)=100        /* The 2nd-level subscript (1,1) is set
                                to the value 100. No value is stored at
                                the 1st-level subscript (^Data(1)). */   
   SET ^Data(^Data)=10       /* The value of global variable ^Data 
                                is the name of the subscript. */
   SET ^Data(a,b)=50         /* The values of local variables a and b
                                are the names of the subscripts. */
   SET ^Data(a+10)=50       

ObjectScript を使用している場合、間接演算を使用して、実行時にグローバル参照を構築できます。

グローバル・ノードへの構造化されたデータ格納

各グローバル・ノードは最長 32K 文字の単独の文字列を含むことが可能です。

データは通常、以下のいずれかの方法でノード内に格納されます。

  • 最長 32K 文字の 1 文字列 (正確には 32K マイナス 1 文字)。

  • 文字で区切った複数のデータ部分で構成する文字列。

    区切り文字を使用してノード内にフィールド一式を格納するには、結合演算子 (_) を使用して値を連結します。以下の ObjectScript の例は、区切り文字として # 記号を使用しています。

       SET ^Data(id)=field(1)_"#"_field(2)_"#"_field(3)
    

    データ取得の際、$PIECE 関数を使用してフィールドを分離できます。

       SET data = $GET(^Data(id))
       FOR i=1:1:3 {
         SET field(i) = $PIECE(data,"#",i)
         }
       QUIT
  • 複数のデータを含む、$LIST でコード化した文字列。

    $LIST 関数は、区切り文字を予約する必要がない特殊な長さエンコード法を使用します (これは Caché オブジェクトと SQL で使用する既定の構造です)。

    ノード内にフィールド一式を格納するには、$LISTBUILD 関数を使用してリストを構築します。

       SET ^Data(id)=$LISTBUILD(field(1),field(2),field(3))
    

    データ取得の際、$LIST 関数または $LISTGET 関数を使用してフィールドを分離できます。

       SET data = $GET(^Data(id))
       FOR i = 1:1:3 {
           SET field(i)=$LIST(data,i)
           }
       QUIT
  • 大きなデータの一部 (ストリームや “BLOB”など)。

    個々のノードの容量が 32K に限られているので、例えばストリームのような大規模な構造は、連続した複数のノードにデータを格納することで実装します。

       SET ^Data("Stream1",1) = "First part of stream...."
       SET ^Data("Stream1",2) = "Second part of stream...."
       SET ^Data("Stream1",3) = "Third part of stream...."
    

    ストリームをフェッチするコード (%GlobalCharacterStreamOpens in a new tab クラスで提供されるコードなど) は、連続した文字列としてデータを提供する構造で、継続的にノードをループします。

  • 文字列。

    ビットマップ・インデックスを実装する場合は、インデックス・グローバルのノード値をビット文字列に設定します。ビットマップ・インデックスとは、ビット文字列のビットがテーブルの行に対応するインデックスです。Caché は、エンコーディング・ビット文字列に圧縮アルゴリズムを使用します。したがって、ビット文字列は、Caché の $BIT 関数を使用してのみ操作できます。ビット文字列に関する詳細は、“ビット文字列関数の概要” を参照してください。

  • 空ノード。

    必要なデータがノード自体に収められている場合、一般的には実際の添え字を NULL 文字列 ("") に設定します。例えば、名前を ID 値に関連付けるインデックスであれば、通常は以下のとおりです。

      SET ^Data("APPLE",1) = ""
      SET ^Data("ORANGE",2) = ""
      SET ^Data("BANANA",3) = ""

グローバル・ノードの削除

グローバル・ノード、サブノードのグループ、またはグローバル全体をデータベースから削除するには、ObjectScript では KILL コマンドまたは ZKILL コマンドを使用し、Caché Basic では Erase コマンドを使用します。

KILL コマンドは、下位ノードを含むすべてのノード (データおよび配列内にあるそのエントリ) を特定のグローバル参照で削除します。つまり、指定した添え字で開始するすべてのノードが削除されます。

例えば、以下の ObjectScript 文を考えます。

  KILL ^Data

これは ^Data グローバル全体を削除します。このグローバルを引き続き参照すると <UNDEFINED> エラーを返します。

以下にもう 1 つ ObjectScript 文があります。

   KILL ^Data(100)

これは、^Data グローバルのノード 100 のコンテンツを削除します。^Data(100,1)、^Data(100,2)、^Data(100,1,2,3) などの下位ノードがある場合、同様に削除されます。

ObjectScript の ZKILL コマンドは、指定されたグローバルやグローバルの添え字ノードを削除します。下位ノードは削除しません。

Note:

大規模なグローバルを削除した後、そのグローバルで使用していた領域が完全には解放されていないことがあります。これは、そのブロックがガベージ・コレクタ・デーモンによってバックグラウンドで解放としてマークされているからです。したがって、大規模なグローバルを削除した直後に SYS.DatabaseOpens in a new tab クラスの ReturnUnusedSpace メソッドを呼び出すと、そのグローバルで使用していたブロックがまだ解放されていないので、想定よりも少ない領域の値が返される場合があります。

グローバル変数には New コマンドを使用できません。

グローバル・ノードの存在有無のテスト

特定のグローバル (またはその下位ノード) がデータを含むかどうかをテストするには、ObjectScript の $DATA 関数を使用します。

$DATA は、指定したグローバル参照が存在するか否かを示した値を返します。以下がその値の例です。

状態値 意味
0 グローバル変数が定義されていません。
1 グローバル変数が存在し、データを含みますが、下位ノードはありません。NULL 文字列 ("") もデータと見なされます。
10 グローバル変数に下位ノードがありますが (下位ノードへの下方ポインタを含みます)、そのノード自身はデータを含みません。このような変数への直接参照は、<UNDEFINED> エラーになります。例えば、$DATA(^y) が 10 を返す場合、SET x=^y は <UNDEFINED> エラーを生成します。
11 データと下位ノードを含むグローバル変数です (下位ノードへの下方ポインタも含みます)。

グローバル・ノード値の検索

特定のグローバル・ノード内に格納された値を取得するには、グローバル参照を式として使用します。

Caché Basic の使用法

color = ^Data("Color")   ' assign to a local variable
Print ^Data("Color")     ' use as an argument to a command
MyMethod(^Data("Color")) ' use as a function argument

Caché ObjectScript の使用法

   SET color = ^Data("Color")    ; assign to a local variable
   WRITE ^Data("Color")          ; use as a command argument
   SET x=$LENGTH(^Data("Color")) ; use as a function parameter

$GET 関数

ObjectScript では、$GET 関数を使用してグローバル・ノードの値を取得することもできます。

   SET mydata = $GET(^Data("Color"))

指定ノードの値を取得する (存在する場合) か、ノードに値がない場合は NULL 文字列 ("") を返します。$GET のオプションの 2 番目の引数を使用して、ノードに値がない場合に指定された既定値を返すこともできます。

WRITE コマンド、ZWRITE コマンド、ZZDUMP コマンド

ObjectScript のさまざまな表示コマンドを使用して、グローバルやグローバル・サブノードのコンテンツを表示できます。WRITE コマンドは、指定されたグローバルやサブノードの値を文字列として返します。ZWRITE コマンドは、グローバル変数名とその値、および下位ノードとその値を返します。ZZDUMP コマンドは、指定されたグローバルやサブノードの値を 16 進数ダンプ形式で返します。

グローバル内のデータ走査

グローバル内に格納されているデータの走査 (反復) には多くの方法があります。

$ORDER (Next / Previous) 関数

ObjectScript の $ORDER 関数 (および Caché Basic の Traverse) では、グローバル内の各ノードに順にアクセスできます。

$ORDER 関数は、指定されたレベル (添え字番号) で次の添え字値を返します。例えば、以下のグローバルを定義するとします。

 Set ^Data(1) = ""
 Set ^Data(1,1) = ""
 Set ^Data(1,2) = ""
 Set ^Data(2) = ""
 Set ^Data(2,1) = ""
 Set ^Data(2,2) = ""
 Set ^Data(5,1,2) = ""

最初のファースト・レベル添え字を検索するには、以下を使用します。

 SET key = $ORDER(^Data(""))

これは、NULL 文字列 ("") に続けて最初のファースト・レベル添え字を返します (NULL 文字列は、最初のエントリのの添え字値を表すために使用します。また、以降の添え字値がないことを示す返り値としても使用されます)。この例では、key が値 1 を含みます。

1、または $ORDER 式の key を使用して、2 番目のファースト・レベル添え字を検索できます。

 SET key = $ORDER(^Data(key))

key に初期値 1 がある場合、この文で 2 に設定されます (^Data(2) が次のファースト・レベル添え字であるため)。この文を再度実行すると、key は次のファースト・レベル添え字の 5 に設定されます。^Data(5) に直接格納されているデータはありませんが、5 を返すという点に注意してください。この文を再実行しても、これ以上ファースト・レベル添え字が存在しないため、key は NULL 文字列 ("") に設定されます。

追加の添え字を $ORDER 関数で使用することで、異なる添え字レベルを繰り返すことができます。$ORDER は、引数リストにある最終添え字の次の値を返します。以下の文は、上記のデータを使用した例です。

 SET key = $ORDER(^Data(1,""))

ここでは、^Data(1,1) が次のセカンド・レベルの添え字であるため、key が 1 に設定されます。この文を再度実行すると、key は次のセカンド・レベル添え字の 2 に設定されます。この文をもう一度実行しても、ノード ^Data(1) にはこれ以上セカンド・レベル添え字が存在しないため、key は “” に設定されます。

$ORDER によるループ

以下の ObjectScript コードは単純グローバルを定義し、そのファースト・レベル添え字をすべてループします。

 // clear ^Data in case it has data
 Kill ^Data

 // fill in ^Data with sample data
 For i = 1:1:100 {
     // Set each node to a random person's name
     Set ^Data(i) = ##class(%PopulateUtils).Name()
 }

 // loop over every node
 // Find first node
 Set key = $Order(^Data(""))

 While (key '= "") {
     // Write out contents
     Write "#", key, " ", ^Data(key),!

     // Find next node
     Set key = $Order(^Data(key))
 }

追加の $ORDER 引数

ObjectScript の $ORDER 関数は、オプションとして 2 番目の引数や 3 番目の引数を持ちます。2 番目の引数は方向フラグで、グローバルを検索する方向を示します。既定値の 1 は前方検索を指定し、-1 は後方検索を指定します。

3 番目の引数はローカル変数名を指定します。$ORDER で見つかったノードがデータを含む場合、そのデータがこのローカル変数に書き込まれます。グローバルをループし、添え字値とノード値を取得する場合は、この引数を使用すると効率的です。

グローバルの反復

指定されたグローバルが連続値の添え字を使用する構成であることがわかっている場合は、単純な For ループでその値を使用して繰り返し処理が可能です。例えば Caché Basic では以下のとおりです。

For i = 1 To 100
    Print ^Data(i)
Next

また、ObjectScript では以下のようになります。

 For i = 1:1:100 {
     Write ^Data(i),!
 }

通常は、上記で説明した $ORDER 関数を使用することをお勧めします。その方が効率も良く、削除されたノードのようなデータ間の欠落部分を心配する必要もありません。

$QUERY 関数

サブノード間を移動して、グローバルにある各ノードと各サブノードにアクセスする場合は、ObjectScript の $QUERY 関数を使用します(または、入れ子にした $ORDER ループでも可能です)。

$QUERY 関数はグローバル参照をとり、グローバルにある次のノードのグローバル参照を含む文字列 (後にノードが続かない場合は "") を返します。$QUERY で返された値を使用するには、ObjectScript の間接演算子 (@) を使用する必要があります。

例えば、以下のグローバルを定義するとします。

 Set ^Data(1) = ""
 Set ^Data(1,1) = ""
 Set ^Data(1,2) = ""
 Set ^Data(2) = ""
 Set ^Data(2,1) = ""
 Set ^Data(2,2) = ""
 Set ^Data(5,1,2) = ""

以下は $QUERY の呼び出しです。

 SET node = $QUERY(^Data(""))

これは、node を文字列 “^Data(1)” に設定します。“^Data(1)” は、グローバルの最初のノードのアドレスです。その後、グローバルの次のノードを取得するには、$QUERY を再度呼び出し、node で間接演算子を使用します。

 SET node = $QUERY(@node)

この時点で、node は文字列 “^Data(1,1)” を含みます。

以下の例では、グローバル・ノードを設定した後、$QUERY を使用して検索し、各ノードのアドレスを記述します。

 Kill ^Data // make sure ^Data is empty

 // place some data into ^Data
 Set ^Data(1) = ""
 Set ^Data(1,1) = ""
 Set ^Data(1,2) = ""
 Set ^Data(2) = ""
 Set ^Data(2,1) = ""
 Set ^Data(2,2) = ""
 Set ^Data(5,1,2) = ""

 // now walk over ^Data
 // find first node
 Set node = $Query(^Data(""))
 While (node '= "") {
     Write node,!
     // get next node
     Set node = $Query(@node)
 }

グローバル内でのデータのコピー

グローバルのコンテンツ (全体または一部) を別のグローバル (またはローカル配列) にコピーするには、ObjectScript の MERGE コマンドを使用します。

以下の例は、OldData グローバルのコンテンツ全体を NewData グローバルにコピーする MERGE コマンドの使用法を示しています。

 Merge ^NewData = ^OldData

MERGE コマンドのソース引数に添え字がある場合、そのノード内のすべてのデータと派生ノードがコピーされます。方向引数に添え字がある場合、宛先のアドレスを最上位ノードとして使用し、データをコピーします。以下はコードの例です。

 Merge ^NewData(1,2) = ^OldData(5,6,7)

これは、^OldData(5,6,7) と、その下にあるすべてのデータを ^NewData(1,2) にコピーします。

グローバルでの共有カウンタ保持

大規模なトランザクション処理では、一意の識別子を作成することで並行処理上の大きなボトルネックが発生することがあります。例えば新規送り状に、それぞれ一意の識別子番号を付ける注文処理作業を考えてみます。従来の方法としては、カウンタ・テーブルのようなものを保持します。新規送り状作成の各過程では、カウンタのロックを取得し、その値をインクリメントし、ロックの解放を行います。その結果、この単独のレコード上で、リソースが頻繁に競合することになります。

この問題を処理するために、Caché には、ObjectScript の $INCREMENT 関数があります。$INCREMENT は、自動的にグローバル・ノードの値をインクリメントします (ノードに値がない場合は 1 に設定されます)。$INCREMENT の基本的な性質としてロックは不要です。他のプロセスからの干渉なしで、インクリメントされた値を返す機能が保証されているからです。

以下のようにして $INCREMENT を使用できます。まず、カウンタを保持するグローバル・ノードを決定します。次に、新規のカウンタ値が必要になるたびに $INCREMENT を実行します。

 SET counter = $INCREMENT(^MyCounter)

Caché オブジェクトと SQL で使用される既定のストレージ構造は、$INCREMENT を使用して固有のオブジェクト (行) 識別子の値を割り当てます。

一時グローバルの使用法

特定の処理に対して、永続性を要求せずに、グローバルの強力な性能が必要になる場合もあります。例えば、グローバルを使用して、ディスクに保存する必要のないデータをソートするとします。このような処理に備え、Caché には一時グローバルの機能が用意されています。

一時グローバルには、以下のような特性があります。

  • 一時グローバルは、常にローカルとして定義される (つまり非ネットワーク) データベースである CACHETEMP データベース内に格納されます。CACHETEMP データベースにマップされたグローバルは、すべて一時グローバルとして扱われます。

  • 一時グローバルに対する変更は、ディスクに書き込まれません。その代わりに、そのような変更はメモリ内のバッファ・プールに保持されます。バッファ・プールに十分なスペースがない場合、大規模な一時グローバルはディスクに書き込まれます。

  • 最大限に効率を良くするため、一時グローバルへの変更はジャーナル・ファイルにはログ記録されません。

  • Caché システムを再起動すると、一時グローバルは自動的に削除されます。(メモ : システムの再起動には長時間を要する場合があるので、一時グローバルを削除する目的でこの方法を使用するのは避けてください。)

既定では、名前が “CacheTemp” で始まるグローバルはすべて、一時グローバルとして定義されます。Caché 自体が使用する一時グローバルとの衝突を避けるため、使用する一時グローバル名は “CacheTempUser” で始めることをお勧めします。

Caché SQL は、一時グローバルを複雑なクエリの最適化用スクラッチ・スペースとして使用します。また、特定のクエリ (ソート、グループ分け、集約の計算用など) の実行中には、一時インデックスとしても使用されます。

グローバルでのデータのソート

グローバルに格納されたデータは、添え字値に従って自動的にソートされます。例えば、以下の ObjectScript コードは、グローバル一式を (順不同で) 定義し、繰り返すことにより、グローバル・ノードが添え字により自動的にソートされることを示します。

 // Erase any existing data
 Kill ^Data
 
 // Define a set of global nodes
 Set ^Data("Cambridge") = ""
 Set ^Data("New York") = ""
 Set ^Data("Boston") = ""
 Set ^Data("London") = ""
 Set ^Data("Athens") = ""

 // Now iterate and display (in order)
 Set key = $Order(^Data(""))
 While (key '= "") {
     Write key,!
     Set key = $Order(^Data(key)) // next subscript
 }

グローバルが提供する自動ソートをアプリケーションで活用すると、ソート処理や、順序付けされた相互参照付インデックスを特定値に保持する処理などが可能です。Caché SQL と ObjectScript は、グローバルを使用してこのようなタスクを自動的に実行します。

グローバル・ノードの照合

グローバルのノードがソートされる順序 (照合と呼びます) は、グローバル自体とそのグローバルを使用しているアプリケーションの 2 段階で制御されます。

アプリケーション・レベルでは、添え字として使用される値のデータ変換を行うことで、グローバル・ノードの照合方法を制御できます (Caché SQL とオブジェクトはユーザ指定の照合機能でこれを実行します)。例えば、大文字小文字は関係なくアルファベット順でソートされた名前リストを生成する場合、一般的にその名前を大文字で表記して添え字として使用します。

 // Erase any existing data
 Kill ^Data
 
 // Define a set of global nodes for sorting
 For name = "Cobra","jackal","zebra","AARDVark" {
     // use UPPERCASE name as subscript
     Set ^Data($ZCONVERT(name,"U")) = name
 }

 // Now iterate and display (in order)
 Set key = $Order(^Data(""))
 While (key '= "") {
     Write ^Data(key),!  // write untransformed name
     Set key = $Order(^Data(key)) // next subscript
 }

この例は、添え字が大文字小文字を区別せずにソートされるように、名前をそれぞれ大文字に変換します ($ZCONVERT 関数を使用します)。元の値が表示されるように、各ノードには未変換の値を保持します。

数値添え字と文字列値添え字

数値は文字列値より先に照合されます。つまり、1 の値は “a” の値よりも先に処理されます。指定された添え字に対して数値と文字列値の両方を使用する場合は、この事実は認識しておく必要があります。(値を基にデータをソートするため) インデックスにグローバルを使用する場合、通常、値は数字 (例えば給与) としてソートするか、文字列 (例えば郵便番号) としてソートします。

数値的に照合されたノードに対する一般的な解決法としては、単項演算子 + を使用して、添え字値を強制的に数値にします。例えば、id 値を age でソートするインデックスを構築する場合、以下のように age が必ず数値になるように強制できます。

 Set ^Data(+age,id) = ""

値を文字列としてソートする場合は (例えば “0022”、“0342”、“1584”)、スペース文字 (“ ”) を付けることで、添え字値が必ず文字列となるように強制できます。例えば、id 値を zipcode (郵便番号) でソートするインデックスを構築する場合、以下のように zipcode が必ず文字列になるように強制できます。

 Set ^Data(" "_zipcode,id) = ""

これにより、“0022” など、先頭に 0 が付く値は常に文字列として扱われます。

$SORTBEGIN 関数と $SORTEND 関数

通常、Caché 内データのソートに関しては心配する必要はありません。SQL を使用するか直接グローバル・アクセスを使用するかによって、自動的にソート処理されます。

しかし、場合によっては、さらに効率的なソート処理が可能な場合もあります。特に、(1) 順不同 (つまりソートされていない状態) で多数のグローバル・ノードを設定する必要があり、(2) 結果グローバルの合計サイズが Caché バッファ・プールの大部分を占める場合は、(データがキャッシュに収まりきらないので) SET 処理の多くはディスクで処理されるようになります。この結果、パフォーマンスが悪影響を受けることがあります。通常は、一時グローバルの大容量データのロード、インデックスの集合、インデックスなしの値のソートなど、インデックス・グローバルの生成にかかわる場合、上記のような状況になります。

これらの状況に効率的に対処するため、ObjectScript では $SORTBEGIN 関数と $SORTEND 関数が用意されています。$SORTBEGIN 関数はグローバル (またはその部分) の特別ノードを初期化します。グローバルへのデータ・セットはスクラッチ・バッファに書き込まれ、メモリ (または一時ディスク・ストレージ) でソートされます。$SORTEND 関数が処理の最後に呼び出されると、データは実際のグローバル・ストレージに順に書き込まれます。ディスク処理を以前ほど要求されずに、書き込みが適切に終了しているため、操作全体がより効率的です。

$SORTBEGIN 関数の使用法は非常に簡単です。ソートを開始する前にソート対象のグローバル名で起動し、処理が完了した時点で $SORTEND を呼び出します。

 // Erase any existing data
 Kill ^Data

 // Initiate sort mode for ^Data global
 Set ret = $SortBegin(^Data)

 // Write random data into ^Data
 For i = 1:1:10000 {
     Set ^Data($Random(1000000)) = ""
 }

 Set ret = $SortEnd(^Data)

 // ^Data is now set and sorted

 // Now iterate and display (in order)
 Set key = $Order(^Data(""))
 While (key '= "") {
     Write key,!
     Set key = $Order(^Data(key)) // next subscript
 }

$SORTBEGIN 関数はグローバル作成の特殊なケースに対して設計されており、使用の際には注意が必要です。特に $SORTBEGIN モードの場合、書き込みをしているグローバルからの読み取りはできません。これは、データが書き込まれていないと、読み取りが正しく行われないためです。

Caché SQL は自動的にこれら関数を使用して、一時インデックス・グローバルを作成します。これは、インデックスの付いていないフィールドでのソート処理などで使用します。

グローバルを使用した間接演算の使用法

間接演算を使用して、ObjectScript は実行時のグローバル参照の作成方法を提供します。これはプログラムの完了時、グローバル構造や名前がわからないアプリケーションにおいて便利です。

間接演算は間接演算子 @ でサポートされ、式を含んだ文字列をデリファレンスします。間接演算には @ 演算子の使用法によって複数のタイプがあります。

以下のコードは、グローバル参照を含む文字列をデリファレンスするときに @ 演算子を使用する、名前間接演算の例を提供します。

 // Erase any existing data
 Kill ^Data

 // Set var to an global reference expression
 Set var = "^Data(100)"

 // Now use indirection to set ^Data(100)
 Set @var = "This data was set indirectly."

 // Now display the value directly:
 Write "Value: ",^Data(100)

添え字間接演算を使用して、式 (変数またはリテラル値) を間接演算文内で混在させることもできます。

 // Erase any existing data
 Kill ^Data

 // Set var to a subscript value
 Set glvn = "^Data"

 // Now use indirection to set ^Data(1) to ^Data(10)
 For i = 1:1:10 {
     Set @glvn@(i) = "This data was set indirectly."
 }

 // Now display the values directly:
 Set key = $Order(^Data(""))
 While (key '= "") {
     Write "Value ",key, ": ", ^Data(key),!
     Set key = $Order(^Data(key))
 }

間接演算は ObjectScript の基本機能で、グローバル参照に制限されません。詳細は、“Caché ObjectScript の使用法” の “演算子” の章の “間接演算” を参照してください。間接演算は直接アクセスほど効果的ではないので、その点を考慮して使用してください。

トランザクション管理

Caché は、グローバルを使用した本格的なトランザクション処理の実装に必要な初期演算を提供します。Caché オブジェクトと SQL は、これらの機能を自動的に利用します。トランザクション用データをグローバルに直接書き込む場合に、これらの演算子を使用できます。

トランザクション・コマンドは、トランザクションの開始を定義する TSTART、現在のトランザクションを実行する TCOMMIT、現在のトランザクションを一時停止して、トランザクションの始めからのグローバルへの変更を元に戻す TROLLBACK です。

例えば、以下の ObjectScript コードは、トランザクションの開始を定義し、いくつかのグローバル・ノードを設定して、ok 値によってトランザクションを実行またはロールバックします。

 TSTART

 Set ^Data(1) = "Apple"
 Set ^Data(2) = "Berry"

 If (ok) {
     TCOMMIT
 }
 Else {
     TROLLBACK
 }

TSTART は、トランザクション開始マーカを Caché ジャーナル・ファイルに書き込みます。これは、トランザクション開始の境界線を定義します。変数 ok が上記の例で True (ゼロ以外) の場合、TCOMMIT コマンドはトランザクションの終わりをマークし、トランザクション終了マーカがジャーナル・ファイルに書き込まれます。ok が False (0) の場合、TROLLBACK コマンドはセットすべてを元に戻すか、またはトランザクションの開始時点からの演算を無効にします。この場合、^Data(1)^Data(2) は前の値にリストアされます。

トランザクションの正常終了の時点で、書き込まれるデータはありません。これは、トランザクション中のデータベースの変更すべてが、正常としてトランザクションの過程で実行されるためです。ロールバックの場合にのみ、データベースのデータに影響します。これは、この例のトランザクションが備えている分離が、限定された範囲にとどまることを意味します。つまり、トランザクションがコミットされていないうちは、修正されたグローバル値を他のプロセスからも見ることができます。これは通常、コミットされていない読み取りと呼ばれます。これが良いか悪いかは、アプリケーションの要件によって異なりますが、多くの場合では問題ありません。アプリケーションに強力な分離が必要な場合は、ロックを使用します。これは、以下のセクションで説明します。

ロックとトランザクション

トランザクションを分離し、修正されたデータがトランザクションのコミット前に他のプロセスから見えないようにするには、ロックを使用する必要があります。ObjectScript では、LOCK コマンドを使用して、ロックを直接取得および解放できます。ロックは規約に従って機能します。指定されたデータ構造 (永続オブジェクトに使用されるものなど) に対し、ロックを必要とするすべてのコードは、すべて同じ論理ロック参照 (例えば LOCK コマンドによって同じアドレスが使用される) を使用します。

トランザクションでは、ロックは独特の動作をします。つまり、トランザクション中に取得されたすべてのロックは、トランザクションが終了するまで解放されません。その理由を、通常のトランザクションで実行されるアクションで考えてみます。

  1. TSTART を使用して、トランザクションを開始します。

  2. 修正を希望するノード (単数または複数) で、ロック (単数または複数) を取得します。これは通常 “書き込み” ロックと呼ばれます。

  3. ノード (1 つ、または複数) を変更します。

  4. ロック (1 つ、または複数) を解放します。トランザクション中のため、これらのロックはこの時点では実際には解放されません。

  5. TCOMMIT を使用して、トランザクションを終了します。この時点で、前の手順で解放したすべてのロックが実際に解放されます。

他のプロセスがこのトランザクションに関連するノードを見る際に、コミットされていない変更箇所が表示されないようにする場合は、そのノードからデータを読み取る前にロック (“読み取り” ロックと呼ばれます) のテストを行います。書き込みロックはトランザクション終了まで保持されているので、トランザクションが完了 (コミットまたはロールバック) するまで、読み取りプロセスにはデータが表示されません。

大半のデータベース管理システムが、類似した機能を使用して、トランザクション分離を提供します。Caché は、このメカニズムを開発者が使用できるという点がユニークです。トランザクションをサポートしているときでも、新規アプリケーション・タイプのカスタム・データベース構造の生成を可能にします。当然、Caché オブジェクトまたは SQL でデータの管理やトランザクション管理を自動的に行うことができます。

入れ子にされた TSTART の呼び出し

Caché には、特別なシステム変数 $TLEVEL があります。これは、TSTART コマンドを呼び出した回数をトラッキングします。$TLEVEL は値 0 から始まります。TSTART を呼び出すたびに $TLEVEL の値は 1 ずつインクリメントされます。また、TCOMMIT を呼び出すたびに値が 1 ずつディクリメントされます。TCOMMIT の呼び出しによって $TLEVEL が 0 に戻ると、トランザクションは (コミットされて) 終了します。

TROLLBACK コマンドへの呼び出しは常に、現在のトランザクションを終了させ、$TLEVEL をその値に関係なく元の 0 に設定します。

この動作をアプリケーションで利用すると、トランザクションを含むコード (オブジェクト・メソッドなど) を別のトランザクションで包むことができます。例えば、永続オブジェクトに備わっている %Save メソッドは、常に、トランザクションとしてのオペレーションを実行します。TSTARTTCOMMIT を明示的に呼び出すことで、複数のオブジェクト保存処理を含むさらに大きなトランザクションを生成することができます。

 TSTART
 Set sc = object1.%Save()
 If ($$$ISOK(sc)) {
     // first save worked, do the second
    Set sc = object2.%Save()
 }

 If ($$$ISERR(sc)) {
     // one of the saves failed, rollback
     TROLLBACK
 }
 Else {
     // everything is ok, commit
  TCOMMIT
 }

並行処理の管理

シングル・グローバル・ノードの設定、または検索はアトミックです。 必ず、常に一定の結果が得られます。多次元ノードの操作やトランザクションの分離制御について (ロックとトランザクション のセクション参照)、Caché はロックを取得する機能と解放する機能を提供します。

ロックは、Caché ロック・マネージャで管理されます。ObjectScript では、LOCK コマンドを使用して、ロックを直接取得および解放できます。(Caché オブジェクトと SQL は、ロックの取得と解放を必要に応じて自動的に行います)。

LOCK コマンドの詳細は、リファレンス・ページの “LOCK コマンド” を参照してください。

最新のグローバル参照のチェック

最新のグローバル参照は、ObjectScript の $ZREFERENCE 特殊変数に記録されます。$ZREFERENCE には、指定によって添え字と拡張グローバル参照など最新のグローバル参照が組み込まれています。$ZREFERENCE は、グローバル参照が成功したかどうか、あるいは指定グローバルが存在するかどうかを示すものではありません。Caché は、指定された最新のグローバル参照を記録しているにすぎません。

ネイキッド・グローバル参照

Caché は、添え字付きグローバル参照の後にグローバル名と添え字レベルを示すネイキッド・インジケータを設定します。その後、ネイキッド・グローバル参照を使用して、同じグローバルと添え字レベルに連続して参照を作成します。グローバル名と上位レベルの添え字は削除されます。これにより、同じ (または下位の) 添え字レベルの同じグローバルに対する参照の繰り返しを能率的に行います。

ネイキッド参照に下位の添え字レベルを指定すると、そのレベルに合わせてネイキッド・インジケータがリセットされます。したがって、ネイキッド・グローバル参照を使用する場合、常に最新のグローバル参照で構築した添え字レベルで作業することになります。

ネイキッド・インジケータ値は、$ZREFERENCE 特殊変数に記録されます。この値は NULL 文字列に初期化されます。ネイキッド・インジケータが設定されていない状態でネイキッド・グローバル参照を試みると、<NAKED> エラーが生じます。ネームスペースを変更すると、ネイキッド・インジケータも再初期化されます。$ZREFERENCE を NULL 文字列 ("") に設定することで、ネイキッド・インジケータを再初期化できます。

以下の例は、添え字付きのグローバル ^Produce("fruit",1) が最初の参照に指定されています。Caché は、このグローバル名と添え字をネイキッド・インジケータに保存します。したがって、次のネイキッド・グローバル参照ではグローバル名 "Produce" と上位の添え字レベル "fruit" を省略できます。^(3,1) ネイキッド参照が下位の添え字レベルに移動した場合、この新規の添え字レベルが、その後に続くネイキッド・グローバル参照の条件となります。

   SET ^Produce("fruit",1)="Apples"  /* Full global reference  */
   SET ^(2)="Oranges"                /* Naked global references */
   SET ^(3)="Pears"                  /* assume subscript level 2 */
   SET ^(3,1)="Bartlett pears"       /* Go to subscript level 3  */
   SET ^(2)="Anjou pears"            /* Assume subscript level 3 */
   WRITE "latest global reference is: ",$ZREFERENCE,!
   ZWRITE ^Produce
   KILL ^Produce

この例は、グローバル変数 ^Produce("fruit",1)、^Produce("fruit",2)、^Produce("fruit",3)、^Produce("fruit",3,1)、^Produce("fruit",3,2) を設定します。

例外はありますが、ほとんどのグローバル参照 (完全あるいはネイキッド) がネイキッド・インジケータを設定します。$ZREFERENCE 特殊変数は、ネイキッド・グローバル参照の場合でも、最新のグローバル参照の完全なグローバル名と添え字を保持しています。ZWRITE コマンドも、ネイキッド参照を使用して設定したかどうかにかかわらず、各グローバルの完全な名前と添え字を表示します。

ネイキッド・グローバル参照は注意して使用する必要があります。Caché は、ネイキッド・インジケータを常に明確に設定するわけではないからです。以下はその例です。

  • ネイキッド・インジケータは、完全グローバル参照によって最初に設定されます。そのグローバル参照が失敗しても、それ以降の完全グローバル参照やネイキッド・グローバル参照によって、ネイキッド・インジケータが変更されます。例えば、存在しないグローバルの値を WRITE しようとすると、ネイキッド・インジケータが設定されます。

  • 添え字付きのグローバルを参照するコマンド後置条件では、Caché が後置条件を評価する方法に関係なく、ネイキッド・インジケータが設定されます。

  • 添え字付きのグローバルを参照するオプション関数の引数は、Caché がすべての引数を評価するかどうかによって、ネイキッド・インジケータを設定する場合としない場合があります。例えば、$GET の 2 番目の引数を指定すると、その既定値が使用されなくても、必ずネイキッド・インジケータが設定されます。Caché は、左から右の順番で引数を評価します。したがって、最後の引数は、最初の引数で設定されたネイキッド・インジケータをリセットする場合があります。

  • トランザクションをロールバックする TROLLBACK コマンドは、ネイキッド・インジケータをトランザクションの最初の値にはロールバックしません。

完全グローバル参照に拡張グローバル参照を含む場合、その後に続くネイキッド・グローバル参照は、同じ拡張グローバル参照と見なされます。つまり、ネイキッド・グローバル参照の一部として拡張参照を指定する必要はありません。

FeedbackOpens in a new tab