Written by

👨‍💻 InterSystems IRIS Specialist
Article Andrew Sklyarov · 9 hr ago 4m read

InterSystems ObjectScript Tips & TricksContestant

Hey Community, 

Yeah, I know we have a lot of tips & tricks articles - we even have a special tag for it, right? But I can not share my own collection. In this collection, you can find snippets for beginners, as well as some everyday ObjectScript constructions. In fact, it's also a way to learn something new about my favorite programming language. So, if you guys have anything to add, you are welcome!

 

1) Returning an error using format text:

Return $$$ERROR($$$GeneralError, $$$FormatText("Failed to open message with ID = %1", id))

2) Error check when we use Embedded SQL (&sql()):

Return:SQLCODE<0 $$$ERROR($$$GeneralError, $$$FormatText("Operation failed. SQLCODE = %1: %2", SQLCODE, $SYSTEM.SQL.Functions.SQLCODE(SQLCODE)))

3) Emptiness check that covers any null-like values:

If $ASCII(object.field)>0 {
    // Not empty
}

Here is a need to explain, I believe. For example, external systems via SOAP protocols can return null as $CHAR(0). In Cache null, it is an empty string, but checking for an empty string will not work correctly. This expression is a single-line way to solve this problem.

4) Auto-populated record update timestamp in a table:

Property UpdatedAt As %TimeStamp [ SqlComputeCode = { Set {*} = $ZDATETIME($NOW(), 3) }, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];

5) Reading a value from LookupTable (named Settings):

Set value = $GET(^Ens.LookupTable("Settings", "my_key"))

6) Writing a value to LookupTable (named Settings):

Set tSC = ##class(Ens.Util.LookupTable).%UpdateValue("Settings", "my_key", value)

7) Importing Python packages:

XData %import [ MimeType = application/python ]
{
import iris
from confluent_kafka import Consumer, KafkaException, TopicPartition, KafkaError
}

8) Returning IRIS statuses from Python:

return iris.system.Status.OK()
return iris.system.Status.Error(5001, f'Request body is invalid: {e}')

9) Tracing in Python code:

Method Trace(message As %String)
{
    $$$TRACE(message)
}

Method PyImpl() [ Language = python ]
{
    self.Trace(f'Received message: {input.value}')
}

10) Converting JSON array (%DynamicArray) to a comma-separated string and back:

Set jsonArray = ["test1","test2","test3"]
Set str = $TRANSLATE(jsonArray.%ToJSON(), "[]")
Set jsonArray = [].%FromJSON("["_str_"]")

11) Formatting JSON:

Do ##class(%JSON.Formatter).%New().FormatToString({"test":"test"}, .formatted)

12) Converting %Status into exception and back for Try/Catch:

try {
    $$$ThrowOnError(object.%Save())
} catch ex {
    Return ex.AsStatus()
}

13) Writing a $$ERROR into the Application Error Log (when we need a "silent" exception):

Do ##class(%Exception.StatusException).CreateFromStatus(tSC).Log()

14) Normalizing an input string:

Set value = $ZSTRIP(value, "<>WC")

15) One way to iterate over a %DynamicArray:

Set it = array.%GetIterator()
While it.%GetNext(.key, .value) {
    Write !, key, ":", value
}

16) One way to execute a SQL query:

Set rs = ##class(%SQL.Statement).%ExecDirect(, "SELECT ID, Name FROM MyTable WHERE Status = ?", status)
Return:rs.%SQLCODE<0 $$$ERROR($$$GeneralError, $$$FormatText("Execute failed. SQLCODE = %1: %2", rs.%SQLCODE, rs.%Message))
While rs.%Next() {
    Write !, rs.ID_", "_rs.Name
}

17) Another way to execute a SQL query (using cursors):

&sql(DECLARE C1 CURSOR FOR SELECT ID, Name INTO :id, :name FROM MyTable WHERE Status = :status)
&sql(OPEN C1)
Return:SQLCODE $SYSTEM.Error.FromSQLCode(SQLCODE, %msg).Status
&sql(FETCH C1)
While SQLCODE = 0 {
    Write !, $$$FormatText("id = %1, name = %2", id, name)       
    &sql(FETCH C1)
}
&sql(CLOSE C1)

18) The third way to execute an SQL query (using SQL functions):

Query Test(status As %String) As %SQLQuery [ SqlProc ]
{
    SELECT ID, Name FROM MyTable WHERE Status = :status
}

ClassMethod TestRun()
{
    Set rs = ..TestFunc("Active")
    While rs.Next() {
        Write !, rs.ID_", "_rs.Name
    }
}

These are the main three, but there are other ways to execute a SQL query. I won't list them all here, let's move on.

19) Running code in a single instance in whichever process gets there first (locking):

Lock +^MyLock(id):0
Return:'$TEST $$$OK
Try {
    // Parallel execution is prohibited for this block
} Catch ex {
    Set tSC = ex.AsStatus()
}
Lock -^MyLock(id)

