Persistent Objects and Storage Globals
Persistent classes allow you to save objects to the database and retrieve them as objects or via SQL. Regardless of how they are accessed, these objects are stored in globals, which can be thought of as persistent multidimensional arrays.
When you define a class that uses the default storage class (%Storage.Persistent), global names for your class are generated when you compile the class. You can see these names in the storage definition for the class in your IDE.
The following sections describe the default global naming scheme, how to generate short global names for better performance, and how to directly control global names.
Standard Global Names
When you define a class in your IDE, global names for your class are generated based on the class name.
For example, let’s define the following class, GlobalsTest.President:
Class GlobalsTest.President Extends %Persistent
{
/// President's name (last,first)
Property Name As %String(PATTERN="1U.L1"",""1U.L");
/// Year of birth
Property BirthYear As %Integer;
/// Short biography
Property Bio As %Stream.GlobalCharacter;
/// Index for Name
Index NameIndex On Name;
/// Index for BirthYear
Index DOBIndex On BirthYear;
}
After compiling the class, we can see the following storage definition generated at the bottom of the class:
Storage Default
{
<Data name="PresidentDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>BirthYear</Value>
</Value>
<Value name="4">
<Value>Bio</Value>
</Value>
</Data>
<DataLocation>^GlobalsTest.PresidentD</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<IdLocation>^GlobalsTest.PresidentD</IdLocation>
<IndexLocation>^GlobalsTest.PresidentI</IndexLocation>
<StreamLocation>^GlobalsTest.PresidentS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Notice, in particular, the following storage keywords:
-
The DataLocation is the global where class data will be stored. The name of the global is the complete class name (including the package name) with a “D” appended to the name, in this case, ^GlobalsTest.PresidentD.
-
The IdLocation (often the same as the DataLocation) is the global where the ID counter will be stored, at its root.
-
The IndexLocation is the global where the indexes for the class will be stored. The name of the global is the complete class name with an “I” appended to the name, or, ^GlobalsTest.PresidentI.
-
The StreamLocation is the global where any stream properties will be stored. The name of the global is the complete class name with an “S” appended to the name, or, ^GlobalsTest.PresidentS.
After creating and storing a few objects of our class, we can view the contents of these globals in the Terminal:
USER>zwrite ^GlobalsTest.PresidentD
^GlobalsTest.PresidentD=3
^GlobalsTest.PresidentD(1)=$lb("",1732,"1","Washington,George")
^GlobalsTest.PresidentD(2)=$lb("",1735,"2","Adams,John")
^GlobalsTest.PresidentD(3)=$lb("",1743,"3","Jefferson,Thomas")
USER>zwrite ^GlobalsTest.PresidentI
^GlobalsTest.PresidentI("DOBIndex",1732,1)=""
^GlobalsTest.PresidentI("DOBIndex",1735,2)=""
^GlobalsTest.PresidentI("DOBIndex",1743,3)=""
^GlobalsTest.PresidentI("NameIndex"," ADAMS,JOHN",2)=""
^GlobalsTest.PresidentI("NameIndex"," JEFFERSON,THOMAS",3)=""
^GlobalsTest.PresidentI("NameIndex"," WASHINGTON,GEORGE",1)=""
USER>zwrite ^GlobalsTest.PresidentS
^GlobalsTest.PresidentS=3
^GlobalsTest.PresidentS(1)="1,239"
^GlobalsTest.PresidentS(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^GlobalsTest.PresidentS(2)="1,195"
^GlobalsTest.PresidentS(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^GlobalsTest.PresidentS(3)="1,202"
^GlobalsTest.PresidentS(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."
The subscript of ^GlobalsTest.PresidentD is the IDKey. Since we did not define one of our indexes as the IDKey, the ID is used as the IDKey. For more information on IDs, see Controlling How IDs Are Generated.
The first subscript of ^GlobalsTest.PresidentI is the name of the index.
The first subscript of ^GlobalsTest.PresidentS is the ID of the bio entry, not the ID of the president.
You can also view these globals in the Management Portal (System Explorer > Globals).
Only the first 31 characters in a global name are significant, so if a complete class name is very long, you might see global names like ^package1.pC347.VeryLongCla4F4AD. The system generates names such as these to ensure that all of the global names for your class are unique. If you plan to work directly with the globals of a class, make sure to examine the storage definition so that you know the actual name of the global. Alternatively, you can control the global names by using the DEFAULTGLOBAL parameter in your class definition. See User-Defined Global Names.
Hashed Global Names
The system will generate shorter global names if you set the USEEXTENTSET parameter to the value 1. (The default value for this parameter is 0, meaning use the standard global names.) These shorter global names are created from a hash of the package name and a hash of the class name, followed by a suffix. While the standard names are more readable, the shorter names can contribute to better performance.
When you set USEEXTENTSET to 1, each index is also assigned to a separate global, instead of using a single index global with different first subscripts. Again, this is done for increased performance.
To use hashed global names for the GlobalsTest.President class we defined earlier, we would add the following to the class definition:
/// Use hashed global names
Parameter USEEXTENTSET = 1;
After deleting the storage definition and recompiling the class, we can see the new storage definition with hashed global names:
Storage Default
{
...
<DataLocation>^Ebnm.EKUy.1</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<ExtentLocation>^Ebnm.EKUy</ExtentLocation>
<IdLocation>^Ebnm.EKUy.1</IdLocation>
<Index name="DOBIndex">
<Location>^Ebnm.EKUy.2</Location>
</Index>
<Index name="IDKEY">
<Location>^Ebnm.EKUy.1</Location>
</Index>
<Index name="NameIndex">
<Location>^Ebnm.EKUy.3</Location>
</Index>
<IndexLocation>^Ebnm.EKUy.I</IndexLocation>
<StreamLocation>^Ebnm.EKUy.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Notice, in particular, the following storage keywords:
-
The ExtentLocation is the hashed value that will be used to calculate global names for this class, in this case, ^Ebnm.EKUy.
-
The DataLocation (equivalent to the IDKEY index), where class data will be stored, is now the hashed value with a “.1” appended to the name, in this case, ^Ebnm.EKUy.1.
-
Each index now has its own Location and thus its own separate global. The name of the IdKey index global is equivalent to the hashed value with a ”.1” appended to the name, in this example, ^Ebnm.EKUy.1. The globals for the remaining indexes have “.2” to “.N” appended to the name. Here, the DOBIndex is stored in global ^Ebnm.EKUy.2 and the NameIndex is stored in ^Ebnm.EKUy.3.
-
The IndexLocation is the hashed value with “.I” appended to the name, or ^Ebnm.EKUy.I, however, this global is often not used.
-
The StreamLocation is the hashed value with “.S” appended to the name, or ^Ebnm.EKUy.S.
After creating and storing a few objects, the contents of these globals might look as follows, again using the Terminal:
USER>zwrite ^Ebnm.EKUy.1
^Ebnm.EKUy.1=3
^Ebnm.EKUy.1(1)=$lb("","Washington,George",1732,"1")
^Ebnm.EKUy.1(2)=$lb("","Adams,John",1735,"2")
^Ebnm.EKUy.1(3)=$lb("","Jefferson,Thomas",1743,"3")
USER>zwrite ^Ebnm.EKUy.2
^Ebnm.EKUy.2(1732,1)=""
^Ebnm.EKUy.2(1735,2)=""
^Ebnm.EKUy.2(1743,3)=""
USER>zwrite ^Ebnm.EKUy.3
^Ebnm.EKUy.3(" ADAMS,JOHN",2)=""
^Ebnm.EKUy.3(" JEFFERSON,THOMAS",3)=""
^Ebnm.EKUy.3(" WASHINGTON,GEORGE",1)=""
USER>zwrite ^Ebnm.EKUy.S
^Ebnm.EKUy.S=3
^Ebnm.EKUy.S(1)="1,239"
^Ebnm.EKUy.S(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^Ebnm.EKUy.S(2)="1,195"
^Ebnm.EKUy.S(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^Ebnm.EKUy.S(3)="1,202"
^Ebnm.EKUy.S(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."
For classes defined using an SQL CREATE TABLE statement, the default for the USEEXTENTSET parameter is 1. For more information on creating tables, see Defining Tables.
For example, let’s create a table using the Management Portal (System Explorer > SQL > Execute Query):
CREATE TABLE GlobalsTest.State (NAME CHAR (30) NOT NULL, ADMITYEAR INT)
After populating the table with some data, we see the globals ^Ebnm.BndZ.1 and ^Ebnm.BndZ.2 in the Management Portal (System Explorer > Globals). Notice that the package name is still GlobalsTest, so the first segment of the global names for GlobalsTest.State is the same as for GlobalsTest.President.
![Management Portal page showing two new globals for GlobalsTest.State, plus the existing globals for GlobalsTest.President.](images/gobj_globals.png)
Using the Terminal, the contents of the globals might look like:
USER>zwrite ^Ebnm.BndZ.1
^Ebnm.BndZ.1=3
^Ebnm.BndZ.1(1)=$lb("Delaware",1787)
^Ebnm.BndZ.1(2)=$lb("Pennsylvania",1787)
^Ebnm.BndZ.1(3)=$lb("New Jersey",1787)
USER>zwrite ^Ebnm.BndZ.2
^Ebnm.BndZ.2(1)=$zwc(412,1,0)/*$bit(2..4)*/
The global ^Ebnm.BndZ.1 contains the States data and ^Ebnm.BndZ.2 is a bitmap extent index. See Bitmap Extent Index.
If we wanted to use standard global names with a class created via SQL, we could set the USEEXTENTSET parameter to the value 0:
CREATE TABLE GlobalsTest.State (%CLASSPARAMETER USEEXTENTSET 0, NAME CHAR (30) NOT NULL, ADMITYEAR INT)
This would generate the standard global names ^GlobalsTest.StateD and ^GlobalsTest.StateI.
You can change the default value used for the USEEXTENTSET parameter to 0 for classes created via a CREATE TABLE statement by executing the command do $SYSTEM.SQL.SetDDLUseExtentSet(0, .oldval). The previous default value is returned in oldval.
For classes created via XEP, the default for the USEEXTENTSET parameter is 1 and may not be changed. You can read more about XEP in Persisting Java Objects with InterSystems XEP.
User-Defined Global Names
For finer control of the global names for a class, use the DEFAULTGLOBAL parameter. This parameter works in conjunction with the USEEXTENTSET parameter to determine the global naming scheme.
For example, let’s add the DEFAULTGLOBAL parameter to set the root of the global names for the GlobalsTest.President class to ^GT.Pres:
/// Use hashed global names
Parameter USEEXTENTSET = 1;
/// Set the root of the global names
Parameter DEFAULTGLOBAL = "^GT.Pres";
After deleting the storage definition and recompiling the class, we can see the following global names:
Storage Default
{
...
<DataLocation>^GT.Pres.1</DataLocation>
<DefaultData>PresidentDefaultData</DefaultData>
<ExtentLocation>^GT.Pres</ExtentLocation>
<IdLocation>^GT.Pres.1</IdLocation>
<Index name="DOBIndex">
<Location>^GT.Pres.2</Location>
</Index>
<Index name="IDKEY">
<Location>^GT.Pres.1</Location>
</Index>
<Index name="NameIndex">
<Location>^GT.Pres.3</Location>
</Index>
<IndexLocation>^GT.Pres.I</IndexLocation>
<StreamLocation>^GT.Pres.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Likewise, we can use the DEFAULTGLOBAL parameter when defining a class using SQL:
CREATE TABLE GlobalsTest.State (%CLASSPARAMETER USEEXTENTSET 0, %CLASSPARAMETER DEFAULTGLOBAL = '^GT.State',
NAME CHAR (30) NOT NULL, ADMITYEAR INT)
This would generate the global names ^GT.StateD and ^GT.StateI.
Redefining Global Names
If you edit a class definition in a way that redefines the previously existing global names, for example, by changing the values of the USEEXTENTSET or DEFAULTGLOBAL parameters, you must delete the existing storage definition to allow the compiler to generate a new storage definition. Note that any data in the existing globals is preserved. Any data to be retained must be migrated to the new global structure.
For more information, see Redefining a Persistent Class That Has Stored Data.