Useful auto-generated methods

Primary tabs

For each defined property, query or an index, several corresponding methods would be automatically generated on a class compilation. These methods can be very useful. In this article, I would describe some of them.

Properties

Let’s say you defined a property named “Property”. The following methods would be automatically available (bold property is a variable part, equal to property name):

ClassMethod PropertyGetStored(id)

For datatype properties this method returns their logical value, for object properties, it returns the id. It’s a wrapped global reference to the class data global and the fastest way to retrieve the singular property value. This method is only available for stored properties.

Method PropertyGet()

Is a property getter. Can be redefined.

Method PropertySet(val) As %Status

Is a property setter. Can be redefined.


Object properties

If it’s an object property, some additional methods, related to ID and OID access become available:

Method PropertySetObjectId(id)

This method sets property value by ID, so there is no need to open an object to set it as a property value.

Method PropertyGetObjectId()

This method  returns property value ID.

Method PropertySetObject(oid)

This method sets property value by OID.

Method PropertyGetObject()

This method returns property value OID.


Datatype properties

For a datatype property several other methods for conversion between different formats become available:

ClassMethod PropertyDisplayToLogical(val)
ClassMethod PropertyLogicalToDisplay(val)
ClassMethod PropertyOdbcToLogical(val)
ClassMethod PropertyLogicalToOdbc(val)
ClassMethod PropertyXSDToLogical(val)
ClassMethod PropertyLogicalToXSD(val)
ClassMethod PropertyIsValid(val) As %Status

Checks if val is a valid property value

ClassMethod PropertyNormalize(val)

Returns normalized logical value


Notes

  • Relationships are properties and can be get/set with these methods
  • Input val is always a logical value, except for format conversion methods.


Indexes

For an index named “Index”, the following methods would be automatically available

ClassMethod IndexExists(val) As %Boolean

Returns 1 or 0 depending on whatever object with this val exists, where val is a logical value of the indexed property.


Unique Indexes

For unique indexes, additional methods become available:

ClassMethod IndexExists(val, Output id) As %Boolean

Returns 1 or 0 depending on whatever object with this val exists, where val is a logical value of the indexed property. Also returns object id (if found) as a second argument.

ClassMethod IndexDelete(val, concurrency = -1) As %Status

Deletes entry with index value equal to val.

ClassMethod IndexOpen(val, concurrency, sc As %Status)  

Returns existing object with index value equal to val.


Notes:

a) As an index may be based upon several properties, the method signature would change to have several values as an input, for example, consider this index:

Index MyIndex On (Prop1, Prop2);

Then IndexExists method would have the following signature:

ClassMethod IndexExists(val1, val2) As %Boolean

Where val1 corresponds to Prop1 value and val2 corresponds to Prop2 value. Other methods follow the same logic.

b) Caché generates an IDKEY index that indexes the ID field (RowID). It can be redefined by the user and can also contain several properties. For example, to check if class has some property defined execute:

Write ##class(%Dictionary.PropertyDefinition).IDKEYExists(class, property)

c) All index methods check for a logical value

d) Documentation

Queries

For a query (which can be a simple SQL query or a custom class query, here’s my post about them) named “Query” Func method gets generated:

ClassMethod QueryFunc(Arg1, Arg2) As %SQL.StatementResult

which returns a %SQL.StatementResult used to iterate over the query. For example Sample.Person class in Samples namespace has a ByName query accepting one parameter. It can be called from object context with this code:

Set ResultSet=##class(Sample.Person).ByNameFunc("A")
While ResultSet.%Next() { Write ResultSet.Name,! }

Additionally, there is a demo class on GitHub demonstrating these  methods.

Comments

Edward, thank you for the very useful article! As addition to your article it would be great to add a demo class with direct examples of all the features.

Regarding this method:

ClassMethod IndexOpen(val, concurrency, sc As %Status)

"val" should be in Upper case I suppose?

 

"val" should be in Upper case I suppose?

No.  IndexOpen calls IndexExists to get object ID.  In IndexExists "val" is matched to corresponding ID with the following SQL expression (except for IDKEYExists. It calls %ExistsId):

SELECT %ID INTO :id 
FROM Package.Class 
WHERE 
  (val IS NOT NULL AND IndexProperty = val) OR 
  (val IS NULL     AND IndexProperty IS NULL)

The interesting question would be - why not traverse index global to get id instead of using SQL?

The interesting question would be - why not traverse index global to get id instead of using SQL?

Really good question...

Another question what is the sense to check "val" for IS NULL for Unique Index?

