Julius Kavay · Apr 8, 2020 go to post

OK, I installed VSCode together with the objectscript extension on my Linux. Now, I would like to play a little bit with this IDE, merlely for an VSCode beginner it's somewhat difficult. According to https://openexchange.intersystems.com/package/VSCode-ObjectScript there is a  settings file, ".vscode/settings.json",  to make a connection to my  iris server...  I located a file  ".config/Code/User/settings.json". After entering the connection info -this works now. Question, how or where can I enter a second (or even more) Cache/Iris servers?  Next, created a short test routine and saved, but it's not compiled. How to compile it?

I have never used VSCode before, I work since beginning with the Cache-Studio (this means some 20 years or so) and other twenty years before with other editors. Is there somewhere a short tutorial or some other info? It seems, I will need some time to catch this "modern" stuff ;-)

Thank you.
 

Julius Kavay · Apr 2, 2020 go to post

You could try this way:

Class My.Table2 Extends %Persistent
{

Property Name As %String;

Property Age As %Numeric;

Property City As %String;

Property Phone As %String;

ClassMethod Test()
{
    // This is your Data-Object...
    set data=[]
    do data.%Push({"Name":"Joe", "Age":44, "City":"Boston", "Phone":"1-234-4567"})
    do data.%Push({"Name":"Ron", "Age":48, "City":"Dallas", "Phone":"1-234-5678"})
    do data.%Push({"Name":"Eve", "Age":40, "City":"Miami",  "Phone":"1-234-4567"})
    
    do data.%Push($lb("Tommy", 50, "New York", "1-345-6789"))
    do data.%Push($lb("Alexa", 35, "Portland", "1-567-8901"))

    // Now insert all the above data into your table...
    if 'data.%Size() quit
    
    set cnt=0, size=data.%Size()
    while $i(cnt)<=size {
        set rowData=data.%Get(cnt-1)
        &sql(
            INSERT INTO My.Table2 (Name,Age,City,Phone)
            VALUES (My.Table2_DataProvider(:rowData), :row(2), :row(3), :row(4))
        )
    }
} 

ClassMethod DataProvider(rowData) As %String [ PublicList = row, SqlProc ]
{
    kill row
    if $isobject(rowData) {
        set it=rowData.%GetIterator()
        while it.%GetNext(,.val) { set row($i(row))=val } 

    } else {
        for i=1:1:$ll(rowData) { set row(i)=$lg(rowData,i) }
    }
    quit row(1)
}

Storage Default
{
<Data name="Table2DefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>Age</Value>
</Value>
<Value name="4">
<Value>City</Value>
</Value>
<Value name="5">
<Value>Phone</Value>
</Value>
</Data>
<DataLocation>^My.Table2D</DataLocation>
<DefaultData>Table2DefaultData</DefaultData>
<IdLocation>^My.Table2D</IdLocation>
<IndexLocation>^My.Table2I</IndexLocation>
<StreamLocation>^My.Table2S</StreamLocation>
<Type>%Library.CacheStorage</Type>
} }

Create your data for insert and then

INSERT into yourtable (Prop1, Prop2, ...)

VALUES (sqlProcForTheFirstValue(), :localVarForOtherValues(2), :localVatForOtherValues(3),...)

see the above example.

Take care of the sequence of INSERT names and row(i) values.

Julius Kavay · Apr 1, 2020 go to post

Oh, I just see now, I forgot to add the two test lines

Enter into browser : http://localhost:52773/csp/user/DC.Upload.cls?pswd=123&rpt=456

Enter into Terminal:

IDEV:USER>write ##class(DC.Download).GetFile(123,456,"/tmp/inpfiles/")  ==> 1
Julius Kavay · Apr 1, 2020 go to post

Sorry, but it's not clear to me what you want to achieve.

If you want to provide data if somebody connected to your server,  then use the DC.Upload class (see below).

If somebody has data for you and you want to programatically download this data, use the DC.Download class (see below).

