Question
Iryna Mykhailova · Nov 9, 2022

Unique index from base class doesn't work

Hi guys!

My student asked my why his unit tests don't work as they should, and I just could answer him. Here is the simplified case.

There is a class with a required unique property Name:

Class Test.NewClass [ Abstract ]
{
Property Name As %String [ Required ];
Index NameIndex On Name [ Unique ];
}

And there is an inherited class:

Class Test.NewClass1 Extends (%Persistent, Test.NewClass)
{
}

What I expect to happen, is when I save two objects of class Test.NewClass1 with the same value of property Name, for the second one to get an error stating that it violates the unique index.

 set a = ##class(Test.NewClass1).%New()
 set a.Name = "A1"
 w a.%Save()
 
 set a = ##class(Test.NewClass1).%New()
 set a.Name = "A1"
 w a.%Save() 

 Right now, it saves both objects without any problems. And there's no index global as well.

What is wrong with this picture and how do I explain why it doesn't work as one would expect?

Product version: IRIS 2022.1
4
0 425
Discussion (9)2
Log in or sign up to continue

As I learned the hard way, abstract classes do not have storage implementation, hence no indices either

FWIW, Abstract classes inheriting from %Persistent (or even %SerialObject) do have a storage definition and can define indices too. They just can't be instantiated. To prevent the creation of a storage definition, NoExtent is the keyword to use.

To achieve the expected result of individual storage you may take this approach:

Class Test.NewClass Extends %Persistent [ NoExtent ]
{
Property Name As %String [ Required ];
Index NameIndex On Name [ Unique ];
}

and
 

Class Test.NewClass1 Extends Test.NewClass
{
Storage Default
{
<Data name="NewClass1DefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
</Data>
<DataLocation>^Test.NewClass1D</DataLocation>
<DefaultData>NewClass1DefaultData</DefaultData>
<IdLocation>^Test.NewClass1D</IdLocation>
<IndexLocation>^Test.NewClass1I</IndexLocation>
<StreamLocation>^Test.NewClass1S</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

and you see:
ERROR #5808: Key not unique: Test.NewClass1:NameIndex:^Test.NewClass1I("NameIndex"," A1")

For the record: it's not the Abstract-ness that leads to the index being ignored, it's the fact that the index is not defined in the primary superclass of Test.NewClass1. From secondary superclasses, only parameters, properties and methods are inherited, but not indices. So you can keep Test.NewClass Abstract, but make it inherit from %Persistent itself and then have Test.NewClass1 point to it as its primary superclass. I'll ask the doc team to make this a little clearer, as it's not obvious from this section of the doc nor the one on indices right now.

This said, @Robert Cemper 's suggested use of NoExtent is likely what you wanted to achieve.

Yes, that's what I did (because, obviously, the class being abstract is not the cause - works great in other scenarios):

Class Test.NewClass Extends %Persistent [ Abstract, NoExtent ]
{}

Class Test.NewClass1 Extends Test.NewClass
{}

I do know you don't inherit all from the secondary class, but I though the number of things I do inherit was more than just parameters, properties and methods. I take it queries, foreign keys, triggers, projections are out as well?

I think you get everything from secondary superclass(es) except indexes, storage, and relationships.