Julius Kavay · May 5, 2021 go to post

I assume (according to the error message you show) you are trying to import some JSON-formatted data into an IRIS class. In addition I recommend the reading of https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cl…

To achieve this, you must define two IRIS classes:

Class DC.Rick.MemberData Extends (%Persistent, %JSON.Adaptor)
{
Property members As list Of Member;
}

Class DC.Rick.Member Extends (%SerialObject, %JSON.Adaptor)
{
Property dob As %Date;
Property firstName As %String;
Property middleName As %String;
Property nameSuffix As %String;
Property genderCode As %String;
Property lastName As %String;
Property memberId As %Integer;
Property relationship As %String;
}

Furthermore, I assume you have data like this (I shortened your example to keep things simple):

set memb0={"dob":"1990-07-18", "firstName":"Bob", "memberId":123956}
set memb1={"dob":"1990-05-25", "firstName":"Bill", "memberId":12345}
set memb2={"dob":"1990-10-30", "firstName":"Tommy", "memberId":4567}
set data(1)={"members":[(memb0)]}.%ToJSON()         // one member
set data(2)={"members":null}.%ToJSON()              // no member at all
set data(3)={"members":[(memb1),(memb2)]}.%ToJSON() // two members

check the examples:

for i=1:1:3 write data(i),!

the output should be:

{"members":[{"dob":"1990-07-18","firstName":"Bob","memberId":123956}]}
{"members":null}
{"members":[{"dob":"1990-05-25","firstName":"Bill","memberId":12345},{"dob":"1990-10-30","firstName":"Tommy","memberId":4567}]}

now import those data

for i=1:1:3 {
   set oref=##class(DC.Rick.MembersData).%New()
   if oref.%JSONImport(data(i)), oref.%Save() { write "OK",! } else { write "ERR",! }
}

If everything goes well, you should get three "OK"s and your data global looks like this

zwrite ^DC.Rick.MemberDataD
^DC.Rick.MemberDataD=3
^DC.Rick.MemberDataD(1)=$lb("",$lb($lb($lb(54620,"Bob","","","","",123956,""))))
^DC.Rick.MemberDataD(2)=$lb("","")
^DC.Rick.MemberDataD(3)=$lb("",$lb($lb($lb(54566,"Bill","","","","",12345,"")),$lb($lb(54724,"Tommy","","","","",4567,""))))

check member sizes:

for i=1:1:3 set oref=##class(DC.Rick.MemberData).%OpenId(i) write oref.members.Size,!

and the output should be:

1
0
2

I hope this is a good starting point for you...

Julius Kavay · May 4, 2021 go to post

It depends on...

Who is sitting at the other end? A Cache/IRIS server or a third-party product?

If Cache/IRIS: Mirroring, shadowing are the catchwords, you have to look for. In case of third-party SQL-DB: how fast (how often) want to do your updates? Once a day or (nearly)realtime?

I did something like that several years ago... the procedure is (just as a starting point):

Our application uses objects, so all the involved classes have an %OnAfterSave() method, something like this

Method %OnAfterSave(insert As %Boolean) As %Status
{
   do ..addToTransfer(..%Id())
}

with some smartness, like do not add if the record is already in the transfer queue, etc.  If you use SQL instead of objects, triggers are your friend.

We have also a task,  which crates (based on the class definition) a series of INSERT/UPDATE statement(s) and does the transfer with the help of  %SQLGatewayConnection.

Julius Kavay · May 4, 2021 go to post

The simplest solution was already answered by Robert Cemper in https://community.intersystems.com/post/how-select-random-row-table. I just want to show a more "universal variant" of that solution.

First, create an SQL stored procedure

class SP.Utilis Extends %RegisteredObject
{
ClassMethod Random(number As %Integer, dummy As %String) As %Integer [SqlProc]
{
   quit $random(number) // we do not use dummy but we need it!!
}
}

then make your query as follows:

select top 10 * from whatever.table where SP.Utils_Random(100,ID)<50

