I get it now. The values for the p3 and p4 properties are invalid JSON. I think of the () around values as enabling us to embed ObjectScript inside JSON.

set myObject = { "p1":"abcd", "p2":true, "p3":(value), "p4":(sum+2) }
<- ObjScript -><-------------------- JSON----><ISOS-><-JSON><-ISOS><JSON>

What do you mean by "the last two properties (p3, p4) are invalid JSON properties"? If you set the referenced variables, and add the required parentheses, it works, right?


USER>set value = 2, sum = 3
USER>set myObject = { "p1":"abcd", "p2":true, "p3":(value), "p4":(sum+2) }
USER>zw myObject
myObject={"p1":"abcd","p2":true,"p3":2,"p4":5} ; 
Joel Solon · Jul 7, 2025 go to post

I don't use docker compose. I just use a shell script and the docker command. I pass

--hostname ABC123

which shows up at the top left of the Portal (server ABC123).

I think that would be hostname: ABC123 in a docker compose file.

Joel Solon · Jun 30, 2025 go to post

One quick answer is a helper ObjectScript class with a wrapper classmethod for each macro. If the helper class can inherit from the class that provides the macros (for example: CustomPackage.TestCase extends %UnitTest.TestCase) then you can replace $$$Macro(args) with self.Macro(args). Otherwise, your helper class must include the INC file that defines the macros, and other classes can call the class methods directly: iris.CustomPackage.HelperClass.Macro(args).

Joel Solon · Jun 6, 2025 go to post

I'd use LIKE and OR directly in the query. If there are more than 2-3 comparisons (which would make something like InLike() useful), I'd write a class method that uses Dynamic SQL to add an OR for each comparison to the final WHERE clause.

Joel Solon · Apr 7, 2025 go to post

Can also be used as #include inside a method (applies only to that method)

Joel Solon · Apr 9, 2024 go to post

$increment is (or used to be) unique in that it was the only function that changed its argument. It reminded me of learning about "destructive functions" in LISP a long time ago. No matter where/how you use it, it increments. So "set a = $increment(a)" is redundant, but still OK to use. As you saw, developers started to use "if $increment(a)" alone, because that was shorter but also works fine, even thought it looks strange. I wasn't aware of "do $increment(a)" being allowed, which does look nicer, so I also learned something new from this thread, thanks.

And let's not forget to mention the popular and useful "set a($increment(a)) =  something"

Joel Solon · Aug 24, 2023 go to post

Does this example make it clear? Does this meet your needs?

USER>set human = ##class(Simple.Human).%OpenId(1)

USER>zw human
human=20@Simple.Human  ; <OREF>
+----------------- general information ---------------
|      oref value: 20
|      class name: Simple.Human
|           %%OID: $lb("1","Simple.Human")
| reference count: 2
+----------------- attribute values ------------------
|       %Concurrency = 1  <Set>
|            Company = "GlobaDyne Inc."
|               Name = "Smith,John"
|              Phone = "265-288-5681"
|            Version = 2
+----------------- swizzled references ---------------
|             i%Home = $lb("6489 Clinton Street","Denver","NJ",26882)  <Set>
|             r%Home = ""  <Set>
|             i%Work = $lb("9353 Main Drive","Hialeah","MI",72997)  <Set>
|             r%Work = ""  <Set>
+-----------------------------------------------------

USER>write human.%IsModified()
0
USER>write human.PhoneIsModified()
0
USER>set human.Phone = "111-222-3333"

USER>write human.%IsModified()       
1
USER>write human.PhoneIsModified()   
1
USER>write human.Home.Street
6489 Clinton Street
USER>write human.Home.StreetIsModified()
0
USER>set human.Home.Street = "111 High Street"

USER>write human.Home.StreetIsModified()      
1
USER>
Joel Solon · Jul 31, 2023 go to post

%Library.ResultSet (aka %ResultSet) is deprecated. You should switch to using %SQL.Statement instead. It's better in every way. Doc is here: Dynamic SQL and within that there's a section on Metadata.

