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

Working with Global Arrays

This chapter covers the following topics:
Note:
The examples in this chapter assume that an iris object named iris already exists and is connected to the server. The following code was used to create it:
  import irisnative
  conn = irisnative.createConnection('127.0.0.1', 51773, 'USER', '_SYSTEM', 'SYS')
  iris = irisnative.createIris(conn)
For more information, see the Quick Reference entries for createConnection() and createIris().

Creating, Updating, and Deleting Nodes

This section describes the Native API methods used to create, update, and delete nodes. set(), increment(), and kill() are the only methods that can create a global array or alter its contents. The following examples demonstrate how to use each of these methods.
Setting and changing node values
Iris.set() takes a value argument and stores the value at the specified address.
If no node exists at that address, a new one is created.
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.
  iris.set('first','myGlobal','A')  # create node ^myGlobal('A') = 'first'
  iris.set(1,'myGlobal','A')        # change value of ^myGlobal('A') to 1.

set() can create and change values of any supported datatype, as demonstrated in this example.
Incrementing node values
Iris.increment() takes an integer number argument, increments the node value by that amount, and returns the incremented value. The initial target node value can be any supported numeric type, but the incremented value will be an integer. 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.
In the following example, the first call to increment() creates new subnode myGlobal('B') with value -2, and assigns the returned value to total. The next two calls each increment by -2 and assign the new value to total, and the loop exits when the node value is -6.
  done = False
  while not done:
    total = iris.increment(-2,'myGlobal','B')
    if (total <= -6): done = True
  print('total = ', total, " and myGlobal('B') = ", iris.get('myGlobal','B'))
  # Prints: total = -6 and myGlobal('B') = -6

Note:
—Naming rules—
Naming rules
A global name can include letters, numbers, and periods ('.'). The name must begin with a character and may not end with a period. A subscript can be an Integer/Long, Float, Byte string, or Unicode string (case-sensitive, not restricted to alphanumeric characters). See “Global Naming Rules” for more information.
Deleting a node or group of 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.
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

This example will delete the global array by calling kill() on two of its subnodes. The first call will delete node myGlobal('A') and both of its subnodes:
  iris.kill('myGlobal','A')    # also kills myGlobal('A',1) and myGlobal('A',2)
The second call deletes the last remaining subnode with a value, killing the entire global array:
  iris.kill('myGlobal','B',1)  # deletes last value in global array myGlobal
  • The parent node, myGlobal('B'), is deleted because it is valueless and now has no subnodes.
  • Root node myGlobal is valueless and now 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 nodes immediately under the same parent node. Any child node address can be defined by appending one subscript to the subscript list of the parent. For example, the following global array has four child nodes under parent node heroes('dogs'):
The heroes global array
This global array uses the names of several heroic dogs (plus a reckless boy and a pioneering sheep) as subscripts. The values are birth years.
  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

The following methods are used to create an iterator, define the direction of iteration, and set the starting point of the search:
  • Iris.iterator() returns an instance of Iterator for the child nodes of the specified target node.
  • Iterator.reversed() — toggles direction of iteration between forward and reverse collation order.
  • Iterator.startFrom() sets the iterator's starting position to the specified subscript. The subscript is an arbitrary starting point, and does not have to address an existing node.
Read child node values in reverse order
The following code iterates over child nodes of heroes('dogs') in reverse collation order, starting with subscript V:
# Create an iterator for child nodes of heroes('dogs')
  iterDogs = iris.iterator('heroes','dogs').reversed().startFrom('V')
# Iterate in reverse collation order, starting at address heroes('dogs','V')
  output = '\nDog birth years: '
  for key,value in iterDogs.items():
    output +=  '%s:%i  ' % (key, value)
  print(output)

This code prints the following output:
  Dog birth years: Lassie:1940  Hachiko:1923  Balto:1919
In this example, two subnodes of heroes('dogs') are ignored:
  • Child node heroes('dogs','Whitefang') will not be found because it is outside of the search range (Whitefang is higher than V in collation order).
  • Level 3 node heroes('dogs','Lassie','Timmy') will not be found because it is a child of Lassie, not dogs.
See the last section in this chapter (“Testing for Child Nodes and Node Values”) for a discussion of how to iterate over multiple node levels.
Note:
Collation Order
The order in which nodes are retrieved depends on the collation order of the subscripts. When a node is created, it is automatically stored it in the collation order specified by the storage definition. In this example, the child nodes of heroes('dogs') would be stored in the order shown (Balto, Hachiko, Lassie, Whitefang) regardless of the order in which they were created. For more information, see “Collation of Global Nodes” in Using Globals.

Iteration with next()

