#ObjectScript

14 Followers · 1.6K Posts

InterSystems ObjectScript is a scripting language to operate with data using any data model of InterSystems Data Platform (Objects, Relational, Key-Value, Document, Globals) and to develop business logic for serverside applications on InterSystems Data Platform.

Documentation.

Article Timothy Leavitt · Jul 8, 2020 7m read

Introduction

If you're solving complex problems in ObjectScript, you probably have a lot of code that works with %Status values. If you have interacted with persistent classes from an object perspective (%Save, %OpenId, etc.), you have almost certainly seen them. A %Status provides a wrapper around a localizable error message in InterSystems' platforms. An OK status ($$$OK) is just equal to 1, whereas a bad status ($$$ERROR(errorcode,arguments...)) is represented as a 0 followed by a space followed by a $ListBuild list with structured information about the error. $System.Status (see class reference) provides several handy APIs for working with %Status values; the class reference is helpful and I won't bother duplicating it here. There have been a few other useful articles/questions on the topic as well (see links at the end). My focus in this article will be on a few debugging tricks techniques rather than coding best practices (again, if you're looking for those, see links at the end).

8
12 2575
Question Julian Matthews · Apr 11, 2025

Hey everyone.

I'm currently looking at a process where we're utilising the Class Ens.StreamContainer, and was looking to do some deletions outside of any purge routines.

Having been burned before, I wanted to make sure that deleting the container also deletes the contents within.

From looking in the class, the  %OnDelete ClassMethod appears to be removing an index from a search table and nothing more.

Am I looking in the right place, or is there a extended class for Ens.StreamContainer that does in fact delete the %Stream.Object
contained within the Ens.StreamContainer?

2
0 170
Question Gary M Lusso · Apr 9, 2025

I need to add an extra blank OBX segment after a segment containing text, Total Cost:

I get the OBX segment but it is in incorrect location.

It should be after the line containing Total Cost:

It needs to contain:

OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|

