Skip to main content

Persistent Objects and 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 in your IDE.

The following sections describe the default global naming scheme, how to generate short global names for better performance, how to directly control global names, and how the globals differ when using columnar storage.

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).

Important:

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.

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.

Note:

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.

Columnar Storage Globals

The following class definition is basically the same as the one in shown in Standard Global Names, but it has the class parameter STORAGEDEFAULT = "columnar", which specifies that columnar storage is used as the default storage layout for the class. This means that instead of the data being stored in rows, with all of the properties for a president being stored in a row, the data for each property in the class is stored in a column.

Class ColumnarTest.President Extends %Persistent [ DdlAllowed, Final ]
{  

/// Specify columnar storage at the class level 
Parameter STORAGEDEFAULT = "columnar"  

/// 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;

}

Compiling the class yields the storage definition shown below:

Storage Default
{
<Data name="_CDM_Bio">
<Attribute>Bio</Attribute>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_BirthYear">
<Attribute>BirthYear</Attribute>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_Name">
<Attribute>Name</Attribute>
<Structure>vector</Structure>
</Data>
<DataLocation>^ColumnarTest.PresidentD</DataLocation>
<IdLocation>^ColumnarTest.PresidentD</IdLocation>
<IndexLocation>^ColumnarTest.PresidentI</IndexLocation>
<StreamLocation>^ColumnarTest.PresidentS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

This storage definition looks a little different from the one created for the standard class definition, which uses the traditional row-based storage layout. While the names of the globals are the same, you’ll see a <Data> element for each property that uses columnar storage. (“CDM” stands for columnar data map.) The name attribute of the <Data> tag is used as a subscript in the ^ColumnarTest.PresidentI global, the <Attribute> element contains the property name, and a <Structure> element with the value vector indicates that a vector is used to represent the values of the property in columnar form.

After adding a few objects of the new class, the globals might look something like this:

USER>zwrite ^ColumnarTest.PresidentD
^ColumnarTest.PresidentD=3
^ColumnarTest.PresidentD(1)=""
^ColumnarTest.PresidentD(2)=""
^ColumnarTest.PresidentD(3)=""
 
USER>zwrite ^ColumnarTest.PresidentI
^ColumnarTest.PresidentI("$President",1)=$zwc(412,1,0)/*$bit(2..4)*/
^ColumnarTest.PresidentI("DOBIndex",1732,1)=""
^ColumnarTest.PresidentI("DOBIndex",1735,2)=""
^ColumnarTest.PresidentI("DOBIndex",1743,3)=""
^ColumnarTest.PresidentI("NameIndex"," ADAMS,JOHN",2)=""
^ColumnarTest.PresidentI("NameIndex"," JEFFERSON,THOMAS",3)=""
^ColumnarTest.PresidentI("NameIndex"," WASHINGTON,GEORGE",1)=""
^ColumnarTest.PresidentI("_CDM_Bio",1)={"type":"integer", "count":3, "length":4, "vector":[,1,2,3]}  ; <VECTOR>
^ColumnarTest.PresidentI("_CDM_BirthYear",1)={"type":"integer", "count":3, "length":4, "vector":[,1732,1735,1743]}  ; <VECTOR>
^ColumnarTest.PresidentI("_CDM_Name",1)={"type":"string", "count":3, "length":4, "vector":[,"Washington,George","Adams,John","Jefferson,Thomas"]}  ; <VECTOR>

USER>zwrite ^GlobalsTest.PresidentS
^ColumnarTest.PresidentS=3
^ColumnarTest.PresidentS(1)="1,239"
^ColumnarTest.PresidentS(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^ColumnarTest.PresidentS(2)="1,195"
^ColumnarTest.PresidentS(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^ColumnarTest.PresidentS(3)="1,202"
^ColumnarTest.PresidentS(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."

Here, ^ColumnarTest.PresidentI("$President") is the bitmap extent index.

For a class that uses “mixed storage,” with some properties using columnar storage and others using row-based storage, the data for the properties that use row-based storage is stored in the data global (for example, ^ColumnarTest.PresidentD), as is the case for the standard class definition. All of the properties in the above example use columnar storage, since STORAGEDEFAULT = "columnar" was specified at the class level, not the property level. For an example of a class definition that specifies columnar storage at the property level, see Using Columnar Storage.

To use hashed global names for the ColumnarTest.President class, use the following class parameter:

/// Use hashed global names
Parameter USEEXTENTSET = 1;

Compiling the class yields the storage definition shown below:

Storage Default
{
<Data name="_CDM_Bio">
<Attribute>Bio</Attribute>
<ColumnarGlobal>^IEA0.EKUy.1.V1</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_BirthYear">
<Attribute>BirthYear</Attribute>
<ColumnarGlobal>^IEA0.EKUy.1.V2</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_Name">
<Attribute>Name</Attribute>
<ColumnarGlobal>^IEA0.EKUy.1.V3</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<DataLocation>^IEA0.EKUy.1</DataLocation>
<ExtentLocation>^IEA0.EKUy</ExtentLocation>
<IdLocation>^IEA0.EKUy.1</IdLocation>
<Index name="$President">
<Location>^IEA0.EKUy.2</Location>
</Index>
<Index name="DOBIndex">
<Location>^IEA0.EKUy.3</Location>
</Index>
<Index name="IDKEY">
<Location>^IEA0.EKUy.1</Location>
</Index>
<Index name="NameIndex">
<Location>^IEA0.EKUy.4</Location>
</Index>
<IndexLocation>^IEA0.EKUy.I</IndexLocation>
<StreamLocation>^IEA0.EKUy.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}

This storage definition looks like a hybrid of the columnar storage example, above, and the hashed global names example. Here, the <Data> element for each property in this class definition contains a <ColumnarGlobal> attribute, which is used to indicate the name of the global used to store values of that property in columnar form. The $President index stores the bitmap extent index.

After adding a few objects of the new class, the globals might look something like this:

USER>zwrite ^IEA0.EKUy.1.V1
^IEA0.EKUy.1.V1(1)={"type":"integer", "count":3, "length":4, "vector":[,1,2,3]}  ; <VECTOR>
 
USER>zwrite ^IEA0.EKUy.1.V2
^IEA0.EKUy.1.V2(1)={"type":"integer", "count":3, "length":4, "vector":[,1732,1735,1743]}  ; <VECTOR>
 
USER>zwrite ^IEA0.EKUy.1.V3
^IEA0.EKUy.1.V3(1)={"type":"string", "count":3, "length":4, "vector":[,"Washington,George","Adams,John","Jefferson,Thomas"]}  ; <VECTOR>
 
USER>zwrite ^IEA0.EKUy.1
^IEA0.EKUy.1=3
^IEA0.EKUy.1(1)=""
^IEA0.EKUy.1(2)=""
^IEA0.EKUy.1(3)=""
 
USER>zwrite ^IEA0.EKUy.2
^IEA0.EKUy.2(1)=$zwc(412,1,0)/*$bit(2..4)*/
 
USER>zwrite ^IEA0.EKUy.3
^IEA0.EKUy.3(1732,1)=""
^IEA0.EKUy.3(1735,2)=""
^IEA0.EKUy.3(1743,3)=""
 
USER>zwrite ^IEA0.EKUy.4
^IEA0.EKUy.4(" ADAMS,JOHN",2)=""
^IEA0.EKUy.4(" JEFFERSON,THOMAS",3)=""
^IEA0.EKUy.4(" WASHINGTON,GEORGE",1)=""
 
USER>zwrite ^IEA0.EKUy.S
^IEA0.EKUy.S=3
^IEA0.EKUy.S(1)="1,239"
^IEA0.EKUy.S(1,1)="George Washington was born to a moderately prosperous family of planters in colonial ..."
^IEA0.EKUy.S(2)="1,195"
^IEA0.EKUy.S(2,1)="John Adams was born in Braintree, Massachusetts, and entered Harvard College at age 1..."
^IEA0.EKUy.S(3)="1,202"
^IEA0.EKUy.S(3,1)="Thomas Jefferson was born in the colony of Virginia and attended the College of Willi..."

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.

See Also

FeedbackOpens in a new tab