Eduard Lebedyuk · Dec 11, 2019 go to post

I have found a workaround.

1. Create task class as usual.

2. Create this subclass extending (1)

/// Run it daily but it would actually run only on Dates
Class util.CustomDatesTask Extends util.BaseTask
{

Parameter TaskName = "BaseTask (random dates)";

/// Comma separated Dates in YYYY-MM-DD format
/// Example: 2019-12-11,2020-01-17,2020-02-11,2020-03-10,2020-04-09,2020-05-12
Property Dates As %VarString;

/// Check that Dates is valid
Method %OnValidateObject() As %Status
{
    #dim sc As %Status = $$$OK
    set sc = ##super()
    quit:$$$ISERR(sc) sc
    try {
        set dates = $lfs(..Dates)
    } catch ex {
        set sc = ex.AsStatus()
        set sc = $$$ADDSC($$$ERROR($$$GeneralError, "Incorrect Dates value: " _ ..Dates), sc)
    }
    quit:$$$ISERR(sc) sc
    for i=1:1:$ll(dates) {
        try {
            set date = $lg(dates, i)
            set temp = $zdh(date, 3)
        } catch ex {
            set sc = ex.AsStatus()
            set sc = $$$ADDSC($$$ERROR($$$GeneralError, "Incorrect Date value: " _ date), sc)
        }
        quit:$$$ISERR(sc)
    }
    quit sc
}

Method OnTask() As %Status
{
    #dim sc As %Status = $$$OK
    set dates = $lfs(..Dates)
    set curDate = $zd($h, 3)
    if $lf(dates, curDate) {
        // Execute the task
        set sc = ##super()
    }
    quit sc
}
}

The advantage is that schedule is easy to set as a task config property. Drawback is that logs would be created for each day.

Eduard Lebedyuk · Dec 11, 2019 go to post

For example:

set maxrows = 1000
set currentrow = 0
while (ind '= ""){
    set row = ^CacheTemp(repid,"MAIN",ind)
    if currentrow>maxrows  {
        set currentrow = 0
       // swap files
    }
    set currentrow  = currentrow  + 1
    use filemain write row,!
    ; Get next row index for MAIN report
    set ind = $order(^CacheTemp(repid,"MAIN",ind))
}
close filemain

Are you by chance exporting SQL queries to CSV? If so it can be done automatically:

do ##class(%SQL.Statement).%ExecDirect(,"select * from ...")).%DisplayFormatted(100, filename)
Eduard Lebedyuk · Dec 10, 2019 go to post

