Julius Kavay · Sep 5, 2019 go to post

With time measurements keep in mind:

- usually, you are not alone on a Cache server
  There are many other processes, some of them belongs to Cache other to the OS
  
- the time resolution (whatever you use: $now(), $zh) is also limited

- it depends also on the time, how long your mesurement runs (you are not alone!)
 

This is my short testroutine:

Times(iter=1E3,count=4) ; show times

    w ?3,"count   num+1   1+num   =$i()    $i()",!
    w ?15,"times in microseconds",!
    w $tr($j("",40)," ",-1),!
    
    f i=1:1:count d time(iter) s iter=iter*10
    q
    
time(iter)
{
    s f=1E6/iter // factor for "one operation in microseconds"
    
    w $j(iter,8)
    s num=0,t=$zh f i=1:1:iter { s num=num+1 } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { s num=1+num } d t($zh-t*f)
    
    s num=0,t=$zh f i=1:1:iter { s num=$i(num) } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { i $i(num) } d t($zh-t*f)
    w !
}

t(t)
{
    w $j(t,8,3)
}


and this is the output


USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   2.000   1.000   2.000   1.000
      10   0.100   0.100   0.100   0.200
     100   0.030   0.030   0.080   0.080
    1000   0.044   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.064   0.050
 1000000   0.018   0.014   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   4.000   0.000   2.000   1.000
      10   0.100   0.100   0.100   0.100
     100   0.040   0.030   0.080   0.580
    1000   0.044   0.041   0.088   0.088
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.076
 1000000   0.027   0.021   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   1.000   2.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.040   0.030   0.080   0.590
    1000   0.045   0.041   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.015   0.012   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>

USER>

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   0.000   3.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.030   0.030   0.080   0.630
    1000   0.046   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.014   0.012   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>

I consider time measurements only as a rough approximations

Julius Kavay · Sep 4, 2019 go to post

If you want to detect a journal change, it means, you want to do something, when such a change took place.

I think, you could do something like this:

a) Start a process at system start (for example, from %ZSTART), which will handle journal changes
b) In that process, get the current journal file
c) try to open this file
d) if (c) fails, wait a few seconds and then goto c)
e) if (c) succeds,  handle journal file change, then goto b)
   
A simplified routine, but untested (as a suggestion):

journalchanges ; Handle journal file changes

#define SWITCHES $system.Util.GetSwitch()
#define SHUTDOWN ##Expression(2**16)
#define BITAND(%a,%b) $zboolean(%a,%b,1)
   
   set jouFile=$$getCurrentFile()  // get the current journal file
   if jouFile="" Quit              // should not happen, but who knows...
   
   while '$$$BITAND($$$SWITCHES, $$$SHUTDOWN) { // stop, if shutdown is in progress
      open jouFile:"WL":1 // try to get ownership
      continue:'$test     // locked (by the journaling system), we try once again
      close jouFile       // because we were interested in status only
   
      // Now you can use <jouFile> to handle the file change
      // By now, he journaling system uses a new journal file
      
      set jouFile=$$getCurrentFile() // get the new journal file
      if jouFile = "" quit           // just to be on the safe side
   }
   
   quit  // we are done
   
    
