グローバル配列の操作
Node.js Native SDK は、InterSystems IRIS のインスタンスからグローバル配列を操作するメカニズムを提供します。この章では、以下の項目について説明します。
-
グローバル配列の概要 — グローバル配列の概念を示し、Native SDK がどのように使用されるかを簡単に説明します。
-
ノードの作成、更新、および削除 — グローバル配列を作成し、ノード値を設定または変更する方法について説明します。
-
グローバル配列のノードの検索 — グローバル配列のノードへの高速アクセスを可能にする反復メソッドについて説明します。
この章の例では、irisjs という名前の Iris オブジェクトが既に存在し、サーバに接続されていると想定します。これは、以下のコードを使用して作成し、接続されています。
const IRISNative = require('intersystems-iris-native')
let connectionInfo = {host:'127.0.0.1', port:51773, ns:'USER', user:'_SYSTEM', pwd:'SYS'};
const conn = IRISNative.createConnection(connectionInfo);
const irisjs = conn.createIris();
詳細は、createConnection() および createIris() のクイック・リファレンスのエントリを参照してください。
グローバル配列の概要
グローバル配列は、すべてのスパース配列のように、ツリー構造です (連続的に番号付けされたリストではありません)。グローバル配列の背後にある基本概念は、ファイル構造に例えて示すことができます。ツリーの各ディレクトリは、ルート・ディレクトリ識別子とそれに続く一連のサブディレクトリ識別子で構成されるパスによって一意に識別され、ディレクトリにはデータが含まれることも、含まれないこともあります。
グローバル配列の仕組みも同じです。ツリーの各ノードは、グローバル名識別子と一連の添え字識別子で構成されるノード・アドレスによって一意に識別され、ノードには値が含まれることも、含まれないこともあります。例えば、以下は 5 つのノードで構成されるグローバル配列で、そのうち 2 つのノードには値が含まれます。
root -->|--> foo --> SubFoo='A'
|--> bar --> lowbar --> UnderBar=123
値はその他のノード・アドレス (root または root->bar など) に格納できますが、それらのノード・アドレスが値なしの場合、リソースは無駄になりません。InterSystems ObjectScript グローバルの表記では、値を持つ 2 つのノードは次のようになります。
root('foo','SubFoo')
root('bar','lowbar','UnderBar')
グローバル名 (root) の後に、括弧で囲まれたコンマ区切り添え字リストが続きます。この両者で、ノードのパス全体を指定します。
このグローバル配列は、Native SDK set() メソッドへの 2 つの呼び出しで作成されます。
irisjs.set('A', 'root', 'foo', 'SubFoo');
irisjs.set(123, 'root', 'bar', 'lowbar', 'UnderBar');
グローバル配列 root は、最初の呼び出しが値 'A' をノード root('foo','SubFoo') に割り当てるときに作成されます。ノードは任意の順序で、任意の添え字セットを使用して作成できます。これら 2 つの呼び出しの順序を逆にした場合でも、同じグローバル配列が作成されます。値なしノードは自動的に作成され、不要になると自動的に削除されます。詳細は、このドキュメントで後述する “ノードの作成、更新、および削除” を参照してください。
この配列を作成する Native SDK コードを、以下に例示します。Connection オブジェクトをサーバに接続して、クラス Iris のインスタンスを作成します。Iris メソッドを使用してグローバル配列を作成し、生成された永続値をデータベースから読み取った後、グローバル配列を削除します。
// Create a reference to the Native SDK module
const IRISNative = require('intersystems-iris-native')
// Open a connection to the server and create an Iris object
let connectionInfo = {
host: '127.0.0.1',
port: 51773,
ns: 'USER',
user: '_SYSTEM',
pwd: 'SYS'
};
const conn = IRISNative.createConnection(connectionInfo);
const irisjs = conn.createIris();
// Create a global array in the USER namespace on the server
irisjs.set('A', 'root', 'foo', 'SubFoo');
irisjs.set(123, 'root', 'bar', 'lowbar', 'UnderBar');
// Read the values from the database and print them
let subfoo = irisjs.get('root', 'foo', 'SubFoo')
let underbar = irisjs.get('root', 'bar', 'lowbar', 'UnderBar')
console.log('Created two values: ');
console.log(' root("foo","SubFoo")=' + subfoo);
console.log(' root("bar","lowbar","UnderBar")=' + underbar);
NativeDemo は、以下の行を出力します。
Created two values:
root("foo","SubFoo")=A
root("bar","lowbar","UnderBar")=123
この例では、IRISNative は Native SDK のインスタンスです。静的メソッド createConnection() は、USER ネームスペースに関連付けられているデータベースへの接続を提供する、conn という名前の Connection オブジェクトを定義します。Native SDK メソッドは以下のアクションを実行します。
-
Connection.createIRIS() は、サーバ接続 conn を介してデータベースにアクセスする、irisjs という名前の Iris の新しいインスタンスを作成します。
-
Iris.set() は、データベース・ネームスペース USER に新しい永続ノードを作成します。
-
Iris.get() は、データベースに対してクエリを実行して、指定されたノードの値を返します。
-
Iris.kill() は、指定されたルート・ノードとそのサブノードすべてをデータベースから削除します。
次の章では、これらのすべてのメソッドについて詳細に説明し、例を示します。
Native SDK 用語の用語集
ここに示す概念の概要については、前のセクションを参照してください。この用語集の例は、以下に示すグローバル配列構造を指しています。Legs グローバル配列には、10 個のノードと 3 つのノード・レベルがあります。10 個のノードのうち 7 つには、値が含まれます。
Legs // root node, valueless, 3 child nodes
fish = 0 // level 1 node, value=0
mammal // level 1 node, valueless
human = 2 // level 2 node, value=2
dog = 4 // level 2 node, value=4
bug // level 1 node, valueless, 3 child nodes
insect = 6 // level 2 node, value=6
spider = 8 // level 2 node, value=8
millipede = Diplopoda // level 2 node, value='Diplopoda', 1 child node
centipede = 100 // level 3 node, value=100
指定された親ノードの直下にあるノードです。子ノードのアドレスは、親添え字リストの末尾に 1 つの添え字を追加して指定します。例えば、親ノード Legs('mammal') には子ノード Legs('mammal','human') および Legs('mammal','dog') があります。
ルート・ノードの識別子は、グローバル配列全体の名前でもあります。例えば、ルート・ノード識別子 Legs は、グローバル配列 Legs のグローバル名です。
グローバル配列の要素で、グローバル名と任意の数の添え字識別子で構成されるネームスペースによって一意に識別されます。ノードは、データを含むか、子ノードを持つか、またはこれらの両方を持つ必要があります。
ノード・アドレス内の添え字の数。'レベル 2 ノード' は、'2 つの添え字を持つノード' のもう 1 つの表現方法です。例えば、Legs('mammal','dog') は、レベル 2 ノードです。ルート・ノード Legs の 2 レベル下で、Legs('mammal') の 1 レベル下です。
グローバル名とすべての添え字を含む、ノードの完全なネームスペースです。例えば、ノード・アドレス Legs("fish") は、ルート・ノード識別子 'Legs' と 1 つの添え字 'fish' を含むリストで構成されます。コンテキストに応じて、Legs (添え字リストなし) はルート・ノード・アドレスまたはグローバル配列全体を参照することができます。
グローバル配列ツリーの基点にある添え字なしノードです。ルート・ノードの識別子は、その添え字なしのグローバル名です。
特定のノードのすべての下位ノードは、そのノードのサブノードと呼ばれます。例えば、ノード Legs('bug') には 2 つのレベルの 4 つの異なるサブノードがあります。9 つの添え字付きノードはすべて、ルート・ノード Legs のサブノードです。
ルート・ノードの下にあるノードはすべて、グローバル名および 1 つまたは複数の添え字識別子のリストを指定して処理されます(グローバル名に添え字リストを加えたものが、ノード・アドレスです)。
多くの Native SDK メソッドは有効なノード・アドレスを指定する必要がありますが、ノード・アドレスは必ずしも既存のノードを指す必要はありません。例えば、set() メソッドは value 引数とターゲット・アドレスを取り、そのアドレスに値を格納します。ターゲット・アドレスにノードが存在しない場合は、新しいノードが作成されます。
ノードには、サポートされている任意の型の値を含めることができます。子ノードを持たないノードには値を含める必要があり、子ノードを持つノードは値なしにすることができます。
ノードは、データを含むか、子ノードを持つか、またはこれらの両方を持つ必要があります。子ノードを持っているがデータを含まないノードは、値なしノードと呼ばれます。値なしノードは、単に下位レベルのノードへのポインタです。
グローバル命名規則
グローバル名と添え字は、次の規則に従います。
-
ノード・アドレスの長さ (グローバル名とすべての添え字の長さの合計) は最大 511 文字です(入力した一部の文字は、この制限のために、複数のエンコードされた文字としてカウントされることがあります。詳細は、“グローバル参照の最大長” を参照してください)。
-
グローバル名には文字、数字、およびピリオド ('.') を使用でき、最大 31 文字の有効文字を使用できます。文字で始まる必要があり、ピリオドで終了することはできません。
-
添え字は文字列、整数、または数値にすることができます。文字列の添え字は大文字と小文字が区別され、すべてのタイプの文字を使用できます。長さの制限は、ノード・アドレス全体の最大長が 511 文字という制限のみです。
ノードの作成、更新、および削除
ここでは、ノードの作成、更新、および削除に使用する Native SDK について説明します。set()、increment()、および kill() は、グローバル配列を作成したり、そのコンテンツを変更したりできる唯一のメソッドです。以下の例で、これらの各メソッドを使用する方法を示します。
Iris.set() は、value 引数を取り、指定されたアドレスにその値を格納します。
そのアドレスにノードが存在しない場合は、新しいノードが作成されます。
set() メソッドは、サポートされているすべてのデータ型の値を割り当てることができます。以下の例では、set() の最初の呼び出しによって、新しいノードがサブノード・アドレス myGlobal('A') に作成され、このノードの値が文字列 'first' に設定されます。2 回目の呼び出しによって、サブノードの値が変更され、整数 1 に置き換えられます。
irisjs.set('first', 'myGlobal', 'A'); // create node myGlobal('A') = 'first'
irisjs.set(1, 'myGlobal', 'A'); // change value of myGlobal('A') to 1.
set() は、この例に示すように、サポートされている任意のデータ型の値を作成および変更できるポリモーフィック・アクセサです。
Iris.increment() は、整数の number 引数を取り、その数だけノードの値をインクリメントし、そのインクリメントした値を返します。ターゲット・ノードの初期値は、サポートされている任意の数値型ですが、インクリメントした値は整数になります。ターゲット・アドレスにノードがない場合、このメソッドはノードを作成し、値として number 引数を割り当てます。このメソッドはスレッドセーフなアトミック処理を使用して、ノードの値を変更します。したがって、ノードは決してロックされません。
以下の例では、increment() の最初の呼び出しによって、新しいサブノード myGlobal('B') が値 -2 で作成され、返り値が total に割り当てられます。次の 2 回の呼び出しによって、それぞれ -2 だけインクリメントされ、新しい値が total に割り当てられ、ノード値が -6 になるとループが終了します。
do {
var total = irisjs.increment(-2,'myGlobal', 'B');
} while (total > -6)
console.log('total = ' + total + ' and myGlobal('B') = ' + irisjs.get('myGlobal', 'B'))
// Prints: total = -6 and myGlobal('B') = -6
Iris.kill() — 指定したノードとそのサブノードすべてを削除します。ルート・ノードが削除された場合、または値を持つすべてのノードが削除された場合、グローバル配列全体が削除されます。
グローバル配列 myGlobal には、最初に以下のノードが含まれます。
myGlobal = <valueless node>
myGlobal('A') = 0
myGlobal('A',1) = 0
myGlobal('A',2) = 0
myGlobal('B') = <valueless node>
myGlobal('B',1) = 0
この例では、その 2 つのサブノードで kill() を呼び出すことで、グローバル配列を削除します。最初の呼び出しによって、ノード myGlobal('A') とその両方のサブノードが削除されます。
irisjs.kill('myGlobal', 'A');
// also kills child nodes myGlobal('A',1) and myGlobal('A',2)
2 番目の呼び出しによって、値を持つ最後のサブノードが削除され、グローバル配列全体が削除されます。
irisjs.kill('myGlobal','B',1);
-
親ノード myGlobal('B') は、値がなく、サブノードもなくなったため、削除されます。
-
ルート・ノード myGlobal は値がなく、サブノードもなくなったため、グローバル配列全体がデータベースから削除されます。
ルート・ノードで kill() を呼び出すことで、グローバル配列全体を即座に削除できます。
irisjs.kill('myGlobal');
グローバル配列のノードの検索
Native SDK は、1 つのグローバル配列の一部または全部に対して反復処理する方法を提供します。以下のトピックでは、さまざまな反復メソッドについて説明します。
-
一連の子ノードにわたる反復 — 指定した親ノードの下のすべての子ノードにわたって反復する方法について説明します。
-
next() を使用した反復処理 — 反復処理を詳細に制御するメソッドについて説明します。
-
子ノードおよびノード値のテスト — ノード・レベルに関係なくすべてのサブノードを検索して、値を持つノードを識別する方法について説明します。
一連の子ノードにわたる反復
子ノードは、同じ親ノードの直下にある一連のノードです。親の添え字リストに 1 つの添え字を追加することで、子ノード・アドレスを定義できます。例えば、以下のグローバル配列には、親ノード heroes('dogs') の下に 4 つの子ノードがあります。
このグローバル配列では、いくつかのヒーロー犬 (および無謀な少年と先導する羊) の名前を添え字として使用します。値は生まれた年です。
heroes // root node, valueless, 2 child nodes
heroes('dogs') // level 1 node, valueless, 4 child nodes
heroes('dogs','Balto') = 1919 // level 2 node, value=1919
heroes('dogs','Hachiko') = 1923 // level 2 node, value=1923
heroes('dogs','Lassie') = 1940 // level 2 node, value=1940, 1 child node
heroes('dogs','Lassie','Timmy') = 1954 // level 3 node, value=1954
heroes('dogs','Whitefang') = 1906 // level 2 node, value=1906
heroes('sheep') // level 2 node, valueless, 1 child node
heroes('sheep','Dolly') = 1996 // level 2 node, value=1996
以下のメソッドを使用して反復子を作成し、反復処理の方向を定義し、検索の開始位置を設定します。
-
Iris.iterator() は、指定されたターゲット・ノードの子ノードの Iterator のインスタンスを返します。
-
Iterator.reversed() — 順方向と逆方向の照合順序の間で反復処理を切り替えます。
-
Iterator..startFrom() は、反復子の開始位置を指定の添え字に設定します。添え字は任意の開始ポイントであり、既存のノードを処理する必要はありません。
以下のコードは、heroes('dogs') の子ノードを逆方向の照合順序で反復処理します (添え字 V から開始します)。
// Iterate in reverse, seeking nodes lower than heroes('dogs','V') in collation order
let iterDogs = irisjs.iterator('heroes','dogs').reversed().startFrom('V');
let output = '\nDog birth years: ';
for ([key,value] of iterDogs) {
output += key + ':' + value + ' ';
};
console.log(output);
このコードによって、以下のような出力が生成されます。
Dog birth years: Lassie:1940 Hachiko:1923 Balto:1919
この例では、heroes('dogs') の以下の 2 つのサブノードは無視されます。
-
子ノード heroes('dogs','Whitefang') は検索範囲外にあるため、見つかりません (Whitefang は照合順序が V より上位です)。
-
レベル 3 ノード heroes('dogs','Lassie','Timmy') は、dogs ではなく Lassie の子であるため、見つかりません。
複数のノード・レベルにわたって反復処理する方法については、この章の最後のセクション (“子ノードおよびノード値のテスト”) を参照してください。
ノードが取得される順序は、添え字の照合順序によって決まります。ノードが作成されると、ストレージ定義で指定された照合順に、自動的にノードが格納されます。この例では、heroes('dogs') の子ノードは、作成された順序に関係なく、表示されている順序 (Balto、Hachiko、Lassie、Whitefang) で格納されます。詳細は、"グローバルの使用法" の “グローバル・ノードの照合” を参照してください。
next() を使用した反復処理
Native SDK は、標準の next() および以下の返りタイプの反復子のメソッドもサポートしています。
-
Iterator.next() — 次の子ノードに反復子を配置し (次の子ノードがある場合)、プロパティ done と value を含む配列を返します。これ以上ノードがない場合、done プロパティは false になります。反復子が作成されると、既定で entries() 返りタイプになります。
-
Iterator.entries() — 返りタイプを、キー (子ノードの最上位の添え字) とノード値の両方を含む配列に設定します。例えば、ノード heroes(,'dogs','Balto') の返り値は ['Balto',1919] になります。
-
Iterator.keys() — キー (最上位の添え字) のみを返すように返りタイプを設定します。
-
Iterator.values() — ノード値のみを返すように返りタイプを設定します。
以下の例では、next() メソッドを呼び出すたびに、反復子の done プロパティと value プロパティの現在の値を含む配列に変数 iter が設定されます。keys() メソッドは反復子の作成時に呼び出されたため、value プロパティには heroes('dogs') の現在の子ノードのキー (最上位の添え字) のみが含まれます。
// Get a list of child subscripts under node heroes('dogs')
let iterDogs = irisjs.iterator('heroes','dogs').keys();
let output = "\nSubscripts under node heroes('dogs'): ";
let iter = iterDogs.next();
while (!iter.done) {
output += iter.value + ' ';
iter = iterDogs.next();
}
console.log(output);
このコードによって、以下のような出力が生成されます。
Subscripts under node heroes('dogs'): Balto Hachiko Lassie Whitefang
子ノードおよびノード値のテスト
前の例では、検索の範囲は heroes('dogs') の子ノードに制限されています。グローバル配列 heroes の以下の 2 つの値は異なる親の下にあるため、反復子はこれらの値を見つけられません。
-
レベル 3 ノード heroes('dogs','Lassie','Timmy') は、dogs ではなく Lassie の子であるため、見つかりません。
-
レベル 2 ノード heroes('sheep','Dolly') は、dogs ではなく sheep の子であるため、見つかりません。
グローバル配列全体を検索するには、子ノードを持つすべてのノードを見つけて、子ノードのセットごとに反復子を作成する必要があります。isDefined() メソッドは、必要な情報を提供します。
-
Iris.isDefined() — これを使用して、ノードに値、サブノード、またはその両方があるかどうかを確認できます。以下の値を返します。
-
0 — 指定したノードは存在しません。
-
1 — ノードは存在し、値があります。
-
10 — ノードに値はありませんが、子ノードがあります。
-
11 — ノードには値と子ノードの両方があります。
返り値を使用して、いくつかの有用なブーリアン値を確認できます。
let exists = (irisjs.isDefined(root,subscripts) > 0); // returns 1, 10, or 11 let hasValue = (irisjs.isDefined(root,subscripts)%10 > 0); // returns 1 or 11 let hasChild = (irisjs.isDefined(root,subscripts) > 9); // returns 10 or 11
-
以下の例は、2 つのメソッドで構成されます。
-
findAllHeroes() は、現在のノードの子ノードを反復処理して、ノードごとに testNode() を呼び出します。testNode() によって現在のノードに子ノードがあると示された場合、findAllHeroes() は次のレベルの子ノードの新しい反復子を作成します。
-
testNode() は、heroes グローバル配列のノードごとに呼び出されます。現在のノードで isDefined() を呼び出して、ノードに子ノードがあるかどうかを示すブーリアン値を返します。各ノードのノード情報も出力します。
この例では、既知の構造を処理して、入れ子になった単純な呼び出しを使用してさまざまなレベルを検索します。構造に任意の数のレベルがあるようなあまり一般的でない場合には、再帰アルゴリズムを使用できます。
function findAllHeroes() {
const root = 'heroes';
console.log('List all subnodes of root node '+root+':\n'+root)
let iterRoot = irisjs.iterator(root);
let hasChild = false;
// Iterate over children of root node heroes
for ([sub1,value] of iterRoot) {
hasChild = testNode(value,root,sub1);
// Iterate over children of heroes(sub1)
if (hasChild) {
let iterOne = irisjs.iterator(root,sub1);
for ([sub2,value] of iterOne) {
hasChild = testNode(value,root,sub1,sub2);
// Iterate over children of heroes(sub1,sub2)
if (hasChild) {
let iterTwo = irisjs.iterator(root,sub1,sub2);
for ([sub3,value] of iterTwo) {
testNode(value,root,sub1,sub2,sub3); //no child nodes below level 3
}
} //end level 2
}
} //end level 1
} // end main loop
} // end findAllHeroes()
function testNode(value, root, ...subs) {
// Test for values and child nodes
let state = irisjs.isDefined(root,...subs);
let hasValue = (state%10 > 0); // has value if state is 1 or 11
let hasChild = (state > 9); // has child if state is 10 or 11
// format the node address output string
let subList = Array.from(subs);
let level = subList.length-1;
let indent = ' ' + String(' ').slice(0,(level*2));
let address = indent + root+'(' + subList.join() + ')';
// Add node value to string and note special cases
if (hasValue) { // ignore valueless nodes
address += ' = ' + value;
for (name of ['Timmy','Dolly']) {
if (name == subList[level]) {
address += ' (not a dog!)'
}
}
}
console.log(address);
return hasChild;
}
}
このメソッドは、以下の行を記述します。
List all subnodes of root node heroes:
heroes
heroes(dogs)
heroes(dogs,Balto) = 1919
heroes(dogs,Hachiko) = 1923
heroes(dogs,Lassie) = 1940
heroes(dogs,Lassie,Timmy) = 1954 (not a dog!)
heroes(dogs,Whitefang) = 1906
heroes(sheep)
heroes(sheep,Dolly) = 1996 (not a dog!)
testNodes() の出力には、heroes('dogs') の子ノードではないために、前の例では見つからなかったノードがいくつか含まれます。
-
heroes('dogs','Lassie','Timmy') は、dogs ではなく Lassie の子です。
-
heroes('sheep','Dolly') は、dogs ではなく sheep の子です。