Julius Kavay · Jun 28, 2022 go to post

The general syntax for calling routines from another namespace is:

do label^|namesapce|routine

where

- you can omit the label and

- namespace is either the name of the namespace (like set namesapce="USER") or the path to the database (preceded by two carets), where the routine resides.

I see right now, Config.MapGlobals accesses the ^SYS global via the path to the database (take a look at the Storage section) - so in theory, you  can  call all classmethods from the above class as:

do zClassmethodname^|"%SYS"|Config.MapGlobals.1(args...)

merely, I do NOT recommend to do this (the cass is in deployed mode, so we do not know, what the code really does and (instance)methods are private, so you can't call them from outside).

Julius Kavay · Jun 28, 2022 go to post

First, the correct (or better) way for the above code snipet were:

new $NAMESPACE
zn "%SYS"
do ##class(Config.MapGlobals).Delete(...)
quit

second, one can call routines (and (class)methodes are compiled to rotines) from another namespace by using extended syntax, but in that case such a routine uses the globals (if the routine does a global access) from the CALLING namespace. In Your case this won't work because the Config.MapGlobals uses globals which resides in %SYS namespace and not in the namesspace you are in.

Julius Kavay · Jun 7, 2022 go to post

you miss the object reference!

set context.strDocumentEncoded = B64EncodeStream(request.streamPDF)
// ---------------------------^^^^^^^ this should be something
set context.strDocumentEncoded = ##(your.class).B64EncodeStream(request.streamPDF)

but you have another problems too: your context.strDocument and  context.strDocumentEncoded are currently STRING properties (according to the operation you try to do), which are limitet to a maxlength of 3.47MB!

You have to change both to a STREAM, so you can handle PDFs  larger then ca. 2.6MB (because 2.6 * 4 / 3 ==> 3.46MB, the limit for a string variable).

After you change context.strDocument and context.strDocumentEncoded to a stream properties, you could use this code:

Class DC.Someclass Extends %RegisteredObject
{

Parameter CHUNKSIZE = 2097144;

ClassMethod ToBase64(src As %Stream.Object, dst As %Stream.Object) As %Status
{
	i ..#CHUNKSIZE#3=0, src.Rewind(), dst.Rewind() {
		set sts=$$$OK
		while 'src.AtEnd,sts {
			do dst.Write($system.Encryption.Base64Encode(src.Read(..#CHUNKSIZE,.sts),1))
		}
	} else { set sts=$$Error^%apiOBJ(5001,"Chunksize or src/dst-problem") }
	
	quit sts
}

}
if ##class(your.class).ToBase64(context.strDocument,context.strDocumentEncoded) write "OK"

It seems, this task will take some time... you have to check, how context.strDocument is populated and  how context.strDocumentEncoded is later in code used. Good luck.

Julius Kavay · Jun 4, 2022 go to post

As an ObjectScript routine, remove the "ClassMethod" keyword (which, as the name indicates, belongs to classes. Then either  add a "Public" keyword or you remove the brackets ("{", "}") too, but then all variables will have the same scope:

ProcData(file = "c:\temp\zipcitystate.csv") Public
{
    set str=##class(%Stream.FileCharacter).%New()
    do str.LinkToFile(file)
    while 'str.AtEnd {
        set $listbuild(ZIP,CITY,STATE)=$listfromstring(str.ReadLine())
        
        // now you have the individual columns
        // in ZIP, CITY and STATE variables for further processing
        write "Zip=",ZIP,?12,"City=",CITY,?40,"State=",STATE,!
    }
    
    // depending on the way of your implementation, a "kill str"
    // would be needed to free up the file
    kill str
}

Oh, and to invoke the above procedure just do a:

do ProcData^YourRoutineName()
// or
do ProcData^YourRoutineName("path-to-file")
Julius Kavay · Jun 2, 2022 go to post

A simple method like


ClassMethod ProcData(file = "c:\temp\zipcitystate.csv")
{
    set str=##class(%Stream.FileCharacter).%New()
    do str.LinkToFile(file)
    while 'str.AtEnd {
        set $listbuild(ZIP,CITY,STATE)=$listfromstring(str.ReadLine())
        
        // now you have the individual columns
        // in ZIP, CITY and STATE variables for further processing
        write "Zip=",ZIP,?12,"City=",CITY,?40,"State=",STATE,!
    }
    
    // depending on the way of your implementation, a "kill str"
    // would be needed to free up the file
    kill str
}

to do the job. Then call the method as

do ##class(your.class).ProcData()
// or
do ##class(your.class).ProcData("path-to-the-file")
Julius Kavay · May 30, 2022 go to post

your exit statement

If (AxVisM1.P0.ToString = "") Then
	Exit While
End If

is to late, this must come after the $ORDER() statement

AxVisM1.Execute("set P0=$o(^ztonMS(""REF"",P0))")
If (AxVisM1.P0.ToString = "") Then
	Exit While
End If
AxVisM1.Execute("s P1=$g(^ztonMS(""REF"",P0))")

The same goes for the next while-loop a few lines below

Julius Kavay · May 24, 2022 go to post

read json <now, Cntrl-V here the above string, press enter>, then

w ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(json,,.list1) --> 1
w list1 --> 42@%Library.ListOfObjects
w list1.GetAt(1).sensors.GetAt(1).jobGUID --> 0b955ee7-9a54-4b13-9af1-7019721faeab

where is the problem?

Julius Kavay · May 20, 2022 go to post

Try this one. The idea is, find the state (including the separators), everything before is the city and everything after is the zip code. Then we remove the separator chars (whitespaces, commas and dots).

ClassMethod Disjoin(data, cty, sta, zip)
{
    i $locate(data,"(\s|,|\.)[A-Za-z]{2}(\s|,|\.)",3,,sta) {
        s $lb(cty,zip)=$lfs(data,sta), sta=$$s(sta), cty=$$s(cty), zip=$$s(zip)
        
    } else { s (cty,sta,zip)="" }
    
    q sta]""
    
s(x)	q $zstrip(x,"<>w",",.")
}
Some examples
i ##class(DC.Test).Disjoin("CANTON,TX.,75103",.c,.s,.z) w c,", ",s,", ",z --> CANTON, TX, 75103
i ##class(DC.Test).Disjoin("MILFORD, OH 45150",.c,.s,.z) w c,", ",s,", ",z --> MILFORD, OH, 45150
i ##class(DC.Test).Disjoin("MILFORD OH 45150",.c,.s,.z) w c,", ",s,", ",z --> MILFORD, OH, 45150
i ##class(DC.Test).Disjoin("KANSAS CITY, MO, 12345",.c,.s,.z) w c,", ",s,", ",z --> KANSAS CITY, MO, 12345
i ##class(DC.Test).Disjoin("KANSAS CITY MO, 12345",.c,.s,.z) w c,", ",s,", ",z --> KANSAS CITY, MO, 12345
i ##class(DC.Test).Disjoin("ST. LOUIS MO, 12345",.c,.s,.z) w c,", ",s,", ",z --> ST. LOUIS, MO, 12345
i ##class(DC.Test).Disjoin("  ST. LOUIS MO, 12345",.c,.s,.z) w c,", ",s,", ",z --> ST. LOUIS, MO, 12345