So, "val" should exactly match the value of property, case sensitive?

 Another question what is the sense to check "val" for IS NULL for Unique Index?

This check, if hit returns first Id with empty val.

So, "val" should exactly match the value of property, case sensitive?

That depends on property collation. For EXACT/ TRUNCATE/SQLSTRING collation, yes "val" should exactly match the value of the property (compared part of the value), case sensitive. For SQLUPPER - no.

That depends on property collation. For EXACT/ TRUNCATE/SQLSTRING collation, yes "val" should exactly match the value of the property (compared part of the value), case sensitive. For SQLUPPER - no.

Should I see this collation setting in Index or in property definition? 

And what is the default setting - SQLUPPER?

 

Well, that depends.

  1. If an index definition includes an explicitly specified collation for a property, the index uses that collation.
  2. If an index definition does not include an explicitly specified collation for a property, the index uses the collation explicitly specified in the property definition.
  3. If the property definition does not include an explicitly specified collation, then the index uses the collation that is the default for the property data type.
  4. If the property data type does not include an explicitly specified collation, then the index uses namespace default collation
  5. If the namespace default collation is not specified, then SQLUPPER is used.

 

ClassMethod PropertyGetStored(id)

For datatype properties this method returns their logical value, for object properties, it returns the id. It’s a wrapped global reference to the class data global and the fastest way to retrieve the singular property value. This method is only available for stored properties.

Eduard, you say it's the fastest way to take the value. Did you measure it?

 

Sure did. To clarify, it's the fastest way available by default. The fastest way is a direct global reference (provided of course that we do not have the object in a context already available), but we need to either hardcode the global reference or calculate it at compile time. Here's the test class (I won't copy it here - it's fairly long). The results are like this:

Iterations: 10000
Object access: .130111
GetStored access: .014388
SQL access: .020268
Global access: .007717
Object access takes 904.30% of GetStored time
Object access takes 641.95% of SQL time
Object access takes 1686.03% of Global time
GetStored access takes 70.99% of SQL time
GetStored access takes 186.45% of Global time
SQL access takes 262.64% of Global time

Where:

  • Object access is opening an object and retrieving the property
  • SQL access is embedded SQL
  • GetStored is a method discussed in this article
  • Global is a direct global reference to a stored property

One piece of code I'd like to share here is the macro to get global reference for a property by a class and property name:

Class Utils.GetStored Extends %Persistent
{

Property text As %String;

ClassMethod Global() As %Status
{
    #Define GetStoredProp(%cls,%prop,%idvar) ##Expression(##class(Utils.GetStored).GetPropGLVN(%cls,%prop, $name(%idvar)))
    Set Id = $Random(999)+1
    Set Val = $$$GetStoredProp("Utils.GetStored","text", Id)
}

/// Write ##class(Utils.GetStored).GetPropGLVN("Utils.GetStored", "text")
ClassMethod GetPropGLVN(Class, Property, IdVar = "id") As %String
{
    Set StoredCode = $Get($$$EXTPROPondisk($$$pEXT,Class,Property))
    Set StoredCode = $Replace(StoredCode, "(id)", "(" _ IdVar _ ")")
    Return StoredCode
}

On compilation the string with $$$GetStoredProp macro would be compiled into:

Set Val = $listget($g(^Utils.GetStoredD(Id)),2) 

I tried to use one of the "GetStored" methods of %Dictionary.MethodDefinition (specifically, "DescriptionGetStored") and got a "<METHOD DOES NOT EXIST>" error.  I suspect this is because this class has "StorageStrategy=custom".  So if a class specifies a custom storage definition, does that mean these methods won't be auto-generated?

Yes for custom storage you'll need to check the storage for hints.

In the case of %Dictionary package check %LoadData method.

To get method description call:

set desc = $$$defMemberKeyGet(CLASS,$$$cCLASSmethod,METHOD,$$$cMETHdescription)

Actually didn't get what the below method does. 

Method PropertySetObjectId(id)

Do you have an example?

It sets object id directly instead of setting oref.

Consider these 2 classes:

Class Person Extents %Persistent {

Property EmployedAt As Company;

}


Class Company Extends %Persistent {

}

Usually you assign Company to Person this way:

set person = ##class(Person).%New()
set companyId = 123
set company = ##class(Company).%OpenId(companyId)
set person.EmployedAt = company

But with PropertySetObjectId you can expedite things

set person = ##class(Person).%New()
set companyId = 123
do person.EmployedAtSetObjectId(companyId)

The main advantage is that company object doesn't have to be opened.