The Native API also supports the next() method and return type iterator methods:
  • Iterator.next() — positions the iterator at the next child node (if one exists) and returns a tuple containing the subscript and value of the next node in the iteration. The subscript is the first element of the tuple and the value is the second.
  • Iterator.items() — sets return type to an array containing both the subscript and the value of the child node. For example, the returned value for node heroes(,'dogs','Balto') would be ['Balto',1919].
  • Iterator.subscripts() — sets return type to return only the subscript.
  • Iterator.values() — sets return type to return only the node value.
In the following example, iterDogs is set to iterate over child nodes of heroes(,'dogs'). Since the subscripts() method is called when the iterator is created, each call to next() will return only the subscript for the current child node. Each subscript is appended to the output variable, and the entire list will be printed when the loop terminates. A StopIteration exception is thrown when there are no more child nodes in the sequence.
Use next() to list the subscripts under node heroes('dogs')
  # Get a list of child subscripts under node heroes('dogs')
  iterDogs = iris.iterator('heroes','dogs').subscripts()
  output = "\nSubscripts under node heroes('dogs'): "
  try:
    while True: output += '%s ' % iterDogs.next()
  except StopIteration:  # thrown when there are no more child nodes
    print(output + '\n')

This code prints the following output:
  Subscripts under node heroes('dogs'): Balto Hachiko Lassie Whitefang

Testing for Child Nodes and Node Values

In the previous examples, the scope of the search is restricted to child nodes of heroes('dogs'). The iterator fails to find two values in global array heroes because they are under different parents:
  • Level 3 node heroes('dogs','Lassie','Timmy') will not be found because it is a child of Lassie, not dogs.
  • Level 2 node heroes('sheep','Dolly') is not found because it is a child of sheep, not dogs.
To search the entire global array, we need to find all of the nodes that have child nodes, and create an iterator for each set of child nodes. The isDefined() method provides the necessary information:
  • Iris.isDefined() — can be used to determine if a node has a value, a subnode, or both. It returns one of the following values:
    • 0 — the specified node does not exist
    • 1 — the node exists and has a value
    • 10 — the node is valueless but has a child node
    • 11 — the node has both a value and a child node
    The returned value can be used to determine several useful boolean values:
       exists = (iris.isDefined(root,subscripts) > 0)       # returned 1, 10, or 11
       hasValue = (iris.isDefined(root,subscripts)%10 > 0)  # returned 1 or 11
       hasChild = (iris.isDefined(root,subscripts) > 9)     # returned 10 or 11
    
    
The following example consists of two methods:
  • findAllHeroes() iterates over child nodes of the current node, and calls testNode() for each node. Whenever testNode() indicates that the current node has child nodes, findAllHeroes() creates a new iterator for the next level of child nodes.
  • testNode() will be called for each node in the heroes global array. It calls isDefined() on the current node, and returns a boolean value indicating whether the node has child nodes. It also prints node information for each node.
Method findAllHeroes()
This example processes a known structure, and traverses the various levels with simple nested calls. In the less common case where a structure has an arbitrary number of levels, a recursive algorithm could be used.
def findLostHeroes():
  root = 'heroes'
  iterRoot = iris.iterator(root)
  hasChild = False

  # Iterate over children of root node heroes
  for sub1,value in iterRoot:
    hasChild = testNode(value,root,*[sub1])

    # Iterate over children of heroes(sub1)
    if hasChild:
      iterOne = iris.iterator(root,sub1)
      for sub2,value in iterOne:
        hasChild = testNode(value,root,*[sub1,sub2])

      # Iterate over children of heroes(sub1,sub2)
        if hasChild:
          iterTwo = iris.iterator(root,sub1,sub2)
          for sub3,value in iterTwo:
            testNode(value,root,*[sub1,sub2,sub3]) #no child nodes below level 3

Method testNode()
def testNode(value, root, *subs):
  # Test for values and child nodes
  state = iris.isDefined(root,*subs)
  hasValue = (state%10 > 0) # has value if state is 1 or 11
  hasChild = (state > 9)    # has child if state is 10 or 11

  # format the node address output string
  level = len(subs)-1
  address = "  %s%s('%s')" % ("  "*level, root, "','".join(subs))

  # Add node value and note special cases
  if (hasValue):  # ignore valueless nodes
    address += ' = %i' % value
    for name in ['Timmy','Dolly']:
      if name == subs[level]:
        address += ' (not a dog!)'
  print(address)
  return hasChild

This method will write the following lines:
  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!)

The output of testNodes() includes some nodes that were not found in previous examples because they are not child nodes of heroes('dogs'):
  • heroes('dogs','Lassie','Timmy') is a child of Lassie, not dogs.
  • heroes('sheep','Dolly') is a child of sheep, not dogs.
Previous section   Next section