Overriding the property setter and getter methods
I want to override the Get and Set methods of a class property. The class maps to a pre-existing global. The property is defined like so:
Property Invalid As %Library.Boolean;
with the property mapping to a node like ^GLOBAL(Code,"INVALID")=1
Code is a property in the same class.
The value can be 0 or 1 or the node might not exist. When it doesn't exist I want the value of the SQL field to come out as 0 (false).
I'm trying to do it so that it will be self-contained, so that nothing that references the class or uses SQL to access/update the table also won't need to be changed and the field needs to be updatable by SQL as well as retrievable.
I've tried using the Retrieval code of the storage map, but then updating the field from SQL doesn't work. And I've also tried to override the Set and Get methods as follows:
Method InvalidGet() As %Library.Boolean
{
//I've tried both of the below lines
I +$G(^GLOBAL(..Code,"INVALID")) Quit 1
I +$G(^GLOBAL(i%Code,"INVALID")) Quit 1
Quit 0
}
Method InvalidSet(value As %Library.Boolean) As %Status
{
Set i%Invalid=value
quit $$$OK
}
but it doesn't seem to be even running the code. I've cleared cached queries to make sure it's picking up the changes and I'm still getting blank fields when doing an SQL on the table.
The documentation page I've looked at is entitled Using and Overriding property methods.
Anyone got any ideas how to get it to work or why it isn't working?
thanks
Wendy
Comments
Getter and Setter are object related concepts, SQL does not use them. You can, however specify SqlComputeCode for SELECT access to a property. This example stores and retrieves Invalid property value from ^Utils.GlobalPropP global.
Class Utils.GlobalProp Extends %Persistent
{
Parameter InvalidGLVN = "^Utils.GlobalPropP";
Property Invalid As %String [ SqlComputeCode = {set {*} = ##class(Utils.GlobalProp).InvalidStatic()}, SqlComputed, Transient ];
Method InvalidGet() As %String
{
Return ..InvalidStatic()
}
ClassMethod InvalidStatic() As %String
{
Return $Get(@..#InvalidGLVN)
}
Method InvalidSet(val As %String) As %Status
{
Set @..#InvalidGLVN = val
Return $$$OK
}
/// Do ##class(Utils.GlobalProp).Test()
ClassMethod Test()
{
Do ..%KillExtent()
Set obj = ..%New()
Write "Invalid old: " _ obj.Invalid,!
Set obj.Invalid = $Random(100)
Write "Invalid new: " _ obj.Invalid,!
Do obj.%Save()
Kill obj
&sql(SELECT Invalid INTO :invalid FROM Utils.GlobalProp WHERE Id = 1)
Write "SQLCODE: " _ SQLCODE,!
Write "Invalid sql: " _ invalid,!
}
}
I removed InvalidGet method and object access to the property stopped working.
Class Utils.GlobalProp Extends %Persistent
{
Parameter InvalidGLVN = "^Utils.GlobalPropP";
Property Invalid As %String [ SqlComputeCode = {set {*} = ##class(Utils.GlobalProp).InvalidStatic()}, SqlComputed, Transient ];
ClassMethod InvalidStatic() As %String
{
Return $Get(@..#InvalidGLVN)
}
Method InvalidSet(val As %String) As %Status
{
Set @..#InvalidGLVN = val
Return $$$OK
}
/// Do ##class(Utils.GlobalProp).Test()
ClassMethod Test()
{
Do ..%KillExtent()
Set obj = ..%New()
Write "Invalid old: " _ obj.Invalid,!
Set obj.Invalid = $Random(100)
Write "Invalid new: " _ obj.Invalid,!
Do obj.%Save()
Kill obj
&sql(SELECT Invalid INTO :invalid FROM Utils.GlobalProp WHERE Id = 1)
Write "SQLCODE: " _ SQLCODE,!
Write "Invalid sql: " _ invalid,!
}Outputs:
Invalid old: Invalid new: SQLCODE: 0 Invalid sql: 65
Related Int code:
zInvalidCompute(%id)
New %tException,%val set %val = ""
try {
set %val = ##class(Utils.GlobalProp).InvalidStatic()
} catch %tException { throw %tException }
Quit %val
zInvalidGet() public {
If i%Invalid = "" { Set ..Invalid=..InvalidCompute($listget(i%"%%OID")) } Quit i%Invalid }
zInvalidSQLCompute()
// Compute code for field Invalid
set %d(2) = ##class(Utils.GlobalProp).InvalidStatic()
QUIT
Do $System.Status.DisplayError(tStatus)
Write !
}
Quit
}
}In the original post I said I'm trying to do the change without needing to change any existing SQL. If I can't fix it within Cache then I will likely have to pass the problem over to the front end (VB client) support, but thanks again for trying.
Okay, then you need to use your original storage definition, but when selecting invalid, select it in a NVL function:
SELECT
ID, Code, Description, NVL(Invalid, 0)
FROM Wendy.LTCodesIt should return 0 instead of empty string.
Yes, the 1's and 0's come out then, but the field isn't updatable from SQL. Presumably because you've now said it's transcient?
Update Wendy.LTCodes set Invalid=1 where ID = 'N002'
doesn't do anything
These changes fixed it:
- Removed highlited part of storage definition
- Made invalid property Transient
- Added 0 default to $Get
Class Wendy.LTCodes Extends %Persistent [ StorageStrategy = LTCStorage ]
{
Property Code As %String;
Property Description As %String;
Property Invalid As %Library.Boolean [ SqlComputeCode = { set {*} = ##class(Wendy.LTCodes).GetInvalid({Code})}, SqlComputed, Transient ];
Index CodeIndex On Code [ IdKey, PrimaryKey, Unique ];
ClassMethod GetInvalid(WSCode) As %Boolean
{
Quit $G(^LTCODES(WSCode,"INVALID"),0)
}
Method InvalidGet() As %Boolean
{
Quit ..GetInvalid(i%Code)
}
Method InvalidSet(value As %Boolean) As %Status
{
Set ^LTCODES(i%Code,"INVALID")=value
quit $$$OK
}
/// Do ##class(Wendy.LTCodes).SetData()
ClassMethod SetData()
{
kill ^LTCODES
S ^LTCODES("N001")="ANYOLD DESC"
S ^LTCODES("N001","INVALID")=1
S ^LTCODES("N002")="C5 REPEAT 1"
S ^LTCODES("N111")="SPECIMEN COMMENT"
S ^LTCODES("N200")="MSUD SCREEN|MSUD"
S ^LTCODES("N500")="Sickle Cell Screen"
}
Storage LTCStorage
{
<SQLMap name="LTCMap">
<Data name="Description">
<Delimiter>"|"</Delimiter>
<Piece>1</Piece>
</Data>
<Global>^LTCODES</Global>
<Subscript name="1">
<Expression>{Code}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^Wendy.LTCodesS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}
}It doesn't work in the sense that it's not producing either 0 or 1 in the invalid column. It has blanks where it's not set.
I imported your example, executed:
Do ##class(Wendy.LTCodes).SetData()
Then executed this sql:
SELECT ID, Code, Description, Invalid FROM Wendy.LTCodes
and received the following results:
| ID | Code | Description | Invalid |
| N001 | N001 | ANYOLD DESC | 1 |
| N002 | N002 | C5 REPEAT 1 | |
| N111 | N111 | SPECIMEN COMMENT | |
| N200 | N200 | MSUD SCREEN | |
| N500 | N500 | Sickle Cell Screen |
seems to be working.
But then I didn't really understand the use of Parameter InvalidGLVN = "^Utils.GlobalPropP";
it's for use with indirection. Example:
set ^Utils.GlobalPropP = 123
set glvn = "^Utils.GlobalPropP"
write glvn
> ^Utils.GlobalPropP
write @glvn
> 123Firstly thanks for your replies.
I've tried to incorporate your ideas into our code, but I find it's still not working.
But then I didn't really understand the use of Parameter InvalidGLVN = "^Utils.GlobalPropP";
I couldn't see that you're tell it where the "INVALID" node is set, so I've tried to create a self-contained class for my example:
Now with the Storage definition for Invalid (highlighted) that seems to override the compute code and the SQL works but it shows blanks on the rows where it's not set.
But when I remove the storage definition for Invalid and try to query the table with SQL it just falls over with
[SQLCODE: <-400>:<Fatal error occurred>]
[%msg: <Unexpected error occurred: <UNDEFINED>%0Go+1^%sqlcq.USER.cls2.1 *i%%CursorDatad(12)>]
Class Wendy.LTCodes Extends %Persistent [ StorageStrategy = LTCStorage ]
{
Property Code As %String;
Property Description As %String;
Property Invalid As %Library.Boolean [ SqlComputeCode = { set {*} = ##class(Wendy.LTCodes).GetInvalid({Code})}, SqlComputed, SqlComputeOnChange = Code ];
Index CodeIndex On Code [ IdKey, PrimaryKey, Unique ];
ClassMethod GetInvalid(WSCode) As %Boolean
{
Quit +$G(^LTCODES(WSCode,"INVALID"))
}
Method InvalidGet() As %Boolean
{
Quit ..GetInvalid(i%Code)
}
Method InvalidSet(value As %Boolean) As %Status
{
Set ^LTCODES(i%Code,"INVALID")=value
quit $$$OK
}
/// Do ##class(Wendy.LTCodes).SetData()
ClassMethod SetData()
{
S ^LTCODES("N001")="ANYOLD DESC"
S ^LTCODES("N001","INVALID")=1
S ^LTCODES("N002")="C5 REPEAT 1"
S ^LTCODES("N111")="SPECIMEN COMMENT"
S ^LTCODES("N200")="MSUD SCREEN|MSUD"
S ^LTCODES("N500")="Sickle Cell Screen"
}
Storage LTCStorage
{
<SQLMap name="LTCMap">
<Data name="Description">
<Delimiter>"|"</Delimiter>
<Piece>1</Piece>
</Data>
<Data name="Invalid">
<Node>"INVALID"</Node>
</Data>
<Global>^LTCODES</Global>
<Subscript name="1">
<Expression>{Code}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^Wendy.LTCodesS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}
}So is there a way I can get the above to work?
You do not need to override the propGet method here as this will be generated automatically to call the sqlcomputecode if that is defined.
You have two cases, you change storage definition or you can use calculated value. But in case with calculated value, you have two options. It's an object's access and sql access, and such getter it is just only for object's access. For SQL you should define SqlComputeCode. Something like this.
Property Invalid As %Library.Boolean [Calculated, SqlComputed, SqlComputeCode = { s {*}={+$G(^GLOBAL({Code},"INVALID"))} }];
more details in documentation
Anyone else got any ideas on getting this working so Invalid field always displays 0 or 1, but is also updateable from SQL? Example class as follows:
Class Wendy.LTCodes Extends %Persistent [ StorageStrategy = LTCStorage ]
{
Property Code As %String;
Property Description As %String;
Property Invalid As %Library.Boolean [ SqlComputeCode = { set {*} = ##class(Wendy.LTCodes).GetInvalid({Code})}, SqlComputed, SqlComputeOnChange = Code ];
Index CodeIndex On Code [ IdKey, PrimaryKey, Unique ];
ClassMethod GetInvalid(WSCode) As %Boolean
{
Quit +$G(^LTCODES(WSCode,"INVALID"))
}
Method InvalidGet() As %Boolean
{
Quit ..GetInvalid(i%Code)
}
Method InvalidSet(value As %Boolean) As %Status
{
Set ^LTCODES(i%Code,"INVALID")=value
quit $$$OK
}
/// Do ##class(Wendy.LTCodes).SetData()
ClassMethod SetData()
{
S ^LTCODES("N001")="ANYOLD DESC"
S ^LTCODES("N001","INVALID")=1
S ^LTCODES("N002")="C5 REPEAT 1"
S ^LTCODES("N111")="SPECIMEN COMMENT"
S ^LTCODES("N200")="MSUD SCREEN|MSUD"
S ^LTCODES("N500")="Sickle Cell Screen"
}
Storage LTCStorage
{
<SQLMap name="LTCMap">
<Data name="Description">
<Delimiter>"|"</Delimiter>
<Piece>1</Piece>
</Data>
<Global>^LTCODES</Global>
<Subscript name="1">
<Expression>{Code}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^Wendy.LTCodesS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}
}