Learning
Community
Open Exchange
Global Masters
InterSystems IRIS Data Platform 2019.3 / 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:

Creating 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.

Transactions and Locking

The following topics are discussed in this section:
Important:
Never Mix Transaction Models
DO NOT mix the Native transaction model with the SQL (JDBC) transaction model.
  • If you want to use only Native commands within a transaction, you should always use Native transaction methods.
  • If you want to use a mix of Native and JDBC/SQL commands within a transaction, you should turn autoCommit OFF and then always use Native transaction methods.
  • If you want to use only JDBC/SQL commands within a transaction, you can either always use SQL transaction methods, or turn autocommit OFF and then always use Native transaction methods.

Controlling Transactions

The jdbc.IRIS class provides the following methods to control transactions:
  • tCommit() — commits one level of transaction.
  • tStart() — starts a transaction (which may be a nested transaction).
  • getTLevel() — returns an int value indicating the current transaction level (0 if not in a transaction).
  • tRollback() — rolls back all open transactions in the session.
  • tRollbackOne() — rolls back the current level transaction only. If this is a nested transaction, any higher-level transactions will not be rolled back.
The following example starts three levels of nested transaction, setting the value of a different node in each transaction level. All three nodes are printed to prove that they have values. The example then rolls back the second and third levels and commits the first level. All three nodes are printed again to prove that only the first node still has a value.
Controlling Transactions: Using three levels of nested transaction
  String globalName = "myGlobal";
  dbnative.tStart();
  dbnative.set("firstValue", globalName, dbnative.getTLevel());
  // getTLevel() is 1 and ^myGlobal(1) = "firstValue"

  dbnative.tStart();
  dbnative.set("secondValue", globalName, dbnative.getTLevel());
  // getTLevel() is 2 and ^myGlobal(2) = "secondValue"

  dbnative.tStart();
  dbnative.set("thirdValue", globalName, dbnative.getTLevel());
  // getTLevel() is 3 and ^myGlobal(3) = "thirdValue"

  System.out.println("Node values before rollback and commit:");
  for (int ii=1;ii<4;ii++) {
    System.out.print(globalName + "(" + ii + ") = ");
    if (dbnative.isDefined(globalName,ii) > 1) System.out.println(dbnative.getString(globalName,ii));
    else System.out.println("<valueless>");
  }
// prints: Node values before rollback and commit:
//         ^myGlobal(1) = firstValue
//         ^myGlobal(2) = secondValue
//         ^myGlobal(3) = thirdValue

  dbnative.tRollbackOne();
  dbnative.tRollbackOne();  // roll back 2 levels to getTLevel 1
  dbnative.tCommit();  // getTLevel() after commit will be 0
  System.out.println("Node values after the transaction is committed:");
  for (int ii=1;ii<4;ii++) {
    System.out.print(globalName + "(" + ii + ") = ");
    if (dbnative.isDefined(globalName,ii) > 1) System.out.println(dbnative.getString(globalName,ii));
    else System.out.println("<valueless>");
  }
// prints: Node values after the transaction is committed:
//         ^myGlobal(1) = firstValue
//         ^myGlobal(2) = <valueless>
//         ^myGlobal(3) = <valueless>

Acquiring and Releasing Locks

The following methods of jdbc.IRIS are used to acquire and release locks. Both methods take a lockMode argument to specify whether the lock is shared or exclusive:
   lock (String lockMode, Integer timeout, String globalName, String...subscripts)  final boolean
   unlock (String lockMode, String globalName, String...subscripts)  final void
  • lock() — Takes lockMode, timeout, globalName, and subscripts arguments, and locks the node. The lockMode argument specifies whether any previously held locks should be released. This method will time out after a predefined interval if the lock cannot be acquired.
  • unlock() — Takes lockMode, globalName, and subscripts arguments, and releases the lock on a node.
    Methods Connection.close() or jdbc.IRIS.releaseAllLocks() (see “Using Locks in a Transaction”) will release all currently held locks.
The following argument values can be used:
  • lockMode — combination of the following chars, S for shared lock, E for escalating lock, default is empty string (exclusive and non-escalating)
  • timeout — amount to wait to acquire the lock in seconds
Note:
You can use the Management Portal to examine locks. Go to System Operation > Locks to see a list of the locked items on your system.

Using Locks in a Transaction

This section demonstrates incremental locking within a transaction, using the methods previously described (see “Controlling Transactions” and “Acquiring and Releasing Locks”). You can see a list of the locked items on your system by opening the Management Portal and going to System Operation > Locks. The calls to read(pressEnter) in the following code will pause execution so that you can look at the list whenever it changes.
The following methods will release all currently held locks:
  • jdbc.IRIS.releaseAllLocks() — releases all locks currently held by this connection.
  • Connection.close() — releases all locks and other connection resources before it closes the connection.
The following examples demonstrate the various lock and release methods.
Using Locks in a Transaction: Using incremental locking
  dbnative.set("my node", "nodeRef1", "my-node");
  dbnative.set("shared node", "nodeRef2", "shared-node");
  byte[] pressEnter = new byte[4];
  try {
    dbnative.tStart();
// lock ^nodeRef1("my-node") exclusively
    dbnative.lock("E",10,"nodeRef1", "my-node");
// lock ^nodeRef2 shared
    dbnative.lock("ES",10,"nodeRef2", "shared-node");
    System.out.println("Exclusive lock on ^nodeRef1(\"my-node\") and shared lock on ^nodeRef2");
    System.out.println("Press return to release locks individually");
    System.in.read(pressEnter); // Wait for user to press Return

// release ^nodeRef1("my-node") after transaction
    dbnative.unlock("E",,"nodeRef1", "my-node");
// release ^nodeRef2 immediately
    dbnative.unlock("ES",,"nodeRef2", "shared-node");
    System.out.println("Press return to commit transaction");
    System.in.read(pressEnter);
    dbnative.tCommit();
  }
  catch (Exception e) { System.out.println(e.getMessage()); }
  catch (java.io.IOException e) { System.out.println(e.getMessage()); }
Using Locks in a Transaction: Using non-incremental locking
// lock ^nodeRef1("my-node") non-incremental
  dbnative.lock("",10,"nodeRef1", "my-node");
  System.out.println("Exclusive lock on ^nodeRef1(\"my-node\"), return to lock ^nodeRef1 non-incrementally");
  System.in.read(pressEnter);

// lock ^nodeRef2 shared non-incremental
  dbnative.lock("S",10,"nodeRef2", "shared-node");
  System.out.println("Verify that only ^nodeRef2 is now locked, then press return");
  System.in.read(pressEnter);
Using Locks in a Transaction: Using releaseAllLocks() to release all incremental locks
  // lock ^nodeRef1("my-node") shared incremental
  dbnative.lock("SE",10,"nodeRef1", "my-node");;

// lock ^nodeRef2 exclusive incremental
  dbnative.lock("E",10,"nodeRef2", "shared-node");
  System.out.println("Two locks are held (one with lock count 2), return to release both locks");
  System.in.read(pressEnter);

  dbnative.releaseAllLocks();
  System.out.println("Verify both locks have been released");
  System.in.read(pressEnter);
Previous section   Next section