Learning
Community
Open Exchange
Global Masters
InterSystems IRIS Data Platform 2019.4 / Application Development / Using the Native API for Java / Working with Global Arrays
Previous section   Next section

Working with Global Arrays

This chapter covers the following topics:

Introduction to Global Arrays

A global array, like all sparse arrays, is a tree structure rather than a sequential list. The basic concept behind global arrays can be illustrated by analogy to a file structure. Each directory in the tree is uniquely identified by a path composed of a root directory identifier followed by a series of subdirectory identifiers, and any directory may or may not contain data.
Global arrays work the same way: each node in the tree is uniquely identified by a node address composed of a global name identifier and a series of subscript identifiers, and a node may or may not contain a value. For example, here is a global array consisting of six nodes, two of which contain values:
   root -->|--> foo --> SubFoo="A"
           |--> bar --> lowbar --> UnderBar=123
Values could be stored in the other possible node addresses (for example, root or root->bar), but no resources are wasted if those node addresses are valueless. In InterSystems ObjectScript globals notation, the two nodes with values would be:
   root("foo","SubFoo")
   root("bar","lowbar","UnderBar")
The global name (root) is followed by a comma-delimited subscript list in parentheses. Together, they specify the entire path to the node.
This global array could be created by two calls to the Native API Set() method:
  irisObject.Set("A", "root", "foo", "SubFoo");
  irisObject.Set(123, "root", "bar", "lowbar", "UnderBar");

Global array root is created when the first call assigns value "A" to node root("foo","SubFoo"). Nodes can be created in any order, and with any set of subscripts. The same global array would be created if we reversed the order of these two calls. The valueless nodes are created automatically, and will be deleted automatically when no longer needed. For details, see “Creating, Updating, and Deleting Nodes”.
The Native API code to create this array is demonstrated in the following example. An IRISConnection object establishes a connection to the server. The connection will be used by an instance of class IRIS named iris. Native API methods are used to create a global array, read the resulting persistent values from the database, and then delete the global array.
The NativeDemo Program
The Native API for Java is an extension to the InterSystems JDBC driver. For detailed information on installation and usage, see the “Configuration and Requirements” in Using Java with InterSystems Software.
package nativedemo;
import com.intersystems.jdbc.*;
public class NativeDemo {
  public static void main(String[] args) throws Exception {
    try {

//Open a connection to the server and create an IRIS object
      String connStr = "jdbc:IRIS://127.0.0.1:51773/USER";
      String user = "_SYSTEM";
      String password = "SYS";
      IRISConnection conn = (IRISConnection) java.sql.DriverManager.getConnection(connStr,user,password);
      IRIS iris = IRIS.createIRIS(conn);

//Create a global array in the USER namespace on the server
      iris.set("A", "root", "foo", "SubFoo");
      iris.set(123, "root", "bar", "lowbar", "UnderBar");

// Read the values from the database and print them
      String subfoo = iris.getString("root", "foo", "SubFoo");
      String underbar = iris.getString("root", "bar", "lowbar", "UnderBar");
      System.out.println("Created two values: \n"
         + " root(\"foo\",\"SubFoo\")=" + subfoo + "\n"
        + " root(\"bar\",\"lowbar\",\"UnderBar\")=" + underbar);

//Delete the global array and terminate
      iris.kill("root"); // delete global array root
      iris.close();
      conn.close();
    }
    catch (Exception e) {
      System.out.println(e.Message);
    }
  }// end main()
} // end class NativeDemo
NativeDemo prints the following lines:
Created two values:
   root("foo","SubFoo")=A
   root("bar","lowbar","UnderBar")=123
In this example, an IRISConnection object named conn provides a connection to the database associated with the USER namespace. Native API methods perform the following actions:
  • IRIS.createIRIS() creates a new instance of IRIS named iris, which will access the database through conn.
  • IRIS.set() creates new persistent nodes in the database.
  • IRIS.getString() queries the database and returns the values of the specified nodes.
  • IRIS.kill() deletes the specified node and all of its subnodes from the database.
The next chapter provides detailed explanations and examples for all of these methods.

Glossary of Native API Terms