OK, something like this gives a wrong result...
i ##class(DC.Test).Disjoin("   ST. LOUIS MO, 12345",.c,.s,.z) w c,", ",s,", ",z --> , ST, LOUIS MO, 12345

Julius Kavay · May 16, 2022 go to post
Class DC.Test Extends %RegisteredObject
{
/// Return TRUE if val contains an string
ClassMethod IsString(val) As %Boolean
{
    q $a($lb(val),2)<3
}
/// Return TRUE if val contains a number (int, real or double)
ClassMethod IsNumber(val) As %Boolean
{
    q $a($lb(val),2)>3
}
}

w ##class(DC.Test).IsString("abc") //--> 1
w ##class(DC.Test).IsString("123") //--> 1
w ##class(DC.Test).IsString(123) //--> 0
w ##class(DC.Test).IsNumber(123) //--> 1
w ##class(DC.Test).IsNumber("abc") //--> 0
w ##class(DC.Test).IsNumber("123") //--> 0
w ##class(DC.Test).IsNumber(123_345) //--> 0
w ##class(DC.Test).IsNumber(123+345) //--> 1
w ##class(DC.Test).IsString(123_456) //--> 1
w ##class(DC.Test).IsString(123+456) //--> 0

s x=123, y="123"
w ##class(DC.Test).IsString(x) //--> 0
w ##class(DC.Test).IsString(y) //--> 1

