Eduard Lebedyuk · Sep 13, 2019 go to post

InitGlobals process Avg. Duration would be the time between [2] and [16]. So all other processes are "included" in that time.

Eduard Lebedyuk · Sep 12, 2019 go to post

Pool Size setting is shown on Production Configuration page.

i think you have several process jobs.

Finally, can you show Visual Trace with the first process?

Eduard Lebedyuk · Sep 11, 2019 go to post

Interesting.

for i=1,5,7,31,32,33,34,35,36,37,40,41,42,43,44,45,46,51,52,53,54 write $j(i,2),"  ",$C(27)_"["_i_"m"_"Hello"_$C(27)_"[0m",!
Eduard Lebedyuk · Sep 11, 2019 go to post

Probably not the answer you're searching for but here's an idea:

set i = 0
set text = "Hello World!"
set fill = $justify("", $length(text))
for {
  set i = i+1
  write $char(13) _ $case(i#2, 1:text, 0:fill)
  hang 1
}
Eduard Lebedyuk · Sep 9, 2019 go to post

Thank you.  That's it.

I wanted to move calculation into $PIECE which was the root of my troubles.

Interestingly, when I pass empty path value ("") it is recognized as NULL on SQL side and

CASE :path
WHEN NULL 

does not work (never gets hit probably because it compares using equals and not is).

So it's either:

SELECT DISTINCT
    CASE nvl(:path,'')
    WHEN '' THEN $PIECE(Name, '.')
    ELSE $PIECE(Name, '.', 1, $LENGTH(:path, '.') + 1) END Name
FROM %Dictionary.ClassDefinitionQuery_SubclassOf('Ens.BusinessProcessBPL')
WHERE Name %STARTSWITH :path

or:

SELECT DISTINCT
    CASE
    WHEN :path IS NULL THEN $PIECE(Name, '.')
    ELSE $PIECE(Name, '.', 1, $LENGTH(:path, '.') + 1) END Name
FROM %Dictionary.ClassDefinitionQuery_SubclassOf('Ens.BusinessProcessBPL')
WHERE Name %STARTSWITH :path

It raises the question of how to pass empty string to SQL and avoid it being recognized as NULL, but it's irrelevant for my original inquiry.

Eduard Lebedyuk · Sep 5, 2019 go to post

From docs:

Procedure blocks enforce variable scoping: methods cannot see variables defined by their caller. New applications use procedure blocks; non-procedure blocks exist for backwards compatibility.

Eduard Lebedyuk · Sep 5, 2019 go to post

I need to run my code one per job start, that's why OnInit does not help.

OnProductionStart is closer, however it runs in a parent job and not in the Business Process job itself.

Still, thank you for a very throughout explanation.

Eduard Lebedyuk · Sep 5, 2019 go to post

For me the main acceptable case of foreach iteration is business objects - for example we receive array of results and must process them. In that case we, of course, must iterate over them, no way around it.

Everything static: constraints, enums, dictionaries, etc... should be (and usually could be) used without foreach iteration.

Eduard Lebedyuk · Sep 5, 2019 go to post

I agree that there are cases where we need to do something over each element of the collection.

But you said:

constants, static arrays in algorithms, e.g. arbitrary dictionaries

Which implies that foreach iteration is not desired here.

Anyways, in that case what does matter is size.

If you're sure that it would be less than $$$MaxStringLength then you can use $lb, otherwise use locals (which can be converted to ppg/globals easily).

Eduard Lebedyuk · Sep 4, 2019 go to post

1. Before we iterate over rows we need to determine column type, the example is about that.

Here's an example of what I'm talking about.

2. You want to avoid full scan as much as possible. Two techniques for that are:

  • knowing position
  • knowing key

And that determines the type of structure used.

If you don't know either you need to think about possible algorithm improvements.

Eduard Lebedyuk · Sep 3, 2019 go to post

It depends on a use case.

  • For access by position use $lb.
  • For access by key use locals.

Consider the scenario of iterating over the result set. You need to do two things:

  • Convert value based on a column datatype
  • If a value is special translate it

To solve the first one (and assuming we get values by position number) the most efficient solution would be iterating over metadata once and building something like this:

set columns = $lb("int", "bool", "str", ...)

Then in each row we can easily get datatype by:

for i=1:1:colCount {
  set dataType = $lg(columns, i)
  set value = rs.Get(i)
}

Now for a second part - value translation, locals are great here. We prep our translator:

set local(1) = "a"
set local("hi") = "world"

and replace the value if needed

set:$data(local(value), newValue) value = newValue
Eduard Lebedyuk · Sep 2, 2019 go to post

Ok.

Parameter is about 6x faster btw:

ClassMethod GetMonthList1() As %List
{
    Quit $Listfromstring("January,February,March,April,May,June,July,August,September,October,November,December")
}

ClassMethod GetMonthList2() As %List [ CodeMode = expression ]
{
$Listfromstring("January,February,March,April,May,June,July,August,September,October,November,December")
}

ClassMethod GetMonthList3() As %List [ CodeMode = expression ]
{
$lb("January","February","March","April","May","June","July","August","September","October","November","December")
}

Parameter GetMonthList = {$lb("January","February","March","April","May","June","July","August","September","October","November","December")};

ClassMethod Time2()
{
    for method = 1:1:3 {
        set start = $zh
        
        for i=1:1:10000 {
            set result = $classmethod(,"GetMonthList" _ method)
        }
        
        set end = $zh
        
        write $$$FormatText("Method: %1, time: %2", "GetMonthList" _ method, end - start),!
        
    }
    
    set start = $zh
    
    for i=1:1:10000 {
        set result = ..#GetMonthList
    }
    
    set end = $zh
    
    write $$$FormatText("Parameter, time: %1",  end - start),!
}

Results

Method: GetMonthList1, time: .007255
Method: GetMonthList2, time: .006717
Method: GetMonthList3, time: .003878
Parameter, time: .000605
Eduard Lebedyuk · Sep 2, 2019 go to post

Why

ClassMethod GetMonthList1() As %List
{
    Quit $Listfromstring("January,February,March,April,May,June,July,August,September,October,November,December")
}

instead of

ClassMethod GetMonthList3() As %List
{
 Quit $lb("January","February","March","April","May","June","July","August","September","October","November","December")
}
Eduard Lebedyuk · Aug 30, 2019 go to post
  • pCaption -  return localized caption which would be shown above results
  • pResults  - all results go here
  • pTopResults  - copy some of the results here to show them on top
  • pParms  - local array in which you can pass params
  • pSearchKey - no idea

For example

Parameter SETTINGS = "XSLТ:Basic:selector?context={isc.util.EnsSearchUtils/GetXDatas?class=util.XDatas}"

And inside the method you would get

zw pParms 

pParms("class") = "util.XDatas"

Here's sample code

ClassMethod GetXDatas(Output pCaption As %String, Output pTopResults, Output pResults, ByRef pParms As %String, pSearchKey As %String = "") As %Status
{
    Set tStatus = $$$OK
    Kill pResults, pTopResults
    Set pCaption = $$$Text("My Localized Caption")

    Set tClass = $get(pParms("class"))
    If tClass '= "" {
        Set tClassObj = ##class(%Dictionary.CompiledClass).%OpenId(tClass)

        For i=1:1:tClassObj.XDatas.Count() {
            Set tName = tClassObj.XDatas.GetAt(i).Name
            Set pResults($i(pResults)) = tName
            
            Set:tName["import" pTopResults($i(pTopResults)) = tName
        }
    }
    Quit tStatus
}

}

It would search for all XDatas in class parameter (utils.XDatas in our case) and show those of them containing Import word at the top. Caption would be "My Localized Caption" but you can add localizations to it.