This has following advantages:

1) works with all classes, i.e. the ID column has not to be an integer (greater 0), can be a compound column too (like part1||part2, etc)

2) by adjusting the comparison:

Random(1000,ID) < 50   // gives you more "greater" distances then

Random(1000,ID) <500  // between the returned rows

For testing of edge conditions you can use

Random(1000,ID)<0    // no rows will be returned or

Random(1000,ID)<1000 // all rows will be returnd

With the right side of the comparison you can fine tune the distances between the returned rows.

For the dummy argument in the above function you can use an arbitrary column name, the simplest is to use ID because the ID column always exists, it's purpose is to force the SQL-Compiler to call this function for each row (thinking, the result of the Random() function is row-dependet). A comparsion like Random(100)<50 is executed just once. Roberts solution works too because he uses Random(100)<ID but this works only for tables where ID is a Integer (>0). You can verify this by just issuing a simple query

select top 10 * fom your.table where SP.Utils_Random(100)<50

You will see (by repeatedly executing the above query) either 10 (subsequente) rows or nothing

Julius Kavay · May 4, 2021 go to post

According to your $ZV you have a unicode installation, so there should be no problem, neither with skandinavian- nor with other language characters.

Please check your filename string... start a terminal session and then:

zzdump FileNameFullPath

then check, in the same way, the file name in your file system

Julius Kavay · Apr 28, 2021 go to post
ClassMethod Size(file)
{
   if '##class(%File).Exists(file) quit -1
   quit ##class(%File).GetFileSize(file)
}

Returnvalues:

-1=File does not exists, 0...n=Filesize

Julius Kavay · Apr 24, 2021 go to post

nothing against 7zip, merely, I use (win)rar since the mid-nineties. And as you probably know, old habits never die...

Julius Kavay · Apr 24, 2021 go to post

Just my 2 cent suggestion, CACHE.DAT and IRIS.DAT can be (usually) well compressed, the catchwords are winzip and winrar (I prefer winrar over winzip). Winrar, despite the word "win" in name, is also available for linux.


An example: winzip turned a 16GB CACHE.DAT into 3.45GB,  winrar (mode=best) topped this with 2.2GB, but as always, your values will depend on your data. And mind the time you need to compress and decompress the files, which, of course will depend on your hardware...

For example (command line)

rar a -m4 -m512 -v4g <pathTo>cachetransfer <pathTo>cache.dat

will create as many compressed files as needed, each (but the last one) with a size of 4GB, with good compression using dictionary of 512KB size.

You will get, in total, roughly 250 (*.rar) files (each with size of 4GB), I assume, 4TB compresses to 1TB.
When the first 4GB (rar)file is ready, start the transfer in parallel (one job does the compression and the other(s) work(s) on transfer - maybe you have multiple internet connections). Further, suppose you have a continuous (internet) connection between your and the target system with 100 Mbps then, again roughly, the job is done in 28 hours... better then transferring 4TB in a week or more (it's easier to restart a 4GB file as a 4TB file)

Julius Kavay · Apr 15, 2021 go to post

Yes, in the ^TRACE routine itself. From command line (in %SYS namespace) issue a "do ^TRACE" and you will get a nice documentation... ;-)

Julius Kavay · Apr 8, 2021 go to post

not so terrible... but one more thing

set ascii=$char(65,66,196)

set wide=$char(65,66,352)

write $ziswide(ascii)," ",$ziswide(wide)

zzdump ascii,wide

as I wrote in my first answer, you have to care, always to return ASCII data and not WIDE data

Julius Kavay · Apr 8, 2021 go to post

Counterquestion, do you have an example of a 'non-ASCII' char?

Codepoints 0x00-0x7F (0 - 127) are the C0 controls, aka Basic Latin (ASCII)

Codepoints 0x80-0xFF (128-255) are the C1 controls, aka Latin1

Take a look on https://www.unicode.org/charts/PDF/U0080.pdf

