go to post Steven Hobbs · Jan 16 Is the file you are editing in IRISLIB or some other Read-Only database? If so, use the System Management Portal (SMP) to turn off the database Read-Only option.
go to post Steven Hobbs · Dec 16, 2024 I am not sure exactly what you want to do. Is check1 just an ordinary string, or might it sometimes be a string that starts with a number (like a %Status variable)? The unary plus operator (+) turns a string operand into a number. If the string starts with the syntax of a number then +string returns that number and ignores characters in the string following the numeric syntax. If the string does not start with a number (this includes the empty string) then +string returns 0. It is possible for +string to signal <MAXNUMBER> if the characters at the beginning of string are too large for conversion to the computational numbers supported by ObjectScript. You could try ret:+check1 check1which would return check1 only if it were a non-empty string that starts with a non-zero number. If you want to return a string value that could be tested as a boolean that you have to append "1" to the front of the string if +string=0 is true.
go to post Steven Hobbs · Nov 25, 2024 The Class Reference pages for IRIS, available in any IRIS installation, describe all the classes installed in the installation. The Class Reference for the %Regex.Matcher class documents that the %Regex.Matcher class comes from the International Components for Unicode (ICU). The ICU maintains web pages at https://icu.unicode.org . The class reference documentation also contains the following statement for ICU documentation specific to the IRIS %Regex: {quote}The definition and features of the ICU regular expression package can be found in https://unicode-org.github.io/icu/userguide/strings/regexp.html .{quote} Additional documentation on the Unicode Regex package specific to how it interacts with the Unicode character set can be found at https://www.unicode.org/reports/tr18/ .
go to post Steven Hobbs · Oct 15, 2024 A quick comment on performance. A TRY ... CATCH is very efficient at run time if no exception occurs. The only extra code that is executed is a jump at the at end of the TRY block to go around the CATCH block. However, TRY ... CATCH is slower than using the %ZTRAP when an execption does occur. At compile time TRY ... CATCH blocks build tables that include program counters that describe which object code locations are in each TRY block and where the corresponding CATCH block begins. When an error is signaled (or THROWn) at run time, it is necessary to scan the frame stack searching the compile-time generated TRY ... CATCH tables to find the CATCH location to go to after popping the intermediate stack frames. If there are no TRY ... CATCH blocks involved then it is usually faster to just jump to the most recent setting of the $ZTRAP variable. When exceptions are "exceptional" then TRY ... CATCH is most efficient since it avoids pushing $ZTRAP value in preparation for handling an exception and it also avoids popping the $ZTRAP value when the exception does not occur. If exceptions are part of a usual, successful, and often executed code path then it might be better to use $ZTRAP to catch those frequently executed signals.
go to post Steven Hobbs · Oct 8, 2024 Upgrading from an 8-bit instance to a Unicode instance is much simpler as you can just skip your export and import steps. Instead, just reinstall your original IRIS kit as an update kit. During the update, the installation will ask you: Do you want to convert 8-bit to Unicode <No>? Just answer Yes and the instance will be converted. Whenever a string value in a ^global variable contains only 8-bit characters then a Unicode IRIS instance stores that string in IRIS.DAT using 8-bit representation in order to save space. After the update, all your existing global data items are still there and the strings are all in 8-bit. IRIS Unicode instances use the UTF-16 Unicode encoding. If you have any 8-bit strings encoded in UTF-8 then you can use $ZCVT to convert UTF-8 strings to the IRIS default Unicode representation which uses UTF-16. Functions like $wlength, $wextract, etc. do not work on UTF-8 encoded 8-bit strings but they do work on the UTF-16 encoded strings. Note, if you do port IRIS.DAT files between different hardware instances and you also port between big-endian hardware and little-endian hardware (e.g., aix to windows) then there is a documented utility that describes how to convert the IRIS.DAT files between big-endian and little-endian representation. There is no support for automatic conversion starting from a Unicode IRIS.DAT file back to an 8-bit IRIS.DAT file. You can imagine this working if you are very lucky and the ported Unicode IRIS.DAT files just happen to have no Unicode strings, which will not happen with the "%SYS" namespace because the upgrade will add Unicode support to that namespace which will include some Unicode strings. With only a few, easily found Unicode strings then you can use %ZCVT to convert UTF-16 to 8-bit UTF-8. If are so lucky that you can do those conversions to completely remove all UTF-16 strings from a IRIS Unicode instance then you can try to install a new 8-bit instance and keep the IRISSYS and IRISLIB databases and replace the other database files with IRIS.DAT files that now just contain 8-bit user string data. If you fail to convert all the Unicode strings while trying to go back to an 8-bit instance then I believe you will get a <WIDE CHAR> signal if you attempt to access wide UTF-16 data.
go to post Steven Hobbs · Sep 26, 2024 You cannot store {{}} as it is an object reference (oref) and the translation of the oref to a %String is not useful. You can try: Parameter STATUSES = {{}}.%ToJSON(); and where you write 'w ..#STATUSES' you can instead write 'w [].%FromJSON(..#STATUSES)' I assume you can also write 'w [].%FromJSON(##class(Test...).#STATUSES)' although I am not certain that will do what you desire. [[ There is an ancient request for "DWIM" software (Do What I Mean software) to replace "DWIS" software (Do What I Said software). ]] And of course you Parameter modification can be written as: d $system.OBJ.UpdateConfigParam("Test","STATUSES",{"a":10}.%ToJSON())
go to post Steven Hobbs · Aug 13, 2024 The %DynamicAbstractObject (%DAO) subclasses, including the %DynamicArray and %DynamicObject classes, provide the best way to read/send JSON representation into/out-of IRIS databases. The %FromJSON method can create a tree of %DAO objects from external JSON representation. Then elements of %DAO data can be read or accessed using ObjectScript property syntax and using methods like %Get and %GetIterator. Data inside an IRIS database can be used to build a tree of %DAO objects by using the ObjectScript JSON constructor syntax, property assignment statements and using methods like %Set and %Push. If all you do is store values into previously undefined elements of a %DAO object then the performance is usually quite good. Then that %DAO tree can be turned into JSON representation using the %ToJSON method. It is not recommended that data in a tree of %DAO objects be extensively and repetitively modified/updated by the %Set method or by other ways that can modify data in a %DAO. This is especially true if you are repetitively changing the size of data elements. In such cases, the data in %DAO objects should be extracted and converted to some other optimized database structure. Often this can be Relational DataBase access if a mix of rectangular arrays provides the desirable access. It can be a %Stream if sequential access is needed. If a number of data elements are to be searched then columnar vectors can be used. IRIS can supply all these other access methods by layering them on top of ObjectScript multi-dimensional arrays. The IRIS multi-dimensional arrays (both global and local) provide the best tradeoffs across a large variety of differently ordered read/write/update/modification operations. If you have data in a %DAO tree and you need to make small or infrequent modifications of the data then it is acceptable to leave it in %DAO representation. You can also make repetitive reads of %DAO data without needing to convert to some other representation. But otherwise, %DAO objects are best limited to moving data into and out-of JSON representation.
go to post Steven Hobbs · Jul 25, 2024 Assuming that response.message_code is part of an ObjectScript expression then you want to evaluate response."message_code" because _ is an ObjectScript operator so you must quote "message_code" to change it into a method or property name. Otherwise, _ is the string concatenation operator so the value of response.message will be concatenated with the value of code.
go to post Steven Hobbs · Jul 16, 2024 I agree that LOG^%ETN, and ^%ETN as a process terminating trap routine, are the best way to log problems. But we should also mention that DO ^%ERN is the utility that will dump the data that LOG^%ETN places in the ^ERRORS multi-dim global. That dump includes state information, a complete call-stack dump, a list of all active local variables at each stack level, and in recent IRIS releases, it also includes a dump of all Class Objects with an oref active in memory. This is a lot of information for each ^ERRORS entry so you might not want to be over-enthusiastic with your calls on LOG^%ETN.
go to post Steven Hobbs · Jul 15, 2024 I don't know anything about the FhirBinary class but I do know why the %GetNext method in the %Iterator.Array or %Iterator.Object classes is giving the <MAXSTRING> signal. Let's assume the %GetNext in question is accessing a %DynamicObject as the %DynamicArray case is identical. A %DynamicObject (usually created by the %FromJSON method) limits it contained string elements only by the amount of memory that can be obtained from the Operating System running the IRIS instance. Fetching a long string element from a %DynamicObject as an ObjectScript string value will signal <MAXSTRING> when the %DynamicObject string element is too long. However, you can fetch a long string element as a %Stream.DynamicBinary or %Stream.DynamicCharacter class object. Such a %Stream object can contain any string that will into the system memory. Long %Stream contents longer than the ObjectScript maximum string length can be accessed by using the Read(.len,.status) method to access smaller substrings of the %Stream contents. Also, you can copy one class of long %Stream into a different class of long %Stream if you want to move a long string out of memory and into either a a file or an ObjectScript global array. The Class Reference documentation for the %Iterator.Object class does not directly appear in either Class Reference webpage on an IRIS instance nor in the Class Reference web page in the InterSystems IRIS network documentation. This strikes me as a documentation bug. Fortunately you can see the appropriate Class Reference documentation by indirectly going to the Class Reference page for the %Library.DynamicObject class (or %Library.DynamicArray class) and going to the documentation for %GetIterator method. That documentaton contains a link to the %Iterator.Object class. Click on that link and you will go the %Iterator.Object Class Reference page where you can see the documentation for the GetNext(.key,.value,.type) method. %GetNext method is used to iterate over the values in a %DynamicObject. The third argument, .type, is optional. The two argument form, %GetNext(.key,.value) will return the key index in .key and its associated ObjectScript value in .value. If the element value cannot be converted to an ObjectScript value then you will get a <MAXSTRING> or <MAXNUMBER> signal. However, the three argument form, %GetNext(.key,.value,.type) will not signal these errors. In place of a <MAXSTRING> signal, the .type argument will contain "string" and the the .value argument will contain the appropriate in-memory %Stream object value. In place of a <MAXNUMBER> signal, the .type argument will contain "number" and .value will contain a string with the decimal representation of the numeric value. More detail of other uses for the .type argument of the %GetNext(.key,.value,.type) method call can be found on the %Iterator.Object (or %Iterator.Array) Class Reference web page. When the Fhir.Binary class needs to iterate over the elements of a %DynamicArray/Object then it should not use the 2 argument form of %GetNext(.key,.value) but should instead use the the 3 argument form %GetNext(.key,.value,.type) and be prepared when the type of the the .value variable does not match the type specified by the .type argument.
go to post Steven Hobbs · Jun 25, 2024 We need to see more of your code to see what is truly happening. The routine method call Do obj.%Set("data",pStream,"stream>base64") should have no problem providing your system has enough memory to construct entire 'obj' Dynamic Object in your process memory. Your system will want to avoid having many large Dynamic Objects in memory at the same time. If the system runs out memory for all the active processes then the operating system will usually start aborting processes randomly until the operating system decides it now has enough free memory to continue execution. Calling %Set(,,) to read JSON text from a very large %Stream should only use moderate size buffers on the string stack. If a call on %Set(,,) writing from a %Stream generates a <STRINGSTACK> signal then that should be reported to InterSystems as a bug. However, your reply mentions the %ToJSON() function method call. Calling %ToJSON as a function will place the result on the String Stack so it is possible such a function call could signal a <STRINGSTACK> error although if the %ToJSON() function call appears in a very simple expression then I would expect a <MAXSTRING> signal instead of a <STRINGSTACK> signal. Calling %ToJSON() or %ToJSON(outstrm) as a routine method call in a DO statement should not get either a <STRINGSTACK> or a <MAXSTRING> signal since the result is not going to an ObjectScript string value.
go to post Steven Hobbs · Jun 24, 2024 The %FromJSONFile(file) method does not take a %File object as an argument but instead takes a string containing the name of file. You need to replace set newObject = {}.%FromJSONFile(file) with one of set newObject = {}.%FromJSONFile("/tmp/longObjectFile.txt")set newObject = {}.%FromJSON(file) Originally, %FromJSON(input) would accept the 'input' argument as a %File object, a %Stream object, a JSON text string or a filename text string. However, accepting an argument string as either JSON or a filename was considered to be security issue because a malicious user could be requested to enter a json-string but, being malicious, the user would instead enter a filename-string, or vice-versa. As a result %FromJSON no longer accepts filename strings while %FromJSONFile only accepts filename strings. No longer an issue, but it would be possible that legal VMS filename syntax could also be legal JSON text string syntax and such an argument string might be ambiguous on a VMS system. But IRIS no longer supports VMS and Caché for VMS did not support Dynamic Objects.
go to post Steven Hobbs · Jun 13, 2024 %DynamicAbstractObject-s provides the fastest for reading JSON from an external location and once in memory it provides excellent random access reading of data components. Large JSON arrays/objects are supported. I have done %FromJSON on a 20GB JSON file on a system with 16GB of physical memory—required paging of memory but still acceptable performance. You don’t require random access performance and it is best to avoid using more memory than you need. Maybe someday InterSystems can provide methods for reading JSON subobjects while remember how that object was nested in its parent objects. Assuming your JSON is in a file or stream and the outer array contains only elements which are arrays or objects you can try the following. 1. First read the leading ‘[‘; 2. Then use %FromJSON to read the array element and process that JSON element (the read will stop on the closing ‘]’ or ‘}’); 3. Read single chars from your stream/file skipping white space and if that char is a ‘,’ then go back to step 2 but if that char is ‘]’ then you are done.
go to post Steven Hobbs · May 17, 2024 The documentation is not quite clear on all this. If you have written data to a temporary file before first calling the LinkToFile(filename) method then the previously written temporary file disappears and future Read and Write method calls go to the new filename. If you later do a LinkToFile to a different filename then you will detach from the previous filename and attach to the new filename. But the previous file data is still available if you later attach back to the old filename. When you do a LinkToFile(filename) when there is no existing file with that filename then a new file is created. If your filename describes an existing file then your %Stream.FileBinary is attached to the existing file. If the first thing you do after LinkToFile(filename) are Read() method calls (or a Rewind() followed by Read() method calls) then you will Read the existing contents of the file. If after the LinkToFile, you instead do a MoveToEnd() method call followed by Write() method calls, then you keep the current file contents and your data is appended to the end of the existing file. If you do a Write() immediately after a LinkToFile() (or immediately after either a Rewind() or a Read()) then existing file data is deleted and you Write() to the beginning of the existing file. It is possible to Read() data from an existing file by avoiding Write() method calls before you Read() the data. it is possible to append new data to an existing file by using MoveToEnd() followed by Write() method calls.
go to post Steven Hobbs · Apr 10, 2024 Most of the ObjectScript examples above (but not the Python example) take O(n**2) time (i.e., quadratic time) where 'n' is number of values in X. This happens because the $PIECE(X,",",i) starts searching from the beginning of the string or because shortening the string X with set x=$p(x,",",2,*) will copy the string 'x' multiple times with the first element deleted before each copy. The ObjectScript example from Timo Lindenschmid using $LISTNEXT takes O(n) time (linear time). This can be important if $L(X,",") is 1000 or more because such a list with 1000 elements will require about 500,000 scans of "," characters or about 500,000 copies of string characters. Rich Taylor's Python example should also take linear time. Another solution would be to build a %DynamicArray object and then iterate through its contents. Finally, you could build a multi-dim ObjectScript array and iterate through the subscripts with $ORDER. This is method closest to using standard MUMPS/ANSI M/ObjectScript features.
go to post Steven Hobbs · Feb 15, 2024 Some additional picky details: The unary + operator is a numeric operator so it converts its operand to a number. If a string operand starts with a fractional number then unary + produces a fractional number (in its canonical numeric form) and it throws away any unneeded characters. If you want your numeric result to be an integer then you need to throw away the fractional digits by doing an integer-division by 1. Since the integer-division operator, \, is a numeric operator it always converts its operands to numbers so you no longer need the unary + to do the conversion of a string to numeric representation. E.g.s: USER>w "0012.543000abc" 0012.543000abcUSER>w +"0012.543000abc"12.543USER>w +"0012.543000abc"\112USER>w "0012.543000abc"\1 12
go to post Steven Hobbs · Jan 31, 2024 The third argument to %GetNext is not a string value. It is a variable passed by reference: iterator.%GetNext(.key,.value,.type). When this returns then the variable type will contain a string value like “string”, “number”,”object”, etc. If type contains “string” and $isobject(value) is true then value is a %Stream object containing your long string.
go to post Steven Hobbs · Jan 30, 2024 If your method can handle %Stream data then you can copy the large string piece-by-piece into an appropriate %Stream. If your method cannot handle %Stream data then you must modify it to test for a parameter that is a %Stream object instead of a string and then process that data by calling the stream.Read() method to scan through the %Stream. A few built-in methods do have this capability. For example, the %FromJSON(arg) method in the %DynamicAbstractObject class will accept 'arg' being either a string or a %Stream object.
go to post Steven Hobbs · Jan 25, 2024 Well, my DO command suggestion won't work in ObjectScript because ObjectScript has extended the MUMPS DO command to accept syntax for the DO { code } WHILE expr,expr,...statement. The code block in the DO-WHILE extension does not assume the code block opens a new variable scope. However, like most languages designed in the 1950s and 1960s, (including FORTRAN, BASIC and PL/I, but not C) MUMPS does not have reserved words. Therefore, any newly added command can start with a new command word. So we could have BLOCK { code } or SCOPE { code } as new commands that open a new variable scope. We might be able to find a better command word than BLOCK or SCOPE. We could even consider NEW { code }, although it might be considered confusing having an additional NEW command (one with a curly brace code block along with one with no arguments, one with list of argument names and one with a list of names surrounded by a pair of round braces).
go to post Steven Hobbs · Jan 25, 2024 I always wanted to extend argumentless DO so that you could get a new scope without needing the fairly ugly dot-indentation lines. Then you write something like Do { New $roles Set $roles="" If ($extract($roles,1,$length("%All"))="%All") Set tHaveAllRole = 1} You could even write the whole thing on one line like Do {New $roles Set $roles="" If ($extract($roles,1,$length("%All"))="%All") Set tHaveAllRole = 1}