Skip to main content
Previous sectionNext section

Working with Global Arrays

This chapter covers the following topics:

Note:
IRIS Connection Code

The examples in this chapter assume that an IRIS object named iris already exists and is connected to the server. The following code establishes a standard JDBC connection and creates an instance of IRIS:

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

For more information on how to create an instance of IRIS, see the Quick Reference entry for createIRIS(). For general information on creating JDBC connections, see Establishing JDBC Connections in Using Java with InterSystems Software. .

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
Copy code to clipboard

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")
Copy code to clipboard

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");
Copy code to clipboard

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, Accessing, 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
package nativedemo;
import com.intersystems.jdbc.*;
Copy code to clipboard
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:1972/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
Copy code to clipboard

NativeDemo prints the following lines:

Created two values:
   root("foo","SubFoo")=A
   root("bar","lowbar","UnderBar")=123
Copy code to clipboard

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
Copy code to clipboard
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, Accessing, and Deleting Nodes

The Native API provides three methods that can make changes in the database: set() and increment() can create nodes or change node values, and kill() can delete a node or set of nodes. Node values are retrieved by type-specific getter methods such as getInteger() and getString().

Creating Nodes and Setting Node Values

The set() and increment() methods can be used to create a persistent node with a specified value, or to change the value of an existing node.

IRIS.set() takes a value argument of any supported datatype and stores the value at the specified address. If no node exists at the target address, a new one is created.

Setting and changing node values

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.

  iris.set("first", "myGlobal", "A");    // create node myGlobal("A") = "first"
  iris.set(1, "myGlobal", "A");  // change value of myGlobal("A") to 1.
Copy code to clipboard

set() can create and change values of any supported datatype. To read an existing value, you must use a different getter method for each datatype, as described in the next section.

IRIS.increment() takes a number argument, increments the node value by that amount, and returns the incremented value. The number argument 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. This method uses a thread-safe atomic operation to change the value of the node, so the node is never locked.

Incrementing node values

In the following example, the first call to increment() creates new subnode myGlobal("B") with value -2. The next two calls each increment by -2, resulting in a final value of -6:

  for (int loop = 0; loop < 3; loop++) {
    iris.increment(-2,"myGlobal", "B");
  }
Copy code to clipboard
Note:
Global naming rules

The second argument for either set() or increment() is a global array name. The name can include letters, numbers, and periods. It must begin with a character, and may not end with a period. The arguments after the global name are subscripts, which can be either numbers or strings (case-sensitive, not restricted to alphanumeric characters). See “Global Naming Rules” for more information.

Getting Node Values

The set() method can be used with all supported datatypes, but each datatype requires a separate getter. Node values can be any of the following datatypes: Boolean, byte[], Double, Float, Integer, Long, Short, String, Date, Time, Timestamp, plus Object, IRISList, subclasses of java.io.InputStream and java.io.Reader, and objects that implement java.io.Serializable.

The following methods are used to retrieve node values of these datatypes:

For more information on datatypes, see “Class IRIS Supported Datatypes” later in this chapter.

Deleting Nodes

IRIS.kill() deletes the specified node and all of its subnodes. The entire global array will be deleted if the root node is deleted, or if all nodes with values are deleted.

In the following example, global array myGlobal initially contains the following nodes:

   myGlobal = <valueless node>
     myGlobal("A") = 0
       myGlobal("A",1) = 0
       myGlobal("A",2) = 0
     myGlobal("B") = <valueless node>
       myGlobal("B",1) = 0
Copy code to clipboard

The example will delete the entire global array by calling kill() on two of its subnodes, myGlobal("A") and myGlobal("B",1).

Deleting a node or group of nodes

The first call will delete node myGlobal("A") and both of its subnodes:

  iris.kill("myGlobal", "A");
  // also kills child nodes myGlobal("A",1) and myGlobal("A",2)
Copy code to clipboard

The second call deletes myGlobal("B",1), the last remaining subnode with a value:

  iris.kill("myGlobal","B",1);
Copy code to clipboard

Since neither of the remaining nodes has a value, the entire global array is deleted:

  • The parent node, myGlobal("B"), is deleted because it is valueless and now has no subnodes.

  • Now root node myGlobal is valueless and has no subnodes, so 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)
Copy code to clipboard
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());
  }
Copy code to clipboard
  • 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
Copy code to clipboard

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)
Copy code to clipboard

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());
  }
Copy code to clipboard

This code prints the following line:

Descend from last node: "Whitefang"=7 "Lassie" "Hachiko"=8 "Balto"=6
Copy code to clipboard

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.

Class IRIS Supported Datatypes

For simplicity, examples in previous sections of this chapter have always used Integer or String node values, but the IRIS class also provides datatype-specific methods for the following supported datatypes.

IRIS.set()

The IRIS.set() method supports datatypes Boolean, byte[], Double, Integer, Long, Short, Float, String, IRISList, plus Java classes Date, Time, Timestamp, InputStream, Reader, and classes that implement Serializable. A null value is stored as "".

Class IRIS getters for numeric values

The following IRIS methods assume that the node value is numeric, and attempt to convert it to an appropriate Java variable: getBoolean(), getShort(), getInteger(), getLong(), getDouble(), or getFloat(). The numeric fetch methods will throw UndefinedException if the target node is valueless or does not exist.

Given an Integer node value, all numeric methods return meaningful values. The getInteger() and getLong() methods cannot be applied to Double or Float values with reliable results, and may throw an exception for these values.

Class IRIS getters for String, byte[], and IRISList

In the InterSystems IRIS database, String, byte[], and IRISList objects are all stored as strings, and no information about the original datatype is preserved. The IRIS getString(), getBytes(), and getIRISList() methods get string data and return it in the desired format.

The string getters assume that the node value is non-numeric, and attempt to convert it appropriately. They return null if the target node is valueless or does not exist. These methods do not perform any type checking, and will not usually throw an exception if the node value is of the wrong datatype.

Class IRIS getters for Java classes

The IRIS class also supports getters for Java classes Date, Time, Timestamp, InputStream, and Reader. Classes that implement Serializable can be retrieved with getObject().

Important:
Getter methods do not check for incompatible datatypes

These methods are optimized for speed, and never perform type checking. Your application should never depend on an exception being thrown if one of these methods attempts to fetch a value of the wrong datatype. Although an exception may be thrown, it is more likely that the method will fail silently, returning an inaccurate or meaningless value.