For example, Ä or ä are the german umlaut-A respective umlaut-a,

$ascii("Ä") --> 196 and $ascii("ä") --> 228 type in a terminal session on your system: write $char(196) --> Ä

Download and compare the above pdf with your iso-8859-1 data, there should be no difference.

Julius Kavay · Apr 8, 2021 go to post

If you get data as ISO-8859-1 (aka Latin1) and have a Unicode (IRIS/Cache) installation then usually you have nothing to do (except, to process the data). What do you mean with "convert the text to UTF-8"? In IRIS/Cache you have  (and work with) Unicode codepoints, UTF-8 comes into play only when you export your data but in your case, it will rather be ISO-8859-1 or do I something misunderstand?

By the way, if you return your data back to your Latin1 source (as Latin1) then you have to take some precautions because you have an unicode installation, so during the data processing you could mix your Latin1 data with true unicode data from other sources!

See: https://unicode.org/charts/

Also, you may download and read:

https://www.unicode.org/versions/Unicode13.0.0/UnicodeStandard-13.0.pdf

Julius Kavay · Mar 20, 2021 go to post

The best way your problem be solved is, give us a little more information, at least, the error message you get.

Of course, this is not a must

Julius Kavay · Mar 17, 2021 go to post

Of course, if you don't want to check each and every write() for error or success, you can do the check just one time at the beginning

set str=##class(%Stream.FileCharacter).%New()
do str.LinkToFile("/root/my_file.txt")
set sts=str.Write("")
if 'sts { write "We have a problem",! quit }

writing a nullstring to stream does not change the stream but the file opening sequence will be executed

Julius Kavay · Mar 17, 2021 go to post

Checking status codes is a good starting point...

set str=##class(%Stream.FileCharacter).%New()
write str  --> 4@%Stream.FileCharacter
write $system.OBJ.DisplayError(str.LinkToFile("/root/my_file.txt")) --> 1
write $system.OBJ.DisplayError(str.WriteLine("line-1"))  --> ERROR #5005: Cannot open file '/root/my_file.txt'1

Your %Save() returns with 1 (OK), because there is nothing to save...

Note: on linux,  for a standard user (like me now) is forbidden to write to '/root' directory

Julius Kavay · Mar 4, 2021 go to post

In addition to Ben's answer, 

on Windows: netstat -ano | find "LISTENING"

will in the far right column show the process ID of the listening process

on Linux: sudo netstat -pln | grep tcp

the last column shows the process name which is opened the port

Julius Kavay · Feb 21, 2021 go to post

I think, there is a "small problem" at start...

zn "%SYS",n=SL ...

will not work as expected ;-))

but what about this line with 552 chars

zn "%SYS" s n="SL" d ##class(Security.SSLConfigs).Create(n):'##class(Security.SSLConfigs).Exists(n),##class(%Net.URLParser).Parse("https://pm.community.intersystems.com/packages/zpm/latest/installer",.c) s h=##class(%Net.HttpRequest).%New(),h.Server=c("host"),h.Port=443,h.Https=1,h.SSLConfiguration=n,s=h.Get(c("path")) q:'s $System.Status.GetErrorText(s) s x=##class(%File).TempFilename("xml"),f=##class(%Stream.FileBinary).%New(),f.Filename=x d f.CopyFromAndSave(h.HttpResponse.Data) d h.%Close(),$system.OBJ.Load(x,"ck") do ##class(%File).Delete(x)

I only rewrote your line, but haven't tried to execute

Julius Kavay · Feb 18, 2021 go to post

I think, your solution is an  "ad hoc" solution for a particular task, anyway, I want to point out two problems.

First, the solution fails, if the size of <source> is less then than the size of <target>:

set source={"Name":"Joe", "Age":50 }
set target={"Name":"Joe", "Age":50, "Phone":"123-456"}

write CompareJSON(source,target) ---> 1
write CompareJSON(target,source) ---> 0

The same goes for data like:

set source={"Name":"Joe", "Age":50, "Data":[10,20] }
set target={"Name":"Joe", "Age":50, "Data":[10,20,30]}

Maybe your data do not have such cases.

A quick check could be:

if source.%Size()-target.%Size() { quit "Size-problem" }

Second, in a more "general" case, comparing lists could sometimes lead to an philosophical question:
are two lists with the same elements but in a different sequence (of those elements) equal or not?

list1: [aaa, bbb]
list2: [bbb, aaa]

The answer MAY depend on the question, what is stored in those lists?

If the lists contains, for example, some kind of instructions, then the sequence of those instructions will mattern, but if those list are just list of, say my hobbies, then the sequence is unimportant (except, if one implies some kind of weighting, like the first entry is my preferred hobby).

This implies to me, to compare two list, you MAY need an extra info, something like:
dependsOnSequence = yes | no

One more hint, in the line

s tSC= ..CompareJSON(value,target.%Get(key),RefNo)  

what happens if target.%Get(key) is empty (or not a corresponding object)?

Just my2cents

Julius Kavay · Jan 15, 2021 go to post

Ok, here I am... sorry for the delay.

***********************************************

Forget it!

I'm unable to copy and paste a program (in this case a method) into this text box!

After paste operation, some lines are joint, others not,  the indention is lost...

Maybe I'm just too dumb to work with the text box...

*****************************************

If you want this class, you can it download from my FTP.
ftp: ftp.kavay.at
usr: dcmember
psw: member-of-DC
Julius Kavay · Jan 14, 2021 go to post

I'm not sure, what you want to do, but if you want to read a tiff file by byte-by-byte and the interpret it in some kind, there is a very simple example for the start.
 The method below returns the file type (gif,jpg,png or tif) based on the magic number.

/// Identify an image by its magic number
/// (only for gif,jpg,png and tif)
ClassMethod ImageType(file, ByRef err)
{
   o file:"ru":0
   i $t {
      s io=$i, err=""
      u file r x#8
      
      i x?1"GIF8"1(1"7",1"9")1"a" { s typ="gif" }
      elseif $e(x,1,2)=$c(255,216), $$end()=$c(255,217) { s typ="jpg" }
      elseif x=$c(137,80,78,71,13,10,26,10) { s typ="png" }
      elseif $case($e(x,1,4), $c(73,73,42,0):1, $c(77,77,0,42):1, :0) { s typ="tif" }
      else { s typ="", err="File type unknown" }
      
      c file
      u:io]"" io
   
   } else { s typ="", err="Can't open "_file }
   
   q typ

end() s t="" r:$zseek(-2,2) t#2 q t
}

I have also a method to retrive the image size (pixelsWidth and pixelsHeight) for the same (gif,jpg,png and tif) files. If you are working on similar problem, I could post this method too.

Julius Kavay · Jan 14, 2021 go to post

By the way, the above method returns the OS, Cache or IRIS was built for.

Because you can only install a product (Cache or IRIS) on a OS for which the product was built, so the above method returns the OS where your product  runs.

Julius Kavay · Jan 11, 2021 go to post

I see right now,  cut-and-paste without looking-and-checking isn't good! Sorry, for some stupid reason, I copied the wrong lines. The correct ones are:

ClassMethod Diamond(n)
{
   f n=1:1:n w ! f j=1:1:n,n-1:-1:1 w !?n-j f i=1:1:j,j-1:-1:1 w i
}

ClassMethod Infinite(n)
{
   f n=1:1:n w ! f j=1:1:n,n-1:-1:1 w !?n-j f i=1:1:j,j-1:-1:1 w i#10
}

Also, Dimond() works from 0 thru 9 (and not thru 10). The line lengths (with 63 and 66 chars) were correct. Finally, the correct output:

do ##class(DC.CodeGolf).Diamond(3)

1

 1
121
 1

  1
 121
12321
 121
  1
Julius Kavay · Jan 11, 2021 go to post

OK, you want it short and endless? You can get it!

Diamond() works from 0 thru 10 using 63 chars