Julius Kavay · May 8, 2022 go to post

The input variable pInput is an object(reference). You can't save OREFs in a global!

Think about OREFs as memory location (or, if you "speak" C, as a pointer). Trying to save it in a global is the same as saving a C pointer into a file for a later use... Won't work either 

Julius Kavay · Jan 28, 2022 go to post
Class My.Class 
{
Property MyProperty As %String [ReadOnly];

Method %OnNew(init As %String) As %Status [ Private, ServerOnly = 1 ]
{
    set i%MyProperty = init
}
} 

And use it as

set obj = ##class(My.Class).%New("Some initial value")
write obj.MyProperty --> Some initial value
set obj.MyProperty --> <CANNOT SET THIS PROPERTY>
Julius Kavay · Jan 28, 2022 go to post

A possible work-around could be the class below. In short, you work with your json property as intended, merely before saving the object, you save the json-property into a stream and after opening an instance, you restore the json-property from the the stream - that's all. The drawback, no SQL over the json property...

Class DC.Dyn Extends %Persistent
{
Property json As %DynamicObject [ Transient ];
Property jstr As %GlobalCharacterStream [ Internal, Private ];

ClassMethod MyTest(kill = 0)
{
   if kill do ..%KillExtent(1,1)

   set obj=..%New()
   set obj.json.short="A short test text"
   set obj.json.maxstr=$tr($j("",$$$MaxStringLength)," ","X")
   do obj.json.%Set("hugedata",..stream(obj),"stream")

   write "Status : ",obj.%Save(),!
   set id=obj.%Id()
   write "ID : ",id,!
   kill (id)

   set obj=..%OpenId(id)
   write "short : ",obj.json.short,!
   write "maxstr : ",$e(obj.json.maxstr,1,20),"... Size: ",$length(obj.json.maxstr),!
   set stream=obj.json.%Get("hugedata",,"stream")
   write "hugedata: ",stream.Read(20),"... Size: ",stream.Size,!
}

ClassMethod stream(obj)
{
   set stream=##class(%Stream.TmpCharacter).%New()
   do stream.Write(obj.json.short)
   do stream.Write(obj.json.maxstr)
   do stream.Write(obj.json.maxstr)
   quit stream
}

Method %OnOpen() As %Status [ Private, ServerOnly = 1 ]
{
   if ..jstr {
      do ..jstr.Rewind()
      set ..json=##class(%DynamicAbstractObject).%FromJSON(..jstr)
   }
   Quit $$$OK
}

Method %OnAddToSaveSet(depth As %Integer = 3, insert As %Integer = 0, callcount As %Integer = 0) As %Status [ Private, ServerOnly = 1 ]
{
   do ..jstr.Clear(), ..json.%ToJSON(..jstr)
   Quit $$$OK
}
}

Some testing...

IDEV:USER>d ##class(DC.Dyn).MyTest(1)
Status  : 1
ID      : 1
short   : A short test text
maxstr  : XXXXXXXXXXXXXXXXXXXX... Size: 3641144
hugedata: A short test textXXX... Size: 7282305

If your code uses obj.%Reload() then %OnReload() and %OnOpen() should contain the same code.

Julius Kavay · Dec 20, 2021 go to post

A simple

write ##class(%SYS.Namespace).GetPackageDest(yourNsp, yourPackage)

should do the trick

The same goes for globals and routines

write ##class(%SYS.Namespace).GetGlobalDest(yourNsp, yourPackage)

write ##class(%SYS.Namespace).GetRoutineDest(yourNsp, yourPackage)

if yourNsp is not provided, the current Nsp will be used

Julius Kavay · Nov 13, 2021 go to post

The reason is, the database encryption is not activated - see the last line in your screenshot.

Go to MangementPortal, SystemAdministration --> Encryption. There you can create the encryption keys and activate the encryption.

