Julius Kavay · Apr 12, 2024 go to post

Just for the case, you are lost in the working memory space and desperately searching the spot(s) in your programm where a specific object is once again referenced, here a small handy method which could help you

/// find all variables which contain a given object(reference)
/// 
/// I: the OREF you looking for
/// 
/// O: "" if the spool-device can't be opened
///    [] if no variables contain the given OREF
///    [var1, var2, ... varN] an array of variable names (incl. subscripted and orefs)
///    
ClassMethod FindObject(obj)
{
	set res=[]
	if $d(%)#10,%=obj do res.%Push("%")
	new % set %=obj kill obj
	
	lock +^SPOOL("nextID")			// adapt this lines
	open 2:($o(^SPOOL(""),-1)+1):1	// to your method of
	lock -^SPOOL("nextID")			// creating new spool IDs
	
	if $t {
		use 2
		set spl=$zb
		do $system.OBJ.ShowReferences(.%,1)
		
		for i=1:1:$za-1 {
			set x=$p($zstrip(^SPOOL(spl,i),"<=>w",$c(13,10))," ",3)
			do:x]"%.~" res.%Push(x)
		}
		close 2
		kill ^SPOOL(spl)
		
	} else { set res="" }
	
	quit res
}

Example

USER>kill
USER>set pers=##class(DC.Person).%OpenId(1)
USER>set temp=pers, zz(3)=temp
USER>write ##class(DC.Help).FindObject(pers).%ToJSON()
["pers","temp","zz(3)"]

Julius Kavay · Apr 11, 2024 go to post

I'm not sure wha you want to achive, so I ask a puzzling question: do you want to create dungling object? "I want to remove the object from memory even if it is still referenced in memory", as I understand, would free the memory used by an object but let the object referenc(es) intact, so the reference now would point into nirvana. Is that what you want to do? Why? Can you a little bit elaborate, what is your target or the background respectively?

Julius Kavay · Apr 11, 2024 go to post

The other way is, to put the classname into a variable and

set var = "MyPackage.MyClass"
do $classmethod(var,"Main")
set x=$classmethod(var,"Othermethod",params)

// or, if you have an instance
set obj=##class(MyClass).%New()
do $method(obj,"Main")
set x=$method(obj,"Othermethod",params)
Julius Kavay · Apr 11, 2024 go to post

If your class is instantiable (i.e. not an abstarct class) then

set obj = ##class(MyPackage.MyClass).%New()
do obj.Main()
set x=obj.OtherMethod(parameter)
Julius Kavay · Apr 10, 2024 go to post

It seems, nobody likes the execute command... once celebrated, now frowned upon

set x="11,22,33,""Done"""
xecute "for i="_x_" write i,!"
11
22
33
Done

😏

Julius Kavay · Apr 10, 2024 go to post

I have a limited embedded python experience but if you can manage to get a (COS)%List from python then the solution is:

set $lb(val1, val2, val2, ...) = ConvertPythonTupelToCOSList(pythonfunction(param))
set $lb(val1, val2, val2, ...) = ConvertPythonListToCOSList(pythonfunction(param))

/// For example
ClassMethod Test()
{
  return $lb(11,22,33)
}

set $lb(a,b,c)=##class(Some.Class).Test()
write a,?5,b,?10,c ---> 11   22   33
Julius Kavay · Apr 5, 2024 go to post

I have no background information about your problem, but somehow I have the feeling, your have an organizational problem and not an isolational.

If one or more processes perform certain work under transaction and at the same time they feed a queue too ( I call it the WorkQueue) BEFORE the transaction is finished AND the queue is processed in parallel by an third process, then:
- either the third process (that with $order()) has to wait for the transaction to be finished or
- the process under transaction has to inform that third process about the outcome (i.e. rollback) of the transaction by feeding an second queue (I call this as the RolbackQueue).

The above implies, that
- before I put an item into the RollbackQueue, I would check, if it's already processed (from the WorkQueue), and if not, remove it from the WorkQueue and do not put it into the RollbackQueue
- in that third process, before processing an item, I would check, if that item is in the RollbackQueue. If yes, just do not process it (and remove from both queues)

