Question Wendy Griffiths · Jul 22, 2016

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

Eduard Lebedyuk · Jul 22, 2016

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,!
}
}

Code on GitHub.

0
Eduard Lebedyuk  Jul 25, 2016 to Mark Hanson

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
}
}
0
Wendy Griffiths  Jul 28, 2016 to Eduard Lebedyuk

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.

0
Eduard Lebedyuk  Jul 28, 2016 to Wendy Griffiths

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

It should return 0 instead of empty string.

0
Wendy Griffiths  Jul 26, 2016 to Eduard Lebedyuk

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

0
Eduard Lebedyuk  Jul 26, 2016 to Wendy Griffiths

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>
}

}
0
Wendy Griffiths  Jul 26, 2016 to Eduard Lebedyuk

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.

0
Eduard Lebedyuk  Jul 26, 2016 to Wendy Griffiths

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
> 123
0
Wendy Griffiths  Jul 26, 2016 to Eduard Lebedyuk

Firstly 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?

0
Mark Hanson  Jul 23, 2016 to Eduard Lebedyuk

You do not need to override the propGet method here as this will be generated automatically to call the sqlcomputecode if that is defined.

0
Dmitry Maslennikov · Jul 22, 2016

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

0
Wendy Griffiths · Jul 28, 2016

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>
}

}

0