Julius Kavay · Oct 30, 2021 go to post

OK, I start with the second question. I'm not aware of a function to see if a specific global is in a buffer or not but there is a routine which shows which globals are using the most buffers:

znspace "%SYS"
do ^GLOBUFF

For the first question: if a global is used continuously, then it will always be in buffer. That's the simple answer. The reality depends on many other factors: the size of the global, the size of the buffer pool, how many other globals are in use, how often is a global used, etc.

To keep a few specific global(s) always in a buffer, there is a simple trick (assuming, your Cache/IRIS installation uses the default setup and you have an unused block size):

1) Goto SystemAdministration-->Configuration-->AdditionalSettings-->Startup: and edit the DBSizesAllowed setting, by checking one of the 16K or the 32K checkboxes

2) Create a new database with the newly enabled block size. This database will hold those few (always needed) globals.

3) Goto SystemAdministration-->Configuration-->SystemConfiguration-->MemoryAndStartup: and allocate (plenty of) memory for the newly created buffersize. Please consider,  after this chanhe, you have to RESTART your system!

4) Copy the global(s) in question into the newly created database:

  merge ^|"^^c:\path_to_new_database\"|GlobalName = ^|"^^c:\path_to_old_database\"|GlobalName

5) Create a Global mapping for the globals in question to the new location.

6) Start working... If everything is OK (which should be) and you are happy, delete the old global data to free up database space:   

kill merge ^|"^^c:\path_to_old_database\"|GlobalName

7) In a standard installation, you have allocated  one buffer pool (with the standard 8KB buffer size). So all your processes faiting to get the needed globals into that buffer pool.

With the above configuration you have two buffer pools, one for the standard 8KB database blocks and one for the new 16KB (or wahtever size you have choosen) database blocks. So you can keep important globals in a separate buffer pool. If you can manage (this will be application dependent) to give this buffer pool the same size as the database itself, the you will have all data (of this database) in the memory all day long.

Julius Kavay · Oct 22, 2021 go to post