The examples below does not handle the case, where the (Cache/IRIS)Server needs an user authentication (my IRIS  System > Security Management > Web Applications is set to "Unauthenticated")

Server side (Upload)

/// Upload (i.e. provide) data to a remote party
Class DC.Upload Extends %CSP.Page
{
ClassMethod OnPage() As %Status
{
   if %request.Data("myData")]"" {
      do %request.Data("myData").OutputToDevice()
      do %request.Data("myData").%Close() }
   else {
      write "<html><head></head><body>",!
      write "Please provide a correct Password and ReportId<br>",!
      write "</body></html>",!
   }
   quit $$$OK
}

ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
   if ..chkPsw(), ..chkRpt(.file) {
      set data=##class(%FileBinaryStream).%New()
      set size=##class(%File).GetFileSize(file)
      set name=##class(%File).GetFilename(file)
      do data.LinkToFile(file)
      set %response.ContentType="application/pdf"
      do %response.SetHeader("Content-Disposition","attachment;filename="_name)
      do %response.SetHeader("Content-Length",size)
      set time=$h-1
      set %response.Expires=$zd(time,11)_", "_$zd(time,2)_" 00:00:00 GMT"
      set %request.Data("myData")=data
 
      } else {
         set %request.Data("myData")=""
      }
  
      quit $$$OK
}

ClassMethod chkPsw()
{
   set psw=$g(%request.Data("pswd",1))
   if psw]"" quit 1
   quit 0
}

ClassMethod chkRpt(name)
{
   set rpt=$g(%request.Data("rpt",1))
   if rpt]"" {
      set name="/tmp/outfiles/74LS13.pdf"
      quit 1
   }
   quit 0
} 

}

Client side (download)