As simple as that, merely I don't know your requirements.

Julius Kavay · Apr 2, 2024 go to post

A missing configuration is a good starting point... I think on Windows as well as on Linux, first you have to install the packages you are interested in, see here. Then you can import them.

Julius Kavay · Apr 2, 2024 go to post

It's not a "slight difference", it's a real difference, because you are using two different commands (a DO and an IF command)

set a = -1        // sets the variable a to -1
do $increment(a,1) zw "test" // increments <a> by 1, <a> is now 0
                             // the $increment() returns (the value of <a>)
                             // but the DO command do not expects a return value
                             // so the returned 0 is disposed
                             // hereinafter the next command on the line (zw) is executed
if $increment(a,1) zw "test" // increments <a> by 1, <a> is now 0
                             // the $increment() returns (the value of <a>) which is 0
                             // the IF command takes the returned value (0) but the value is
                             // "FALSE", hence the rest of the line is skipped (old style IF)
                             

If yo want to use the IF command and want both commands to be executed and have them on one line then:

// you have several options, a few of them
//
if $increment(a,1) { } zw "test" // empty IF-statement
if $increment(a,1)!1 zw "test"   // an IF with a forced outcome
if $increment(a,1)=a zw "test"   // if a=$increment(a,1) WON'T WORK !!!
//
// The only limit is only your imagination...

By the way, it's not a good idea using commands/functions with abbrevations and at the same time using mixed cases. In your examples I had to take a second look to realize, the one is a capital "i" and not a lowercase "L"

if $I(a,1) // increment a by 1
if $l(a,1) // return the subfieldcount from <a>, delimited by the character "1"
Julius Kavay · Apr 1, 2024 go to post

This works for me (pay attention to the semicolons!)

ClassMethod numpytest() As %String [ Language = python ]
{
 import numpy as np
 import math
 arr = np.array([1, 2, 3, 4, 5]); print(arr); x=np.random.randint(1, 15, 1) # generate a number between 1 and 15
 print("Random num=",x)
 y=math.pi
 print("This is pi:",y)
 x="hello world"
 print(x)
 a=2+5
 print("a=",a)
}

an the output is


USER>d ##class(Py.Demo).numpytest()
[1 2 3 4 5]
Random num= [13]
This is pi: 3.141592653589793
hello world
a= 7

USER>w $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST
USER>
Julius Kavay · Apr 1, 2024 go to post

Just a small hint, if you have more than one statement on one line, separate them with a semicolon

print('one'); print('two') # this is OK
print('one') print('two')  # syntax error
Julius Kavay · Apr 1, 2024 go to post

On IRIS, long strings are enabled per default, you can check that

set x="", $e(x,3641144)="z"  // OK
set x="", $e(x,3641145)="z"  // <MAXSTRING> error
Julius Kavay · Apr 1, 2024 go to post

Just to see things clear, the size of a stream is limited by the size of the database (some GBs or even TBs) and the size of the storage media (hdd,sdd), whichever smaller is (less the size of other globals). The size of a table row (all fields together) is limited by the string size (either 32KB or 3.6MB). In a table row you can put a REFERENCE of a stream but not the stream itself.

Julius Kavay · Mar 29, 2024 go to post

Just a wild guess... somewhere some character type checkbox is checked or not checked? Maybe a wrong driver type (ASCII/Unicode)?

// For example, IRIS sends a username "paul" as a two byte sequence
0x70 0x00 0x61 0x00 0x75 0x00 0x6C 0x00

// the other end sees
0x70 - this is a 'p'
0x00 - C-type end of string (hence, the name is just 'p')

But as I said, it's just a guess...

Julius Kavay · Mar 28, 2024 go to post

Can you please ensure, my answer matches the original question? Thanks! Usually one makes a new question instead of changing an old question to a new one. Currently both my answers hang around without an context to the above question.

Julius Kavay · Mar 28, 2024 go to post

If you process thousands of time splits,  it's a good idea using %List or just a simple %String instead of JSON - you can have a time savings by factor of about 10!

