IndexNameOpen and Navigation Methods for Non-Unique Indexes
Published: 2020-11-20 09:57:17

As you probably know, InterSystems IRIS has a mechanism for auto-generating methods. So, when you create an index in a class, you create methods that make your work easier. There is also a good article on Community that describes such methods.

Take, for example, IndexNameOpen(val), where IndexName is the name of the corresponding index. This method returns an object in which the value of this index corresponds to the value of val.

But this method is available only for unique indexes.

As a solution, let me introduce you to the bondar-nav library, which generates the IndexNameOpen() method for a non-unique index, as well as a number of methods for navigating through elements.

First, I’ll describe how to install the library and present some usage examples. Then I’ll show how the technical part of the library is organized.

## Setting Up

Suppose we have a stored class with both a unique and a non-unique index:

```
Class samples.Person Extends %Persistent
{
Index SerialNumberIndex On SerialNumber [ Unique ];
Index NameIndex On Name;
Property SerialNumber As %String;
Property Name As %String;
}
```

We'll assume following data set:

| ID  | Name   | SerialNumber   |
|---|---|---|
| 1  | Goncharuk,Orson I.   | E1049  |
| 2  | Drabek   | A9914   |
| 3  | Drabek  | S3007  |
| 4  | Drabek   | X3640   |
| 5  | Perez,Mary D.  | H4116    |
| 6  | Xerxes,Julie X.  | P4873  |

If we know the value of SerialNumber, we can immediately obtain the object, since a unique index is defined for the SerialNumber property.

```
USER>set Person = ##class(samples.Person).SerialNumberIndexOpen("E1049")

USER>write Person.Name

Goncharuk, Orson I.
```

However, for a Name property value, we can’t do this because a non-unique index has been defined for this property and this method isn’t supported for such indexes.

The library I’m presenting creates the IndexNameOpen() method for non-unique indexes as well as navigation methods for positioning to other objects with the same value as the indexed property.

## Installing the Library

The best way to install the library is to use the InterSystems ObjectScript Package Manager client, ZPM. Install ZPM if it’s not already installed, then execute the following commands:

```
USER3 > zpm
zpm: USER3>install bondar-nav
[bondar-nav] Reload START
[bondar-nav] Reload SUCCESS
[bondar-nav] Module object refreshed.
[bondar-nav] Validate START
[bondar-nav] Validate SUCCESS
[bondar-nav] Compile START
[bondar-nav] Compile SUCCESS
[bondar-nav] Activate START
[bondar-nav] Configure START
[bondar-nav] Configure SUCCESS
[bondar-nav] Activate SUCCESS
zpm: USER3>
```

### How to Use the Library
Stored classes are inherited from bondar.indexnav.IndexNav:

```
Class samples.Person Extends (%Persistent, bondar.indexnav.IndexNav, %Populate)
{ }
```

After you change how indexes are defined in stored classes, recompile bondar.indexnav.StartGenNavMethods.

The earlier example includes three entries with the same name, “Drabek.” Let’s open the first object named Drabek using the NameIndexOpen() method and sort through the rest using NameIndexNext():

```
ClassMethod NameIndexExample()
{
  set person = ##class(samples.Person).NameIndexOpen("Drabek")
  While $IsObject(person) {
    write !, person.SerialNumber
    set person = person.NameIndexNext("Drabek")
  }
}
```

There are other navigation methods as well:

* IndexNameFirst(val) — Moves to the first object where the value of the indexed property is equal to val.
* IndexNameLast(val) — Moves to the last object where the value of the indexed property is equal to val.
* IndexNamePrevious(val)  — Moves to the previous object where the value of the indexed property is equal to val.

## Inside the Library

During the library development process, we had the idea of making an inherited class that would have method generators. After the compilation step is completed, the methods would be generated on the basis of the description of the indexes in the stored class.

However, we ran into a problem in that we couldn’t change the method name after we executed these generators, and we were also creating several methods based on a single generator method. Therefore, this approach wasn’t suitable.

Luckily, we found another solution. InterSystems IRIS includes the %Dictionary.ClassDefinition class, which contains a description of the classes in the area. We can programmatically use this class to create and edit other classes in the project.

The following example shows how we can open the description of the bondar.indexnav.IndexNav class. We clear all the methods that exist in it and add the appropriate methods for each non-unique index. We take the description of the indexes from the %Dictionary table.IndexDefinition:

```
ClassMethod GenIndexMethods() As %Status
{
Set ClassDef=##class(%Dictionary.ClassDefinition).%OpenId("bondar.indexnav.IndexNav")
do ClassDef.Methods.Clear()
set (qClassName,qIndexName,qProperties)=""
&sql(declare ind cursor for select parent,Name,Properties into :qClassName,:qIndexName,:qProperties
     from %Dictionary.IndexDefinition where parent->System=0 and not (parent [ '%') and _Unique=0
     group by Name)
&sql(open ind)
&sql(fetch ind)
while SQLCODE=0 {
     set Method = ..GetOpenMethodDefinition(qIndexName)
     Do ClassDef.Methods.Insert(Method)
     set Method = ..GetNextMethodDefinition(qIndexName)
     Do ClassDef.Methods.Insert(Method)
     set Method = ..GetPreviousMethodDefinition(qIndexName)
     Do ClassDef.Methods.Insert(Method)
     set Method = ..GetFirstMethodDefinition(qIndexName)
     Do ClassDef.Methods.Insert(Method)
     set Method = ..GetLastMethodDefinition(qIndexName)
     Do ClassDef.Methods.Insert(Method)
     &sql(fetch ind)
}
&sql(close ind)
Set sc=ClassDef.%Save()
write $System.Status.GetErrorText(sc)
Set sc=$System.OBJ.Compile("bondar.indexnav.IndexNav","fko1-d")
write $System.Status.GetErrorText(sc)
quit $$$OK
}
```

Thus, after we launch the GenIndexMethods() method in the bondar.indexnav.IndexNav class, the methods we need to work with non-unique indexes are regenerated.

And, in order to avoid running this method separately during the development process, we created a class with a method generator that simply launches it when the project is compiled:

```
Class bondar.indexnav.StartGenNavMethods [ DependsOn = (bondar.indexnav.IndexNav, bondar.indexnav.GenNavMethods) ]
{
   ClassMethod OnCompile() [ CodeMode = objectgenerator, Internal, ServerOnly = 1 ]
  {
    set tSC = ##class(bondar.indexnav.GenNavMethods).GenIndexMethods()
    quit tSC
  }
}
```

The DependsOn parameter indicates that this class can be compiled once the bondar.indexnavIndexNav and bondar.indexnav.GenNavMethods have been compiled and can be executed.

## Conclusion

I hope you’ll find this tool useful in your work. You can find more information about this library on the Navigation methods for InterSystems Objects Indexed properties. You can also submit your questions and suggestions there.