/// Download (i.e. get) data from remote server
/// 
Class DC.Download Extends %RegisteredObject
{
ClassMethod GetFile(psw, rpt, saveTo = "/tmp/inpfiles/")
{
   set http=##class(%Net.HttpRequest).%New()
   set http.Server="localhost"
   set http.Port=52773
   do http.SetParam("pswd",psw)
   do http.SetParam("rpt",rpt)

   if http.Get("/csp/user/DC.Upload.cls") {
      set file=$piece($g(http.HttpResponse.Headers("CONTENT-DISPOSITION")),"=",2)
      set del=$select($zversion(1)=2:"\", 1:"/")
      set file=saveTo_$s($e(saveTo,*)=del:"",1:del)_$s(file="":"noname.dat",1:file)
      
      open file:"nwu":0
      if $t {
         use file
         do http.HttpResponse.Data.OutputToDevice()
         close file
         quit 1

      } else {
        quit "0,Can't open "_file
      }

   } else {
       quit "0,"_http.HttpResponse.StatusLine
   }
} 
}


Julius Kavay · Apr 1, 2020 go to post

Create a temp table with all properties  you need, store it in a Cachte/IRIS-Temp unde $J of the running job (it could be, your applcation runs in several instances at the same time) an use it in your INSERT / UPDATE.

This is your Table

Class My.Table1 Extends %Persistent
{
Property Name As %String;
Property Age As %Numeric;

ClassMethod Test()
{
  d ##class(My.Temp).%DeleteId($j)
  s tmp=##class(My.Temp).%New()
  s tmp.TempID=$j, tmp.Name="Paul", tmp.Age=69
  d tmp.%Save()
  // or popolate the My.Temp via INSERT...  

  &sql(INSERT INTO My.Table1 (Name,Age)
       SELECT Name,Age FROM My.Temp WHERE TempID=$j
      )
}
}

and this is the Temporary Table

Class My.Temp Extends %Persistent
{
  Parameter DEFAULTGLOBAL = "^CacheTemp.TempTable"; Property TempID As %Integer;
  Property Name As %String;
  Property Age As %Numeric;

  Index main On TempID [ IdKey ];

}
Julius Kavay · Mar 30, 2020 go to post

Do I understand you right, you want to get the Value of a VALUELIST as a return value of a method? If yes, then the answer is yes.

Class Some.Class

{

Property Status As %String  (VALUELIST = ",1,2,3,4"); 

Property Rating As %String(VALUELIST = ",Bad,Good,Excellent");

/// For the Status property only
ClassMethod StatusValues() As %String [ CodeMode = objectgenerator ]
{
 for i=%class.Properties.Count():-1:0 if i,%class.Properties.GetAt(i).Name="Status" quit

 set val=$select(i:%class.Properties.GetAt(i).Parameters.GetAt("VALUELIST"), 1:"")

 do %code.WriteLine($c(9)_"quit """_val_"""")
} 

/// For all properties with a VALUELIST parameter

ClassMethod Values(prop) [ CodeMode = objectgenerator ]
{
 set sel=""
 for i=1:1:%class.Properties.Count() {
     set val=%class.Properties.GetAt(i).Parameters.GetAt("VALUELIST")
     set:val]"" sel=sel_""""_%class.Properties.GetAt(i).Name_""":"""_val_""", "
 }
 do %code.WriteLine($c(9)_"quit $case(prop,"_sel_":"""")")
}

}

And a few examples...

Write ##class(Some.Class).StatusValues()  ==> ,1,2,3

Write ##class(Some.Class).Values("Status") ==> ,1,2,3

Write ##class(Some.Class).Values("Rating") ==> Bad,Good,Excellent

Write ##class(Some.Class).Values("OtherProperty") ==>

Julius Kavay · Mar 30, 2020 go to post

not a built-in, but you can easily create one
 

ClassMethod ParamList() [ CodeMode = objectgenerator ] As %List
{
set params=""
for i=1:1:%class.Parameters.Count() set params=params_$lb(%class.Parameters.GetAt(i).Name)
do %code.WriteLine($c(9)_"quit """_params_"""")
} 

ClassMethod ParamString() As %String
{
quit $listtostring(..ParamList())
}
Julius Kavay · Mar 29, 2020 go to post

as far as I know (subject to verification) you  source code will be compiled into a p-code (pseudo- or portable-code). This means, Cache and IRIS behave (regarding program execution) as virtual machines, like the JVM (Java Virtual Machine).

Julius Kavay · Mar 23, 2020 go to post

$listbuild() and all other functions, like $length(), $char() etc.  are  (I call them) basic  functions of COS and have nothing to do with objects. The language of Cache (and of course IRIS )  handles data as  raw (basic) data and not as objects, like some other (but not all) languages.

For example, in JavaScript you can do

 var  text="Some text";
 alert("'" + text + "' has "+text.length + " characters");
 alert("'Some text' has " + "Some text".length + " characters");

because both, a (text)variable and a (text)string have an (object)property name 'length'.

Beside the raw data type COS also has object data type and most of the standard COS functions will accept an object property as argument, for example:

Class Test.Class Extends %RegisteredObject
{
Property Data as %String;
}

set text="Some text"
set obj = ##class(Test.Class).%New()
set obj.Data = "Some text";
write $length(text),!
write $length(obj.Data),!
Julius Kavay · Mar 19, 2020 go to post

JavaScript, as your code shows, is compiled and executed on client side (in the browser).


The part of the code  #()#  you use is CSP (CacheServerPages), which is compiled  and prepared by the server for  execution by client.

Hence, you can't use the #()# syntax in a pure JavaScript code.

Julius Kavay · Mar 17, 2020 go to post
$lb(data1, data2, data3, ... dataN) is built as a string of item1 item2 item3 ... itemN

itemN:= <len> <type> <dataN>

assuming, you are on a little-endian CPU and

l1 = $length(data)
l2 = l1 + 1 // for type byte
l3 = l2 + 1 // for the length itself

then the length is as follows

if l1<=253 then len: <l3>
elseif l1<=65534 len:= <0> <l2-lowbyte> <l2-highbyte>
else len:= <0> <0> <0> <l2-lowbyte-loworderword> ... <l2-highbyte-highorderword>

And don't forget,
$lb(123) is not the same as $lb(1230/10), hence we have a $ls() function!

Julius Kavay · Mar 15, 2020 go to post

I have no idea how you did your endless loop, but the above code can't do that.

Julius Kavay · Mar 14, 2020 go to post

the functions $order() and $query() are your best friends ;-))

Write, for example, a method like this:

ClassMethod(ref)
{
  if $data(@ref)#10 write ref,"=",@ref,! // do something with this node
  set i=""
  for  set i=$order(@ref@(i)) quit:i=""  do ..Show($name(@ref@(i)))
}

So, for testdata like

set tmp(1,2,3)="Joe"

set tmp(1,2,3,4,1)="Paul"

set tmp(1,2,3,4,2)="Paula"

set tmp(1,2,3,5,6)="Tom"

start some testing


do ##class(yourclass).Show($na(tmp))  // shows all entries

do ##class(yourclass).Show($na(tmp(1,2)))  // also shows all entries

do ##class(yourclass).Show($na(tmp(1,2,3)))  // shows Joe

do ##class(yourclass).Show($na(tmp(1,2,3,4))  // shows Paul and Paula

do ##class(yourclass).Show($na(1,2,3,5)))  // shows Tom

do ##class(yourclass).Show($na(tmp(1,2,3,5,6)))  // shows Tom

do ##class(yourclass).Show($na(tmp(2,3)))  // shows nothing

Now, I hope, you can figure out how to get out your desired data

Julius Kavay · Mar 14, 2020 go to post
 set x="1.2.3", y=$name(tmp)
 for i=1:1:$length(x,".") { set y=$name(@y@($piece(x,".",i))) }
 set @y=""
 kill x,y,i

You can put al the above in one line, if you are in terminal. Or put it in a method

ClassMethod setTemp(x) [ PublicList = tmp]
{
 set y=$name(tmp)
 for i=1:1:$length(x,".") { set y=$name(@y@($piece(x,".",i))) }
 set @y=""
} 

Or you take the shorthand

set x="1.2.3", @("tmp("_$tr(x,".",",")_")=""""")

But this works only as long as x contains number_period_numeber_period_...No leading zeros, no alphabetics...

Julius Kavay · Jan 15, 2020 go to post

Ok, I started my Studio (2018.1.1) --> New Class --> ABC.Try --> Finish.

Then removed the Extends... and I left over with

Class ABC.Try
{

}

Then saved (but no compile) and my test in a terminal:

USER>s aa=##class(%Dictionary.PackageDefinition).GetPackageList()

USER>w aa.Find("ABC")
523
USER>

Julius Kavay · Jan 15, 2020 go to post

In the class definition one can create  persistent-, serial-, registered-, abstract- and data-classes.

All of the above classes are contained in ##class(%Dictionary.PackageDefinition).GetPackageList(). Do you have a example for an class, which is not contained in the above method?

Or there is just a misunderstanding?

Julius Kavay · Jan 15, 2020 go to post

The simple (and documented) answer is something like this:

set name="Test.Package"

if ##class(%Dictionary.PackageDefinition).GetPackageList().Find($zconvert(name,"u")) { w "Yes" } else { w "No" }

@Timothy

using ^oddDEF and/or ^oddCOM works today, we hope, it will work tomorrow too, but there is no garantie

Julius Kavay · Jan 12, 2020 go to post

A warning about long time open transactions will be placed in cconsole.log and alerts.log (both files in $system.Util.InstallDirectory()_"mgr" directory) - at least  this is the case what I see on a customers system. The entries look like this:

04/05/19-02:31:34:470 (21012) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  18624 22208 (only top 5 shown)
08/29/19-14:31:03:802 (18576) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  4128 (only top 5 shown)
09/04/19-17:16:19:090 (21344) 1 [SYSTEM MONITOR] TransOpenSecs Warning: One or more transactions open longer than 10 minutes. Process id  25872 (only top 5 shown)

So it should be as easy as monitoring those two logfiles. But how it looks like on an ECP server, don't ask me.

Julius Kavay · Dec 21, 2019 go to post

You could try this way (I assume, your image is in *.jpg, *.png, *.gif, *.ico or *.bmp  format and you use a Windows OS):

- save the stream to a temp file
- do $zf(-1,"pathToIrfanView.exe pathToInpFile  /resize=(100,100)  /aspectratio  /convert=pathToOutFile")
- read the output into a stream and proceed as needed

Don't forget, if one of the above path contains a space char, you have to put in quotes the whole path.

/resize=(100,100)  /aspectration  means, the picture will be resized to a max width/height of 100 by maintaning the aspect ratio. Example: input=2000x3000 out =67x100,  input=3000x2000  out=100x67

For Unix exists similar tools to use with $zf(-1,...), for example "identify" to get the dimensions of the picture  and "convert" or "imagemagick" for resizing.

Julius Kavay · Dec 21, 2019 go to post

I'm not that java guy, so just a hint only.

There should be a

public static java.lang.String PetNameLogicalToDisplay(...)

method, so use/call this method to display your pets.

Julius Kavay · Sep 22, 2019 go to post

If your routine works in a terminal session (by issuing a DO ^Routinname command) but does not work, when the same routine is started via the Job commannd (JOB ^Routinname) then you lack something in background, what you have in the foreground.

Put an errortrap in your routine and simple report a possible error (and if you wich, additionally the execution progress of your routine).


rvExt2012 ; Test
    set $zt="bgErr"
    // ...
    // ...
    do signal("checkpoint 1") // this is optional
    // ...
    // ...    do signal("checkpoint 2") // this is optional
    // ...
    // ...
    do signal("Done")
    quit

bgErr do signal("Error: "_$ze)
    quit

signal(msg) do $system.Event.Signal($zp, $username_", "_$roles_", "_msg)
    quit

  

Now, in the terminal session enter following  line

 
job ^rvExt2012 do { set $lb(s,m)=$system.Event.WaitMsg("",1) write m,! } while s
Julius Kavay · Sep 14, 2019 go to post

By the way, he above works for unicode chars too, unless your pattern match table is outdated ;-))
Another solution is to use an loop and RegEx:


set str="abcd(123)/,op(56)*&^%$987ABC", i=0
set remove="[:punct:]|[:symbol:]"
set keep = "()"

while $locate(str,remove,i,i,c) { set:keep'[c $e(str,i-1)=" " }

Julius Kavay · Sep 14, 2019 go to post

A little bit $translate(), a little bit of $zstrip() and the job is done (in the example below, I want to keep parenthesis)
 

set str="abcd(123)/,op(56)*&^%$987ABC"

write $tr(str,$zstrip(str,"*AN","()"),$j("",$l(str)))
what to keep generell ------^^^
what to keep extra --------------^^
Julius Kavay · Sep 10, 2019 go to post

The intention behind my post was, to give you one idea (of many other possibilities), how to convert XML to CSV. A empty (chars) element is just one of some other unhandled cases (missing tags, other tags inside of COLx tag, etc.).

If you need some speed and your XML-File obeys following constraints:

- the file is a correct XML-File
- contains only the 'Data', 'Details' and 'ColX' tags
- no selfclosing tags, like <sometag/>

then you could try the QAD-way (quick-and-dirty) of conversion.

Again, below an example routine (without excessive testing).
All ISC people and ordinary programer, please look the other way ;-))