See the previous section for an overview of the concepts listed here. Examples in this glossary will refer to the global array structure listed below. The Legs global array has ten nodes and three node levels. Seven of the ten nodes contain values:
  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

Child node
The nodes immediately under a given parent node. The address of a child node is specified by adding exactly one subscript to the end of the parent subscript list. For example, parent node Legs("mammal") has child nodes Legs("mammal","human") and Legs("mammal","dog").
Global name
The identifier for the root node is also the name of the entire global array. For example, root node identifier Legs is the global name of global array Legs.
Node
An element of a global array, uniquely identified by a namespace consisting of a global name and an arbitrary number of subscript identifiers. A node must either contain data, have child nodes, or both.
Node level
The number of subscripts in the node address. A ‘level 2 node’ is just another way of saying ‘a node with two subscripts’. For example, Legs("mammal","dog") is a level 2 node. It is two levels under root node Legs and one level under Legs("mammal").
Node address
The complete namespace of a node, including the global name and all subscripts. For example, node address Legs("fish") consists of root node identifier Legs plus a list containing one subscript, "fish". Depending on context, Legs (with no subscript list) can refer to either the root node address or the entire global array.
Root node
The unsubscripted node at the base of the global array tree. The identifier for a root node is its global name with no subscripts.
Subnode
All descendants of a given node are referred to as subnodes of that node. For example, node Legs("bug") has four different subnodes on two levels. All nine subscripted nodes are subnodes of root node Legs.
Subscript / Subscript list
All nodes under the root node are addressed by specifying the global name and a list of one or more subscript identifiers. (The global name plus the subscript list is the node address).
Target address
Many Native API methods require you to specify a valid node address that does not necessarily point to an existing node. For example, the set() method takes a value argument and a target address, and stores the value at that address. If no node exists at the target address, a new node is created.
Value
A node can contain a value of any supported type. A node with no child nodes must contain a value; a node that has child nodes can be valueless.
Valueless node
A node must either contain data, have child nodes, or both. A node that has child nodes but does not contain data is called a valueless node. Valueless nodes only exist as pointers to lower level nodes.

Global Naming Rules

Global names and subscripts obey the following rules:
  • The length of a node address (totaling the length of the global name and all subscripts) can be up to 511 characters. (Some typed characters may count as more than one encoded character for this limit. For more information, see “Maximum Length of a Global Reference” in Using Globals).
  • A global name can include letters, numbers, and periods ('.'), and can have a length of up to 31 significant characters. It must begin with a letter, and must not end with a period.
  • A subscript can be a string or a number. String subscripts are case-sensitive and can use all characters (including control and non-printing characters). Length is limited only by the 511 character maximum for the total node address.

Creating, Updating, and Deleting Nodes

The Native API (class jdbc.IRIS) contains numerous methods to connect to a database and read data from the global arrays stored there, but it contains only three methods that can actually make changes in the database: set(), increment(), and kill(). This section describes how these methods are used.
Creating and Changing Nodes with set() and increment()
The following jdbc.IRIS methods can be used to create a persistent node with a specified value, or to change the value of an existing node:
  • set() — takes a value argument and stores the value at the target address. If no node exists at that address, a new one is created when the value is stored. The value argument can be Boolean, byte[], Double, Integer, Long, Short, String, Date, Time, Timestamp, plus Object and abstract classes InputStream, and Reader.
  • increment() — takes an Integer number argument, increments the target node value by that amount, and returns the incremented value as a Long. Unlike set(), it uses a thread-safe atomic operation to change the value of the node, so the node is never locked. The target node value can be Double, Integer, Long, or Short. If there is no node at the target address, the method creates one and assigns the number argument as the value.
The following example uses both set() and increment() to create and change node values:
Creating and Deleting Nodes: Setting and incrementing node values
The set() method can assign values of any supported datatype. In the following example, the first call to set() creates a new node at subnode address ^myGlobal("A") and sets the value of the node to string "first". The second call changes the value of the subnode, replacing it with integer 1.
  dbnative.set("first", "myGlobal", "A");     // create node ^myGlobal("A") = "first"
  dbnative.set(1, "myGlobal", "A");   // change value of ^myGlobal("A") to 1.