/// old line
set res=[]
for t=t(0):int:t(1) do res.%Push($zdt(t\86400_","_(t#86400),3,1))
quit res

/// new line
set res=""
for t=t(0):int:t(1) set res=res_$lb($zdt(t\86400_","_(t#86400),3,1))
quit res

/// or
set res=""
for t=t(0):int:t(1) set res=res_","_$zdt(t\86400_","_(t#86400),3,1)
quit $e(res,2,*)

To see the differences, try loops like this

/// with JSON
s h=$zh f i=1:1:1E5 { s r=[] f j=1:1:10 { d r.%Push("abcd") } } w $zh-h,!

/// with %List
s h=$zh f i=1:1:1E6 { s r="" f j=1:1:10 { s r=r_$lb("abcd") } } w $zh-h,!

/// with %String
s h=$zh f i=1:1:1E6 { s r="" f j=1:1:10 { s r=r_","_"abcd" } } w $zh-h,!
Julius Kavay · Mar 28, 2024 go to post
/// start, end: timestamp format
/// int       : the interval in seconds
/// mod       : 0 = use the time value as is
///            +1 = round-up the timestamp to a multiple of <int>
///            -1 = round-down the timestamp to a multiple of <int>
///            
/// return an JSON array: [time1, time2, ... timeN]
/// 
ClassMethod Intervals(start, end, int = 15*60, mod = 0) As %DynamicArray
{
    set res=[], t(0)=$zdth(start,3,1), t(1)=$zdth(end,3,1)
    
    for i=0,1 {
        set t(i)=t(i)*86400+$p(t(i),",",2) 
        if mod,t(i)#int { set t(i)=t(i)-(t(i)#int) set:mod>0 t(i)=t(i)+int }
    }

    for t=t(0):int:t(1) do res.%Push($zdt(t\86400_","_(t#86400),3,1))
    quit res
}
Julius Kavay · Mar 25, 2024 go to post

You can edit (or enhance) the above code to give you all matching elements. Below I share a code with you where you can choose the result data type (%List or %String) and the result scope (all the matching elements or just the first match).

/// Find common items of two lists or in two delimited strings
/// (as used in a $piece-function)
/// 
/// itm1: first list (as %List or comma-delimited %String)
/// itm2: other list (as %List or comma-delimited %String)
/// ans : 0 = return a comma-delimited %String with the first match found
///       1 = return a comma-delimited %String with all matches found
///       2 = return a %List with the first match found
///       3 = return a %List with all matches found
///       
/// return value: according to <ans> argument
///       
/// Hint: the "$d(var)," part is only needed if the <itm1> argument is
///       of %List type and can contain an "undefined" element like the
///       second element in $lb(11,,33).
///       
ClassMethod FindCommonItems(itm1, itm2, ans = 0)
{
    set ptr=0, res="", all=ans#2
    set:'$lv(itm1) itm1=$lfs(itm1) set:'$lv(itm2) itm2=$lfs(itm2)
    
    while $listnext(itm1,ptr,val) { if $d(val),$lf(itm2,val) { set res=res_$lb(val) quit:'all } }
    quit $s(ans<2:$lts(res), 1:res)
}
Julius Kavay · Feb 19, 2024 go to post

There arises two questions.

First, why do you use a class where the documentation starts with "This class is used internally to hold property/field values needed for computed fields"?
Another word for classes which are marked as "used internally" is, that this classes can be changed (altered in its behavior) without any notice or documentation in one of the next versions. That's definitelly not what you want.
 
Second, what is your real problem (where you think, it's only solvable by using that %Library.PropertyHelper class)? Can you post (and describe) your real problem?

Julius Kavay · Jan 31, 2024 go to post

You have right, I overlooked the [ character, sorry (usually one posts a piece of code and not a piece of picture!).  So the above line would be

if jsonobj, jsonobj.statusCode = 200 {
   for i=0:1:jsonobj.value.labReports.%Size()-1 {
      set pdf(i)=jsonobj.value.labReports.%Get(i).%Get("pdf",,"stream<base64")
   }
   ... // do something with pfd(i) streams
}
Julius Kavay · Jan 30, 2024 go to post

Two notes to your (above) code

- first, if you use the iterator method on an object which can contain long strings (longer what IRIS can handle) then you must specify the thrid argument to the %GetNext() method too, i.e.

while iterator.%GetNext(.key, .value, "stream") { ... }

- second, if you know the name and location of a stream property in an JSON object, like in your case, then just grab the data without using an iterator:

try { set jsonobj = {}.%FromJSON(httprequest.HttpResponse.Data) } catch { jsonobj=0 }
if jsonobj, jsonobj.statusCode = 200 {
   set pdf=jsonobj.value.labReport.%Get("pdf",,"stream<base64")  // according to the picture you provided
   ... // do something with the pdf-stream
}

For another example, how to work with long strings see this thread.

Julius Kavay · Jan 26, 2024 go to post

You have right, do {code} and do {code} while expr are mutually exclusive.

I thought more something like this do {{code}} (no spaces between curly braces) or, as you suggested, a new keyword like WRAP {...}, of course, BLOCK {...} and SCOPE {...} are also acceptable.

By the way, we already have an alternative for argumentless DO
do {code} while 0
It looks ugly and confusing and
- does not preseve $T
- does not establich a new stack level
but one can get over both easily respective do a work-around.

Julius Kavay · Jan 25, 2024 go to post

I hope someone is already working on that extension and it's scheduled for the upcoming release

Julius Kavay · Jan 21, 2024 go to post

Oh, I see, at the very first use of a (new) private global,  the storage is decremented (on my  machine) by 768 bytes. I think, that bytes will hold some management data for each provate global (name).

Instance: cindy:ICINDY
Version : IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST

Username: kav
Password: ******
USER>s x=$storage

USER>set x=$storage, ^myKav=123 write x-$storage
768
USER>set x=$storage, ^myKav2=123 write x-$storage
768
USER>set x=$storage, ^myKav3=123 write x-$storage
768
USER>set x=$storage, ^myKav3=123 write x-$storage
0
USER>set x=$storage, ^myKav2=123 write x-$storage
0
USER>set x=$storage, ^myKav=123 write x-$storage
0
USER>
Julius Kavay · Jan 21, 2024 go to post

I don't see any difference


USER>write $storage,! set ^myTest=123 write $storage
2199022714000
2199022714000
USER>write $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST
USER>

Try the above line. Maybe you have issued one or more "set variable=..." commands between the two "write $storage" statements in your tests...

Julius Kavay · Jan 11, 2024 go to post

In general, $extract() and $zstrip() are your friends.
If you want to strip ONLY the LAST character, then use this

set data="abc,,"
set $extract(data,*)=""
write data --> abc,

If you want to strip ALL (same) trailing characters, use this

set remove=","
set data1="abc,"
set data2="abc,,,"
set data3="abc,,-,,"
set data1=$zstrip(data1,">",remove)
set data2=$zstrip(data2,">",remove)
set data3=$zstrip(data3,">",remove)

write data1 --> abc
write data2 --> abc
write data3 --> abc,,-
Julius Kavay · Jan 9, 2024 go to post

Just a curious question, do you talk about a form with encoding? Something like this

<form method="post" enctype="multipart/form-data" ...>
...
</form>

If yes, I think WRC will be your friend. In any case, I have never worked with such type of encoding. Maybe someone else?

Julius Kavay · Jan 8, 2024 go to post

Maybe you didn't read it carefully enough...

/// the documentation say clearly:
///
/// %request.Data(itemName, itemIndex)
///
set vProfile1 = $Get(%request.Data("profile",1)) // the first value
set vProfile2 = $Get(%request.Data("profile",2)) // the second value
/// etc.

/// or you use a loop
///
kill value
ser value=0
set idx=$order(%request.Data("profile",idx))
while idx]"" {
    set value($increment(value))=%request.Data("profile",idx)
    set idx=$order(%request.Data("profile",idx)
}

/// value = item count
/// value(i) = i-th value