Vitaliy Serdtsev · Nov 20, 2025 go to post

json=[
{"s":"iPhone1"},
{"s":"iPhone2"},
{"s":"IPHONE3"},
{"s":"IPHONE4"},
{"s":"Phone5"}
]

##class(%ASQ.SetUtils).pp(json.apply("$[*]?(@.s like_regex '\p{LL}\w+')"))

Output (IRIS 2025.3CE):
[
{
"s": "iPhone1"
},
{
"s": "iPhone2"
}
]

Vitaliy Serdtsev · Nov 20, 2025 go to post

Check the result outputs of the following code from Studio and from Terminal:

w $zu(67,13,$j)
Vitaliy Serdtsev · Nov 13, 2025 go to post

A long time ago, back in the days of Caché, I made a series of articles on creating a web application based on REST and CSP with my own static file processing. Try it, maybe you can use something from there.

Vitaliy Serdtsev · Nov 13, 2025 go to post

I will assume that you are using "Normal" (in which the user _SYSTEM is turned off) or even "Locked Down" (the user "UnknownUser" has no roles at all): Initial User Security SettingsDOC

I'm using "Locked Down", IRIS 2025.2CE and ran into a lot of different bugs when using WebTerminal v4.9.5:

  • <PROTECT>endSession+25^%SYS.cspServer
  • <LICENSE LIMIT EXCEEDED>
  • etc.
Fortunately, the WebTerminal source code is available for analysis and editing.

The problem can be solved in two ways: either fix the source code or add the missing role. I chose the latter.

In my case, it is enough for the web application /terminalsocket to add the %Admin_Secure role in addition to the existing roles. After that, it doesn't hurt to clean up all existing sessions:

kill ^WebTerminal("AuthUser")
If the <LICENSE LIMIT EXCEEDED> error persists after this, restart the IRIS instance.

PS: Advisory for IRISSECURITY in InterSystems IRIS 2025.2

Vitaliy Serdtsev · Nov 13, 2025 go to post

Credentials are not requested, as most likely your existing session has not expired yet. By default, the timeout is 1 hour (see the "WebTerminal.Engine:WSKEYEXPIRES" class parameter).

Vitaliy Serdtsev · Nov 5, 2025 go to post

I would venture to assume that this difference is due to the overhead of calling the class method. In other words:

  • ##class(%DynamicArray).%New() -> ..%OnNew() -> $ZU()
  • [] -> $ZU()
There was a similar topic: Shared code execution speed

Vitaliy Serdtsev · Oct 31, 2025 go to post

"Real life" *.inc entry is to execute macro routine "Entry^HS.Local.VA.Util.Log(%arr,,"D")" if Global ^GlFSL("Debug") is >0 by calling from a classmethod as $$$TestIf(arr) - no return value is required.
#define TestIf(%arr) if (^GlFSL("Debug")>0) {do Entry^HS.Local.VA.Util.Log(%arr,,"D")}
;or
#define TestIf(%arr) do:^GlFSL("Debug")>0 Entry^HS.Local.VA.Util.Log(%arr,,"D")
Usage:
$$$TestIf(5)

Vitaliy Serdtsev · Oct 31, 2025 go to post

Statement "set a = $$$TestIf(3)" is included into a classmethod with no other code in. Expected output 5
What is the expected result for the next operator?
set $$$TestIf(-3)

Vitaliy Serdtsev · Oct 30, 2025 go to post