The increment() method can both create and increment a node with a numeric value. Unlike set(), it uses a thread-safe atomic operation that never locks the node.
In the following example, increment() is called three times. The first call creates new subnode ^myGlobal("B") with value -2. The next two calls each change the existing value by -2, resulting in a final value of -6:
  for (int loop = 0; loop < 3; loop++) {
    dbnative.increment(-2,"myGlobal", "B");
  }
Deleting Nodes with kill()
A node is deleted from the database when it is valueless and has no subnodes with values. When the last value is deleted from a global array, the entire global array is deleted.
  • kill() — deletes the specified node and all of its subnodes. If the root node is specified, the entire global array is deleted. This method is equivalent to the InterSystems IRIS inclusive KILL command.
The following example assumes that the global array initially contains the following nodes:
   ^myGlobal = <valueless node>
     ^myGlobal("A") = <valueless node>
       ^myGlobal("A",1) = 0
     ^myGlobal("B") = 0
       ^myGlobal("B",1) = 0
       ^myGlobal("B",2) = 0
We could delete the entire global array with one command by specifying only the root node:
  dbnative.kill("myGlobal");
The following example also deletes the entire array in a different way.
Creating and Deleting Nodes: Using kill() to delete a group of nodes or an entire global array
In this example, global array ^myGlobal will be deleted because two separate calls to kill() delete all subnodes with values:
  dbnative.kill("myGlobal","A",1);

// Now only these nodes are left:
//   ^myGlobal = <valueless node>
//     ^myGlobal("B") = 0
//       ^myGlobal("B",1) = 0
//       ^myGlobal("B",2) = 0

  dbnative.kill("myGlobal","B");

// Array no longer exists because all values have been deleted.

The first call to kill() deletes subnode ^myGlobal("A",1). Node ^myGlobal("A") no longer exists because it is valueless and now has no subnodes.
The second call to kill() deletes node ^myGlobal("B") and both of its subnodes. Since root node ^myGlobal is valueless and now has no subnodes, the entire global array is deleted from the database.

Finding Nodes in a Global Array

The Native API provides ways to iterate over part or all of a global array. The following topics describe the various iteration methods:

Iterating Over a Set of Child Nodes

Child nodes are sets of subnodes immediately under the same parent node. Any child of the current target node can be addressed by adding only one subscript to the target address. All child nodes under the same parent are sibling nodes of each other. For example, the following global array has six sibling nodes under parent node ^myNames("people"):
  ^myNames                               (valueless root node)
     ^myNames("people")                  (valueless level 1 node)
        ^myNames("people","Anna") = 2    (first level 2 child node)
        ^myNames("people","Julia") = 4
        ^myNames("people","Misha") = 5
        ^myNames("people","Ruri") = 3
        ^myNames("people","Vlad") = 1
        ^myNames("people","Zorro") = -1  (this node will be deleted in example)

Note:
Collation Order
The iterator returns nodes in collation order (alphabetical order in this case: Anna, Julia, Misha, Ruri, Vlad, Zorro). This is not a function of the iterator. When a node is created, InterSystems IRIS automatically stores it in the collation order specified by the storage definition. The nodes in this example would be stored in the order shown, regardless of the order in which they were created.
This section demonstrates the following methods:
The following example iterates over each child node under ^myNames("people"). It prints the subscript and node value if the value is 0 or more, or deletes the node if the value is negative:
Finding all sibling nodes under ^myNames("people")
// Read child nodes in collation order while iter.hasNext() is true
  System.out.print("Iterate from first node:");
  try {
    IRISIterator iter = dbnative.getIRISIterator("myNames","people");
    while (iter.hasNext()) {
      iter.next();
      if (iter.getValue()>=0) {
        System.out.print(" \"" + iter.getSubscriptValue() + "\"=" + iter.getValue()); }
      else {
        iter.remove();
      }

    };
  } catch  (Exception e) {
    System.out.println( e.getMessage());
  }

  • The call to getIRISIterator() creates iterator instance iter for the immediate children of ^myNames("people").
  • Each iteration of the while loop performs the following actions:
    • next() determines the subscript of the next valid node in collation order and positions the iterator at that node. (In the first iteration, the subscript is "Anna" and the node value is 2).
    • If the node value returned by getValue() is negative, remove() is called to delete the node (including any subnodes. This is equivalent to calling kill() on the current node).
      Otherwise, getSubscriptValue() and getValue() are used to print the subscript and value of the current node.
  • The while loop is terminated when hasNext() returns false, indicating that there are no more child nodes in this sequence.