Joel Solon · Dec 8, 2022 go to post

It's not a big difference between Studio and VS Code - ObjectScript here. Studio automatically does the #dim for you when you do a %New() or %OpenId(), and VS Code - ObjectScript doesn't. But #dim is still necessary in both IDEs for referenced or returned objects:

set person = ##class(Simple.Person).%New()  // Studio WILL provide code completion for person, VS Code WON'T

#dim address as Simple.Address  // without #dim, neither Studio nor VS Code will provide code completion for address

set address = person.Address 

#dim rs as %SQL.StatementResult

set rs = statement.%Execute(args)  // you'll get code completion for rs thanks to #dim
Joel Solon · Nov 1, 2022 go to post

Thanks Michael.

You could code it the oop way, and use $sortbegin on all the index globals for your multiple tables, which means you'd have to know or look up what the index global names are (since they're not always ^D and ^I anymore), and test 1000000 inserts (main table and referenced tables) with the index build deferred to the end, and time it to see how long it takes. 

And then code it the sql way, inserting the same data into the multiple tables using %NOINDEX, and calling %BuildIndices() on all the classes at the end, and time it to see how long that takes. The sql way is supposed to be faster...

Joel Solon · Apr 7, 2021 go to post

...and learn about other useful shortcuts such as zenGetProp() and zenSetProp().

Joel Solon · Feb 24, 2020 go to post

Even though Tim's article talks about this, I'll mention it briefly here. Since ObjectScript Try/Catch construct doesn't have a "Finally" block like some other languages, the code following the Try/Catch is often used for "Finally" code. Since Return inside Try/Catch exits the Try or Catch and terminates the method, this would bypass any "Finally" code at the end. Therefore, I'd recommend avoiding using Return inside Try/Catch.

So if $$$ISERR(tSc), convert tSc into an exception and Throw it, and then handle the error in the Catch. After that, any "Finally" code will run.

Joel Solon · Jan 8, 2020 go to post

Not sure exactly what you're asking for...

You can add additional properties to your custom unit test class (inheriting from %UnitTest.TestCase) and use them to share data between the methods in the class. But this doesn't show up automatically in the results.

You can use the $$$LogMessage macro to display whatever you want in the results, but this is just text, not a new property.

do $$$LogMessage(key _ ":" _ value)

Joel Solon · Oct 3, 2019 go to post

If you want to allow subclasses of class A with a property that references Abstract %Persistent Class B, and allow A objects to use that property to reference any of the subclasses of B, class B must have its own extent (all subclass data stored in the same global). Otherwise, since the A objects only store the ID of the referenced B objects, how would the system determine which subclass of B ID #52 refers to? When all the subclass data is stored in the same global, IDs are unique across all subclasses, and ID #52 refers to one object only.

Adding a BitMap extent index to class B helps compensate for the fact that all B objects are stored together, and improves the performance of queries on the subclasses.

Joel Solon · Jul 29, 2019 go to post

As Eduard said, use a custom resource. Now it depends on what you want to do:

  • If you want to restrict certain Portal pages to a subset of users, create a custom resource (R1 for this example), add it to the pages you want the subset of users to access, and then add Use permission on the R1 resource to an existing or new role. Only users that are members of that role will have access to the pages.
  • If you want to restrict certain users to a subset of Portal pages (your question), create a custom resource (R2 for this example), add it to all the pages the users should not access, and then add Use permission on the R2 resource to an existing or new role. All other users should be in this role, and they will have access to all the pages, as they did before these changes. Any pages that aren't protected by R2 will be the only pages available to the users that aren't in the role. 

Hope that's clear.

Joel Solon · Jul 29, 2019 go to post

Security.System is a persistent class with 1 object in it. The ID of the object is "SYSTEM". So this works

set ss = ##class(Security.System).%OpenId("SYSTEM")
write ss.AutheEnabled

The approach above is best if you want to access several properties. But if all you really want is one or two properties of Security.System, you can do this:

write ##class(Security.System).AutheEnabledGetStored("SYSTEM")
Joel Solon · Mar 5, 2019 go to post

Brendan's comment from the other thread summarizes it nicely. Here's a little more...you have 2 good alternatives to parent-children:

  • Without using either relationship option, just use a simple reference from the "many/child" class to the "one/parent" class, and add the FK constraint yourself (using "cascade"). This allows bitmap indexes in either class.
  • Simulate parent-children relationship using one-many. After setting up the one-many relationship, in the Relationship definition in the "many" class, add the bold items shown here: [Cardinality = one, Inverse = Orders, Required, OnDelete = cascade]. This allows bitmap indexes in either class.
Joel Solon · Oct 31, 2018 go to post

You are correct. EventData is the field to use. Here's an example  query (run from %SYS namespace):

SELECT UTCTimeStamp, EventSource, EventType, Event, EventData, Username, Description
FROM %SYS.Audit
WHERE (UTCTimeStamp BETWEEN '2018-10-27 00:00:00' and '2018-10-27 23:59:59') AND (EventData = '12345') AND (Namespace = 'ABC')
ORDER BY UTCTimeStamp DESC, SystemID DESC, AuditIndex DESC

You probably want to add a WHERE clause on the Namespace column. The docs for the %SYS.Audit class (https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic…) suggest using a WHERE clause on UTCTimeStamp to speed up the search.

Joel Solon · Oct 12, 2018 go to post

I think you are asking: "If I have an object open in one process, and another process updates that object or its corresponding row, what's the best way for the first process to make sure that it has the latest version of the data?"

I recommend using %Reload(). It makes it clearer to another developer what you're actually doing. A quick glance at the %Reload() code makes me think that it would be faster than killing the object and calling %OpenId() to reopen it.

Of course, you might want to make use of Concurrency options so that if you have an object open in one process, other processes are prevented from updating it.

Joel Solon · Apr 15, 2017 go to post

I haven't seen this crash at all. Is it possible to share more details about the crash? Error message, for example. Also, if the last edited routine is locked, that doesn't require a restart of Caché. You can find the entry for the routine in the Lock Table (in System Operations) and delete it.

Joel Solon · Mar 29, 2017 go to post

Regarding the user for Caché/Ensemble processes, the user for those is not always the user that starts/stops Caché/Ensemble. My instance of Ensemble runs on the Mac. For several years, I do a custom install so that I can change the answer to the 4th question below from cacheusr to joelsolon.

User who owns instance: joelsolon
Group allowed to start and stop instance: staff
Effective group for Ensemble processes: cacheusr
Effective user for Ensemble SuperServer: joelsolon

All the Ensemble processes run as OS user joelsolon because of this.

I don't actually have an answer to your question, but maybe these points will help.

Joel Solon · Mar 28, 2017 go to post

It is possible to programmatically add a relationship to two persistent class definitions at runtime, and then compile those classes. That gives you the same result you'd get if you had added the relationship to the class definitions at design time. So I don't think that's what you want.

The term "Relationship" as defined in Caché means "objects of these classes can be linked at runtime, and this relationship will be stored when the objects are saved." So your need to create relationships between persistent objects "as and when they're required" doesn't really match up with this definition. Either a persistent class is in a relationship with another persistent class, or it isn't. It's not possible to have some objects of the class without the relationship definition, and other objects of the class with the relationship definition.

Maybe you just need to substitute one-many relationships for all of your parent-children relationships. One-many relationships are independent; the relationship is not required like it is in parent-children relationships. In v2013.1 and later, you can set the OnDelete action of the one-many relationship to "Cascade" so you get delete behavior similar to parent-children.

Joel Solon · May 2, 2016 go to post

I never updated this post with the workaround, sorry! The problem is with Secure Storage. You can reset it by going into Atelier > Preferences > General > Security > Secure Storage. Click the Contents tab. Under com.intersys.eclipse.connmgr, you'll see an entry for the connection to the Caché server. Delete that entry, and you should be able to recreate the server connection.