Infinite() works from 0 thru Cache's maxint and has 66 chars of source code.

ClassMethod Diamond(n)
{
   f n=1:1:n w ! f j=1:1:n,n-1:-1:1 w !?n-j f i=1:1:2*j-1 w i#-j+j
}

ClassMethod Infinite(n)
{
   f n=1:1:n w ! f j=1:1:n,n-1:-1:1 w !?n-j f i=1:1:2*j-1 w i#-j+j#10
}
 

Some output

USER>

USER>d ##class(DC.CodeGolf).Diamond(0)

USER>d ##class(DC.CodeGolf).Diamond(3)

1

 1
121
 1

  1
 121
12312
 121
  1
USER>d ##class(DC.CodeGolf).Infinite(13)

1

 1
121
 1

  1
 121
12312
 121
  1

   1
  121
 12312
1234123
 12312
  121
   1

    1
   121
  12312
 1234123
123451234
 1234123
  12312
   121
    1

     1
    121
   12312
  1234123
 123451234
12345612345
 123451234
  1234123
   12312
    121
     1

      1
     121
    12312
   1234123
  123451234
 12345612345
1234567123456
 12345612345
  123451234
   1234123
    12312
     121
      1

       1
      121
     12312
    1234123
   123451234
  12345612345
 1234567123456
123456781234567
 1234567123456
  12345612345
   123451234
    1234123
     12312
      121
       1

        1
       121
      12312
     1234123
    123451234
   12345612345
  1234567123456
 123456781234567
12345678912345678
 123456781234567
  1234567123456
   12345612345
    123451234
     1234123
      12312
       121
        1

         1
        121
       12312
      1234123
     123451234
    12345612345
   1234567123456
  123456781234567
 12345678912345678
1234567890123456789
 12345678912345678
  123456781234567
   1234567123456
    12345612345
     123451234
      1234123
       12312
        121
         1

          1
         121
        12312
       1234123
      123451234
     12345612345
    1234567123456
   123456781234567
  12345678912345678
 1234567890123456789
123456789011234567890
 1234567890123456789
  12345678912345678
   123456781234567
    1234567123456
     12345612345
      123451234
       1234123
        12312
         121
          1

           1
          121
         12312
        1234123
       123451234
      12345612345
     1234567123456
    123456781234567
   12345678912345678
  1234567890123456789
 123456789011234567890
12345678901212345678901
 123456789011234567890
  1234567890123456789
   12345678912345678
    123456781234567
     1234567123456
      12345612345
       123451234
        1234123
         12312
          121
           1

            1
           121
          12312
         1234123
        123451234
       12345612345
      1234567123456
     123456781234567
    12345678912345678
   1234567890123456789
  123456789011234567890
 12345678901212345678901
1234567890123123456789012
 12345678901212345678901
  123456789011234567890
   1234567890123456789
    12345678912345678
     123456781234567
      1234567123456
       12345612345
        123451234
         1234123
          12312
           121
            1
USER>
Julius Kavay · Jan 10, 2021 go to post

Maybe there are shorter solutions, but somebody must start the game... (I hope, class- and method name does not count)

Class DC.CodeGolf Extends %RegisteredObject
{ 
/// Diamonds from 1 thru 9 (max)
ClassMethod Diamond9(n)
{
  f n=1:1:n f i=1:1:n-1,n,n-1:-1:1 {s a=$e(1234567890,1,i-1) w ?n-i,a,i,$re(a),!} w !
} 

/// Diamonds from 1 thru 16 (max)
ClassMethod Diamond16(n)
{
   f n=1:1:n f i=1:1:n-1,n,n-1:-1:1 {s a=$e("1234567890abcdef",1,i) w ?n-i,a,$re($e(a,1,*-1)),!} w !
} 

}
Julius Kavay · Dec 31, 2020 go to post

If your input (date and time) format is the same as the example you provided,

set inpDateTime = "12/10/2019 21:41"

then use this one:

write  $tr($zdt($zdth(inpDateTime,1,2),8,1),": ")  --> 20191210214100

and if you want it "a little bit faster", the try this one

write  $tr("abcdefghijkl00","ef/gh/abcd ij:kl",inpDateTime)  --> 20191210214100

and you will get a speed gain of over 33%

Happy New Year!

(But this time, without Covid-19, if possible!)

Julius Kavay · Dec 9, 2020 go to post

SystemAdministration-->Configuration-->NationalLanguageSettings-->Defaults/Defs/Imp/Exp

First you have to start with a predefined/default table, copy under a new name, select this new table and click on the properties.

Julius Kavay · Dec 9, 2020 go to post

To make things clear, the mentioned popup message should be seen by someone, who is (most of the time) working on the servers console. Is it so? Or you want to popup this message on an arbitrary desktop, where a user works?

If I need to send a message to a user, either I send an email or I activate a popup message in my client (I have a very special client-UI).

By the way, you wrote, I quote "an email is NOT a realistic expectation and far from reliable". Today, (almost) everybody has a smartphone and I think, if somebody does not read an email, he/she won't read thos popup messages either. Of course, you should send short plain text messages and not those fancy bloated colorful emails.

So, a way to a solution... you could write a  small program (C++, VB, Delphi, etc.) which is started after user login.  The program should listen on a TCP port for messages from an arbitrary Cache/Iris (possibly background) application. If such a message arrives, this helper program changes to foreground, displays the message with or without an OK button. That's it.

Julius Kavay · Dec 9, 2020 go to post

Store only hashed passwords... that's all.

Class DC.MyUsers Extends %Persistent
{ 
Property Name As %String;
Property Password As %String;
Property passHash As %String [ Internal, Private, ReadOnly ];
Property passSalt As %String [ Internal, Private, ReadOnly ];
Parameter ITER = 1024;
Parameter LENGTH = 20;
Method PassCheck(psw) As %Boolean
{
  set salt = $system.Encryption.Base64Decode(..passSalt) 
  set hash = $system.Encryption.Base64Decode(..passHash)
  quit $system.Encryption.PBKDF2(psw, ..#ITER, salt, ..#LENGTH)=hash
}
Method PasswordSet(psw) As %Status
{
  // optionally, quality/requirement-check
  if '..pswQuality(psw) quit $$Error^%apiOBJ(5001,"Poor password quality")
  
  set salt=$system.Encryption.GenCryptRand(8)
  set hash=$system.Encryption.PBKDF2(psw, ..#ITER, salt, ..#LENGTH)
  set i%passHash=$system.Encryption.Base64Encode(hash)
  set i%passSalt=$system.Encryption.Base64Encode(salt)
  quit $$$OK
}

Method pswQuality(psw) As %Boolean
{
  quit 1
}
}

Julius Kavay · Dec 9, 2020 go to post

There are two points,

the first (catchwords: server, interaction) was already answered by Dmitriy Maslennikov 

the second is your 10 second popup button.

In my over 40 years of IT-experience, there is one thing (along with others) I have learnd, is: every timeout is wrong, but messages with timeouts are evil! Whatever time you use, it's either too short or too long.

Imagine, the phone is ringing abd the user has a hot 20 minute discussion on the phone, in the meantime, your popups comes and goes! Unseen! Sometimes several times!

The only resonable solutions are,

- if the message is (just) informative and the message text never changes, then put it into a logfile and show nothing.  If the message text is a variable text ("Data is saved" vs. "Can't save: No disk space available") then do the popup with one button (or textinput), see below, but DO NOT use timeouts! 

- if the situation allows the user to choose between multiple answers, then let the popup window with those OK, YES, NO, CANCEL, etc. buttons stay there, as long as the user chooses one of them, or as an alternative (application dependent), offer an ordinary text input and at sometime the user types the answer and pushes the enter key.

Messages with (possible with short) timeouts requires a user all the time gazing on the display - which you can't expect.

justmy2cents