code:

    //Do pOutput.SetValueAt(tOBXText_"~~","ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX:5","set") //
         If (tOBXText[ "Total Cost:") {
             Set tSegmentOBX = ##class(EnsLib.HL7.Segment).%New()
             Set tSegmentOBX.SegType = "2.3:OBX"
             Set tSC = tSegmentOBX.SetValueAt("OBX", 0, "set")
             Set tSC = pOutput.

2
0 193
Question Timothy Leavitt · Mar 31, 2025

I'm exploring this right now: given a bunch of types defined as Pydantic models, how can I come up with an equivalent %RegisteredObject/%SerialObject and convert to/from (e.g., to support persistence and match validation as much as possible)?

People who know Python better than I do (e.g., your average undergraduate from this decade): is this a stupid idea or a cool idea? Has anyone else done this before?

5
0 247
Question PagerianDeveloper · Mar 17, 2025

Is there a way to use a property of the type %Persistent class in a  %ZEN.Component.page class?
Example:

// Data classClassExtends
Class


The problem is that when using it the described in the example.
The property can be set in a Method like this:

Method DoStuff() 


But when reading the property in an other Method the content of it is not set:

Is it generally possible to work like this?
Or have I overlooked a special handling?

3
0 207
Question Colin Brough · Mar 25, 2025

Is there a generic process for "walking" the structure of a virtual document - eg an HL7 message (EnsLib.HL7.Message) or an XML document (EnsLib.EDI.XML.Document).

At least we'd want to be able to visit all "nodes" (HL7 fields or sub-fields, XML nodes) in the virtual document and be able to work out/generate the Property Path (so we could call "GetValueAt"). 

We can just about come up with something generic for HL7, since it only nests down to 4 levels within each segment, though we're using numeric Property Path's at that point rather than symbolic ones (MSH:1.3 etc).

7
0 179
Announcement Celeste Canzano · Mar 24, 2025

Hello again IRIS community,

We have officially released our InterSystems IRIS Developer Professional certification exam for beta testing. The beta test will be available until April 20, 2025. As a beta tester, you have the chance to earn the certification for free!

Interested in beta testing? See the InterSystems IRIS Developer Professional Beta Test Developer Community post for exam details, recommended preparation, and instructions on how to schedule and take the beta exam. 

Thank you!

0
0 165
Question Scott Roth · Mar 21, 2025

I was using VSCode to edit a DTL because it seemed easier to copy/paste code from parts of the DTL I was editing. I tried to add <sql> tag and code to call a SELECT statement, but when I compiled I got the following error...

ERROR <Ens>ErrInvalidDTL: Invalid DTL

  > ERROR #5490: Error running generator for method 'GetSourceDocType:osuwmc.Epic.MFN.DTL.EpicMFN949002Normalization'

ERROR: Ens.DataTransformDTL.cls(GetSourceDocType) of generated code compiling subclass 'osuwmc.Epic.MFN.DTL.EpicMFN949002Normalization'

    > ERROR #5030: An error occurred while compiling class 'osuwmc.Epic.MFN.

2
0 153
Question Rob Schoenmakers · Nov 30, 2022

Perhaps this is an issue that has long been discussed somewhere. I have searched for it but still could not find the solution anywhere. In our environment, we send a container of SDA objects to UCR from HealthConnect. To populate the container we do a SQL query on a source system and then populate the appropriate SDA objects. Sometimes it happens that I want to do an update of a field and I want to empty the corresponding SDA field. In other words, send an empty string. The Alert.toTime field of the object in question is filled with the date '2022-11-29Z13:00:00'.

3
2 396
Question Scott Roth · Mar 12, 2025

I have the need to query an external database and write the result set/snapshot to an internal %Persistent [ DdlAllowed ] table that I built. I have built inbound SQL Services before and write them externally to replace SSIS jobs, but how would querying a database via a Service and writing the data to an internal table work?

Can I just take the inbound query structure and write it to the class file of the internal table in a DTL? If so, what would be the Target? Or does this need to be done within a BPL as a Code block?

2
0 129
Article David Hockenbroch · Jul 16, 2024 6m read

For programmers new to ObjectScript, one question will inevitably arise: “What is the difference between methods and class methods?” A typical answer would be: “A class method applies to a class, but a method applies to an instance of that class.” While that answer is correct, it lacks important information on how these methods differ, and how they are used in ObjectScript. Many things could be written as either. For instance, suppose we had a class called “User.Person” with a property called “Name”.

2
6 645
Article Alessandra Carena · Feb 4, 2025 7m read

Introduction

A REST API (Representational State Transfer) is an interface that allows different applications to communicate with each other through the HTTP protocol, using standard operations such as GET, POST, PUT, and DELETE. REST APIs are widely used in software development to expose services accessible by other applications, enabling integration between different systems.

However, to ensure that APIs are easy to understand and use, good documentation is essential. This is where OpenAPI comes in.

OpenAPI is a standard for describing RESTful APIs.

6
5 851
Article Colin Langella · Mar 11, 2025 2m read

Intro

In the process of trying to get more familiar with Objectscript I decided to try to build a general priority queue since I wasn't able to find an implementation anywhere. My though process for implementing this followed this general path.

Binary Heap

My first though to try to make this work quickly was to implement a binary heap and my first attempt at this used a multidimensional to build it. This version was relatively efficient, worked for strings/numbers natively, and worked for objects be letting the user override the comparitor.

Class pqueue.Queue Extends %RegisteredObject
{

Property Data As %Any [ MultiDimensional ];

Property Size As %Integer [ InitialExpression = 0 ];

Property Comparitor As %String [ InitialExpression = "(a,b) return a < b" ];
    
    Method Swap(i As %Integer, j As %Integer) As %Status [ Private ]
    {
    set temp = ..Data(i)
    set ..Data(i) = ..Data(j)
    set ..Data(j) = temp
    }
    
    Method Comp(x As %Any, y As %Any) As %Boolean [ Private ]
    {
    return $XECUTE(..Comparitor, x, y)
    }
    
    Method PercolateUp(idx As %Integer) [ Private ]
    {
    while idx > 0 {
    set newidx = (idx-1)\2

    if ..Comp( ..Data(idx), ..Data(newidx) ) do ..Swap( idx, newidx )
    else                                     Quit

    set idx = newidx
}
}

Method PercolateDown() [ Private ]
{
set idx = 0

while ((idx+1)*2) < ..Size {
        if ..Comp( ..Data(idx*2+2), ..Data(idx*2+1) ) set newidx = idx*2+2
        else                                          set newidx = idx*2+1
    
        if ..Comp( ..Data(idx), ..Data(newidx) ) Quit
    
        do ..Swap( idx, newidx )
        set idx = newidx
    }
    
    if ( (idx*2+1 < ..Size) && ..Comp( ..Data(idx*2+1), ..Data(idx) ) ) do ..Swap( idx, idx*2+1 )
    }
    
    Method IsEmpty() As %Boolean
    {
    return ..Size = 0
    }
    
    Method Top() As %Any
    {
    if ..IsEmpty() return ""
    return ..Data(0)
    }
    
    Method Put(inp As %Any) As %Status
    {
    set sts = $$$OK
    
    set ..Data( ..Size ) = inp
    do ..PercolateUp( ..Size )
    set ..Size = ..Size + 1
    
    return sts
    }
    
    Method Get(Output obj As %Any) As %Status
    {
    set sts = $$$OK
    if ..IsEmpty() {
        set obj = ""
        return $$$ERROR( "Cannot Get() from empty Queue" )
    }
    
    set obj = ..Data( 0 )
    set ..Size = ..Size - 1
    set ..Data( 0 ) = ..Data( ..Size )
    do ..PercolateDown()
    kill ..Data( ..Size )
    return sts
    }
    
    Method GenerateComparitor(operator As %String = "<", transform As %String = "") As %Status
    {
    set ..Comparitor = "(a,b) return a" _ transform _ " " _ operator _ " b" _ transform
    return $$$OK
    }
    
    }

After getting this working I wanted to try out different internal arrays since I assumed multidimensional arrays must be slower than a simple integer indexed array. So I modified the above code to use

*   `Property Data As list Of %Any;` : This works OK but it is about 3-4 times slower than using a multidimensional making it pointless.
*   `Property Data As %DynamicArray;` : I assumed this would be relatively fast but proved to be slow to the point of absurdity. When the amount of data stored in the queue is small it is around as fast as the `list of %Any`, but as the data grows the time it takes to make inserts and gets grows linearly which makes it pointless to build a heap on top of.

## Using the Multidimensional Array's Self-sorting

On its face this idea seems pretty simple, use the fact that the multidimensional array is always sorted to grab the lowest cost (highest priority) item. A problem with this are that indexing by an object simply uses the string representation of that object which isn't useful. To handle this a customer `evaluator` function is needed to return the integer or string evaluation of an object which is then sorted, it is then stored as `data( evaluation, obj_str_rep ) = object` which ensures that objects are correctly sorted even when two objects evaluate to the same value.

    Class pqueue.SparseQueue Extends %RegisteredObject
    {
    
    // Parameter Comp(a,b) As $XECUTE(..Comparitor, a, b);
    
    Property Data As %Any [ MultiDimensional ];
    
    Property Size As %Integer [ InitialExpression = 0 ];
    
    Property Evaluator As %String [ InitialExpression = "(a) return a" ];
    
    Method IsEmpty() As %Boolean
    {
        return ..Size = 0
    }
    
    Method Top() As %Any
    {
        if ..IsEmpty() return ""
        return $Order( ..Data("") )
    }
    
    Method Put(inp As %Any) As %Status
    {
        set sts = $$$OK
    
        set ..Data( $XECUTE(..Evaluator, inp), inp ) = inp
        set ..Size = ..Size + 1
    
        return sts
    }
    
    Method Get(Output obj As %Any) As %Status
    {
        set sts = $$$OK
        if ..IsEmpty() {
            set obj = ""
            return $$$ERROR( "Cannot Get() from empty Queue" )
        }
    
        set loc = $ORDER( ..Data("") )
        set obj = ..Data(loc, $ORDER( ..Data(loc, "")))
    
        set ..Size = ..Size - 1
        kill ..Data( loc, obj )
        return sts
    }
    
    Method GenerateEvaluator(transform As %String = "") As %Status
    {
        set ..Evaluator = "(a) return a" _ transform
        return $$$OK
    }
    
    }

This method proved to be by far the fastest. It has some slight disadvantages in that it can be harder to write an evaluator than a comparitor and, in the form given above, it cannot hold the same object at the same value twice (this is a rare case but could theoretically be a problem or a benefit).

## Speed Test

To test these out I wrote a simple script that generates a large randomized weighted directed graph and runs [Dijkstra's shortest path algorithm](https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/) using a given queue object. Using a graph with `150000` vertices where each has `10` neighbors, the output is printed below. Here while the algorithm is running an update is printed every `10000` edges checked, where the time since the last 10 thousand and size of the priority queue is printed, with some final stats at the end. The order they are run in is

*   Self-sorting Multidimensional

*   Heap Multidimensional

*   Heap Dynamic Array

*   Heap List of Object

> Generated Random Graph
0
0 249
Question Igor Pak · Mar 7, 2025

Hello, dear colleagues.

I need to connect to a remote JavaGateway from an Ensemble service.

I am trying to use the EnsLib.JavaGateway.Service with a remote host where the JVM is running.

I can successfully ping the remote Java Gateway from EnsLib.JavaGateway.Service, and Ensemble reports that the service status is OK.

There are no network issues, and all necessary ports are accessible.

My requests work without any problems when I specify localhost in EnsLib.JavaGateway.Service.

1
0 198
Article Evgeny Shvarov · Feb 26, 2025 1m read

Hi colleagues!

Yet another time I figured that there is no super-simple way to display error from %Status variable, but I need it relatively often in a terminal.

Yes, I know about $$$ Macro, but they are not superhelpful in a terminal.

My usual behavior is to try to remember by heart or copy from somewhere the formula:

USER>w $System.Status.DisplayError(st)

15
2 374
Question Scott Roth · Sep 13, 2022

I have created a class file that I want to execute daily to gather Metrics (Message Header, Space Available, etc..) and write the data into a Cache table. Now that I have written the class I want to add it to the Task Scheduler within Ensemble to run every morning. How do I go about getting the class file created as a Task within the Task Scheduler? The Documentation isn't as clear cut for creating custom tasks as one would expect.

8
0 849
Question Scott Roth · Mar 4, 2025

I already mentioned in a Previous post I am trying to build a list from a repeatable field within a HL7 message. I figured out how to build the list by using a context list string variable within the Business Process (BPL) and doing a

do

 when I am looping through the field. I want to do it one step farther though... I want to search the list to see if the value exists before I do the insert. I only want to insert if the value is different than what is in the list already.

IF$LF##class

I tried using $LF or $LISTFIND, but I keep getting the same error...

1
0 166
Question Scott Roth · Mar 4, 2025

I have a repeatable field within HL7 that I want to create a List from. Do I have to initialize the List by using $LB, or can I just use $LI to keep adding on to the end of the list as it is looping through the field?

4
0 133
Question Thembelani Mlalazi · Feb 28, 2025

I have been trying to get to grips with the new dot Net Gateway used in IRIS as the import of the DLL to construct proxy classes is no longer supported in IRIS I have a third party DLL that when I try to instantiate throws an error complaining about the class not instantiated as it does not support parameterless constructor .I am using this new 

set$systemdo
5
0 145
Article Steve Pisani · Apr 3, 2024 2m read

Hi - Recently I have been investigating an annoying situation whilst editing ObjectScript classes or routines in VSCode.

What was happening to me was, as I was typing in lines of code into my class (for example, adding a new Method, or changing the Class signature, or a block of code),  this would quickly get syntax checked, re-formatted, and compiled - inevitably, (since I would be mid-way through my typing), this would generate compilation errors.

12
1 810
Question Oliver Wilms · Sep 19, 2024

Hello, I try to develop a REST interface where I need to interact with legacy MUMPS routines. How can I pass in input to a Read without modifying the legacy code?

I think in linux I can execute command < inputfile to read from file, but how does it work in ObjectScript?

7
0 343