Persistent Objects and Extents
The term extent refers to all the records, on disk, for a given persistent class. The %PersistentOpens in a new tab class provides several methods that operate on the extent of class.
Introduction to Extents
InterSystems IRIS® data platform uses an unconventional and powerful interpretation of the object-table mapping. By default, the extent of a given persistent class includes the extents of any subclasses. Therefore:
-
If the persistent class Person has the subclass Employee, the Person extent includes all instances of Person and all instances of Employee.
-
For any given instance of class Employee, that instance is included in the Person extent and in the Employee extent.
Indices automatically span the entire extent of the class in which they are defined. The indexes defined in Person contain both Person instances and Employee instances. Indexes defined in the Employee extent contain only Employee instances.
The subclass can define additional properties not defined in its superclass. These are available in the extent of the subclass, but not in the extent of the superclass. For example, the Employee extent might include the Department field, which is not included in the Person extent.
The preceding points mean that it is comparatively easy in InterSystems IRIS to write a query that retrieves all records of the same type. For example, if you want to count people of all types, you can run a query against the Person table. If you want to count only employees, run the same query against the Employee table.
Similarly, the methods that use the ID all behave polymorphically. That is, they can operate on different types of objects depending on the ID value it is passed.
For example, the extent for Sample.Person objects includes instances of Sample.Person as well as instances of Sample.Employee. When you call %OpenId() for the Sample.Person class, the resulting OREF could be an instance of either Sample.Person or Sample.Employee, depending on what is stored within the database:
// Open person "10"
Set obj = ##class(Sample.Person).%OpenId(10)
Write $ClassName(obj),! // Sample.Person
// Open person "110"
Set obj = ##class(Sample.Person).%OpenId(110)
Write $ClassName(obj),! // Sample.Employee
Note that the %OpenId() method for the Sample.Employee class will not return an object if we try to open ID 10, because the ID 10 is not the Sample.Employee extent:
// Open employee "10"
Set obj = ##class(Sample.Employee).%OpenId(10)
Write $IsObject(obj),! // 0
// Open employee "110"
Set obj = ##class(Sample.Employee).%OpenId(110)
Write $IsObject(obj),! // 1
Extent Definitions
For classes that use the default storage class (%Storage.Persistent), InterSystems IRIS maintains extent definitions and globals that those extents have registered for use with its Extent Manager. The interface to the Extent Manager is through the %ExtentMgr.UtilOpens in a new tab class. This registration process occurs during class compilation.
If there are any errors or name conflicts, these cause the compile to fail. For example:
ERROR #5564: Storage reference: '^This.App.Global used in 'User.ClassA.cls' is already registered for use by 'User.ClassB.cls'
For compilation to succeed, resolve the conflicts; this usually involves either changing the name of the index or adding explicit storage locations for the data.
If an application has multiple classes intentionally sharing a global reference, specify that MANAGEDEXTENT equals 0 for all the relevant classes, if they use default storage.
The MANAGEDEXTENT class parameter has a default value of 1; this value causes global name registration and a conflicting use check. A value of 0 specifies that there is neither registration nor conflict checking.
Another possibility is that InterSystems IRIS contains obsolete extent definitions. For example, deleting a class does not delete the extent definition by default.
Continuing with the example above, perhaps User.ClassA.cls was deleted, but its extent definition remains. Here, the solution is to delete the obsolete extent definition with the following command:
set st = ##class(%ExtentMgr.Util).DeleteExtentDefinition("User.ClassA", "cls")
Now, User.ClassB.cls can be compiled without a conflict.
To delete a class and its extent definition, use one of the following calls:
-
$SYSTEM.OBJ.Delete(classname,qspec) where classname is the class to delete and qspec includes the flag e or the qualifier /deleteextent.
-
$SYSTEM.OBJ.DeletePackage(packagename,qspec) where packagename is the package to delete and qspec includes the flag e or the qualifier /deleteextent.
-
$SYSTEM.OBJ.DeleteAll(qspec) where qspec includes the flag e or the qualifier /deleteextent. This deletes all classes in the namespace.
These calls are methods of the %SYSTEM.OBJOpens in a new tab class.
Extent Indexes
An extent index is an index of all extents and subextents defined in the current namespace. The class compiler maintains this index for locally compiled classes that use the default storage class (%Storage.Persistent). For these classes, when package mappings, routine mappings, or global mappings are changed for the namespace, InterSystems IRIS automatically rebuilds this index.
However, classes mapped from other namespaces are not automatically added to or removed from the index when changes to the class runtime occur in the original namespace. Instead, such changes must be updated in the local namespace’s extent index by using one of following calls:
-
$SYSTEM.OBJ.RebuildExtentIndex(updatemode,lockmode) rebuilds the entire extent index for a given namespace and returns a status value indicating success or failure. If updatemode is true then the index is not purged and only detected differences are updated in the index. If updatemode is false then the index is purged and rebuilt entirely. A lockmode of 1 causes an exclusive lock to be taken out on the entire index structure, a value of 2 causes individual locks to be taken out on class nodes only for the duration of indexing that class, and a value of 3 causes a shared lock to be taken out on the entire index structure. If the requested locks cannot be obtained then an error is reported to the caller. A lockmode of 0 indicates no locking.
-
$SYSTEM.OBJ.RebuildExtentIndexOne(classname,lockmode) rebuilds the extent index for a single class and returns a status value indicating success or failure. This method updates the index nodes in the extent index for the class indicated by classname. The values for lockmode are the same as for RebuildExtentIndex(), but values of 1, 2, and 3 are interpreted as locks on the individual class nodes.
These calls are methods of the %SYSTEM.OBJOpens in a new tab class.
Extent Queries
Every persistent class automatically includes a class query called "Extent" that provides a set of all the IDs in the extent.
For general information on using class queries, see Defining and Using Class Queries. The following example uses a class query to display all the IDs for the Sample.Person class:
set query = ##class(%SQL.Statement).%New()
set qStatus = query.%PrepareClassQuery("Sample.Person","Extent")
if $$$ISERR(qStatus) {write "%Prepare failed:" do $SYSTEM.Status.DisplayError(qStatus) quit}
set rset=query.%Execute()
if (rset.%SQLCODE '= 0) {write "%Execute failed:", !, "SQLCODE ", rset.%SQLCODE, ": ", rset.%Message quit}
while rset.%Next()
{
write rset.%Get("ID"),!
}
if (rset.%SQLCODE < 0) {write "%Next failed:", !, "SQLCODE ", rset.%SQLCODE, ": ", rset.%Message quit}
The Sample.Person extent includes all instances of Sample.Person as well as its subclasses. For an explanation of this, see Defining Persistent Classes.
The "Extent" query is equivalent to the following SQL query:
SELECT %ID FROM Sample.Person
Note that you cannot rely on the order in which ID values are returned using either of these methods: InterSystems IRIS may determine that it is more efficient to use an index that is ordered using some other property value to satisfy this request. You can add an ORDER BY %ID clause to the SQL query if you need to.