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
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.
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:
"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):
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?
This check, if hit returns first Id with empty val.
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.
Added a demo class.
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:
Where:
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.
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.
This is nice, thank you, Ed!