You need to specify pFormat parameter, it defaults to aceloqtw, where:

  • 1-9 : indent with this number of spaces (4 is the default with the 'i' format specifier)
  • a - output null arrays/objects
  • b - line break before opening { of objects
  • c - output the Caché-specific "_class" and "_id" properties
  • d - output Caché numeric properties that have value "" as null
  • e - output empty object properties
  • i - indent with 4 spaces unless 't' or 1-9
  • l - output empty lists
  • n - newline (lf)
  • o - output empty arrays/objects
  • q - output numeric values unquoted even when they come from a non-numeric property
  • s - use strict JSON output - NOTE: special care should be taken when sending data to a browser, as using this flag may expose you to cross site scripting (XSS) vulnerabilities if the data is sent inside <script> tags. Zen uses this technique extensively, so this flag should NOT be specified for jsonProviders in Zen pages.
  • t - indent with tab character
  • u - output pre-converted to UTF-8 instead of in native internal format
  • w - Windows-style cr/lf newline

In your case explicitly remove c: aeloqtw. This is quoted from documentation.

Additionally if you want to output json to the current device it would be better to use %WriteJSONFromObject - it has the same arguments, except stream, so there's no extra object and io redirect costs:

$$$TOE(tSC, ##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONFromObject(,Store,,,,"aeloqtw")
Eduard Lebedyuk · Dec 9, 2019 go to post

Depends on a coding style.

I prefer not to use/propagate exceptions, but rather %Status.

Eduard Lebedyuk · Dec 5, 2019 go to post

It's a custom psudo-random day each month.

How to reschedule task on completion on a custom date?

Eduard Lebedyuk · Dec 4, 2019 go to post

Thanks, Marc.

Found useful index:

write ##class(Ens.Config.Item).NameExists(##class(Ens.Director).GetActiveProductionName(),"HostName")
Eduard Lebedyuk · Dec 4, 2019 go to post

You need to index columns used in conditions, for the specified query:

  • etatTitre
  • numRemise

If there are less than 6400 possible values you can use bitmap indices.

Start with individual indices (so the number of indices equals the number of condition columns).

Eduard Lebedyuk · Dec 3, 2019 go to post

Can/will moving to an async style of API help this?

No.

long strings aren't enabled in our instance(s) of Cache

You should enable long strings. That's been the default for years.

Anyway, use streams for response processing, they ignore string limits altogether.

Eduard Lebedyuk · Dec 3, 2019 go to post

Check Web Gateway timeouts, specifically Server Response Timeout setting. Also Web Server can impose additional limitations.

That said, I'd advice you to move to async style of API, here's how.

Currently you have one call, say /GetData and it takes 10 minutes.

Split it into 2 calls:

  • /StartTask - JOBs a task (GetData) and returns GUID (child pid in the most simple case)
  • /GetTask/:GUID - returns current JOB status and if it's done returns the data

Here's a sample ASYNC REST broker.

This will save you a lot of problems down the line.

Eduard Lebedyuk · Dec 2, 2019 go to post

I'm using the same workaround.

It works like this because $zf(-1) spawns a new process.

If you want to change the environment variable for a current process, you can use this utility.

Eduard Lebedyuk · Dec 2, 2019 go to post

Well, as a cacheusr you don't have access to /root. Why not write into some temp directory?

Eduard Lebedyuk · Dec 2, 2019 go to post

That's for Docker. GCP, while it uses containers, has a setup where all container's ports could possibly be available, but firewall prevents all access except specified firewall allow rules.

Adding firewall access, as specified by @Dmitry.Maslennikovshould help.

Eduard Lebedyuk · Dec 2, 2019 go to post

You can use any ODBC/JDBC database management software. I use DataGrip. It has code completion for table/field names and standard SQL syntax.

Here's my URL template

jdbc:Cache://{host}[:{port}]/{database}

Eduard Lebedyuk · Dec 2, 2019 go to post

First of all I encourage you to update to 2016.2 or a later version, because JSON syntax differs considerably on 2016.1. Since 2016.2 we have a syntax-stable JSON API.

Anyway, on 2016.1 you can do this:

write obj.$toJSON()
Eduard Lebedyuk · Nov 29, 2019 go to post

Note that instead of:

do ##class(isc.py.util.Shell).Shell()

Python Shell can be entered by simply typing

zpy

In there you can just directly type python code:

exec(open('disk:/path/to/your/folder/testfile.py').read())

Or don't open the shell and call:

set sc=##class(isc.py.Main).SimpleString("exec(open('disk:/path/to/your/folder/testfile.py').read())",,,.sc)
Eduard Lebedyuk · Nov 29, 2019 go to post
set enMsg = "hello"
set esMsg = "hola"
set obj = {"language1":(enMsg),"language2":(esMsg)}
write obj.%ToJSON()

Outputs:

{"language1":"hello","language2":"hola"}
Eduard Lebedyuk · Nov 29, 2019 go to post

This should work for Python Gateway:

set filename = "/path/to/code.py"
set file = ##class(%Stream.FileCharacter).%New()
set sc = file.LinkToFile(filename)
set sc = ##class(isc.py.Main).ExecuteCode(file)
Eduard Lebedyuk · Nov 28, 2019 go to post

1. Underscored properties are extracted. Get them by quoting property name (case is important too):

set centro.codCias = objeto.GetAt(i)."cod_cias"

2. You don't need to init json provider as you're calling a classmethod, replace:

set claseAux = ##class(%ZEN.Auxiliary.jsonProvider).%New()
set tSC= claseAux.%ConvertJSONToObject(.linea,"EsquemasDatos.miSCS.GestionFavoritos.tns.infoCentro",.objeto,1)

with:

set tSC= ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(.linea,"EsquemasDatos.miSCS.GestionFavoritos.tns.infoCentro",.objeto,1)

or even better:


set class = ##class(EsquemasDatos.miSCS.GestionFavoritos.tns.infoCentro).%ClassName(1)
set tSC= ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(.linea,class,.objeto,1)

3. Iterators for JSON. Recommended approach is upgrading to 2016.2+ as  it contains way better json handling (dynamic objects) with iterators.

For %ZEN.proxy object you can call %CopyToArray and iterate over it.

set tSC = ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject("{""prop_a"":1,""prop_b"":2}",,.obj)
do obj.%CopyToArray(.array)
for {
    set key=$order(array(key),1,val)
    quit:key=""
    write key," = ",val,!
}

4. Iterators for classes. Use a method generator to write the code iterating over properties. Relevant discussion.

Eduard Lebedyuk · Nov 28, 2019 go to post

Call this sqlproc passing class and prop names:

ClassMethod Params(class, prop) As %String [SQLProc]
{
    set result = ""
    set param = ""
    for {
        set param = $$$defparamMemberNext(class,$$$cCLASSproperty,prop,$$$cPROPparameter,param)
        quit:(param ="")
        set value = $$$defMemberArrayGet(class,$$$cCLASSproperty,prop,$$$cPROPparameter,param)
        set result = result _ param _ "=" _ value _ ","
    }
    quit result
}