getCurrentFile()
{
   try { return ##class(%SYS.Journal.System).GetCurrentFile().Name } catch { return "" }
}

Julius Kavay · Aug 26, 2019 go to post

Yes, you have right, thank you for the hint.  One never should add an alternate function without testing it!

The correct form is:

ClassMethod CountQ(node) As %Integer
{
end=node
  if $data(@node)#10 set sum=1 else set sum=0 }
  while 1 set node=$query(@node) quit:node=""||($name(@node,$qlength(end))'=end) if $increment(sum) }
  quit sum
}
Julius Kavay · Aug 24, 2019 go to post

Class community.counter Extends %RegisteredObject
{
/// Example:
/// set ^x(1)=111
/// set ^x(3,5)=222
/// set ^x(3,7)=333
/// 
/// The above global has 5 nodes:
/// ^x without a value
/// ^x(1) with value
/// ^x(3) without a value
/// ^x(3,5) with value
/// ^x(3,7) with value
/// 
/// write ##class(community.counter).CountQ($name(^x)) --> 3
/// write ##class(community.counter).CountR($name(^x)) --> 3
/// 
/// Using your example:
/// write ##class(community.counter).CountQ($name(^Locations)) --> 5
/// write ##class(community.counter).CountQ($name(^Locations("USA")) --> 3
/// 
/// 
/// N.B.
/// Recursion is a tricky thing!
/// It helps one to get a clearly laid out solution
/// but you should take care about runtimes.
/// 
/// CountQ(...) is about 4-5 times faster then CountR(...)
/// 
/// --------------------------------------------------------
/// 
/// Return the count of nodes of a global- or a local variable
/// which have a value, using $QUERY() function
/// 
/// node:
/// a local or global variable, example: $na(^myGloabl), $na(abc)
/// or a local or global reference example: $na(^myGlobal(1,2))
/// 
ClassMethod CountQ(node) As %Integer
{
 if $data(@node)#10 set sum=1 else set sum=0 }
 while 1 set node=$query(@node) quit:node=""  if $increment(sum) }
 quit sum
}

/// Return the count of nodes of a global- or a local variable
/// which have a value, using recursion, using recursion
/// 
/// node:
/// a local or global variable, example: $na(^myGlobal), $na(abc)
/// or a local or global reference example: $na(^myGlobal(1,2))
///       
ClassMethod CountR(node) As %Integer
{
 set sum=0
 do ..nodeCnt($name(@node), .sum)
 quit sum
}

ClassMethod nodeCnt(ref, ByRef sum) As %Integer [ Internal, Private ]
{
 if $data(@ref)#10, $increment(sum)
 set i=""
 while 1 set i=$order(@ref@(i)) quit:i=""  do ..nodeCnt($na(@ref@(i)),.sum) }
}

}
Julius Kavay · Aug 19, 2019 go to post

Of course, the same goes for the other methods, which have a wide (...W) variant

Julius Kavay · Aug 19, 2019 go to post

I can see it right now, do you use

  • conn.Prepare(...) or
  • conn.PrepareW(...) ?

This should be coordinated with your MySQL installation.

Julius Kavay · Aug 19, 2019 go to post

Some hints/questions:

  • did you installed the correct driver? I mean, do have Cache/Iris and the
    driver the same architecture (32 vs. 64 bit)?

  • I'm not an MySQL expert (I do not even have an MySQL DB), so I ask, is there any problem with the character size (1 byte vs. 2 bytes/unicode), if this applies?

  • with a DB-Tool, like WinSQL, try to make an SELECT (as suggested by Eduard Lebedyuk) statement. What is the error message?

Julius Kavay · Aug 16, 2019 go to post

HELP: what are the markdown characters?