Convert() Public
{
   set len=32000 // chunk size, a safe value is:
                 // 32767 - longestColumnValue - tagSizes
   set fi="c:\temp\example-t.xml" // inp file (xml)
   set fo="c:\temp\example-t.csv" // output file (csv)

   open fi:"ru":1 open:$t fo:"nw":1
   if '$t close fi quit

   set xml=$$inp(fi,len) // first xml-chunk

   set i=0, p=0
   while 1 {
      set i=$locate(xml,"\<\/?\w+\>",i,j,v) // next (opening or closing) tag

      if i {                                            // let see, what we got 
         if v="<Details>" set row="", p=-1 // start a new row
elseif v="</Details>" out(fo,row) p=0 // complete, write out
elseif p,v["<Col" p=j, o=$zstrip(v,"*AP") // new column, keep start
elseif p,v["</Col" {$li(row,o)=$$val($e(xml,p,i-1)) // get value
         }                                   // everything else is don't care
         set i=j

else {
         set tmp=$$inp(fi,len) // next xml-chunk
         if tmp="" quit                                   // done

         // remove processed data, add new one
         if p>0 set xml=$e(xml,p,*)_tmp,p=1,i=0 else xml=$e(xml,i,*)_tmp,i=0 }
      }
   }
   close fi
   close fo
}

val(val)
{
   quit $zstrip(val,"<>w") // add handling of charcter entities like &lt; etc.
}

out(fo,row)
{
   use fo
   write $listtostring(row,";",1),! // delimiter!
}

inp(fn,len)
{
   use fn
   try read xml#len catch xml="" // in case, $zeof-mode is off
   quit xml
}

The above converter reads a simple test XML-file with two million 'ColX' items in 5 seconds and creates a CSV file with 100000 rows and 20 columns (in each row).

Instead of file you can also use an stream.

Julius Kavay · Sep 9, 2019 go to post

If you "donate" your XML-File a version info and an extra root-node:

<?xml version="1.0" encoding="UTF-8" ?>
<Data>
  <Details>
   ...
  </Details>
</Data>

and use, for example, the %XML.Textreader class (see belov).

Then with few lines of code the job is done:

XML ; XML to CSV
#define ROOT "Data"
#define DEL    ";"        ##; your CSV-delimiter, $c(9) or ";" or ...
#;
   set inpFile="c:\temp\example.xml"
   set outFile="c:\temp\example.csv"
   if ##class(%XML.TextReader).ParseFile(inpFile, .rdr) {
      if rdr.Read(), rdr.NodeType="element", rdr.Name=$$$ROOT {
         open outFile:"nw":1
         if $t {
            use outFile
            while rdr.Read() {
               if rdr.NodeType="element",rdr.Name="Details" {
                  set line=""
               } elseif rdr.NodeType="chars" {
                 set line=line_$lb(rdr.Value)
               } elseif rdr.NodeType="endelement",rdr.Name="Details" {
                 w $lts(line,$$$DEL),!
               } elseif rdr.NodeType="endelement",rdr.Name=$$$ROOT {
                  close outFile
                  quit
               }
            }
         } else { w "file open problem",! }
      } else { w "XML root-element problem",! }
   } else { w "XML structure problem",! }

Julius Kavay · Sep 8, 2019 go to post

The short answer is: NO.

The long answer is:

if you try to make a trick, like the below code: it won't work.

ClassMethod name() { 
  set mynull={"def":null}
  set result={"value1":123, "value2":(mynull.def) }
  quit result
 }

now the surprise:

write mynull.%GetTypeOf("def") ---> null
write result.%GetTypeOf("value2") --> string
Julius Kavay · Sep 8, 2019 go to post

It will depend on the structure of your xml file (next time a short sample would be helpfull).

Usually, you can do it with the help of %XLM.TextReader  / %XML.Reader class.

Julius Kavay · Sep 6, 2019 go to post

Do you really think it makes a difference if my routine contains "set xx=xx+1" instead of "set xx=1+xx"?

If yes, try the following:

Times2 ; execution time measurement

  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  q

my output values are

USER>d ^Times2
0.047048
0.038218

USER>d ^Times2
0.034727
0.035160

USER>d ^Times2
0.044252
0.036175

USER>d ^Times2
0.045639
0.035366

Both loops are exactly the same! And now, please explain why the times are partly more than 20% different?