20) $CASE or $SELECT instead of an If/Else chain:

Set label = $CASE(status, "A":"Active", "I":"Inactive", "P":"Pending", :"Unknown")
Set label = $SELECT(status="A": "Active", status="I": "Inactive", status="P":"Pending", 1:"Unknown")

21) Temporary (process-private) global:

Set ^||Temp(1) = "Will be removed automatically when the process ends"

22) Adding leading zeros:

Return $REPLACE($JUSTIFY(docNumber,4)," ",0)

23) Setting a Numeric field in a JSON object:

Set json = {"id":(+value)}

24) Setting a Boolean field in a JSON object:

Do json.%Set("active", isActive, "boolean")

25) Alternative to AND (&&) in conditions:

If '$ISOBJECT(obj),obj.prop'="" {
    Write !, obj.prop
}

26) Iterating over an array (same for the Globals):

Set node = ""
For {
    Set node = $ORDER(array(node))
    Quit:node=""
    Set subnode = ""
    For {
        Set subnode = $ORDER(array(node,subnode))
        Quit:subnode=""
        Write !, "array(", "node, ",", subnode, ") = ", array(node,subnode)
    } 
}

27) Dynamic global name:

Set MyGlobal = "^Sample.Data"
Write !, $GET(@MyGlobal@(node))

28) Your turn! 🙂

Comments

Ali Nasser · 3 hr ago

This is not a trick but a feature that I don't see used very often, but defining a unique index provides you with auto generated methods to get an oref to a row in a table by column value rather than by knowing the %ID of the row.

For example, here's a simple table with two columns:

class DC.Example Extends %Persistent {
    Property Name As %String;
    Property DOB As %Date;
    
    Index NameIDX On Name [Unique];
}

Let's insert a value into this table:

INSERT INTO DC.Example(Name, DOB) VALUES('John',TO_DATE('01 Jan 1986'))

Suppose you know that one of the rows in a table contains the name "John" but you want to get their DOB, you can get the oref by using an auto-generated method called "NameIDXOpen()" like so:

set oref = ##class(DC.Example).NameIDXOpen("John")
write $ZDATE(oref.DOB) //prints out "01/01/86"

For the curious: https://docs.intersystems.com/iris20261/csp/docbook/DocBook.UI.Page.cls…

0
Julius Kavay  2 hr ago to Ali Nasser

That's neither a trick nor a feature but an intended behavoir (for what feels like 100 years).
Each unique index over one or more table columns can be used to open that object just by using the column(s) from that index.

class DC.Example2 Extends %Persistent { 
Property Column1 As %String; 
Property Column2 As %String; 
Property Column3 As %String; 
Property Column4 As %String; 
    
Index one On Column1 [Unique]; 
Index two On (Column2,Column3) [Unique]; 
} 

set obj=##class(DC.Example2).oneOpen(colValue1)   // default lock, or 
set obj=##class(DC.Example2).oneOpen(colValue1,0) // without lock 
if (obj) { write obj.%Id() } else { write "Record does not exist or the index one is corrupted" } 

set obj=##class(DC.Example2).twoOpen(colValue2,colValue3)   // default lock, or 
set obj=##class(DC.Example2).twoOpen(colValue2,colValue3,0) // without lock 
if (obj) { write obj.%Id() } else { write "Record does not exist or the index two is corrupted" } 
0
Enrico Parisi · 2 hr ago

Nice Tips & Tricks list! 🙂
Very useful.

Regarding #10 "Converting JSON array (%DynamicArray) to a comma-separated string and back:"

USER>Set jsonArray = ["test1","test2","test3"] 
  
USER>Set str = $TRANSLATE(jsonArray.%ToJSON(), "[]") 
  
USER>w str 
"test1","test2","test3"

The returned is not really what I consider a "comma separated string".
To return my definition of "comma separated string" it would be:

USER>Set str = $TRANSLATE(jsonArray.%ToJSON(), """[]") 
  
USER>w str 
test1,test2,test3

Then, going back...it's another story 😉

Regarding #2 "Error check when we use Embedded SQL (&sql())"

In addition to error code and description it's often useful to get/include error details, like:

Return:SQLCODE<0 $$$ERROR($$$GeneralError, $$$FormatText("Operation failed. SQLCODE = %1: %2 (%3)", SQLCODE, $SYSTEM.SQL.Functions.SQLCODE(SQLCODE),%msg))
0
Julius Kavay  1 hr ago to Enrico Parisi

Tipp #10 gives us plenty of opportunity for discussions like 
a) may that %DynArray have an embedded %DynArrays [1, 2, ["a", "b"], 4, 5]
b) may an element contain a comma                                [1, 2, "a,b", 4]
etc.

instead of the original solution: Set str = $TRANSLATE(jsonArray.%ToJSON(), "[]") 
I would use a more accurate way : set str = $e(jsonArray.%ToJSON(),2,*-1)
0