You have two mistakes.

  • Instead
    for {i=1:1:%count set x=
    should be
    for i=1:1:%count {set x=
  • Macros ≠ Function

A working example:

  #define MyLoop(%count,%result) set ref="",%result="" for i=1:1:%count set ref=$order(^%SYS("JOURNAL",ref),-1),%result=%result_$listbuild(ref)

  $$$MyLoop(5,x)   write $listtostring(x)

Vitaliy Serdtsev · Oct 30, 2025 go to post

Example of @Robert Cemper compiles without errors for me (even on Caché 2018.xxx):

Class dc.a Abstract ]
{

ClassMethod Test() {   #define MyLoop(%count) set x="" for i=1:1:%count set x=$order(^%SYS("JOURNAL",x),-1) write x,!    $$$MyLoop(5) }

}

Vitaliy Serdtsev · Oct 30, 2025 go to post

If the array is multidimensional, then you can't do without loops (the code is without error handling):

  #define ArrayToStr(%arr,%str) set ref=$name(%arr),%str="s " for  {set ref=$query(@ref,1,val) quit:ref=""  set %str=%str_$$FormatName^%qcr(ref,1)"="##class(%Utility).FormatString(val)_","} set $extract(%str,*)=""
  #define StrToArray(%str) xecute %str

Usage:

  set a(0)=7
  set a(1,"color")="green"
  set a(1,"color","green")=""
  set a("color",$listbuild($double(3)))=$listbuild("red","blue")

  $$$ArrayToStr(a,str)   zwrite str   kill a   $$$StrToArray(str)   zwrite a

Vitaliy Serdtsev · Oct 30, 2025 go to post

I would like to have *inc file entry to convert an array into a string and string into an array.
If the array is one-dimensional, then you can do without cycles altogether. I'll give you a small example below:

  #define Array1ToJSONString(%arr,%json)##continue
  s ##unique(new)=##class(%ZEN.proxyObject).%New()##continue
  d ##unique(old).%CopyFromArray(.%arr),##continue
    ##class(%ZEN.Auxiliary.altJSONProvider).%WriteJSONStreamFromObject(.%json,##unique(old))##continue
  s %json=%json.Read(3641144)
  
  #define JSONStringToArray1(%json,%arr)##continue
  d ##class(%ZEN.Auxiliary.altJSONProvider).%ConvertJSONToObject(%json,,.##unique(new)),##continue
    ##unique(old).%CopyToArray(.%arr)

Usage:

  set a("color")=$listbuild("red","blue")
  set a("price")="expensive"
  set a("size")=$listbuild("large","small")
    
  $$$Array1ToJSONString(a,jsonStr)
  zwrite jsonStr
  $$$JSONStringToArray1(jsonStr,b)
  zwrite b

Vitaliy Serdtsev · Oct 29, 2025 go to post

Attention: You can INCLUDE just 1 single *.INC in a class definition.
Including Include Files

To include multiple include files at the beginning of a class definition, the syntax is of the form:

Include (MyMacros, YourMacros)

Vitaliy Serdtsev · Aug 21, 2025 go to post

import iris

# first, we connect to the %SYS namespace conn = iris.connect('localhost:1972/%SYS','_SYSTEM',pwd,10,True) irispy = iris.createIRIS(conn)

# here we switch to the USER namespace newNameSpace = irispy.classMethodString('%SYSTEM.Process','SetNamespace','USER')

# now we can call the method from the new namespace #print(irispy.classMethodInteger('DC.Unix','RoundPosixToSeconds',1154669852181849976))

conn.close()

Vitaliy Serdtsev · Aug 15, 2025 go to post
#Include %sqlx
set newposix $$$sqlxPosixTimeEncode(+$p($$$sqlxPosixTimeDecode(posix),".",1))

Сheck:

for posix = 1154669852181849976, -6979664624441081856, 1406323805406846975 {

  set newposix $$$sqlxPosixTimeEncode(+$p($$$sqlxPosixTimeDecode(posix),".",1))

  write ##class(%PosixTime).LogicalToTimeStamp(posix),!,
        ##class(%PosixTime).LogicalToTimeStamp(newposix),!!
}

2025-05-27 12:06:15.003
2025-05-27 12:06:15
 
0001-01-01 00:00:00
0001-01-01 00:00:00
 
9999-12-31 23:59:59.999999
9999-12-31 23:59:59
Vitaliy Serdtsev · Aug 11, 2025 go to post
 

source code of dc.observation_lab

Class dc.a Extends %Persistent SqlTableName observation_lab ]
{

Index iCode On code;

Index iCodeText On codetext;

Property code As %String(MAXLEN 2000);

Property codetext As %String(MAXLEN 2000) [ SqlFieldName code_1_text ];

/// d ##class(dc.a).Test()
ClassMethod Test(= {5e6})
{
  t=$zh
  d DISABLE^%NOJRN
  ^dc.aD,^dc.aI
  i=1:1:c=i-1#1000+1,^dc.aD(i)=$lb("",c,"code_1_text_"_c)
  ^dc.aD=N
  ENABLE^%NOJRN
  "(time) insert = ",$zh-t,!
  
  t=$zh
  d ..%BuildIndices(,,,$$$NO)
  "(time) %BuildIndices = ",$zh-t,!
  
  t=$zh
  d $system.SQL.TuneTable("dc.observation_lab",,1,,,,,,,"100%")
  ;&sql(TUNE TABLE dc.observation_lab %SAMPLE_PERCENT '100%')
  "(time) tune table = ",$zh-t,!
}

}

The following query does not use iCode/iCodeText indexes (2025.2.CE):

select code_1_textcount(code_1_text)
  from dc.observation_lab
  group by code_1_text
  order by desc

PS: if possible, I would try to convert the text into a number in some way and index this number already, perhaps even with the bitmap type.

Vitaliy Serdtsev · Aug 4, 2025 go to post

I completely agree with you.👍

I hope someone from InterSystems will pay attention to your post.

PS: and in addition, it would also be nice to solve the issue with %FromOref(): either the code or the documentation needs to be fixed. On version 2025.2.CE it still doesn't work.

Vitaliy Serdtsev · Aug 4, 2025 go to post

Here explained to us that this is not a bug, but a feature: <PROTECT> *Function not allowed in IRIS Native python

Starting from 2024.1 IRIS Native disallows routine invocations. Please use class methods instead.

For reference, these changes can be identified as DP-422635 and DP-424156.

PS: and yes, additional roles and resources like %Native_*/%All etc. no eliminate the <PROTECT> error. Checked on version 2025.2.CE

Vitaliy Serdtsev · Jul 16, 2025 go to post

Very strange syntax:

But if we tried at once with a single statment with a select into (1 value) it fails
INSERT INTO Sample.YoungPeople (PASReligionCodevalues (SELECT internalPatientNumber FROM
Pennine_TIE_Clinicom_Link.PMISPECIALREGNCA where InternalPatientNumber=100)

See:

Vitaliy Serdtsev · Jun 30, 2025 go to post

Yes. Or you can immediately increase the logical size of the IRISTEMP to the desired value, for example:

%SYS>set status=##class(Config.Databases).Get("IRISTEMP",.props)
%SYS>set status=##class(SYS.Database).ModifyDatabase(props("Directory"),5*1024) ; 5Gb
In this case, you will immediately increase the size from 240Mb to 5Gb, bypassing many small expansions.

Vitaliy Serdtsev · Jun 13, 2025 go to post

I simplified the code a bit for testing:

ROUTINE ztest23
Version()  quit 20250613
  ;
  ; do init^ztest22(100)  ; in one irissession
  ;
  ; in another irissession:
  ; do test^ztest23()
  ;

test(N=1E7) public {
  do runOne^ztest23("convertInRunFar",N,.dt1),
     runOne^ztest23("convertInBigMacFar",N,.dt2)
  write $fnumber(dt2-dt1*100/dt1,"",2)_"% difference",!
}

runOne(pLabel,N=1E7,&dtpublic ; kill dt do runOne^ztest23("convertInRunFar",1E7,.dt)
{
  if pLabel="convertInRunFar" {
    set comment="looping $$convertIn^ztestLib (far)"
  }else{
    set comment="%New^ztestBigMac, looping $$convertIn^ztestLib (far)"
    set convertName=$$%New^ztestBigMac()
  }
  
  set convertIny=$zconvert("Маленькая умная Коричневая Лиса прыгает через лежащую сонную Пятнистую Собаку","o","UTF8")
  
  set dt0=$zhorolog  for i=1:1:set convertInx=""
  set dt0=($zhorolog-dt0)
  set dt1=$zhorolog for i=1:1:set convertInx=$$convertIn^ztestLib(convertIny,"UTF8")
  set dt=($zhorolog-dt1-dt0)*1E6,dt=dt/N
  write pLabel,?30,$fnumber(dt,"",3),?45,comment,!
}
Vitaliy Serdtsev · Jun 12, 2025 go to post

IRIS 2025.1 CE

I made 4 launches in a row - the result fluctuates greatly:

USER>set N=1E7 do runOne^ztest22("convertInRunFar",N,.fieldsset dt1=$get(fields("dt")) do runOne^ztest22("convertInBigMacFar",N,.fieldsset dt2=$get(fields("dt")) w $fn(dt2-dt1*100/dt1,"",2)_"% difference",!
convertInRunFar               0.473          looping $$convertIn^ztestLib (far)
convertInBigMacFar            0.478          %New^ztestBigMac, looping $$convertIn^ztestLib (far)
1.13% difference
 
USER>set N=1E7 do runOne^ztest22("convertInRunFar",N,.fieldsset dt1=$get(fields("dt")) do runOne^ztest22("convertInBigMacFar",N,.fieldsset dt2=$get(fields("dt")) w $fn(dt2-dt1*100/dt1,"",2)_"% difference",!
convertInRunFar               0.474          looping $$convertIn^ztestLib (far)
convertInBigMacFar            0.598          %New^ztestBigMac, looping $$convertIn^ztestLib (far)
25.99% difference
 
USER>set N=1E7 do runOne^ztest22("convertInRunFar",N,.fieldsset dt1=$get(fields("dt")) do runOne^ztest22("convertInBigMacFar",N,.fieldsset dt2=$get(fields("dt")) w $fn(dt2-dt1*100/dt1,"",2)_"% difference",!
convertInRunFar               0.461          looping $$convertIn^ztestLib (far)
convertInBigMacFar            0.597          %New^ztestBigMac, looping $$convertIn^ztestLib (far)
29.70% difference
 
USER>set N=1E7 do runOne^ztest22("convertInRunFar",N,.fieldsset dt1=$get(fields("dt")) do runOne^ztest22("convertInBigMacFar",N,.fieldsset dt2=$get(fields("dt")) w $fn(dt2-dt1*100/dt1,"",2)_"% difference",!
convertInRunFar               0.511          looping $$convertIn^ztestLib (far)
convertInBigMacFar            0.551          %New^ztestBigMac, looping $$convertIn^ztestLib (far)
7.88% difference
 
USER>

I'm afraid we can't do without the WRC.