In the above answer, I put some words between angle braces, now all they are lost! OK, I try once again with an apostrophe.

  1. set stat=conn.Connect("odbcname", "username" ,"password") "username" is a MySQL user.

  2. If the content of the variable "variableX" is an alphanumeric value then put a single quote char (') around the value. Beforehand remove (or replace) all single quotes (with something else) from "variableX".

Julius Kavay · Aug 16, 2019 go to post

Some thoughts/hints to your problem:

  1. set stat=conn.Connect(, , ) is a MySQL user. Does this user have the rights for UPDATE and INSERT?

  2. What are the status codes after

    • set sc=conn.Prepare(...) and
    • set sc=conn.Execute(...)?
  3. Your query-string should have blanks after the table name and also before and after the VALUES keyword.

  4. If the variables value1, value2, ... valueN CAN CONATIN a backslash character then you should either duplicate them set valueX = $replace(valueX, "", "\") or switch the escaping off at the start your query-string : set pQuery="SET sql_mode='NO_BACKSLASH_ESCAPES'; INSERT INTO ..." see also: https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html

  5. If the content of the variable is an alphanumeric value then put a single quote char (') around the value. Beforehand remove (or replace) all single quotes (with something else) from .

Julius Kavay · Aug 15, 2019 go to post

To make a connection to a MySQL Database via ODBC is easy. In the next steps I assume you are on a Windows system (Linux/Unix is similar):

  1. download (https://dev.mysql.com/downloads/connector/odbc/) and install the proper ODBC driver

  2. go to ControlPanel-->AdministrativeTools-->DataSources(ODBC)

  3. select the SystemDNS-Tab, click Add

    • give a unique name to this datasource (and remember it for step 4)
    • fill in the necessary fields as desired
  4. use the %SQLGatewayConnection class to get/put your data from/into MySQL, something like:

    set gtw=##class(%SQLGatewayConnection).%New() if gtw.Connect(datasourceName, user, pass) { // do your work } else { // can't connect }

Julius Kavay · Aug 13, 2019 go to post

If you work in Cache-Terminal, go to: ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->DeviceSubtypes

Select: C-Cache-Terminal, click on Edit Go to bottom line (Caption: ZUFormFeed) and change the value from $C(27,91,72,27,91,74) to your needs. For example, if you want to retain the first and the second lines, to: $C(27,91,51,59,48,72,27,91,74).

In case, your device type is not the Cache-Terminal, search for your device in ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->Devices

Select your device, for example TERM, |TNT| or whatever device type you have. Click on edit and notice the Sub-Type. Now, as above, go to the sub-types, and change the ZUFormFeed field.

In case, you wont find your device and/or device sub-type, there is a direct way too, to set the desired FormFeed (i.e. write #) and the Backspace behavior:

do ##class(%Device).SetFFBS($C(27,91,51,59,48,72,27,91,74),$c(8,32,8)) The first sequence, as the method name implies, is the FormFeed sequence and the second is the Backspace sequence

Julius Kavay · Aug 13, 2019 go to post

If you have your binary or character data in (sequential) global nodes, then:

  • create your own stream clas -in the %OnNew() method fetch the data from your global

and voila, you are ready to use your data with stream methods.

set ^mydata(1)="abcd" set ^mydata(2)="efgh"

set ^yourdata("id",1)="1234" set ^yourdata("id",2)="5678"

Class Test.Stream Extends %Stream.TmpBinary { Method %OnNew(Global, initval As %String = "") As %Status [ Private ] { Set i%%Location=$select(initval="":$$$streamGlobal,1:initval) do ..Clear() for set Global=$query(@Global,1,data) quit:Global="" do ..Write(data) do ..Rewind() Quit $$$OK } }

set stream=##class(Test.Stream).%New($na(^mydata)) while 'stream.AtEnd { write stream.Read(5) } abcdefgh

set stream=##class(Test.Stream).%New($na(^yourdata("id"))) while 'stream.AtEnd { write stream.Read(5) } 12345678

Julius Kavay · Aug 10, 2019 go to post

set a=1.2 // your number

write a\1+''(a#1)   // this is a NOT NOT and not a quote!

Julius Kavay · Aug 1, 2019 go to post

No, it works and all installations but yes, it needs 8 Bit  characters.

A simple command like

write "abcd"

writes out four 8-Bit characters, independent of your installation (8-Bit or Unicode)

On Evgeny Shvarov's PC, a command like

write "Физз"

would write out four 16Bit characters, in sum 2*4 = 8 characters, each with 8 Bits

Julius Kavay · Jul 31, 2019 go to post

It depends on, what are the requirements... Usually we want to occupy as few (RAM) bytes as possible. Yes, it uses the fewest characters but far more bytes as Robert's solution.

Julius Kavay · Jul 31, 2019 go to post

We can save some more bytes:

f i=1:1:100 w $p(i_",Fizz,Buzz,FizzBuzz",",",i#5=0*2+'(i#3)+1),!

"640K ought to be enough for anybody." Oh, I meant, 64 bytes should be enough.

Julius Kavay · Mar 13, 2019 go to post

As Jeffrey already wrote, this is charCode x2022 (hex) but take a look at

https://www.unicode.org/Public/12.0.0/charts/CodeCharts.pdf

There you will find many punctuation and mathematical characters and geometric shapes. For web developers, this is like the land of plenty. My favorit characters are x2700 - x27ff (hex, of course) ✔ ✘ ✆ etc.

Julius Kavay · Mar 12, 2019 go to post

If you need to reorder a given set of characters (as above, from "12/03/2019" to "2019-03-12") then the $translate(...) function, used in "backward-mode" is your best friend.

set p="12/03/2019"
write $tr("abcd-ef-gh","ef/gh/abcd",p) --> 2019-12-03

Your gain: 50% of speed.

Nota bene

(1) $translate(inputdata, fromChars, toChars) --> outputdata
(2) $translate(toSequence, fromSequence, inputdata) --> outputdata

Where (1) is the Forward-Mode (the regular usage) and (2) the Backward-Mode

Julius Kavay · Mar 1, 2019 go to post

The correct way is:

ClassMethod ind() [ PublicList = (active,reactive) ]
{
  new active,reactive

  kill info
 // the above command (kill info) is a kind of NOP (no-operation)
 // at the time of entry into a methode there are no variables,
 // except those in the public list (if defined)

  set active = 1
  set reactive = 2

  for i="active","reactive" {
    set info(i)= @i
  }

  zw info

  break
}

If you want to use name-indirection in a procedure block, then

- variables, used by indirection, must be made public (via PublicList)
- and, depending on the situation, should be saved with the NEW command
Julius Kavay · Feb 21, 2019 go to post

Maybe you want something like this:

set data1 = "%sqlcq.something.value.foo" set data2 = "sqlcq.something.value.bar"

for x=data1, data2 { if x ? .1"%"1"sqlcq".e { write "OK" } else { write "NOT" } w ! }

Your output should be: OK OK

Regards

Julius Kavay · Jan 17, 2019 go to post

What do you mean with "from Code I am getting"? From which Code? ["\"KY\",\"TN\",\"AL\",\"GA\""] seems to be the same as the terminal output, except the quote chars are escaped...

Julius Kavay · Jan 17, 2019 go to post

"like a centipede with a wooden leg: 99 times tic and 1 toc the stream is then truncated and still requires extra coding"

That's the whole point! If we turncate the stream to $$$MaxStringLength, no matter where (in a calculated property or in a GETter method), just to present this (string)value to a variable, a function or whatever, then we can instantly stick to a string.

By the way, it's possible to store $$$MaxStringLength bytes in a local or global variable. Properties of classes are stored in globals as $listelements, so the maxlength for a single string property depends on things like count and types of other properties in the class and not least, does this class extends %Persistent or extends another class(es) which extends %Persistent - in this case we need some extra bytes for those (extended) class names too.

Julius Kavay · Jan 17, 2019 go to post

Supposed, you have a magical datatype of %StringOrMaybeStream and your property would be something like this:

Property TheText As %StringOrMaybeStream

So the next question is, how would your application use this magical property? The very first question is, how would you put your data into this magical property?

Give me some (hypothetical) examples of use and I will try to give you a solution, if there is any.

Julius Kavay · Sep 27, 2018 go to post

HANG command exists.

You can put between each command (if you have enought time ;-)) ) an hang command:

while response.StringValue="Processing" { hang 5 // pause for 5 seconds // get new response }

Regards and have a nice day Julius

Julius Kavay · Sep 20, 2018 go to post

Hello Kevin,

if I anderstand you correctly, several your Cache systems (development, life, ...) will store their files on a third server (possibly on a file server) and you want to put the files from each (source) Cache (instance) into a different (target) folder, where the folder name being the "name" of the source Cache system - am I correct?

If yes, I think, your best choice ist:

...\hostNameOfCache\instanceNameofCache...

or, as John Murray suggested

...\GUIDofCacheInstance...

or just a fixed string like

...\kevinNotebook(development)...

You can put (John's) GUID or my "fixed string" in a (possibly system) Global like ^%Zconfig("path")=...

Why? Things like MAC- or IP-Addresses can change more often then you think. Especially IP-Addresses will change, hence are there DNS servers.

On the other hand, it's uncommon to change host- or instance names (of Cache or of whatever installation).

Your IP-Address preference has an other downside too. Many servers have more then one IP-Address (and if they do not have one today, maybe tomorrow!).

If you persist to take the systems IP-Address, here is, what you wanted:

set iplist = $system.INetInfo.GetListOfConfiguredInterfaces(0) for i = 1:1:$listlength(iplist) zw $list(iplist,i)

Regards and have a nice day Julius

Julius Kavay · Sep 14, 2018 go to post

You can do it either this way:

set OrgTypeId = $piece(line, ",", 2) // assuming, item 2 is an OID of ZenCrm.OrgType if ##class(ZenCrm.OrgType).ExistsId(OrgTypeId) { set rec.OrgType = ##class(ZenCrm.OrgType).%OpenId(OrgTypeId,0) }

or this way: do rec.OrgTypeSetObjectId($piece(line, ",", 2))

Julius Kavay · Aug 7, 2018 go to post

You are comparing apples wih oranges!

The line  (your case 1):

set dat=$lg(^TestD(id))    //dat=$lb("a","b","c","d","e")

sets dat to the FIRST list item (if present, or to "", if the list item is NOT present) of ^TestD(id)  but ONLY if ^TestD(id) is defined AND it is a Cache list.

In elsecase, you will get an <UNDEF> if ^TestD(id) does not exists or an <LIST> error, if ^TestD(id) exists but the content is not a list!

The line (your case 2):

 set dat=$g(^TEST2(id))   //dat = "a#b#c#d#e"

sets dat to the content of ^TEST2(id) , if  it exists or to "", if there is no ^TEST2(id)

Julius Kavay · Aug 1, 2018 go to post

If you want to compare collections, then you have to know what do you compare.
Let look on this examples:

1) Hobbies
   old: music, sport
   new: sport, music
   
   One could say, there is no difference (if all hobbies are equally preferred) 
   
2) Work instructions
   old: format_disk, create_backup
   new: create_backup, format_disk
   
   In this example, if the work is done in a wrong sequence, the data are lost.

In other words, if you compare collections, you have to take in account the
importance or unimportance of sequencies, which means, the compare function
needs a third input argument

compare(old, new, relyOnSequence, ...)

By the way, your test() method has his very special "point of view" of lists:

set old1=$lb("blue","red","green"),    new1=$lb("green","red","blue")
set null=$lb("", "", "")

do ##class(...).test(old, new, .add, .del) write add=nul, del=nul --> 11
do ##class(...).test(old, old, .add, .del) write add=nul, del=nul --> 11
do ##class(...).test(nul, nul, .add, .del) write add=nul, del=nul --> 11

Is this an expected behavior?
 

Julius Kavay · Jul 5, 2018 go to post

On-the-fly and untested:

set del=<your_csv_delimiter> // <;>, <tab>, <whatever...>

 while'stream.AtEnd {
        set line=stream.ReadLine()
        write line,! // this is csv

       set json=[]

     for i=1:1:$l(line,del) do json.%Push($p(line,del,i))

    write json.%ToJSON(),!  // or whatever other action you need...
}

HTH

Julius

Julius Kavay · Jan 7, 2018 go to post

The  short answer ist:

a) look for Mnemonics 

b) write /cup(line, column)

c) use ?tab

For example:

 write #, "Hello", !, /cup(3,25),"Let's go....", !, "And now", ?20, "we are in column 20", !