This code prints the following line (element "Zorro" was not printed because its value was negative):
  Iterate from first node: "Anna"=2 "Julia"=4 "Misha"=5 "Ruri"=3 "Vlad"=1
This example is extremely simple, and would fail in several situations. What if we don’t want to start with the first or last node? What if the code attempts to get a value from a valueless node? What if the global array has data on more than one level? The following sections describe how to deal with these situations.

Finding Subnodes on All Levels

The next example will search a slightly more complex set of subnodes. We’ll add new child node "dogs" to ^myNames and use it as the target node for this example:
  ^myNames                                         (valueless root node)
     ^myNames("dogs")                              (valueless level 1 node)
        ^myNames("dogs","Balto") = 6
        ^myNames("dogs","Hachiko") = 8
        ^myNames("dogs","Lassie")                  (valueless level 2 node)
           ^myNames("dogs","Lassie","Timmy") = 10  (level 3 node)
        ^myNames("dogs","Whitefang") = 7
     ^myNames("people")                            (valueless level 1 node)
        [five child nodes]                         (as listed in previous example)

Target node ^myNames("dogs") has five subnodes, but only four of them are child nodes. In addition to the four level 2 subnodes, there is also a level 3 subnode, ^myNames("dogs","Lassie","Timmy"). The search will not find "Timmy" because this subnode is the child of "Lassie" (not "dogs"), and therefore is not a sibling of the others.
Note:
Subscript Lists and Node Levels
The term node level refers to the number of subscripts in the subscript list. For example, ^myGlobal("a","b","c") is a “level 3 node,” which is just another way of saying “a node with three subscripts.”
Although node ^myNames("dogs","Lassie") has a child node, it does not have a value. A call to getValue() will return null in this case. The following example searches for children of ^myNames("dogs") in reverse collation order:
Get nodes in reverse order from last node under ^myNames("dogs")
// Read child nodes in descending order while iter.next() is true
  System.out.print("Descend from last node:");
  try {
    IRISIterator iter = dbnative.getIRISIterator("myNames","dogs");
    while (iter.hasPrevious()) {
      iter.previous();
      System.out.print(" \"" + iter.getSubscriptValue() + "\"")
      if (iter.getValue()=null) set(^myNames("dogs",iter.getSubscriptValue()),0);
      System.out.print("=" + iter.getValue())
    };
  } catch  (Exception e) {
    System.out.println( e.getMessage());
  }

This code prints the following line:
Descend from last node: "Whitefang"=7 "Lassie" "Hachiko"=8 "Balto"=6
In the previous example, the search misses several of the nodes in global array ^myNames because the scope of the search is restricted in various ways:
  • Node ^myNames("dogs","Lassie","Timmy") is not found because it is not a level 2 subnode of ^myNames("dogs").
  • Level 2 nodes under ^myNames("people") are not found because they are not siblings of the level 2 nodes under ^myNames("dogs").
The problem in both cases is that previous() and next() only find nodes that are under the same parent and on the same level as the starting address. You must specify a different starting address for each group of sibling nodes.
In most cases, you will probably be processing a known structure, and will traverse the various levels with simple nested calls. In the less common case where a structure has an arbitrary number of levels, the following jdbc.IRIS method can be used to determine if a given node has subnodes:
  • isDefined() — returns 0 if the specified node does not exist, 1 if the node exists and has a value. 10 if the node is valueless but has subnodes, or 11 if it has both a value and subnodes.
If isDefined() returns 10 or 11, subnodes exist and can be processed by creating an iterator as described in the previous examples. A recursive algorithm could use this test to process any number of levels.
Previous section   Next section