I fear  you have to be some kind of a magician, to solve this problem...
You need  two things (a) a time-zone-offset, which is not the problem (it's more or less static) and (b) DST-offset, which is a problem, because there are databases for the past but not for future. Maybe you can put the DST-offset into a global for each of the geographic region you need. And yes, you have to maintain it...

Some starting points:  https://en.wikipedia.org/wiki/Tz_database  and http://web.cs.ucla.edu/~eggert/tz/tz-link.htm. In case you you work with python, take a look at https://pypi.org/project/pytz/

The README file from tz_database says the problem in a nutshell:
"The Time Zone Database (called tz, tzdb or zoneinfo) contains code and data that represent the history of local time for many representative locations around the globe.  It is updated periodically to reflect changes made by political bodies to time zone boundaries, UTC offsets, and daylight-saving rules."

Julius Kavay · Oct 14, 2021 go to post

You have a problem with the scoping!

Indirection has a global scoping, you have to put things with indirection in a global scope:

ClassMethod testvalidator(class As %String, value As %String) As %Status [ PublicList = (validator, sc) ]
{
   new validator, sc
   set validator = "sc = ##class("_class_").IsValid("""_value_""")"
   write validator,!
   set @validator
   write sc,!
   quit sc
}
set result = ##class(...).testvalidator("%Library.Numeric","BLABLA")
do $system.OBJ.DisplayError(result) --> ERROR #7207: Datatype value 'BLABLA' is not a valid number
Julius Kavay · Oct 2, 2021 go to post

There is one thing you should check, than this could trigger effects observed by you.
Objects are tracked by reference counts, as long as an objects reference count is greater then one, locks won't be released and the object isn't deleted.

set obj = ##class(Some.Class).%OpenId(id, 4) // the obj's ref count is one
... // more commands
    // now, the application does something like this
set tmp = obj    // obj's ref count is now two!
... // more commands

set obj = "" // the application intents to close the object
             // but the object still exists due to the fact that the ref count is one
             // (the object is still referenced by <tmp>)

There are methods to detect such a situation:
- $system.OBJ.ShowObjects(), lists all objects with reference counts
- $system.OBJ.ShowReferences(obj), list all variables which contains a reference to <obj>

A quick and dirty approach:

set filename = "...some file name"
open filename:"nw":0
if $t { use filename
        do $system.ShowObjects()
        do $system.ShowReferences(obj) 
        close filename
      }
set obj = "" 

Give it a try, maybe your object has multiple references which cause the problem

Julius Kavay · Sep 28, 2021 go to post

The %SYS.Namespace class contains the methods, you are looking for.

write ##class(%SYS.Namespace).GetGlobalDest( [namspace], "global") --> DB where the global lies
write ##class(%SYS.Namespace).GetRoutineDest( [namspace], "routine") --> DB where the routine lies
write ##class(%SYS.Namespace).GetPackageDest( [namspace], "package") --> DB where the package lies
Julius Kavay · Sep 26, 2021 go to post
set old = ##class(%Stream.TmpCharacter).%New()
do old.Write("This is my text")

So, now you have an old stream, "This is my text" but want to have a new stream as "This is my NEW text".

set new = ##class(%Stream.TmpCharacter).%New()
do old.Rewind()
set pos = 10 // This is my
do new.Write(old.Read(pos)), new.Write(" NEW"), new.Write(old.Read(old.Size-pos))

And now, check the resulty

do new.Rewind()
write new.Read(new.Size) --> This is my NEW text
Julius Kavay · Aug 31, 2021 go to post

You could create an abstract class, which adds all the information you need to your class(es), see the example below.
Then include this class into the class(es) where you need this kind of information:

Class Your.Class Extends (%Persistent, DC.ClassInfo)  { ... }
Class Your.OtherClass Extends (%Persistent, DC.ClassInfo) { ... }

and use it as follows:

set allProps = ##class(Your.Class).PropertyInfo()
write allProps.%ToJSON() --> [prop1, prop2, ...]  // here you get the names of all properties
set oneProp = ##class(Your.Class).PropertyInfo("aPropertyName") --> {"Type":"%String"} // Info about one property

The same goes for the methods too.

Class definition for the (example) DC.ClassInfo class:

Class DC.ClassInfo [ Abstract ]
{
/// Return information about properties
/// 
/// 1) return list of all properties
///        obj.PropertyInfo() --> [Propertynam1, Propertyname2, Propertyname3, ...]
/// 
/// 2) return info for a specific property
///    obj.PropertyInfo(propertyname) --> {"Type":type, "MaxLen":nn, "Scale":n, ...}
///    
ClassMethod PropertyInfo(name = "") As %DynamicObject [ CodeMode = objectgenerator ]
{
    set prp=%compiledclass.Properties, all={}

    for i=1:1:prp.Count() {
        set p=prp.GetAt(i), inf={}
        // Add all the infos you need...
        set inf.Type=p.Type
        set x=p.Parameters.GetAt("MAXLEN") set:x]"" inf.MaxLen=x
        set x=p.Parameters.GetAt("SCALE") set:x]"" inf.Scale=x
        do all.%Set(p.Name,inf)
    }
    
    do %code.WriteLine($c(9)_"set prp="_all.%ToJSON())
    do %code.WriteLine($c(9)_"if name]"""" quit prp.%Get(name)")
    do %code.WriteLine($c(9)_"set itr=prp.%GetIterator(), names=[]")
    do %code.WriteLine($c(9)_"while itr.%GetNext(.k) { do names.%Push(k) }")
    do %code.WriteLine($c(9)_"quit names")
    quit $$$OK
}

