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 sequence0x70 0x00 0x61 0x00 0x75 0x00 0x6C 0x00

// the other end sees0x70 - 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 lineset res=[]
for t=t(0):int:t(1) do res.%Push($zdt(t\86400_","_(t#86400),3,1))
quit res

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

/// orset 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 JSONsh=$zh f i=1:1:1E5 { sr=[] f j=1:1:10 { dr.%Push("abcd") } } w$zh-h,!

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

/// with %Stringsh=$zh f i=1:1:1E6 { sr="" f j=1:1:10 { sr=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#2set:'$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 20202208:49:51 EST

Username: kav
Password: ******
USER>sx=$storage

USER>setx=$storage, ^myKav=123writex-$storage768
USER>setx=$storage, ^myKav2=123writex-$storage768
USER>setx=$storage, ^myKav3=123writex-$storage768
USER>setx=$storage, ^myKav3=123writex-$storage0
USER>setx=$storage, ^myKav2=123writex-$storage0
USER>setx=$storage, ^myKav=123writex-$storage0
USER>
Julius Kavay · Jan 21, 2024 go to post

I don't see any difference


USER>write$storage,! set^myTest=123write$storage21990227140002199022714000
USER>write$zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20202208: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 valueset vProfile2 = $Get(%request.Data("profile",2)) // the second value/// etc./// or you use a loop///kill value
ser value=0set 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
Julius Kavay · Dec 21, 2023 go to post

If you know which record is locked (i.e. ^My.Global(123) ) then you can identify the locking process (and therefore the user) in a simple method

Class DC.LockExtends%RegisteredObject
{
/// For a given (global) reference/// return the (exclusive) locking processID and username/// /// ref: a global reference, for example: $name(^My.Global(1,2,3))/// /// For other lock types (shared, remote)/// use the infos obtained by info_types OWNER, MODE, FLAGS and COUNTS, see/// https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_slock/// ClassMethod Who(ref)
{
    if ^$LOCK(ref,"MODE")="X" {
        set pid=^$LOCK(ref,"OWNER")
        if pid {
            setjob=##class(%SYS.ProcessQuery).%OpenId(pid)
            quit {"pid":(pid), "usr":($s(job:job.UserName,1:""))}
        }
        
    } else  { quit {} }
}
}

For example:

set ref=$name(^My.Global(123))
lock +@ref:1if '$test {
    // in case, the node is locked,// check up, by who is the node lockedset who=##class(DC.Lock).Who(ref)
    write who.%ToJSON() --> {"pid":"2396","usr":"kav"}
}
Julius Kavay · Dec 15, 2023 go to post

A few lines of code and you have your own object cloner

Class DC.ObjCloner [ Abstract ]
{
/// obj: Cache/IRIS or a Dynamic(JSON) Object/// /// For simplicity, JSON-Types null, true, false, etc. are ignoredClassMethod Clone(obj)
{
    if$isobject(obj) {
        if obj.%IsA("%DynamicAbstractObject") {
            if obj.%IsA("%DynamicObject") { setnew={},arr=0 } else { setnew=[], arr=1 }
            set iter=obj.%GetIterator()
            while iter.%GetNext(.key,.val) {
                set:$isobject(val) val=..Clone(val)
                do$case(arr, 1:new.%Push(val), :new.%Set(key,val))
            }
            quitnew
            
        } else { quit obj.%ConstructClone(1) }
    } else { quit"" }
}
}
Julius Kavay · Dec 13, 2023 go to post
Class DC.Import Extends (%Persistent, %JSON.Adaptor)
{

Property thingone As%String;Property thingtwo As%String;ClassMethod Test()
{
	// create a test streamset myStream=##class(%Stream.TmpCharacter).%New()
	do myStream.Write("[{""thingone"":""Red"",""thingtwo"":""Green""},{""thingone"":""Blue"",""thingtwo"":""Yellow""}]")
	
	// convert input (JSON) stream into a JSON objectset data={}.%FromJSON(myStream)
	
	// loop over the JSON-Array and import each array element into your databasefor i=0:1:data.%Size()-1 {
		set obj=..%New()
		set sts=obj.%JSONImport(data.%Get(i))
		if sts set sts=obj.%Save()
		if sts continuewrite"Error: i=",i,", reason=",$system.Status.GetOneErrorText(sts),!
	}
}

Of course, instead of "myStream" you should use your REST-input stream.

do##class(DC.Import).Test()

zwrite^DC.ImportD
^DC.ImportD=2^DC.ImportD(1)=$lb("","Red","Green")
^DC.ImportD(2)=$lb("","Blue","Yellow")

Julius Kavay · Dec 12, 2023 go to post

Shouldn't be a problem, $ZTZ isn't the only thing you can change.

Whatever value or setting you change, $namespace, $zeof, $horolog, etc. you have to consider a simple rule, if you change things (which can be used by subsequent routines, methods, etc.) for your own use, then after using them, you have to restore them to their original value. That's all.

YouRESTorWHATEVERmethod()
{
  set oldZTZ=$ztzset$ztz=<your preferred value>
  ...
  set$ztz=oldZTZ
  return
}
Julius Kavay · Dec 11, 2023 go to post

You can change the Timezone system variable

//  for example EST (Eastern Standard Time, 5 hours behind UTC)set$ztz = 300//  you can even consider daylight saving//  for example CET (Central European Time, 1 hour ahead of UTC)#define DSTOFFSET	-60
#; -60=CET, 300=EST#define LOCALZONE   -60set$ztz = $$$LOCALZONE+$s($system.Util.IsDST():$$$DSTOFFSET,1:0)
    

Setting $ZTZ affects the current job only (the job, which sets $ztz), so there is no danger for other processes.

Julius Kavay · Nov 29, 2023 go to post

If you want, for whatever reason, to compute the CRC of a Stream then write an short method like this (or use this):

/// Input : str - a sting or a stream///         typ - CRC-Type (0..7, See ISC documentation)///         /// Output: crc-value/// ClassMethod CRC(str, typ = 7)
{
    i $isobject(str),str.%IsA("%Stream.Object") {
        d str.Rewind() s crc=0while 'str.AtEnd { s crc=$zcrc(str.Read(32000),typ,crc) }
        ret crc
        
    } else { ret $zcrc(str,typ) }
}

Where is the problem?

Julius Kavay · Nov 29, 2023 go to post

Just a short update, the problem is fixed in IRIS 2022.1 and later versions.

Julius Kavay · Nov 23, 2023 go to post

I already have a database (named APPLIB) where such classes, routines and (some) data are stored and mapped to %ALL. By the way, I use %APP since it was introduced.
Nevertheless, mapping the above mentioned class (Py.Utility) to APPLIB has the same effect: it works, if I'm in %SYS but does not work for in other (for example USER) namespace.
I thought, it's simpler to explain the problem if I use a percent-class (%Zpy.Utility).
 

USER>

USER>w##class(Py.Utility).Info()

 set val = ..internal(arg)
 ^
<OBJECT DISPATCH>zInfo+1^Py.Utility.1 *python object not found
USER 2e1>q

USER>

USER>w##class(%SYS.Namespace).GetPackageDest(,"Py")
^/opt/isc/icindy/db/applib/
USER>

USER>zn"%SYS"%SYS>w##class(Py.Utility).Info()
def
%SYS>
Julius Kavay · Nov 21, 2023 go to post

Take a look on %SYSTEM.OBJ class

do$system.OBJ.Compile("your.classname")
// or to compile a whole packagedo$system.OBJ.CompilePackage("your.package")
Julius Kavay · Nov 13, 2023 go to post
/// Use the propertydefinition-class:/// Open your.classname || propertyname/// For example:set def=##class(%Dictionary.PropertyDefinition).%OpenId("Sample.Person||Name")
 if def write def.Type
 
 
Julius Kavay · Nov 1, 2023 go to post

the above approach ist the right way. And I do not see any problem there:

First, in the very first line (of the question) it's stated: "I need to develop a tool ... what data is being consumed by a certain process, ... to build an automated test scenario.", which means, this will be used during a development and/or test phase to gather informations about the touched globals (for automated tests). So the performance is not an issue.
 
Second, the suggestion of Paul Waterman can always run, assuming the process runs with the required right and flag. One can always provide the required conditions.

Julius Kavay · Oct 16, 2023 go to post

Your example shows https but you try a http connection.   Is this a mistake or a typo?