/// Return information about properties
/// 
/// 1) return list of all properties
///        obj.PropertyInfo() --> [Propertynam1, Propertyname2, Propertyname3, ...]
/// 
/// 2) return info for a specific property
///    obj.PropertyInfo(propertyname) --> {"Type":type, "MaxLen":nn, "Scale":n, ...}
///    
ClassMethod MethodInfo(name = "") As %DynamicObject [ CodeMode = objectgenerator ]
{
    set mth=%compiledclass.Methods, all={}
    
    for i=1:1:mth.Count() {
        set m=mth.GetAt(i)
        // Add all the infos you need...            
        set inf={}, spc=m.FormalSpecParsed
        set inf.SqlProc=m.SqlProc            
        set inf.ClassMethod=m.ClassMethod
        set inf.ReturnType=m.ReturnType
        set inf.Args=[]
        for j=1:1:$ll(spc) {
            set arg={}, itm=$li(spc,j), arg.Name=$li(itm), arg.Type=$li(itm,2)
            set arg.ByRef=$li(itm,3)="&", arg.DefValue=$li(itm,4)
            do inf.Args.%Push(arg)
        }
        do all.%Set(m.Name,inf)
    }
    
    do %code.WriteLine($c(9)_"set mth="_all.%ToJSON())
    do %code.WriteLine($c(9)_"if name]"""" quit mth.%Get(name)")
    do %code.WriteLine($c(9)_"set itr=mth.%GetIterator(), names=[]")
    do %code.WriteLine($c(9)_"while itr.%GetNext(.k) { do names.%Push(k) }")
    do %code.WriteLine($c(9)_"quit names")
    quit $$$OK
}
}

The class should work, but it's only partially tested. Also, you may want to omit all the inherited properties and methods

set p=prp.GetAt(i)  continue:p.Origin '= %class.Name // skip inherited propps
set m=mth.GetAt(i) continue:m.Origin '= %class.Name // skip inherited methods

it's up to you. Also you can add other informations, depending on your needs, see the respective classes (%Dictionary.CompiledClass, %Dictionary.CompiledProperties and %Dictionary.CompiledMethods)

Julius Kavay · Aug 27, 2021 go to post

You are on a right way, but obviously you use indirection on local variables in block environment.

the wrong way:

Method testErr()
{
   set myvar(1)=123, ref=$na(myvar)
   set ref=$query(@ref)  -->  this gives you always: ref=""
}

The correct way

Method testOK() [ PublicList = myvar]
{
   new myvar
   set myvar(1)=123, ref=$name(myvar)
   set ref=$query(@ref) 
   write ref," ",@ref ---> myvar(1)," ",123
}

i.e. indirection needs variables with global scope

Julius Kavay · Aug 18, 2021 go to post

I think, the $zconvert() function will cover only the necessary entities. But you can use a simple method to convert characters to currently known(*) entities.

ClassMethod ToHTML(str)
{
   for i=$length(str):-1:1 set c=$ascii(str,i) set:$data(^entityChars(0,c),c) $extract(str,i)=c
   quit str
}

ClassMethod FromHTML(str)
{
   set i=0
   while $locate(str,"&[A-Za-z]+;",i,j,v) {
   set:$data(^entityChars(1,v),c) s=$length(v), $extract(str,j-s,j-1)=$c(c), j=j-s+1
   set i=j
   }
   quit str
}

I have a table (the ^entityChars() global) which contains more the 1400 entities. You can download the above class, together with the table from my FTP server (File: DC.Entity.xml):

Adr: ftp.kavay.at
Usr: dcmember
Psw: member-of-DC

A sample output:

USER>write ##class(DC.Entity).ToHTML("Flávio Lúcio Naves Júnior")
Fl&aacute;vio L&uacute;cio Naves J&uacute;nior
USER>write ##class(DC.Entity).FromHTML("Fl&aacute;vio L&uacute;cio Naves J&uacute;nior")
Flávio Lúcio Naves Júnior

(*) Currently known, because (1) I do not have all the currently known entities in my table and (2) with each new day, the W3C and the Unicode consortium can extend the current entity list.

Julius Kavay · Aug 7, 2021 go to post

sorry, a doubleclick by mistake...

May be the underlying software could prevent such stupid things in the future...

Julius Kavay · Aug 5, 2021 go to post

For such a task, the Horner's method was introduced. Fast and simple.

ClassMethod BinToDec(bin)
{
   if $translate(bin,10)="" { // formal check, bin should only contain '1' and '0'
      set res=0
      for i=1:1:$length(bin) set res=res*2+$extract(bin,i)
      quit res
   } else { ztrap "NBIN" }
}


Hardcore ObjectScript programer place those few commands into one line

bin2dec(bin) { s res=0 f i=1:1:$l(bin) { s res=res*2+$e(bin,i) } q res }


and doesn't care about errors ;-))